{ pkgs, lib, config, ... }: { # Optional Weston (the reference Wayland compositor) with the RDP # backend, surfaced as a per-agent hyperhive option. An agent turns # it on from its own `agent.nix`: # # hyperhive.westonRdp.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. options.hyperhive.westonRdp.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run Weston with the RDP backend as a systemd service, for remote-desktop access to the agent's container. A self-signed TLS cert/key pair is generated on first start under `/var/lib/weston`. Renders in software (pixman) — no GPU, DRM, or VT access, so no extra container capabilities are needed. 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 an `ExecStartPre` that always exits 0. 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`.) Host networking means the RDP port (3389) is reachable on the host. With more than one RDP-enabled agent the port would collide — add a port knob to this option before that happens. ''; }; config = lib.mkIf config.hyperhive.westonRdp.enable { systemd.services.weston = { description = "Weston Wayland compositor (RDP backend)"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { # `simple`, not `notify`: switch-to-configuration must not # wait on weston signalling readiness. A `notify` unit that # never sends READY=1 fails after TimeoutStartSec, which # aborts `nixos-container update` on every reload — exactly # the trap `tea-login` documents. Type = "simple"; # Creates /var/lib/weston (0700 root) before ExecStartPre. StateDirectory = "weston"; Environment = "XDG_RUNTIME_DIR=/run/user/0"; # Runtime dir + first-boot cert generation. No `set -e`, and # it always exits 0: a cert hiccup must not fail the unit # start (that would abort the rebuild). If the cert/key end # up missing, weston's own ExecStart fails into the Restart # loop instead — graceful degradation, not a blocked deploy. ExecStartPre = pkgs.writeShellScript "weston-rdp-setup" '' mkdir -p /run/user/0 && chmod 700 /run/user/0 || true CERT=/var/lib/weston/weston-rdp.crt KEY=/var/lib/weston/weston-rdp.key if [ ! -f "$CERT" ] || [ ! -f "$KEY" ]; then if ${pkgs.openssl}/bin/openssl req -x509 -newkey rsa:2048 \ -keyout "$KEY" -out "$CERT" -days 3650 -nodes \ -subj "/CN=localhost" 2>/dev/null; then chmod 600 "$KEY" 2>/dev/null || true chmod 644 "$CERT" 2>/dev/null || true else echo "weston-rdp-setup: cert generation failed; weston will retry" fi fi exit 0 ''; ExecStart = lib.concatStringsSep " " [ "${pkgs.weston}/bin/weston" "--backend=rdp-backend.so" "--renderer=pixman" "--rdp-tls-cert=/var/lib/weston/weston-rdp.crt" "--rdp-tls-key=/var/lib/weston/weston-rdp.key" ]; 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 ]; }; }