nix: replace weston RDP backend with VNC, add hyperhive.gui.enable option

Removes weston-rdp.nix (hyperhive.westonRdp.enable) and adds
weston-vnc.nix (hyperhive.gui.enable).

The ExecStart wrapper script computes a deterministic VNC port via
FNV-1a hash of the agent name (derived from hostname, same algorithm
as lifecycle::agent_web_port) in the range [15900, 16799], then writes
/etc/hyperhive/gui.json {"vnc_port": N, "auth": "none"} for the
harness WebSocket relay (issue #51), and execs weston with the VNC
backend + pixman renderer.

Type=simple so it can never abort nixos-container update; a
misconfigured weston degrades to a restart loop, not a blocked rebuild.

Closes #50
This commit is contained in:
iris 2026-05-20 14:01:53 +02:00
parent 0b237d7d8c
commit 37522fd629
3 changed files with 136 additions and 19 deletions

View file

@ -0,0 +1,116 @@
{
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.<name>.*`
# 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 = hostname with leading "h-" stripped, mirroring
# lifecycle::agent_web_port in hive-c0re/src/lifecycle.rs.
# VNC_PORT_BASE=15900, VNC_PORT_RANGE=900 → [15900, 16799].
AGENT_NAME=$(${pkgs.coreutils}/bin/hostname | ${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
exec ${pkgs.weston}/bin/weston \
--backend=vnc-backend.so \
--renderer=pixman \
--port="$VNC_PORT"
'';
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 ];
};
}