docs/matrix.md: extract hive-matrix prose (#718 batch 5)
New top-level matrix doc covering everything that doesn't fit elsewhere: - Container shape (nixos-container, shared host netns, name choice, state persistence) — sibling to gateway.md::hive-forge container shape. - Identity vs API listener: serverName vs gatewayHost split with the #660 breaking change. - Default-closed firewall + federation port 8448 caveat. - Provisioning flow: registration token activation, bind-mount, LoadCredential, hive-c0re's per-agent register + access_token persistence. Captures #565 first-boot race + #644 / iris 8043 ownership shape. - Assertion rationale (serverName, gatewayHost == ""). - fluffychat-web build fixes (#685): Imaging.{js,wasm} emscripten derivation + dart compile worker fixup, build-CWD path lesson from #685 / #733. - Sequencing history. In-code # comments trim to short purpose statements + docs pointers. description = '' blocks (operator-facing options docs) preserved per iris #718. ~140 lines removed from hive-matrix.nix. `nix flake check` clean; `nix fmt` clean.
This commit is contained in:
parent
ac83404f1c
commit
a8d8159038
2 changed files with 251 additions and 142 deletions
|
|
@ -9,26 +9,16 @@ let
|
|||
hyperhiveDomain = config.services.hyperhive.domain;
|
||||
effectiveServerName = if cfg.serverName != null then cfg.serverName else hyperhiveDomain;
|
||||
|
||||
# Three files are missing from `pkgs.fluffychat-web` because
|
||||
# `flutter341.buildFlutterApplication` doesn't run the dart
|
||||
# web-worker compile pass + doesn't run the native_imaging emscripten
|
||||
# build (#685). `fluffychat-web-imaging` below builds the latter from
|
||||
# source via `pkgs.emscripten`; the worker compile is inline in
|
||||
# `fluffychat-web-fixed.postInstall`. Drop both when nixpkgs's
|
||||
# flutter builder grows worker + emcc support upstream.
|
||||
# fluffychat-web build fixes: nixpkgs's `flutter341.buildFlutterApplication`
|
||||
# skips the dart web-worker compile + the emscripten native_imaging
|
||||
# build. Two derivations below cover both. Full rationale (why
|
||||
# passthru.pubspecLock.dependencySources, why `dontConfigure`, why
|
||||
# `make -C js`, why build-CWD-relative dart path): docs/matrix.md::
|
||||
# fluffychat-web build fixes.
|
||||
|
||||
# `Imaging.{js,wasm}` built from `native_imaging`'s C source via
|
||||
# emscripten. Source comes from
|
||||
# `pkgs.fluffychat-web.passthru.pubspecLock.dependencySources` so
|
||||
# there's no parallel hash pin — version auto-syncs with nixpkgs
|
||||
# bumps. Build closure +~3.6 GiB (emscripten LLVM); runtime closure
|
||||
# is just the two output files.
|
||||
fluffychat-web-imaging = pkgs.stdenv.mkDerivation {
|
||||
pname = "fluffychat-web-imaging";
|
||||
version = pkgs.fluffychat-web.passthru.pubspecLock.dependencyVersions.native_imaging;
|
||||
|
||||
# The pub-cache derivation that fluffychat-web's flutter build uses.
|
||||
# Already in the build closure; no `fetchurl` or own hash pin.
|
||||
src = pkgs.fluffychat-web.passthru.pubspecLock.dependencySources.native_imaging;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
|
|
@ -38,21 +28,18 @@ let
|
|||
jq
|
||||
];
|
||||
|
||||
# cmake config runs inside `js/Makefile` (via `emcmake cmake`) —
|
||||
# skip the default `configurePhase` which would try to invoke
|
||||
# cmake against the package root and fail (no CMakeLists at top).
|
||||
# cmake runs inside js/Makefile via `emcmake cmake`; the default
|
||||
# configurePhase would invoke cmake at the package root (no
|
||||
# CMakeLists) and fail.
|
||||
dontConfigure = true;
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
# emscripten needs HOME + a writable cache dir for its sysroot
|
||||
# build (libc, libc++, etc. compiled to wasm on demand).
|
||||
# emscripten on-demand sysroot build needs writable HOME + cache.
|
||||
export HOME=$TMPDIR
|
||||
export EM_CACHE=$TMPDIR/.emscriptencache
|
||||
mkdir -p $EM_CACHE
|
||||
# `make -C js` keeps the build phase pwd at the source root so
|
||||
# installPhase doesn't have to know about the cd (argus 🟡 on
|
||||
# PR #697 v2 — robust against future reorders / `dontBuild`).
|
||||
# `make -C js` keeps pwd at source root for the installPhase.
|
||||
make -C js Imaging.js Imaging.wasm
|
||||
runHook postBuild
|
||||
'';
|
||||
|
|
@ -66,71 +53,37 @@ let
|
|||
'';
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "Imaging.js + Imaging.wasm built from the native_imaging dart package for fluffychat-web (#685)";
|
||||
description = "Imaging.js + Imaging.wasm built from the native_imaging dart package for fluffychat-web";
|
||||
homepage = "https://pub.dev/packages/native_imaging";
|
||||
license = licenses.agpl3Plus;
|
||||
};
|
||||
};
|
||||
|
||||
# `pkgs.fluffychat-web` with #685's three missing files patched in
|
||||
# via postInstall. Mount point is `matrix.<hive>/` (#772); upstream
|
||||
# `--base-href "/"` is correct at sub-domain root, no override.
|
||||
fluffychat-web-fixed = pkgs.fluffychat-web.overrideAttrs (old: {
|
||||
# `dart` from the flutter341 closure (already pulled, no
|
||||
# incremental closure cost) so we can compile the web-worker
|
||||
# entry point that buildFlutterApplication skips.
|
||||
# dart from the flutter341 closure (already pulled, no incremental
|
||||
# cost) to compile the web-worker entry point.
|
||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.flutter341.dart ];
|
||||
|
||||
postInstall =
|
||||
(old.postInstall or "")
|
||||
+ ''
|
||||
# `web/...` is relative to build CWD so dart's package_config
|
||||
# walk-up hits buildFlutterApplication's pub-get output (#685
|
||||
# / #733 fixup — `$src/web/...` would walk up to a read-only
|
||||
# store path with no `.dart_tool/`).
|
||||
${pkgs.flutter341.dart}/bin/dart compile js \
|
||||
-o $out/native_executor.js \
|
||||
web/native_executor.dart
|
||||
postInstall = (old.postInstall or "") + ''
|
||||
# `web/...` is BUILD-CWD-relative (not `$src/...`) so dart's
|
||||
# package_config walk-up hits buildFlutterApplication's
|
||||
# pub-get output `.dart_tool/`.
|
||||
${pkgs.flutter341.dart}/bin/dart compile js \
|
||||
-o $out/native_executor.js \
|
||||
web/native_executor.dart
|
||||
|
||||
install -m 644 ${fluffychat-web-imaging}/Imaging.js $out/Imaging.js
|
||||
install -m 644 ${fluffychat-web-imaging}/Imaging.wasm $out/Imaging.wasm
|
||||
'';
|
||||
install -m 644 ${fluffychat-web-imaging}/Imaging.js $out/Imaging.js
|
||||
install -m 644 ${fluffychat-web-imaging}/Imaging.wasm $out/Imaging.wasm
|
||||
'';
|
||||
});
|
||||
in
|
||||
{
|
||||
# Private Matrix homeserver (matrix-tuwunel — the official conduwuit
|
||||
# successor) for hyperhive agents, wrapped in a nixos-container so it
|
||||
# doesn't fight any existing `services.matrix-*` the operator may
|
||||
# already run on the host. Same shape as `nix/modules/hive-forge.nix`:
|
||||
# shared host netns (`privateNetwork = false`) so agents reach it at
|
||||
# `http://localhost:<httpPort>` (or via the configured server_name
|
||||
# for federation), nixos-container only here for state + systemd-unit
|
||||
# isolation.
|
||||
#
|
||||
# Container name `hive-matrix` (not `h-*`) so the lifecycle scanner
|
||||
# ignores it; operator manages via the standard `nixos-container` CLI.
|
||||
#
|
||||
# Persistent state at `/var/lib/nixos-containers/hive-matrix/var/lib/
|
||||
# matrix-tuwunel/` (survives container restart / host reboot). To
|
||||
# wipe, destroy the container.
|
||||
#
|
||||
# Initial rollout (#548): federation enabled (needed for multi-hive
|
||||
# swarms; trusted_servers starts empty so no actual federation traffic
|
||||
# leaves until peers are explicitly listed), registration enabled via
|
||||
# a `registration_token_file` known only to hive-c0re (so agents can't
|
||||
# self-register without going through the coordinator), e2ee disabled
|
||||
# per operator call (tracked for follow-up at #551).
|
||||
#
|
||||
# Provisioning model (matches `nix/modules/hive-forge.nix` shape):
|
||||
# hive-c0re generates a 32-byte random `registration_token` on first
|
||||
# boot, writes it to `/var/lib/hyperhive/matrix-register-token` (mode
|
||||
# 0600, root-only), and bind-mounts that file read-only into the
|
||||
# tuwunel container at the same path so tuwunel can read it via
|
||||
# `registration_token_file`. hive-c0re then uses the token to register
|
||||
# each agent account via the matrix-spec UIAA registration flow, and
|
||||
# persists the returned `access_token` to `<agent-state>/matrix-token`
|
||||
# so the agent's matrix MCP client can authenticate without ever
|
||||
# seeing the shared registration token.
|
||||
# Private matrix-tuwunel homeserver wrapped in a nixos-container,
|
||||
# optional fluffychat-web client at matrix.<hive>/. Container shape,
|
||||
# serverName vs gatewayHost split, provisioning flow (registration
|
||||
# token + LoadCredential), assertion rationale, initial rollout
|
||||
# settings: docs/matrix.md. Vhost map + discovery flow + tuning
|
||||
# knobs: docs/gateway.md.
|
||||
|
||||
options.services.hyperhive.matrix = {
|
||||
enable = lib.mkOption {
|
||||
|
|
@ -322,12 +275,10 @@ in
|
|||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# mara on #548: "there is no default, but it is required. add
|
||||
# assertion." — fail eval with a helpful message rather than
|
||||
# spawning a homeserver with a bogus server_name we can never
|
||||
# change later. `services.hyperhive.domain` is host-wide; matrix derives
|
||||
# the server_name from it (or from `cfg.serverName` if the
|
||||
# operator wants to override).
|
||||
# serverName must exist (mara on #548 — irrevocably embedded in
|
||||
# user/room IDs); gatewayHost may not be "" (argus 🟡 on #764 —
|
||||
# same footgun as forge.domain). docs/matrix.md::Assertion
|
||||
# rationale.
|
||||
assertions = [
|
||||
{
|
||||
assertion = hyperhiveDomain != null || cfg.serverName != null;
|
||||
|
|
@ -344,11 +295,6 @@ in
|
|||
'';
|
||||
}
|
||||
{
|
||||
# Same footgun as forge.domain (#754): empty string renders
|
||||
# `.<hive>` shaped garbage in both nginx server_name (treated
|
||||
# as wildcard catch-all, surprising) and /etc/hosts (invalid
|
||||
# entry). Argus 🟡 on #764 — fail loud here rather than ship
|
||||
# the surprising behaviour.
|
||||
assertion = cfg.gatewayHost == null || cfg.gatewayHost != "";
|
||||
message = ''
|
||||
services.hyperhive.matrix.gatewayHost = "" is rejected. The
|
||||
|
|
@ -361,22 +307,10 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
# Generate the registration token at system activation time, BEFORE
|
||||
# the hive-matrix container would otherwise start with an empty
|
||||
# bind-mount target (argus nit on #565: nspawn creates an empty
|
||||
# file when the host path is missing, tuwunel reads it as
|
||||
# `registration_token_file=""` and rejects every registration
|
||||
# until the next restart). Idempotent: only writes when the file
|
||||
# doesn't exist. 32-byte hex = 64 chars, same shape hive-c0re's
|
||||
# `matrix::ensure_register_token` would produce.
|
||||
#
|
||||
# Ownership: plain `root:root 0600` — tuwunel inside the container
|
||||
# runs as a hardened dynamic user (#644) and reads the token via
|
||||
# systemd's `LoadCredential=` mechanism (see container config
|
||||
# below), so it never needs direct read access on the host-side
|
||||
# file. No `chown :tuwunel` / `chmod 0640` / GID-pin gymnastics
|
||||
# required (per iris on #644 8043, dropping the shape #649
|
||||
# shipped with).
|
||||
# Activation-time token generation (argus #565: the bind-mount
|
||||
# would otherwise hand tuwunel an empty file on first boot and
|
||||
# break every registration until restart). Idempotent;
|
||||
# docs/matrix.md::Provisioning flow.
|
||||
system.activationScripts.hive-matrix-register-token = lib.stringAfter [ "var" ] ''
|
||||
tokenFile=${lib.escapeShellArg (toString cfg.registrationTokenFile)}
|
||||
if [ ! -s "$tokenFile" ]; then
|
||||
|
|
@ -385,25 +319,17 @@ in
|
|||
echo >> "$tokenFile"
|
||||
echo "hive-matrix: generated registration token at $tokenFile"
|
||||
fi
|
||||
# Always re-apply 0600 (idempotent on already-correct files;
|
||||
# also normalises any 0640 / world-readable carry-over from
|
||||
# pre-LoadCredential deployments).
|
||||
# Re-apply 0600 (normalises any pre-LoadCredential carry-over).
|
||||
chmod 0600 "$tokenFile"
|
||||
'';
|
||||
|
||||
containers.hive-matrix = {
|
||||
autoStart = true;
|
||||
ephemeral = false;
|
||||
# Share host netns — tuwunel's listeners look exactly like
|
||||
# host-side services, no port-forward plumbing, and agent
|
||||
# containers (also host netns) reach it via plain `localhost`.
|
||||
# Shared host netns — agents reach tuwunel at localhost:<port>.
|
||||
privateNetwork = false;
|
||||
# Read-only bind of the host-managed registration token so
|
||||
# tuwunel can resolve `registration_token_file` to a real
|
||||
# file inside the container. The activation script above
|
||||
# ensures the host path exists with a valid 64-char hex token
|
||||
# before any container starts, so the bind always finds real
|
||||
# content (no first-boot empty-file race; argus #565 nit).
|
||||
# Read-only bind of the host-managed registration token; tuwunel
|
||||
# reads it via systemd LoadCredential below (not directly).
|
||||
bindMounts.${cfg.registrationTokenFile} = {
|
||||
hostPath = cfg.registrationTokenFile;
|
||||
isReadOnly = true;
|
||||
|
|
@ -417,44 +343,28 @@ in
|
|||
package = cfg.package;
|
||||
settings.global = {
|
||||
server_name = effectiveServerName;
|
||||
# `address` is `listOf nonEmptyStr` upstream (multi-bind
|
||||
# support). Single-host bind goes through as a one-element list.
|
||||
# `address` + `port` are upstream `listOf` — wrap singles.
|
||||
address = [ "0.0.0.0" ];
|
||||
# `port` is `listOf port` upstream. Same shape.
|
||||
port = [ cfg.httpPort ];
|
||||
max_request_size = cfg.maxRequestSize;
|
||||
# Federation enabled at the protocol level so swarms
|
||||
# can be wired up later by extending `trustedServers`
|
||||
# without a homeserver restart. Empty trusted_servers
|
||||
# keeps it effectively closed until peers are listed.
|
||||
# Federation enabled at the protocol level; empty
|
||||
# trustedServers keeps it effectively closed.
|
||||
allow_federation = true;
|
||||
trusted_servers = cfg.trustedServers;
|
||||
# Token-gated registration: hive-c0re holds the token,
|
||||
# agents never see it. allow_registration must be true
|
||||
# for the token flow to engage; the absent
|
||||
# Token-gated registration. The absent
|
||||
# `yes_i_am_very_very_sure_…_open_registration_…` flag
|
||||
# keeps the server closed to anyone without the token.
|
||||
allow_registration = true;
|
||||
# Read the registration token via systemd's
|
||||
# `LoadCredential=` mechanism (wired below) instead of
|
||||
# the bind-mount path directly. systemd copies the host-
|
||||
# owned 0600 root:root file into a per-service
|
||||
# credentials dir owned by tuwunel's dynamic user with
|
||||
# mode 0400 — keeps `DynamicUser=true` + `PrivateUsers=true`
|
||||
# intact, no host-side `chown :tuwunel` / GID-pin
|
||||
# gymnastics required (#644 / iris on 8043).
|
||||
# LoadCredential below copies the host file into a
|
||||
# 0400 dynamic-user-owned path; tuwunel reads from there.
|
||||
registration_token_file = "/run/credentials/tuwunel.service/registration_token";
|
||||
# E2EE disabled in initial rollout per operator call
|
||||
# (#548) — re-enabling tracked at #551.
|
||||
# E2EE disabled in initial rollout (#548); re-enable at #551.
|
||||
allow_encryption = false;
|
||||
};
|
||||
};
|
||||
# `LoadCredential=<id>:<host-path>` makes systemd copy the
|
||||
# bind-mounted host file into `/run/credentials/tuwunel.service/<id>`
|
||||
# owned by the service's (dynamic) user with mode 0400 at
|
||||
# service start. The hardcoded path in `registration_token_file`
|
||||
# above is the systemd-stable credentials dir; see
|
||||
# `man systemd.exec` → LoadCredential.
|
||||
# Keeps DynamicUser=true + PrivateUsers=true intact — no
|
||||
# host-side chown :tuwunel / GID-pin gymnastics needed (#644 /
|
||||
# iris on 8043). See `man systemd.exec` → LoadCredential.
|
||||
systemd.services.tuwunel.serviceConfig.LoadCredential = [
|
||||
"registration_token:${toString cfg.registrationTokenFile}"
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in a new issue