diff --git a/CLAUDE.md b/CLAUDE.md index bf5c0d15..c60a7263 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -130,8 +130,6 @@ 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-rdp.nix optional `hyperhive.westonRdp.enable` - — weston + RDP backend systemd unit docs/ conventions.md naming, identity=socket, async forms, commit style @@ -185,33 +183,6 @@ read them à la carte. In-flight or recent context that hasn't earned a section yet. Prune freely. -- **Just landed:** failed `nixos-container update` self-documents. - `lifecycle::run` now appends the tail (40 lines) of the target - container's own journal to the bail message when an `update` - fails. `nixos-container`'s own stderr on a reload-phase failure - is terse ("failed to reload container"); the real cause — - which unit failed `switch-to-configuration` — lives in the - *container* journal. Scoped to `update` (container's still up - on the old generation, so `journalctl -M` works); best-effort, - 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.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/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index ad96f6f7..c48300ce 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -959,40 +959,7 @@ async fn run(args: &[&str]) -> Result<()> { .cloned() .collect::>() .join("\n"); - let journal = container_journal_tail(args).await; - bail!("nixos-container {cmdline} failed ({status}): {tail}{journal}"); + bail!("nixos-container {cmdline} failed ({status}): {tail}"); } Ok(()) } - -/// On a failed `nixos-container update`, the stderr nixos-container -/// itself prints is often terse ("failed to reload container") — the -/// real reason (which unit failed `switch-to-configuration` during -/// the reload phase) lands in the *container's* own journal, not on -/// the host. Fetch the tail of it so a failed rebuild self-documents -/// the failing unit in the error string, no second round-trip. -/// -/// Scoped to `update`: that's the reload-phase case, and the -/// container is still up (running the old generation) so -/// `journalctl -M` works. Best-effort — returns "" for other verbs -/// or when the journal can't be read (machine gone, journalctl -/// missing); it never produces an error of its own. -async fn container_journal_tail(args: &[&str]) -> String { - if args.first().copied() != Some("update") { - return String::new(); - } - let Some(container) = args.get(1) else { - return String::new(); - }; - let out = Command::new("journalctl") - .args(["-M", container, "-n", "40", "--no-pager", "--output=short"]) - .output() - .await; - match out { - Ok(o) if !o.stdout.is_empty() => format!( - "\n--- last 40 journal lines from container '{container}' ---\n{}", - String::from_utf8_lossy(&o.stdout).trim_end() - ), - _ => String::new(), - } -} diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index d1601927..26d059e3 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -10,11 +10,6 @@ # this. The systemd service that actually runs the harness binary # differs per role and lives in the child module. - # 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-rdp.nix ]; - options.hyperhive.allowedRecipients = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; diff --git a/nix/templates/weston-rdp.nix b/nix/templates/weston-rdp.nix deleted file mode 100644 index c6462186..00000000 --- a/nix/templates/weston-rdp.nix +++ /dev/null @@ -1,97 +0,0 @@ -{ - 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 ]; - }; -}