diff --git a/CLAUDE.md b/CLAUDE.md index 029a065..16c815c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -145,10 +145,8 @@ nix/ templates/harness-base.nix shared scaffolding for sub-agents + manager templates/agent-base.nix sub-agent nixosConfiguration templates/manager.nix manager nixosConfiguration - templates/weston-vnc.nix optional `hyperhive.gui.enable` - — weston + VNC backend systemd unit; writes - /etc/hyperhive/gui.json (vnc_port + auth) for - the harness WebSocket relay (issue #51) + templates/weston-rdp.nix optional `hyperhive.westonRdp.enable` + — weston + RDP backend systemd unit docs/ conventions.md naming, identity=socket, async forms, commit style @@ -257,21 +255,22 @@ Prune freely. appends nothing if the journal can't be read. The manager's `update` tool / rebuild errors now carry the failing-unit detail without a second `get_logs` call. -- **Just landed:** `hyperhive.gui.enable` option (replaces - `hyperhive.westonRdp.enable`). New - `nix/templates/weston-vnc.nix` declares a per-agent bool; - enabling it runs weston with the VNC backend as a systemd - service (software/pixman render). Port is deterministic: - FNV-1a hash of the agent name (from hostname) in - [15900, 16799], mirroring `lifecycle::agent_web_port`. - The ExecStart wrapper script computes the port, writes - `/etc/hyperhive/gui.json` (`{ "vnc_port": N, "auth": "none" }`) - for the harness WebSocket relay (issue #51), then execs - weston. Imported by `harness-base.nix`; an agent opts in - from its own `agent.nix`. `Type = "simple"` so it can never - abort `nixos-container update`. A misconfigured weston - degrades to a restart loop in `journalctl`, not a blocked - rebuild. Old `weston-rdp.nix` / `westonRdp.enable` removed. +- **Just landed:** `hyperhive.westonRdp.enable` option. New + `nix/templates/weston-rdp.nix` declares a per-agent bool; + enabling it runs weston with the RDP backend as a systemd + service (software/pixman render, self-signed TLS cert + generated first-boot under `/var/lib/weston`). Imported by + `harness-base.nix` so every agent has the option; an agent + opts in from its own `agent.nix`. Design note: it's a FLAT + per-agent option, not `hyperhive.agents..*` — each + agent is its own nixosConfiguration with no cross-agent + view, so the `` indirection is meaningless. The unit + is `Type = "simple"` with an always-exit-0 `ExecStartPre` so + it can never abort `nixos-container update` (a `Type=notify` + weston that never signals READY would fail activation every + reload — the trap `tea-login` documents). A misconfigured + weston degrades to a restart loop in `journalctl`, not a + blocked rebuild. - **Just landed:** `get_logs` now resolves the machine name. `journalctl -M` wants the *machine* name (`h-gui`), not the logical agent name (`gui`) — `get_logs` was the one manager diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 63368e8..a663eed 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -13,7 +13,7 @@ # Optional feature modules. Each declares its own `hyperhive.*` # option(s), default-off, so every agent has them available but # only opts in from its own `agent.nix`. - imports = [ ./weston-vnc.nix ]; + imports = [ ./weston-rdp.nix ]; options.hyperhive.allowedBashPatterns = lib.mkOption { type = lib.types.listOf lib.types.str; diff --git a/nix/templates/weston-vnc.nix b/nix/templates/weston-vnc.nix deleted file mode 100644 index 401760f..0000000 --- a/nix/templates/weston-vnc.nix +++ /dev/null @@ -1,125 +0,0 @@ -{ - pkgs, - lib, - config, - ... -}: -{ - # Optional Weston (the reference Wayland compositor) with the VNC - # backend, surfaced as a per-agent hyperhive option. An agent turns - # it on from its own `agent.nix`: - # - # hyperhive.gui.enable = true; - # - # Imported by `harness-base.nix`, so every sub-agent + the manager - # has the option available; only those that flip it on get the - # service. This is a flat per-agent option (evaluated inside that - # agent's own container build) — NOT a `hyperhive.agents..*` - # registry, which can't work: each agent is its own - # nixosConfiguration and has no cross-agent view. - # - # VNC port selection: a deterministic FNV-1a hash of the agent name - # (derived from the container hostname at runtime) maps into the - # range [15900, 16799], mirroring lifecycle::agent_web_port. The - # computed port is written to `/etc/hyperhive/gui.json` at service - # start; the harness (issue #51) reads that file to know where to - # relay WebSocket connections. - # - # Note: weston's VNC backend does not expose a CLI bind-address flag - # (unlike the RDP backend's `--address`), so VNC listens on all - # interfaces. The harness WebSocket relay (issue #51) connects only - # via 127.0.0.1, and the host firewall should block external access - # to the VNC port range. A future weston.ini `[vnc] address=` can - # restrict this once upstream supports it. - - options.hyperhive.gui.enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Run Weston with the VNC backend as a systemd service, for - in-browser GUI access via the harness WebSocket relay (see - issue #51). Renders in software (pixman) — no GPU, DRM, - or VT access, so no extra container capabilities are needed. - - The VNC port is deterministic: FNV-1a hash of the agent name - (taken from the container hostname) mapped into [15900, 16799]. - The port and auth mode are written to `/etc/hyperhive/gui.json` - at service start so the harness can relay connections. - - The unit is deliberately built so enabling it can NEVER abort - the agent's `nixos-container update`: `Type = "simple"` (so - `switch-to-configuration` doesn't block on weston readiness) - and the ExecStart script always tries to exec weston after - setup — a misconfigured weston degrades to a restart loop - visible in `journalctl`, it does not block the rebuild. (Same - reasoning as the `tea-login` unit in `harness-base.nix`.) - ''; - }; - - config = lib.mkIf config.hyperhive.gui.enable { - systemd.services.weston = { - description = "Weston Wayland compositor (VNC backend)"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - # `simple`, not `notify`: switch-to-configuration must not - # wait on weston signalling readiness. See weston-rdp.nix and - # the `tea-login` unit for the same reasoning. - Type = "simple"; - # Creates /var/lib/weston (0700 root) at start. - StateDirectory = "weston"; - Environment = "XDG_RUNTIME_DIR=/run/user/0"; - # Wrapper script: computes the deterministic VNC port, writes - # /etc/hyperhive/gui.json for the harness (issue #51), then - # execs weston. Using `exec` keeps the PID stable so systemd - # tracks the weston process correctly under Type=simple. - # Any failure before the exec triggers Restart=on-failure - # (graceful degradation) rather than blocking the rebuild. - ExecStart = pkgs.writeShellScript "weston-vnc" '' - mkdir -p /run/user/0 && chmod 700 /run/user/0 || true - - # --- Compute deterministic VNC port via FNV-1a --- - # Agent name = container hostname with leading "h-" stripped, - # mirroring lifecycle::agent_web_port in hive-c0re/src/lifecycle.rs. - # Read from /etc/hostname (always present in NixOS containers) - # to avoid a dependency on the `hostname` binary (which lives in - # pkgs.inetutils, not pkgs.coreutils). - # VNC_PORT_BASE=15900, VNC_PORT_RANGE=900 → [15900, 16799]. - RAW_HOST=$(${pkgs.coreutils}/bin/cat /etc/hostname) - AGENT_NAME=$(${pkgs.coreutils}/bin/printf '%s' "$RAW_HOST" \ - | ${pkgs.gnused}/bin/sed 's/^h-//') - hash=2166136261 - for byte in $(${pkgs.coreutils}/bin/printf '%s' "$AGENT_NAME" \ - | ${pkgs.coreutils}/bin/od -An -tu1 \ - | ${pkgs.coreutils}/bin/tr -s ' \n' ' '); do - [ -n "$byte" ] || continue - hash=$(( ((hash ^ byte) * 16777619) & 4294967295 )) - done - VNC_PORT=$((15900 + hash % 900)) - - # --- Write gui.json marker --- - # The harness reads this at startup (issue #51) to know the - # VNC port and auth mode for the WebSocket relay. - ${pkgs.coreutils}/bin/mkdir -p /etc/hyperhive - ${pkgs.coreutils}/bin/printf '{"vnc_port":%d,"auth":"none"}\n' \ - "$VNC_PORT" > /etc/hyperhive/gui.json || true - - # --disable-transport-layer-security: VNC is loopback-only - # (relayed by the harness WebSocket proxy); TLS would require - # cert generation and adds no real security benefit here. - exec ${pkgs.weston}/bin/weston \ - --backend=vnc-backend.so \ - --renderer=pixman \ - --port="$VNC_PORT" \ - --disable-transport-layer-security - ''; - Restart = "on-failure"; - RestartSec = "5s"; - }; - }; - - # weston on the agent's interactive PATH too, so claude can run - # Wayland clients / `weston-info` against the compositor. - environment.systemPackages = [ pkgs.weston ]; - }; -}