The hive dashboard and the per-agent page lose their control-by-control UI description and endpoint tables from the handbook. The full text moves to 4-implementation/web-ui/dashboard.md and agent-page.md, and the handbook pages keep what an operator needs to find and use them. Code comments that named the old sections point at the new pages.
216 lines
9.7 KiB
Nix
216 lines
9.7 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
# GUI processes run as the agent's own non-root user — the same user
|
|
# hive-agent runs as (declared + home-chowned by ./user.nix) — so
|
|
# weston, the wayland client, and the agent share one user session.
|
|
# `services.hyperhive.agent.user.name` is set per-agent by the meta-flake renderer.
|
|
userName = config.services.hyperhive.agent.user.name;
|
|
|
|
# Static weston config. `[core] idle-time=0` disables the 300s idle /
|
|
# lock screen; `[vnc] auth-method=none` + the `--disable-transport-
|
|
# layer-security` flag below advertise plain auth types directly. A
|
|
# store file (not a runtime mktemp) so weston's ExecStart is a direct
|
|
# exec — no wrapper script, which keeps the journal SyslogIdentifier
|
|
# clean (`weston`, not a store-path basename).
|
|
westonIni = pkgs.writeText "weston.ini" ''
|
|
[core]
|
|
idle-time=0
|
|
|
|
[vnc]
|
|
auth-method=none
|
|
|
|
[output]
|
|
name=VNC-1
|
|
mode=1280x720
|
|
'';
|
|
in
|
|
{
|
|
# Optional Weston (Wayland compositor) with the VNC backend,
|
|
# surfaced as a per-agent `services.hyperhive.agent.gui.enable` option. Imported
|
|
# from ./default.nix so every sub-agent + the manager sees the
|
|
# option; only those that flip it on get the service.
|
|
#
|
|
# Port allocation, weston bind-address quirk, PAM service name, the
|
|
# Type=simple choice, idle-time=0: all in
|
|
# docs/5-project/process/gotchas.md::Weston VNC compositor.
|
|
# Harness-side WebSocket relay shape: docs/4-implementation/web-ui/agent-page.md::Per-agent
|
|
# endpoints (`/screen` + `/screen/ws`).
|
|
|
|
options.services.hyperhive.agent.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 `/screen/ws` WebSocket
|
|
relay. Renders in software (pixman) — no GPU, DRM, or VT
|
|
access, so no extra container capabilities are needed.
|
|
|
|
Weston binds a fixed VNC port (`services.hyperhive.agent.gui.vncPort`) on the
|
|
container's own loopback. Network isolation is unconditional
|
|
(each agent has its own netns), so a fixed port can't collide
|
|
across containers — no per-agent hashing needed. The harness
|
|
learns the port from the `HIVE_GUI_VNC_PORT` env var (set by the
|
|
harness service when gui is enabled). The unit is
|
|
`Type = "simple"` so a misconfigured weston degrades to a restart
|
|
loop instead of blocking `nixos-container update`.
|
|
|
|
Weston, the wayland client and the agent harness run as the
|
|
agent's own non-root user (`services.hyperhive.agent.user.name`), sharing one
|
|
session: a fixed `XDG_RUNTIME_DIR=/run/gui`, one wayland display,
|
|
and one D-Bus session bus at `/run/gui/bus` (gui-dbus.service),
|
|
so GUI clients need no private `dbus-run-session`.
|
|
'';
|
|
};
|
|
|
|
# Fixed VNC port weston binds inside the container. Safe to be the
|
|
# same for every agent because network isolation is unconditional
|
|
# (private netns per container — see hive-network.nix), so the port
|
|
# is container-local and can't collide. Internal: the harness reads
|
|
# the value via the `HIVE_GUI_VNC_PORT` env var the harness service
|
|
# injects from this option, not directly.
|
|
options.services.hyperhive.agent.gui.vncPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 5900;
|
|
internal = true;
|
|
description = ''
|
|
VNC port weston binds inside the container (default 5900, the
|
|
standard VNC port). Container-local, so the same value for every
|
|
agent is fine. Surfaced to the harness as `HIVE_GUI_VNC_PORT`.
|
|
'';
|
|
};
|
|
|
|
config = lib.mkIf config.services.hyperhive.agent.gui.enable {
|
|
# The GUI must run non-root: weston + the wayland client share the
|
|
# agent's own user session. `user.name` is the agent name for every
|
|
# spawned agent; only a misconfigured root-named agent would trip this.
|
|
assertions = [
|
|
{
|
|
assertion = userName != "root";
|
|
message = "services.hyperhive.agent.gui.enable requires a non-root services.hyperhive.agent.user.name (the GUI runs as that user).";
|
|
}
|
|
];
|
|
|
|
# neatvnc ≥ 0.9 always calls the PAM auth callback for Apple-DH
|
|
# (type 30), regardless of weston.ini auth-method=none.
|
|
# pam_permit.so accepts the browser's empty Apple-DH credentials.
|
|
# Service name MUST be the literal `weston-remote-access` — that's
|
|
# the string libweston passes to pam_start() in libweston/auth.c.
|
|
security.pam.services."weston-remote-access".text = ''
|
|
auth sufficient pam_permit.so
|
|
account sufficient pam_permit.so
|
|
session sufficient pam_permit.so
|
|
'';
|
|
|
|
# One shared D-Bus *session* bus for the whole GUI session, bound at
|
|
# /run/gui/bus and owned by the agent user. Wayland GUI clients
|
|
# (chromium / electron via ozone) refuse to map a toplevel without a
|
|
# reachable session bus ("Failed to connect to the bus" -> binds
|
|
# xdg_wm_base then destroys it = invisible window, though CDP still
|
|
# works). Running ONE persistent bus here -- instead of each client
|
|
# wrapping itself in `dbus-run-session` (a private throwaway bus per
|
|
# process) -- keeps weston, the agent harness and the GUI client in a
|
|
# single session: one user, one XDG_RUNTIME_DIR, one wayland display,
|
|
# one bus. The address is exported via globalEnvironment below so
|
|
# every unit in the container inherits it.
|
|
systemd.services.gui-dbus = {
|
|
description = "Shared D-Bus session bus for the GUI session";
|
|
wantedBy = [ "multi-user.target" ];
|
|
before = [ "weston.service" ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = userName;
|
|
Group = userName;
|
|
# Share /run/gui with weston; ordered `before` weston so this
|
|
# creates + chowns the dir first. Preserve across restarts so
|
|
# weston + clients don't lose the dir holding their sockets.
|
|
RuntimeDirectory = "gui";
|
|
RuntimeDirectoryMode = "0700";
|
|
RuntimeDirectoryPreserve = "yes";
|
|
Environment = "XDG_RUNTIME_DIR=/run/gui";
|
|
# dbus-daemon unlinks a stale path socket before binding, so a
|
|
# restart re-binds /run/gui/bus cleanly.
|
|
ExecStart = "${pkgs.dbus}/bin/dbus-daemon --session --nofork --nopidfile --address=unix:path=/run/gui/bus";
|
|
SyslogIdentifier = "gui-dbus";
|
|
Restart = "on-failure";
|
|
RestartSec = "2s";
|
|
};
|
|
};
|
|
|
|
systemd.services.weston = {
|
|
description = "Weston Wayland compositor (VNC backend)";
|
|
after = [
|
|
"network.target"
|
|
"gui-dbus.service"
|
|
];
|
|
wants = [ "gui-dbus.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
StateDirectory = "weston";
|
|
# Run as the agent's own user; share a fixed runtime dir at
|
|
# /run/gui (RuntimeDirectory creates+chowns it). Preserve it
|
|
# across weston restarts so the wayland client sharing the
|
|
# /run/gui/wayland-0 socket doesn't lose the dir under it. 0700
|
|
# because a wayland XDG_RUNTIME_DIR must not be group/world-accessible.
|
|
User = userName;
|
|
Group = userName;
|
|
RuntimeDirectory = "gui";
|
|
RuntimeDirectoryMode = "0700";
|
|
RuntimeDirectoryPreserve = "yes";
|
|
Environment = "XDG_RUNTIME_DIR=/run/gui";
|
|
# Direct exec (no wrapper script): fixed `--port`, static config.
|
|
# `--socket=wayland-0` pins the compositor's Wayland socket name
|
|
# (weston otherwise picks any free name like `wayland-1`), so the
|
|
# `WAYLAND_DISPLAY=wayland-0` globalEnvironment injection below
|
|
# reaches every wayland client in the container deterministically
|
|
# (fixes double-screen: VNC showing a blank weston desktop while a
|
|
# client renders on a different seat). `--disable-transport-layer-
|
|
# security` skips the VeNCrypt TLS wrapper so plain auth types
|
|
# (incl. Apple-DH type 30) are advertised directly.
|
|
ExecStart = ''
|
|
${pkgs.weston}/bin/weston \
|
|
--config=${westonIni} \
|
|
--backend=vnc-backend.so \
|
|
--renderer=pixman \
|
|
--port=${toString config.services.hyperhive.agent.gui.vncPort} \
|
|
--socket=wayland-0 \
|
|
--disable-transport-layer-security
|
|
'';
|
|
# ExecStart is already a direct `weston` exec (basename is clean), but
|
|
# pin the identity explicitly so it can't drift if a wrapper is ever
|
|
# introduced (sweep per the systemd SyslogIdentifier convention).
|
|
SyslogIdentifier = "weston";
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
};
|
|
};
|
|
|
|
# Expose the compositor's socket to every systemd service in the
|
|
# container so Wayland clients (e.g. bitburner started via
|
|
# `systemd.services.*` in agent.nix) can find the compositor
|
|
# without per-service wiring. `systemd.globalEnvironment` is the
|
|
# correct path — it sets DefaultEnvironment in systemd.conf,
|
|
# reaching all units started by PID 1. `environment.variables`
|
|
# goes to /etc/environment (PAM sessions only) and is NOT visible
|
|
# to systemd service units. Without these vars a service starting
|
|
# a Wayland client would either fail to connect (libwayland falls
|
|
# back to a headless display) or open a second compositor — VNC
|
|
# shows a blank desktop. DBUS_SESSION_BUS_ADDRESS points every unit
|
|
# at the one shared session bus (gui-dbus.service above) so GUI
|
|
# clients use it instead of spawning a private `dbus-run-session`.
|
|
systemd.globalEnvironment = {
|
|
WAYLAND_DISPLAY = "wayland-0";
|
|
XDG_RUNTIME_DIR = "/run/gui";
|
|
DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/gui/bus";
|
|
};
|
|
|
|
# weston on the agent's interactive PATH so claude can run Wayland
|
|
# clients / weston-info against the compositor.
|
|
environment.systemPackages = [ pkgs.weston ];
|
|
};
|
|
}
|