hive-matrix

Private Matrix homeserver (matrix-tuwunel — the conduwuit successor) wrapped in a nixos-container, plus optional fluffychat-web client at matrix.<hive>/. Configured via services.hyperhive.matrix.*; vhost routing lives in gateway.md.

Container shape

Same shape as gateway.md::hive-forge container shape:

Identity vs API listener: serverName vs gatewayHost

Two distinct hostnames:

Breaking change: serverName used to default to matrix.${services.hyperhive.domain}. Existing homeservers must set the option explicitly to preserve their existing user / room IDs before rebuilding. The default flipped because the bare hive-domain makes for cleaner matrix IDs and .well-known delegation hides the sub-domain from the user-facing identifier.

Default-closed firewall

openFirewall defaults to false (secure-by-default): the homeserver is reachable from the host + every agent container via loopback either way (shared netns), so the firewall hole only matters for access from outside the host. Flip to true when announcing the homeserver to other hives or when an external matrix client needs to reach the client-server API directly.

Breaking change: used to default to true. Operators relying on external reach must add services.hyperhive.matrix.openFirewall = true; before rebuilding.

Federation port 8448 is intentionally not opened here — tuwunel serves the federation API on the same httpPort as client-server by default. Reaching it on 8448 needs either an explicit tuwunel bind to that port OR a reverse-proxy + .well-known/matrix/server delegation (the latter lives in gateway.md::Discovery flow).

Provisioning flow (registration token)

Token-gated registration: hive-c0re holds the token, agents never see it. The agent only receives the resulting access_token.

  1. System activation writes a 32-byte random hex token (64 chars) to cfg.registrationTokenFile (/var/lib/hyperhive/matrix-register-token by default), mode 0600 root:root, before any container start. Idempotent — only writes when the file is missing or empty; always re-applies 0600 (normalises any 0640 / world-readable carry-over from pre-LoadCredential deployments). This runs at activation time (not first container start) to dodge a race where nspawn creates an empty file when the bind-mount target is missing and tuwunel reads registration_token_file="", rejecting every registration until next restart.
  2. Read-only bind-mount maps the host file into the tuwunel container at the same path.
  3. systemd LoadCredential= inside the container copies the bind-mounted file into /run/credentials/tuwunel.service/registration_token, owned by tuwunel's dynamic user with mode 0400, at service start. The host file stays root:root 0600 — no chown :tuwunel / chmod 0640 / GID-pin gymnastics required. Keeps DynamicUser = true + PrivateUsers = true intact.
  4. tuwunel's registration_token_file points at the credentials path, not the original bind-mount path.
  5. hive-c0re uses the token to register each agent account via the matrix-spec UIAA registration flow, persists the returned access_token to <agent-state>/matrix-token. The agent's matrix MCP client authenticates with that access_token and never touches the shared registration token.
  6. hive-c0re restarts hive-matrix-daemon for the agent immediately after writing the token so the daemon picks up the new credential without waiting for a full container restart. If the restart fails (e.g. daemon not yet running on first boot) the error is logged as a warning and the .path-trigger sibling (hive-matrix-daemon.path watching for matrix-token appearance) brings the daemon up on the same boot cycle anyway.

Initial rollout settings:

Hive Matrix Space

On first boot (after all agent accounts are provisioned), hive-c0re creates a private Matrix Space named "hive" using the admin account (@hive:<server_name>) and invites every provisioned agent into it. This gives the operator a single Space in FluffyChat or any Matrix client that groups all agent-to-agent + operator rooms in one place.

The sweep also provisions a default hive-chat room as an m.space.child of the Space. Joining a Space doesn't auto-join child rooms — the explicit room entry ensures the operator and every agent can find a common chat room without manual setup. Room join is restricted (any Space member including the operator can join; agents are explicitly invited). Room version pinned to 10 for the restricted join floor.

State: both room IDs are persisted to /var/lib/hyperhive/matrix/ (mode 0600, owned by the hive-c0re service user):

These paths are outside every agent state dir and are NOT deleted by nixos-container destroy --purge — both survive full agent purges and are reused on re-provision.

Idempotent: if the files exist and are non-empty, the Space and room are considered already created. Delete the files to force re-creation (e.g. after a homeserver wipe).

Configuration tuning

services.hyperhive.matrix = {
  trustedServers  = [ "matrix.org" "example.com" ];   # default: []
  maxRequestSize  = 20000000;                          # default: 20 MB
};

trustedServers (default []) — list of peer homeserver names whose signing keys tuwunel will fetch and trust. Federation is enabled at the protocol level from first boot (allow_federation = true) but no remote homeserver is trusted until listed here. For a closed single-hive deployment the default empty list is correct — add peer hive domains here when connecting hives into a swarm (see docs/swarm.md).

maxRequestSize (default 20_000_000 bytes = 20 MB) — maximum size of a single matrix client request body. Matches the matrix-spec recommendation for media uploads and the upstream tuwunel default. Raise for deployments that need large file transfers; lower for resource-constrained hosts where a 20 MB request is unexpectedly large.

Assertion rationale

Two config.assertions entries fail eval early rather than ship surprising behaviour:

fluffychat-web build fixes

pkgs.fluffychat-web ships from flutter341.buildFlutterApplication, which has two upstream gaps for fluffychat's web target:

Both fixed in nix/modules/hive-matrix.nix via two derivations:

Two non-obvious fixes from review history:

Drop both derivations when nixpkgs's flutter builder grows worker

Mount point is matrix.<hive>/; upstream --base-href "/" is correct at sub-domain root, no override.