Services started by systemd in a gui-enabled container didn't have WAYLAND_DISPLAY set, so Wayland clients couldn't find the compositor. libwayland would fall back to a headless display or error out, leaving apps running invisibly while the VNC session showed a blank weston desktop (the double-screen problem). Fix in weston-vnc.nix: - Pass --socket=wayland-0 to weston so the socket name is deterministic (weston normally picks any free wayland-N name). - Set WAYLAND_DISPLAY=wayland-0 and XDG_RUNTIME_DIR=/run/user/0 as global environment.variables gated on hyperhive.gui.enable, so every service in the container inherits them automatically. - Update gui.json to include wayland_display for tooling that reads it. Update docs/gotchas.md with the rationale and pointer to #540.
134 lines
6.1 KiB
Nix
134 lines
6.1 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
{
|
|
# 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.
|
|
|
|
The VNC port is a deterministic FNV-1a hash of the agent name
|
|
mapped into `[15900, 16799]`, written to
|
|
`/etc/hyperhive/gui.json` at service start so the harness can
|
|
relay connections without a separate config flag. The unit is
|
|
`Type = "simple"` so a misconfigured weston degrades to a
|
|
restart loop instead of blocking `nixos-container update`.
|
|
'';
|
|
};
|
|
|
|
config = lib.mkIf config.hyperhive.gui.enable {
|
|
# 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";
|
|
Environment = "XDG_RUNTIME_DIR=/run/user/0";
|
|
# Wrapper script: computes the deterministic VNC port, writes
|
|
# /etc/hyperhive/gui.json for the harness, then execs weston.
|
|
# `exec` keeps the PID stable so systemd tracks the weston
|
|
# process correctly under Type=simple.
|
|
ExecStart = pkgs.writeShellScript "weston-vnc" ''
|
|
mkdir -p /run/user/0 && chmod 700 /run/user/0 || true
|
|
|
|
# --- Compute deterministic VNC port via FNV-1a ---
|
|
# Agent name = container hostname with leading `h-` stripped.
|
|
# Read from /etc/hostname (always present in NixOS containers)
|
|
# to avoid depending on `hostname` (lives in pkgs.inetutils,
|
|
# not pkgs.coreutils).
|
|
RAW_HOST=$(${pkgs.coreutils}/bin/cat /etc/hostname)
|
|
AGENT_NAME=$(${pkgs.coreutils}/bin/printf '%s' "$RAW_HOST" \
|
|
| ${pkgs.gnused}/bin/sed 's/^h-//')
|
|
hash=2166136261
|
|
for byte in $(${pkgs.coreutils}/bin/printf '%s' "$AGENT_NAME" \
|
|
| ${pkgs.coreutils}/bin/od -An -tu1 \
|
|
| ${pkgs.coreutils}/bin/tr -s ' \n' ' '); do
|
|
[ -n "$byte" ] || continue
|
|
hash=$(( ((hash ^ byte) * 16777619) & 4294967295 ))
|
|
done
|
|
VNC_PORT=$((15900 + hash % 900))
|
|
|
|
# Marker file the harness reads at startup. Also records the
|
|
# fixed Wayland socket name (`wayland-0`) so other tooling can
|
|
# read it without inspecting the env-var injection below.
|
|
${pkgs.coreutils}/bin/mkdir -p /etc/hyperhive
|
|
${pkgs.coreutils}/bin/printf '{"vnc_port":%d,"auth":"none","wayland_display":"wayland-0"}\n' \
|
|
"$VNC_PORT" > /etc/hyperhive/gui.json || true
|
|
|
|
# --disable-transport-layer-security: skips the VeNCrypt TLS
|
|
# wrapper so plain auth types (incl. Apple-DH type 30) are
|
|
# advertised directly. [core] idle-time=0 disables the
|
|
# compositor's 300s idle/lock screen.
|
|
WESTON_INI=$(${pkgs.coreutils}/bin/mktemp /tmp/weston-XXXXXX.ini)
|
|
${pkgs.coreutils}/bin/printf '[core]\nidle-time=0\n\n[vnc]\nauth-method=none\n' > "$WESTON_INI"
|
|
|
|
# --socket=wayland-0: pin the compositor's Wayland socket name
|
|
# to `wayland-0` (weston default is to pick any free name such
|
|
# as `wayland-1`). Pinning lets the WAYLAND_DISPLAY=wayland-0
|
|
# global env injection below (see environment.variables) take
|
|
# effect unconditionally — any Wayland client launched by any
|
|
# systemd service in this container automatically connects to
|
|
# this compositor instead of failing or starting a second
|
|
# isolated display. Closes #540 (double-screen: VNC shows blank
|
|
# weston desktop while services render on a different seat).
|
|
exec ${pkgs.weston}/bin/weston \
|
|
--config="$WESTON_INI" \
|
|
--backend=vnc-backend.so \
|
|
--renderer=pixman \
|
|
--port="$VNC_PORT" \
|
|
--socket=wayland-0 \
|
|
--disable-transport-layer-security
|
|
'';
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
};
|
|
};
|
|
|
|
# Expose the compositor's socket to every process in the container
|
|
# so Wayland clients started by any systemd service (e.g. bitburner
|
|
# launched via a `systemd.services.*` declaration in agent.nix) can
|
|
# find the compositor without per-service wiring. Without these vars
|
|
# a service starting a Wayland client would either fail to connect
|
|
# (libwayland falls back to creating a headless display) or open a
|
|
# second compositor entirely — both cause the VNC session to show a
|
|
# blank desktop while apps appear to "work" elsewhere (#540).
|
|
environment.variables = {
|
|
WAYLAND_DISPLAY = "wayland-0";
|
|
XDG_RUNTIME_DIR = "/run/user/0";
|
|
};
|
|
|
|
# weston on the agent's interactive PATH so claude can run Wayland
|
|
# clients / weston-info against the compositor.
|
|
environment.systemPackages = [ pkgs.weston ];
|
|
};
|
|
}
|