nix: add hive-matrix module + hyperhive.domain option (#548 part 1)

This commit is contained in:
damocles 2026-05-29 01:25:29 +02:00
commit 2b1c1b54ac
3 changed files with 259 additions and 4 deletions

View file

@ -197,10 +197,18 @@ nix/
modules/hive-c0re.nix systemd service + firewall + git wiring;
`contextWindowTokens` attrset (per-model,
injected as env vars into all containers);
imports hive-forge.nix
top-level `hyperhive.domain` option
(nullable; required when matrix.enable);
imports hive-forge.nix + hive-matrix.nix
modules/hive-forge.nix optional in-container Forgejo
(`hyperhive.forge.enable`, default on);
Catppuccin Mocha theme via tmpfiles C+ copy
modules/hive-matrix.nix optional in-container matrix-tuwunel
homeserver (`hyperhive.matrix.enable`,
default off); server_name derives from
`hyperhive.domain` (subdomain by default);
federation on (empty trusted_servers),
registration off, e2ee deferred (#551)
templates/harness-base.nix shared scaffolding for sub-agents + manager;
`hyperhive.model` option (HIVE_DEFAULT_MODEL)
templates/agent-base.nix sub-agent nixosConfiguration

View file

@ -17,8 +17,32 @@ in
{
# The forge is part of the standard install — hive-c0re mirrors
# every agent's applied config repo into it. On by default; opt out
# with `hyperhive.forge.enable = false`.
imports = [ ./hive-forge.nix ];
# with `hyperhive.forge.enable = false`. hive-matrix is opt-in (off
# by default) and asserts that `hyperhive.domain` is set before it
# can be enabled.
imports = [
./hive-forge.nix
./hive-matrix.nix
];
# Top-level option shared by any hyperhive subsystem that needs a
# stable hostname (matrix server_name today, forge ROOT_URL likely
# next). Type is nullable + default null so existing operator
# configs that don't set it still evaluate; subsystems that
# actually need it (matrix) assert non-null in their own config
# block with a helpful message.
options.hyperhive.domain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "darkest.space";
description = ''
Canonical host domain for hyperhive subsystems that need a
stable name (currently: `hyperhive.matrix.serverName` derives
from this when `useSubdomain = true`). No default subsystems
that opt to require it assert non-null in their own config and
fail eval with a helpful message if it's missing.
'';
};
options.services.hive-c0re = {
enable = lib.mkEnableOption "hive-c0re hyperhive coordinator daemon";
@ -171,7 +195,8 @@ in
# serves this via `tower_http::ServeDir` for any path it doesn't
# match against an API/action route.
HIVE_STATIC_DIR = "${cfg.frontend}/dashboard";
} // lib.optionalAttrs config.hyperhive.forge.enable {
}
// lib.optionalAttrs config.hyperhive.forge.enable {
# Agents poll this URL for Forgejo notifications. Derived from
# hyperhive.forge.{domain,httpPort} so it tracks forge config changes.
HIVE_FORGE_URL = "http://${config.hyperhive.forge.domain}:${toString config.hyperhive.forge.httpPort}";

222
nix/modules/hive-matrix.nix Normal file
View file

@ -0,0 +1,222 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.hyperhive.matrix;
hyperhiveDomain = config.hyperhive.domain;
effectiveServerName =
if cfg.serverName != null then
cfg.serverName
else if cfg.useSubdomain then
"matrix.${hyperhiveDomain}"
else
hyperhiveDomain;
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 via admin
# API only, e2ee disabled per operator call (tracked for follow-up at
# #551).
options.hyperhive.matrix = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Run hive-matrix a private matrix-tuwunel homeserver (in a
nixos-container) for hyperhive agents. Off by default while
the integration phases in; flip to `true` once the operator
has set `hyperhive.domain` and is ready to onboard agents.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.matrix-tuwunel;
defaultText = lib.literalExpression "pkgs.matrix-tuwunel";
description = ''
matrix-tuwunel package to run inside the container. Defaults
to nixpkgs's `pkgs.matrix-tuwunel`. Override to pin a
specific upstream if you need an unreleased feature.
'';
};
serverName = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "matrix.darkest.space";
description = ''
Matrix `server_name` the host part of every user ID
(`@argus:<server_name>`) and room ID minted on this
homeserver. CRITICAL: must be stable from day one because
it's embedded irrevocably in the identifiers. Defaults to
`matrix.''${hyperhive.domain}` when `useSubdomain = true`
(the typical path keeps the root domain free for the
dashboard or forge), or to `''${hyperhive.domain}` itself
when `useSubdomain = false`. Override here only if you need
a name that doesn't follow either pattern.
'';
};
useSubdomain = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
When `serverName` is unset, use `matrix.''${hyperhive.domain}`
rather than `''${hyperhive.domain}` itself. The default is
true because the root domain is usually claimed by the
dashboard or the forge; turn off only if the root domain is
free AND you want matrix to serve from there.
'';
};
httpPort = lib.mkOption {
type = lib.types.port;
default = 8008;
description = ''
TCP port tuwunel serves the matrix client-server API on.
Default 8008 is the matrix-spec well-known port. Sits
outside hyperhive's claimed ranges (dashboard 7000, manager
8000, sub-agents 8100..8999). Federation listens on
`federationPort` separately.
'';
};
federationPort = lib.mkOption {
type = lib.types.port;
default = 8448;
description = ''
TCP port tuwunel serves the matrix server-server (federation)
API on. Default 8448 is the matrix-spec well-known port.
Open in the host firewall when `openFirewall = true`; needed
for multi-hive swarms to talk to each other.
'';
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Open `httpPort` + `federationPort` in the host firewall. Off
when the homeserver should only be reachable from inside the
host (e.g. while bringing the integration up before
announcing it to other hives).
'';
};
trustedServers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "matrix.org" ];
description = ''
List of trusted matrix servers (homeservers whose signing
keys this server will fetch identity-server-style). Empty
by default federation is enabled at the protocol level
but no peer is trusted until listed here, so the homeserver
is effectively closed until the operator declares hive
peers explicitly.
'';
};
maxRequestSize = lib.mkOption {
type = lib.types.str;
default = "20000000";
description = ''
Maximum size in bytes of a single matrix client request body.
Default 20 MB matches the matrix-spec recommendation for
media uploads. String-typed to match tuwunel's TOML config
shape (it accepts both integer + size-with-suffix strings).
'';
};
};
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. `hyperhive.domain` is host-wide; matrix derives
# the server_name from it (or from `cfg.serverName` if the
# operator wants to override).
assertions = [
{
assertion = hyperhiveDomain != null || cfg.serverName != null;
message = ''
hyperhive.matrix.enable = true requires either:
- hyperhive.domain set to your host's canonical domain
(recommended; shared with forge / dashboard), or
- hyperhive.matrix.serverName set explicitly.
The matrix server_name is embedded into every user ID and
room ID on this homeserver it cannot be changed later
without losing every account and chat history. Pick a
stable hostname before enabling.
'';
}
];
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`.
privateNetwork = false;
config =
{ ... }:
{
system.stateVersion = "25.11";
services.matrix-tuwunel = {
enable = true;
package = cfg.package;
settings.global = {
server_name = effectiveServerName;
address = "0.0.0.0";
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.
allow_federation = true;
trusted_servers = cfg.trustedServers;
# Registration off — operator seeds agent accounts via
# the tuwunel admin API (mirrors the forge pattern;
# see `hive-c0re/src/matrix.rs` once #548 PR 2 lands).
allow_registration = false;
# E2EE disabled in initial rollout per operator call
# (#548) — re-enabling tracked at #551.
allow_encryption = false;
};
};
environment.systemPackages = [ cfg.package ];
};
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.httpPort
cfg.federationPort
];
};
};
}