refactor: nix/host-modules + nix/agent-modules layout, update doc paths
This commit is contained in:
parent
cb755b677c
commit
4a48ce5024
52 changed files with 48 additions and 44 deletions
216
nix/agent-modules/weston-vnc.nix
Normal file
216
nix/agent-modules/weston-vnc.nix
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# GUI processes run as the agent's own non-root user — the same user
|
||||
# hive-ag3nt runs as (declared + home-chowned by harness-base.nix) — so
|
||||
# weston, the wayland client, and the agent share one user session.
|
||||
# `hyperhive.user.name` is set per-agent by the meta-flake renderer.
|
||||
userName = config.hyperhive.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 `hyperhive.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/gotchas.md::Weston VNC compositor.
|
||||
# Harness-side WebSocket relay shape: docs/web-ui/agent.md::Per-agent
|
||||
# endpoints (`/screen` + `/screen/ws`).
|
||||
|
||||
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 `/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 (`hyperhive.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 (`hyperhive.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.hyperhive.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.hyperhive.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 = "hyperhive.gui.enable requires a non-root hyperhive.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.hyperhive.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 ];
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue