{ 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 ]; }; }