164 lines
7 KiB
Nix
164 lines
7 KiB
Nix
{
|
|
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
|
|
'';
|
|
in
|
|
{
|
|
# Optional Weston (Wayland compositor) with the VNC backend,
|
|
# surfaced as a per-agent `hyperhive.gui.enable` option. Imported
|
|
# from harness-base.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 and the wayland client run as the agent's own non-root
|
|
user (`hyperhive.user.name`), sharing one user session with a
|
|
fixed `XDG_RUNTIME_DIR=/run/gui`.
|
|
'';
|
|
};
|
|
|
|
# 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
|
|
'';
|
|
|
|
systemd.services.weston = {
|
|
description = "Weston Wayland compositor (VNC backend)";
|
|
after = [ "network.target" ];
|
|
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
|
|
'';
|
|
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.
|
|
systemd.globalEnvironment = {
|
|
WAYLAND_DISPLAY = "wayland-0";
|
|
XDG_RUNTIME_DIR = "/run/gui";
|
|
};
|
|
|
|
# weston on the agent's interactive PATH so claude can run Wayland
|
|
# clients / weston-info against the compositor.
|
|
environment.systemPackages = [ pkgs.weston ];
|
|
};
|
|
}
|