docs/gotchas.md: extract nix/{assets,docs,templates/weston-vnc} prose (#718 batch 3)

- assets.nix: cargo-cache-invalidation rationale → "Split asset
  derivations away from the rust workspace" section.
- templates/weston-vnc.nix: port allocation, weston bind-address
  quirk, PAM service name, Type=simple choice, idle-time=0 →
  "Weston VNC compositor (per-agent hyperhive.gui.enable)" section.
- docs/default.nix: rendering pipeline + subtree-pick + output-tree
  history → "Nix options reference" section.

In-code comments trimmed to short purpose statements + docs pointers.
description = '' blocks (operator-facing options docs) preserved per
iris #718.

`nix flake check` + `nix build .#docs` clean.
This commit is contained in:
atlas 2026-05-31 15:18:48 +02:00 committed by mara
commit db2a48cde6
4 changed files with 170 additions and 174 deletions

View file

@ -238,3 +238,105 @@ and fail outright if the host daemon's
`nix/templates/harness-base.nix` does `lib.mkForce true` so builds `nix/templates/harness-base.nix` does `lib.mkForce true` so builds
fall back to unsandboxed local builds rather than failing. Security fall back to unsandboxed local builds rather than failing. Security
implications: `docs/security.md`. implications: `docs/security.md`.
## Split asset derivations away from the rust workspace
`nix/assets.nix` builds the branding SVG/PNG family + claude
system-prompt template + claude-settings JSON as its own derivation,
separate from the hive-ag3nt / hive-c0re crates. Reason: when the
rust build's `src` was the whole repo tree, any tweak to
`branding/agent-configs.svg` or `hive-ag3nt/prompts/system.md`
invalidated the cargo cache and forced a full rebuild. crane (and
naersk before it) couldn't see "these inputs are unused by rust" on
its own — the split breaks the coupling at the derivation boundary.
The agent-configs PNG is rendered from the SVG via `rsvg-convert` at
build time; librsvg dependency lives here, not in the rust
derivation's `nativeBuildInputs`.
## Weston VNC compositor (per-agent `hyperhive.gui.enable`)
`nix/templates/weston-vnc.nix` adds an optional Weston Wayland
compositor with the VNC backend, surfaced as
`hyperhive.gui.enable = true` per-agent. The harness's
`/screen/ws` WebSocket relay (`docs/web-ui.md::Per-agent endpoints`)
connects to the compositor at `127.0.0.1:<vnc_port>`.
- **Port allocation**: deterministic FNV-1a of the agent name
(read from `/etc/hostname`, leading `h-` stripped) mapped into
`[15900, 16799]`. Mirrors the agent web-UI port pattern from
`docs/gotchas.md::Web UI ports collide on hash` — same FNV-1a
constant, different range. The compositor's startup script writes
`/etc/hyperhive/gui.json = {"vnc_port":N,"auth":"none"}` so the
harness reads the port at runtime; no nix-side / harness-side hash
duplication.
- **VNC bind address**: weston's VNC backend has no CLI
bind-address flag (unlike the RDP backend's `--address`), so the
listener binds `0.0.0.0`. The harness relay only connects via
`127.0.0.1`; the host firewall blocks the per-agent VNC port range
from external access. A future weston.ini `[vnc] address=` will
let us restrict the bind directly once upstream supports it.
- **PAM service name**: literal `weston-remote-access` — that's the
string libweston passes to `pam_start()` in `libweston/auth.c`.
Using `weston` falls back to the system default PAM stack and
rejects auth. The service is configured to `pam_permit.so` for
all three module types (auth / account / session) so the
browser's empty Apple-DH credentials (type 30) always pass —
neatvnc ≥ 0.9 calls the PAM auth callback regardless of
`weston.ini` `auth-method=none`, so the permit fallback is what
actually lets the empty-cred client through.
- **`Type = "simple"` (not `notify`)**: `switch-to-configuration`
must never block on weston signalling readiness. A misconfigured
weston degrades to a `Restart=on-failure` loop visible in
`journalctl`, it does not abort the `nixos-container update`.
Same reasoning as the `tea-login` unit in `harness-base.nix`.
- **`[core] idle-time=0`**: disables weston's 300-second idle
timeout. Without it the VNC desktop fades to black and
desktop-shell shows its click-to-unlock screen — useless for an
agent desktop viewed over `/screen`. `idle-time=0` updates the
idle timer with a 0ms delay, which
`wl_event_source_timer_update` treats as "disarm", so the
compositor never goes idle and never locks.
## Nix options reference (`nix/docs/default.nix`)
`pkgs.nixosOptionsDoc` over two evaluated module trees:
`hostEval` (a stub NixOS system loading `self.nixosModules.default`
with every hyperhive subsystem `mkForce false` so heavy build
inputs stay out of the eval) and `agentEval` (reuses the already-evaluated
`agent-base` container config so the per-agent options tree is
identical to what a real agent container sees).
Three output trees consumed by `flake.nix`:
- `docs-host` — operator-facing host module options
(`services.hyperhive.*`)
- `docs-agent` — per-agent harness options (`hyperhive.*`
declared in `nix/templates/harness-base.nix`)
- `docs` — bundled static site (`index.html` + `host.html` +
`agent.html`, plus `.md` source-of-truth versions of each
options page)
Rendering pipeline:
- CommonMark from `nixosOptionsDoc.optionsCommonMark` — source of
truth, kept as `.md` in the bundle.
- HTML via `pkgs.cmark-gfm` over the CommonMark, wrapped in a
minimal inline-CSS template. `cmark-gfm` (not plain `cmark`) so
any future tables / autolinks Just Work without revisiting.
- Inline `<style>` from `nix/docs/style.css` so the bundle is
single-file-per-page and nginx's `/options/` mount needs no MIME
setup for separate `.css` files and no cache-busting.
- Asset paths inside rendered HTML are all relative
(`./host.html`, etc.) so the bundle can mount at any URL prefix
without rewriting.
- `transformOptions` strips the nix-store prefix from option
declaration paths and rewrites them as forge URLs, so the
rendered docs link back to the source.
Post-#615 (closes #630) host options live entirely under
`services.hyperhive.*`. Pre-#615 had a mix of `hyperhive.*` (forge,
matrix, domain) and `services.hive-c0re.*`; picking against the
old roots on current main silently produced an empty options tree,
so the rendered host page was just template chrome with no `<h2>`
headers. The `pickSubtrees` filter is rooted at
`["services" "hyperhive"]` for that reason.

View file

@ -4,38 +4,24 @@
librsvg, librsvg,
}: }:
# Static assets the rust workspace reads at runtime: the project's # Branding SVG/PNG family + claude prompts, split out from the rust
# branding SVG/PNG family + the claude system-prompt template + # workspace so a tweak here doesn't invalidate the rust cargo cache.
# claude-settings JSON. Lives as its own derivation so a tweak to # Rationale + agent-configs PNG rendering: docs/gotchas.md::Split asset
# branding/agent-configs.svg or hive-ag3nt/prompts/system.md doesn't # derivations away from the rust workspace.
# invalidate the rust derivation's cargo cache (closes #555 follow-up
# to #538 — naersk previously paired with `src = ./.;` invalidating
# every rust build on any branding/prompt edit; crane inherited that
# coupling and this split breaks it cleanly).
# #
# Output layout: # Output layout:
# # $out/share/hyperhive/branding/{hyperhive,agent-configs}.{svg,png}
# $out/share/hyperhive/branding/{hyperhive.svg, hyperhive.png,
# agent-configs.svg, agent-configs.png}
# $out/share/hyperhive/prompts/{system.md, claude-settings.json} # $out/share/hyperhive/prompts/{system.md, claude-settings.json}
#
# The agent-configs PNG is rendered at build time from the SVG via
# rsvg-convert — same shape as the old `hive-c0re/build.rs` rasteriser,
# just hoisted into nix so the librsvg dependency stays *here* instead
# of in the rust derivation's nativeBuildInputs.
stdenv.mkDerivation { stdenv.mkDerivation {
pname = "hyperhive-assets"; pname = "hyperhive-assets";
version = "0.1.0"; version = "0.1.0";
# `src` is intentionally narrow — only branding/ + the hive-ag3nt/prompts/ # Narrow `srcs` (branding/ + hive-ag3nt/prompts/) is what decouples
# subdir, NOT the whole tree. Keeps the input hash decoupled from # this derivation's input hash from the rest of the tree.
# rust source / docs / nix module edits.
srcs = [ srcs = [
../branding ../branding
../hive-ag3nt/prompts ../hive-ag3nt/prompts
]; ];
# `unpackPhase` would normally extract each src to its own dir; we
# just want them side-by-side, so hand-roll a flat copy.
unpackPhase = '' unpackPhase = ''
runHook preUnpack runHook preUnpack
cp -r ${../branding} branding cp -r ${../branding} branding
@ -46,10 +32,8 @@ stdenv.mkDerivation {
nativeBuildInputs = [ librsvg ]; nativeBuildInputs = [ librsvg ];
# No real build step — just render the agent-configs PNG alongside # 300×300 matches branding/hyperhive.png — the size Forgejo's avatar
# its SVG. 300×300 matches branding/hyperhive.png, which is the size # endpoint accepts without resampling on upload.
# Forgejo's avatar endpoint accepts without resampling on upload (the
# same constraint hive-c0re/build.rs encoded).
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild
rsvg-convert --width 300 --height 300 \ rsvg-convert --width 300 --height 300 \

View file

@ -4,34 +4,15 @@
self, self,
nixosSystem, nixosSystem,
}: }:
# Options documentation for hyperhive's NixOS module surfaces. # Nix options reference: `pkgs.nixosOptionsDoc` over two evaluated
# Closes #616. HTML output added per mara on internal-requests #8. # module trees, rendered as CommonMark + HTML + bundled static site
# # the operator's nginx serves from `/options/`. Full pipeline +
# Three rendering layers: # subtree-pick / output-tree rationale: docs/gotchas.md::Nix options
# CommonMark — `pkgs.nixosOptionsDoc.optionsCommonMark`. Source of # reference.
# truth; kept as `.md` files in the bundle.
# HTML — `pkgs.cmark-gfm` over the CommonMark output, wrapped
# in a minimal inline-CSS template. Primary surface; the
# bundle's `index.html` / `host.html` / `agent.html` are
# what the operator's nginx serves from
# `hyperhive.darkest.space/options/`.
#
# Three output trees consumed by `flake.nix`:
# docs-host — operator-facing host-module options
# (`services.hive-c0re.*`, `hyperhive.{domain,forge,matrix}.*`)
# docs-agent — per-agent harness options
# (`hyperhive.{model,allowedRecipients,extraMcpServers,…}`)
# docs — bundled static site (index + host + agent, .html + .md)
#
# All asset paths inside the rendered HTML are relative (e.g.
# `./host.html`) so the bundle can be mounted at any URL prefix
# without rewriting; styles are inline so there's no second-fetch
# request for the operator's browser.
let let
# Evaluate the host module under a stub NixOS system. Stubs satisfy # Stub host system: every hyperhive subsystem `mkForce false` so
# the few hard-required options (filesystems, stateVersion) without # heavy build inputs (matrix container, forge, etc.) stay out of
# actually enabling the hive — we only want the option *declarations* # the eval — only option *declarations* matter for the doc walk.
# to evaluate, not the config.
hostEval = nixosSystem { hostEval = nixosSystem {
system = pkgs.stdenv.hostPlatform.system; system = pkgs.stdenv.hostPlatform.system;
modules = [ modules = [
@ -46,10 +27,6 @@ let
}; };
boot.loader.grub.enable = false; boot.loader.grub.enable = false;
system.stateVersion = "25.11"; system.stateVersion = "25.11";
# Force-disable every hyperhive subsystem so config evaluation
# doesn't pull in heavy build inputs (matrix container, forge,
# etc.). Options are still fully declared either way — that's
# what nixosOptionsDoc traverses.
services.hyperhive.enable = lib.mkForce false; services.hyperhive.enable = lib.mkForce false;
services.hyperhive.forge.enable = lib.mkForce false; services.hyperhive.forge.enable = lib.mkForce false;
services.hyperhive.matrix.enable = lib.mkForce false; services.hyperhive.matrix.enable = lib.mkForce false;
@ -59,14 +36,12 @@ let
]; ];
}; };
# Agent options live in the already-evaluated `agent-base` container # Reuse the already-evaluated agent-base config — its options tree is
# config. Reusing it avoids re-evaluating the harness module against # identical to what a real agent container sees, no second eval needed.
# a fresh stub — the options tree is identical to what a real agent
# container sees.
agentEval = self.nixosConfigurations.agent-base; agentEval = self.nixosConfigurations.agent-base;
# Strip the nix-store prefix from option declaration paths and rewrite # Rewrite option declaration paths from nix-store absolute paths to
# them as forge URLs so the rendered docs link back to the source. # forge URLs so rendered docs link back to source.
forgeRoot = "https://forge.darkest.space/hyperhive/hyperhive/src/branch/main"; forgeRoot = "https://forge.darkest.space/hyperhive/hyperhive/src/branch/main";
storePrefix = toString self + "/"; storePrefix = toString self + "/";
transformOptions = transformOptions =
@ -90,10 +65,10 @@ let
) opt.declarations; ) opt.declarations;
}; };
# Filter an evaluated `options` tree down to a set of top-level # Filter to a set of top-level subtree roots — keeps the rendered docs
# subtrees we care about. Anything outside the listed roots is # focused on hyperhive's surface instead of NixOS's 10k+ default
# dropped — keeps the rendered docs focused on hyperhive's surface # options. Root choice matters: see docs/gotchas.md::Nix options
# instead of NixOS's 10k+ default options. # reference for the post-#615 services.hyperhive consolidation history.
pickSubtrees = pickSubtrees =
options: roots: options: roots:
let let
@ -106,12 +81,6 @@ let
in in
lib.foldl' lib.recursiveUpdate { } (map pick roots); lib.foldl' lib.recursiveUpdate { } (map pick roots);
# Post-#615, host options live entirely under `services.hyperhive.*`
# (closes #630). Pre-#615 had a mix of `hyperhive.*` (forge, matrix,
# domain) and `services.hive-c0re.*` — picking against those roots
# silently produced an empty options tree on current main, so the
# rendered host page was just the template chrome with no `<h2>`
# option headers underneath.
hostOptions = pickSubtrees hostEval.options [ hostOptions = pickSubtrees hostEval.options [
[ [
"services" "services"
@ -133,8 +102,7 @@ let
inherit transformOptions; inherit transformOptions;
}; };
# Plain-markdown page (with a short header). Source of truth; the # CommonMark .md = source of truth; HTML is rendered from this.
# HTML version is rendered from this.
mkMarkdownPage = mkMarkdownPage =
name: title: doc: name: title: doc:
pkgs.runCommand "hyperhive-${name}.md" { } '' pkgs.runCommand "hyperhive-${name}.md" { } ''
@ -149,16 +117,11 @@ let
} > $out } > $out
''; '';
# Inline-stylesheet, loaded as plain text from `./style.css` so it's # Loaded as text so it's editable with normal CSS tooling and
# editable with normal CSS tooling (#625). Inlined into every page # inlined into every page (no second-fetch dependency).
# so the bundle doesn't depend on a second HTTP fetch — keeps the
# `/options/` mount trivial for nginx (no MIME guessing for separate
# .css files, no cache-busting needed when this updates).
styleCSS = builtins.readFile ./style.css; styleCSS = builtins.readFile ./style.css;
# HTML page: CommonMark → cmark-gfm → minimal template with inline # CommonMark → cmark-gfm → minimal template, inline CSS, relative links.
# CSS + relative-only links. cmark-gfm rather than plain cmark so
# any future tables / autolinks Just Work without revisiting.
mkHtmlPage = mkHtmlPage =
name: title: doc: name: title: doc:
pkgs.runCommand "hyperhive-${name}.html" { nativeBuildInputs = [ pkgs.cmark-gfm ]; } '' pkgs.runCommand "hyperhive-${name}.html" { nativeBuildInputs = [ pkgs.cmark-gfm ]; } ''
@ -192,9 +155,7 @@ let
} > $out } > $out
''; '';
# Landing page — same template shape as the option pages but # Landing page — same template shape, hand-authored intro + cross-links.
# hand-authored content (short intro + cross-links). Kept tight; the
# detail lives on the two option pages.
indexHTML = pkgs.runCommand "hyperhive-docs-index.html" { } '' indexHTML = pkgs.runCommand "hyperhive-docs-index.html" { } ''
{ {
echo '<!doctype html>' echo '<!doctype html>'
@ -240,14 +201,11 @@ let
agentMD = mkMarkdownPage "docs-agent" "hyperhive per-agent options" agentDoc; agentMD = mkMarkdownPage "docs-agent" "hyperhive per-agent options" agentDoc;
in in
{ {
# Individual page outputs (HTML is the primary surface; the .md
# source is one `nix build` step away if needed).
host = hostHTML; host = hostHTML;
agent = agentHTML; agent = agentHTML;
# Bundled static site for nginx to serve at `/options/`. Asset paths # Bundled static site nginx serves at `/options/`. Asset paths are
# are all relative, no root-absolute references, so the prefix can # all relative so the prefix can change without rebuild.
# change without rebuild.
bundle = pkgs.runCommand "hyperhive-options-docs" { } '' bundle = pkgs.runCommand "hyperhive-options-docs" { } ''
mkdir -p $out mkdir -p $out
cp ${indexHTML} $out/index.html cp ${indexHTML} $out/index.html

View file

@ -5,66 +5,41 @@
... ...
}: }:
{ {
# Optional Weston (the reference Wayland compositor) with the VNC # Optional Weston (Wayland compositor) with the VNC backend,
# backend, surfaced as a per-agent hyperhive option. An agent turns # surfaced as a per-agent `hyperhive.gui.enable` option. Imported
# it on from its own `agent.nix`: # from harness-base.nix so every sub-agent + the manager sees the
# option; only those that flip it on get the service.
# #
# hyperhive.gui.enable = true; # Port allocation, weston bind-address quirk, PAM service name, the
# # Type=simple choice, idle-time=0: all in
# Imported by `harness-base.nix`, so every sub-agent + the manager # docs/gotchas.md::Weston VNC compositor.
# has the option available; only those that flip it on get the # Harness-side WebSocket relay shape: docs/web-ui.md::Per-agent
# service. This is a flat per-agent option (evaluated inside that # endpoints (`/screen` + `/screen/ws`).
# agent's own container build) — NOT a `hyperhive.agents.<name>.*`
# registry, which can't work: each agent is its own
# nixosConfiguration and has no cross-agent view.
#
# VNC port selection: a deterministic FNV-1a hash of the agent name
# (derived from the container hostname at runtime) maps into the
# range [15900, 16799], mirroring lifecycle::agent_web_port. The
# computed port is written to `/etc/hyperhive/gui.json` at service
# start; the harness (issue #51) reads that file to know where to
# relay WebSocket connections.
#
# Note: weston's VNC backend does not expose a CLI bind-address flag
# (unlike the RDP backend's `--address`), so VNC listens on all
# interfaces. The harness WebSocket relay (issue #51) connects only
# via 127.0.0.1, and the host firewall should block external access
# to the VNC port range. A future weston.ini `[vnc] address=` can
# restrict this once upstream supports it.
options.hyperhive.gui.enable = lib.mkOption { options.hyperhive.gui.enable = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
description = '' description = ''
Run Weston with the VNC backend as a systemd service, for Run Weston with the VNC backend as a systemd service, for
in-browser GUI access via the harness WebSocket relay (see in-browser GUI access via the harness `/screen/ws` WebSocket
issue #51). Renders in software (pixman) — no GPU, DRM, relay. Renders in software (pixman) no GPU, DRM, or VT
or VT access, so no extra container capabilities are needed. access, so no extra container capabilities are needed.
The VNC port is deterministic: FNV-1a hash of the agent name The VNC port is a deterministic FNV-1a hash of the agent name
(taken from the container hostname) mapped into [15900, 16799]. mapped into `[15900, 16799]`, written to
The port and auth mode are written to `/etc/hyperhive/gui.json` `/etc/hyperhive/gui.json` at service start so the harness can
at service start so the harness can relay connections. relay connections without a separate config flag. The unit is
`Type = "simple"` so a misconfigured weston degrades to a
The unit is deliberately built so enabling it can NEVER abort restart loop instead of blocking `nixos-container update`.
the agent's `nixos-container update`: `Type = "simple"` (so
`switch-to-configuration` doesn't block on weston readiness)
and the ExecStart script always tries to exec weston after
setup 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`.)
''; '';
}; };
config = lib.mkIf config.hyperhive.gui.enable { config = lib.mkIf config.hyperhive.gui.enable {
# neatvnc 0.9 always calls the PAM auth callback (weston_authenticate_user) # neatvnc ≥ 0.9 always calls the PAM auth callback for Apple-DH
# for Apple-DH (type 30), regardless of weston.ini auth-method=none. # (type 30), regardless of weston.ini auth-method=none.
# pam_permit.so makes the PAM service accept any credentials so the # pam_permit.so accepts the browser's empty Apple-DH credentials.
# browser's empty Apple-DH credentials always pass. # Service name MUST be the literal `weston-remote-access` — that's
# # the string libweston passes to pam_start() in libweston/auth.c.
# The service name is "weston-remote-access" — that is the literal string
# passed to pam_start() inside libweston (libweston/auth.c). Using "weston"
# instead silently falls back to the system default and rejects auth.
security.pam.services."weston-remote-access".text = '' security.pam.services."weston-remote-access".text = ''
auth sufficient pam_permit.so auth sufficient pam_permit.so
account sufficient pam_permit.so account sufficient pam_permit.so
@ -76,29 +51,21 @@
after = [ "network.target" ]; after = [ "network.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
# `simple`, not `notify`: switch-to-configuration must not
# wait on weston signalling readiness (same reasoning as the
# `tea-login` unit in harness-base.nix).
Type = "simple"; Type = "simple";
# Creates /var/lib/weston (0700 root) at start.
StateDirectory = "weston"; StateDirectory = "weston";
Environment = "XDG_RUNTIME_DIR=/run/user/0"; Environment = "XDG_RUNTIME_DIR=/run/user/0";
# Wrapper script: computes the deterministic VNC port, writes # Wrapper script: computes the deterministic VNC port, writes
# /etc/hyperhive/gui.json for the harness (issue #51), then # /etc/hyperhive/gui.json for the harness, then execs weston.
# execs weston. Using `exec` keeps the PID stable so systemd # `exec` keeps the PID stable so systemd tracks the weston
# tracks the weston process correctly under Type=simple. # process correctly under Type=simple.
# Any failure before the exec triggers Restart=on-failure
# (graceful degradation) rather than blocking the rebuild.
ExecStart = pkgs.writeShellScript "weston-vnc" '' ExecStart = pkgs.writeShellScript "weston-vnc" ''
mkdir -p /run/user/0 && chmod 700 /run/user/0 || true mkdir -p /run/user/0 && chmod 700 /run/user/0 || true
# --- Compute deterministic VNC port via FNV-1a --- # --- Compute deterministic VNC port via FNV-1a ---
# Agent name = container hostname with leading "h-" stripped, # Agent name = container hostname with leading `h-` stripped.
# mirroring lifecycle::agent_web_port in hive-c0re/src/lifecycle.rs.
# Read from /etc/hostname (always present in NixOS containers) # Read from /etc/hostname (always present in NixOS containers)
# to avoid a dependency on the `hostname` binary (which lives in # to avoid depending on `hostname` (lives in pkgs.inetutils,
# pkgs.inetutils, not pkgs.coreutils). # not pkgs.coreutils).
# VNC_PORT_BASE=15900, VNC_PORT_RANGE=900 → [15900, 16799].
RAW_HOST=$(${pkgs.coreutils}/bin/cat /etc/hostname) RAW_HOST=$(${pkgs.coreutils}/bin/cat /etc/hostname)
AGENT_NAME=$(${pkgs.coreutils}/bin/printf '%s' "$RAW_HOST" \ AGENT_NAME=$(${pkgs.coreutils}/bin/printf '%s' "$RAW_HOST" \
| ${pkgs.gnused}/bin/sed 's/^h-//') | ${pkgs.gnused}/bin/sed 's/^h-//')
@ -111,30 +78,15 @@
done done
VNC_PORT=$((15900 + hash % 900)) VNC_PORT=$((15900 + hash % 900))
# --- Write gui.json marker --- # Marker file the harness reads at startup.
# The harness reads this at startup (issue #51) to know the
# VNC port and auth mode for the WebSocket relay.
${pkgs.coreutils}/bin/mkdir -p /etc/hyperhive ${pkgs.coreutils}/bin/mkdir -p /etc/hyperhive
${pkgs.coreutils}/bin/printf '{"vnc_port":%d,"auth":"none"}\n' \ ${pkgs.coreutils}/bin/printf '{"vnc_port":%d,"auth":"none"}\n' \
"$VNC_PORT" > /etc/hyperhive/gui.json || true "$VNC_PORT" > /etc/hyperhive/gui.json || true
# neatvnc ≥ 0.9 advertises RSA-AES and Apple-DH security types # --disable-transport-layer-security: skips the VeNCrypt TLS
# when auth is compiled in. The browser client handles Apple-DH # wrapper so plain auth types (incl. Apple-DH type 30) are
# (type 30) with empty credentials. # advertised directly. [core] idle-time=0 disables the
# # compositor's 300s idle/lock screen.
# weston.ini [vnc] auth-method=none: weston uses an always-accept
# auth callback instead of PAM. Without this, weston defaults to
# PAM authentication which rejects empty credentials (SecurityResult=1).
#
# --disable-transport-layer-security prevents the VeNCrypt TLS
# wrapper; plain auth types (incl. type 30) are advertised directly.
# [core] idle-time=0 disables weston's idle timeout (default
# 300s). Without it the VNC desktop fades to black after 5 min
# idle and desktop-shell shows its click-to-unlock lock screen
# — useless for an agent desktop viewed over /screen (issue
# #180). idle-time=0 → the idle timer is updated with a 0ms
# delay, which wl_event_source_timer_update treats as "disarm",
# so the compositor never goes idle and never locks.
WESTON_INI=$(${pkgs.coreutils}/bin/mktemp /tmp/weston-XXXXXX.ini) 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" ${pkgs.coreutils}/bin/printf '[core]\nidle-time=0\n\n[vnc]\nauth-method=none\n' > "$WESTON_INI"
@ -150,8 +102,8 @@
}; };
}; };
# weston on the agent's interactive PATH too, so claude can run # weston on the agent's interactive PATH so claude can run Wayland
# Wayland clients / `weston-info` against the compositor. # clients / weston-info against the compositor.
environment.systemPackages = [ pkgs.weston ]; environment.systemPackages = [ pkgs.weston ];
}; };
} }