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:
parent
f60a90d752
commit
db2a48cde6
4 changed files with 170 additions and 174 deletions
102
docs/gotchas.md
102
docs/gotchas.md
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue