Commit graph hyperhive/nix/host-modules/hive-matrix.nix
Author SHA1 Message Date
atlas
f022e97813 feat(3150): deliver the OIDC client secret from authelia to the homeserver
Runs on the host: the two containers share a network namespace but not a
filesystem root, so this is the only place both trees are addressable.

A copy rather than a bindMounts entry. nixos-container refuses to start
when a bind source is missing, and the secret does not exist until
authelia's first boot has minted it. The registration token dodges that
with an activation script that pre-creates the file; that is unavailable
here, because tuwunel requires the secret to exist and be non-empty, so a
placeholder would satisfy the mount and then stop the homeserver.

Bounded wait then fail, never a silent skip: authelia's container can be
up while its generator is still minting.
2026-08-14 10:56:34 +02:00
atlas
59ecefe0e1 feat(3150): wire tuwunel's identity_provider + register the authelia client
Stage 1's actual login path. The provider entry is rendered only when
sso.enable is set, via optionalAttrs rather than an empty list, so a hive
that has not opted in renders byte-identical tuwunel settings.

The secret reaches tuwunel through LoadCredential rather than as a direct
path: upstream requires the file to survive systemd sandboxing and
suggests /etc/tuwunel/, which this container has no writable etc for. The
registration token two units below already solves it the same way, and
for the same reason -- DynamicUser and PrivateUsers stay intact.

Three assertions fail at eval instead of at boot. tuwunel reads identity
providers from its config file, so a half-configured one does not hide a
login button, it can stop the homeserver from starting.
2026-08-14 10:50:29 +02:00
atlas
9fa75a7f2f feat(3150): matrix SSO options — opt-in OIDC relying party
Stage 1 of the staged rollout mara laid out on the issue: the options
and nothing that changes a running hive. `enable` defaults false, so
this commit is inert until someone opts in.

Three rules taken from hive-forge/default.nix rather than re-derived,
since it is the same pattern already merged and reviewed:

- `enable` ADDS a login path and leaves password login alone. An
  identity provider that can take the homeserver offline when it
  hiccups is worse than two ways in. Making authelia the only path is
  tuwunel's `login_with_password`, a separate and reversible switch,
  deliberately not folded in here.
- the client secret is a PATH, never a value: it has two holders in two
  containers (authelia keeps a hash, the homeserver needs plaintext),
  and a literal would be rendered into the world-readable store.
- required when enabled, no fallback. Worse here than for the forge:
  tuwunel reads OIDC from its config file rather than a database row,
  so a malformed block can stop the server outright instead of merely
  hiding a button.

Also records the constraint that shapes the whole issue — matrix SSO
lives inside the homeserver, never behind a forward-auth proxy,
because the client-server API is spoken by non-browser clients holding
matrix access tokens plus federation.
2026-08-14 10:19:50 +02:00
atlas
6caf177416 refactor(3202): matrix declares its own vhost, dns name and SPA map
Moves the matrix sub-domain vhost out of the gateway's vhosts.nix, its
`address=` rule out of dnsmasq.nix, and the Accept-header
`$matrix_spa_target` map out of the gateway's appendHttpConfig — all
three into hive-matrix.nix.

The map is the one that had no business being where it was: it exists
solely for the SPA fallback in the vhost's `/` location, and
`appendHttpConfig` is a `lines` option, so a module can contribute to
it without the gateway assembling it.

The `.well-known/matrix/*` delegation deliberately stays on the hive's
own vhost. The spec requires it at the SERVER NAME, which is the hive
domain: that is the hive answering "where is my homeserver", not the
homeserver answering for itself. Moving it would have been the obvious
symmetric thing and it would have been wrong.
2026-08-13 16:14:37 +02:00
atlas
b12be26f20 docs(3191): drop the migration history from the gateway comments
Per mara on the PR: how a thing used to be deployed stops being
relevant the moment this is merged and the hives are rebuilt, so the
comments state the current constraint and nothing about the container
that used to hold it.

Removes the two ⚠️ blocks this PR added (hive-tls-ca's ordering, the
resign propagation), the matrix ordering's, dnsmasq's resolv.conf
paragraph, and the pre-existing 'REMOVED WITH THE CONTAINER' inventory
in the gateway module. The 🚨 do-not-simplify warning on the cert-copy
unit keeps both its reasons and loses the container framing.

Kept deliberately: the two operator-facing 'this used to default to
true' option notes (migration information for someone upgrading), and
the SupplementaryGroups block, which documents why an absence is
load-bearing rather than how a deployment used to look.
2026-08-12 13:26:58 +02:00
atlas
d70b1e0a97 fix(3191): order gateway TLS against the units that read it, not a dead container
Removing the gateway container retired two things that were still
depended on: an ordering edge and a delivery path.

- hive-tls-ca ordered itself before/requiredBy
  container@hive-gateway.service. That unit no longer exists, so the CA
  was sequenced against nothing while its real consumer,
  hive-gateway-self-signed-cert (which nginx Requires=), could win the
  race and fail its copy under set -eu, blocking nginx.

- hive-tls-resign propagated a rotated leaf with
  `systemctl -M hive-gateway … || true`. The machine is gone, so both
  calls failed and both failures were swallowed: the unit logged
  "propagating" and exited 0 while nginx served the stale copy until it
  expired. Host units now, so no -M and no || true — a failed
  propagation fails the timer.

- container@hive-matrix ordered after the gateway container to get the
  resolver up first. dnsmasq is a host service now, so it orders after
  dnsmasq.service, which is what the edge always meant.
2026-08-12 12:18:13 +02:00
atlas
038d085001 feat(nix): the matrix container gets the swarm-internal trust anchor
`security.pki.certificateFiles` is read when the system is BUILT and the
swarm root is deliberately a runtime file (its key must never enter the
world-readable store), so there was nothing build-time to name and the
container trusted no swarm-internal CA. Federation with a peer whose
cert chains to the swarm root did not validate.

The bridge already existed: `lib/hive-ca-trust.nix`, which `hive-ci` and
`hive-forge` both cross. This wires the matrix container to it -- read-only
bind of the trust bundle, container ordered after `hive-tls-ca.service` --
rather than inventing a second mechanism.

Consumption is the per-runtime half, and it is not what it looks like.
tuwunel links no openssl, which makes `SSL_CERT_FILE` appear inapplicable.
It isn't: the outbound client is `reqwest` with the `rustls` feature,
which builds a `rustls_platform_verifier::Verifier`, and because tuwunel
calls `tls_certs_merge` (additive) rather than `tls_certs_only`, the
platform roots stay alongside its compiled-in webpki set. On Linux that
verifier resolves through `rustls-native-certs` to `openssl-probe`, which
reads `SSL_CERT_FILE`. Established by reading tuwunel 1.8.0 and reqwest
0.13.4 source, not inferred from the feature list -- an earlier reading of
mine concluded the opposite from the absence of a `native-roots` feature
name and was wrong.

The variable therefore names a CONCATENATION (system CAs + the bundle),
never the anchor alone: `openssl-probe` uses it *instead of* the default
store, so pointing it at the bundle would drop every public CA and break
federation with the wider matrix network -- trading a small outage for a
much larger one.

`container@hive-matrix` needed `mkMerge`: it already assigned `after` for
the gateway ordering and the helper contributes its own `after`/`requires`,
so two bare assignments would have conflicted rather than combined.

⚠️ Gate honesty: `state/eval-3093.sh` proves the mount, the ordering, the
bundle service and the env var are RENDERED -- 6 probes, including two
separate absence probes (mount and consumption are wired at different
sites, and the gateway outage happened precisely because two of three
sites tolerated a missing cert and the third did not). It CANNOT prove
tuwunel loads them: a build that ignores the variable evaluates
identically to one that honours it, and the chain above is
version-specific. That half wants an executed check on a real deploy.
2026-08-09 19:53:07 +02:00
atlas
a5210311bf feat(nix): the matrix server_name follows the swarm domain too
A swarm runs one homeserver, so its identity belongs to the swarm and
not to whichever hive happens to host it -- otherwise relocating the
container between hives reads as a different homeserver rather than a
move. `gatewayHost` moved for that reason a commit ago; `serverName`
was left behind, which made the identity hive-shaped and the routing
swarm-shaped.

⚠️ This is the one value on the page that a rebuild cannot undo.
`gatewayHost` is a routing detail clients rediscover through
`.well-known`; `server_name` is embedded in every user and room id, so
adopting a new one strands the existing accounts and rooms rather than
renaming them -- their ids still name a homeserver that stops
answering. Moving the DEFAULT is safe; moving a RUNNING deployment is
not, and existing hives must pin the value they already minted ids
under before rebuilding.

The fallback stays total on a null swarm domain for the same reason as
its neighbours: the required-domain assertion in hive-network.nix is
what should fire, not a coercion error from an unrelated option
interpolating null.

The legacy-pin eval probe now covers `serverName` as well. It existed
to answer "what do I set so old deployments don't change", and was
proving that only for the two values that are cheap to change -- the
irreversible one was the one it did not assert.
2026-08-09 17:32:44 +02:00
atlas
ff84ca947d feat(nix): the matrix gateway host moves to chat.<swarm.domain>
Both halves change: the parent, because a swarm runs one homeserver and
every hive reaches it; and the label, because `chat` names the service
people use rather than the protocol it speaks.

⚠️ `serverName` is deliberately NOT touched, and the two are now
documented as the different things they are. `gatewayHost` is a routing
detail -- the API listener nginx proxies `/_matrix/*` to, which clients
rediscover through `.well-known`. `serverName` is the matrix identifier
baked into every user and room id: changing it is a different
homeserver, not a rename, so it still falls back to the bare hive
domain. A note at the fallback says so, since that binding is where a
future edit would most plausibly "fix" the inconsistency.

Old deployments pin `matrix.<hive domain>` -- exactly what the old
default rendered -- and dnsmasq already lists `gatewayHost` explicitly,
so a pinned or moved name stays routed either way.
2026-08-09 17:32:44 +02:00
atlas
433b294099 refactor(nix): swarm.peers becomes swarm.hives, a directory of every hive
One attrset describing every hive in the swarm including this one,
identical on every host, with hiveName selecting which entry is us.
"My peers" is derived (swarm.peerHives) rather than declared.

Every field in the old per-host peer list was intrinsic to the hive it
described, never to the pair -- so the list was a directory each host
kept its own copy of. Beyond the deduplication it removes a bug class:
two hosts could hold different endpoints for the same third hive with
nothing to detect the disagreement.

Drops the per-hive caCert. Trust inside a swarm derives from the swarm
root, which every hive chains to. What that genuinely removes is
trusting a hive whose root this swarm does not own -- a cross-swarm
problem that wants a mechanism of its own, not a field that happened to
work.

The matrix container's certificateFiles block goes with it and could
NOT be migrated: that list is read at build time and the swarm root is
a runtime file (its key must never enter the store), so there is no
build-time name to put there. caCert being a nix path was precisely
what made it the build-time distribution channel. Agents are unaffected
-- hive-tls folds the root into the hive trust bundle and the meta
renderer embeds that one file. Tracked separately.

Migration is an assertion plus warnings, not a rename: hives is peers
union {self}, and the set gains a member no existing config has written
down. A rename migrates a name and a default can re-root a meaning;
neither can conjure a new member. The warning explains, the self-entry
assertion stops the build.
2026-08-05 20:44:16 +02:00
atlas
048bdd29a8 refactor(nix): make all-local a deployment mode, not a default
mara: enableAllLocalDefaults is more of a deployment mode via settings
set, less a default setting.

That reframing is the change. A mode asserts values; an option declares
what it is when nobody asks. Written as default = <flag>, every service
option had to name a switch it has no relationship to, and the answer to
what does all-local turn on was spread across five files.

Two modules now hold the two tiers - local-defaults.nix for the mode and
what it asserts directly, swarm-required-services.nix for the
services-live-here switch and the per-service enables under it. Each
service module keeps its own declaration and loses only the line about
when a deployment wants it.

mkDefault is the right precedence rather than a compromise: it beats an
option default and yields to any explicit definition, so the mode fills
in for an operator who has not spoken and never argues with one who has.
Evaluated config is unchanged in both directions.
2026-08-05 19:41:11 +02:00
atlas
6ea43c1151 feat(nix): matrix follows the swarm's shared-services toggle
Matrix is a swarm-wide service — one homeserver, not one per hive — so
its `enable` defaults from `swarm.enableRequiredServices` rather than
being flipped on its own. That toggle is off by default, so matrix is
off by default exactly as before; what changes is that a host declaring
"the swarm's services live here" gets the homeserver with it.

The forge needs no equivalent: it is mandatory and deploys with
hyperhive itself, so it has no enable to derive.
2026-08-05 17:58:31 +02:00
atlas
747f405c6f fix(nix): keep the domain-derived defaults total
The required-domain assertion in hive-network.nix could not be reached:
`forge.<domain>` and `matrix.<domain>` are evaluated while the
assertion list is, so an unset domain threw `cannot coerce null to a
string` naming one of those options instead of printing the message
that says which option to set.

Both defaults now fall back to a name under the reserved `.invalid`
TLD, which the assertion refuses to let out the door.
2026-08-05 15:56:49 +02:00
atlas
fcdff04b23 refactor(nix): move the matrix host options under services.hyperhive.swarm
Second slice of the swarm-global service consolidation, same shape as
the forge move: the operator-facing host options become
services.hyperhive.swarm.matrix.*, and one mkRenamedOptionModule on the
namespace carries the whole subtree (nested gui.* included), so there is
no leaf list to forget an entry from.

The rename lives in hive-matrix.nix, the module that declares the
options, so each service's migration stays independent of its siblings.

The per-agent hyperhive.matrix.{enable,url} and hyperhive.matrixAccounts
are a different namespace -- a client pointer at the service, not the
service -- and deliberately do not move.
2026-08-05 13:45:09 +02:00
atlas
bd06f81294 wip(#2860): c0re-facing matrix.apiUrl option + HIVE_MATRIX_API_URL export
Nix half of the 4th layer mara found (47535: core cannot assume matrix
is on localhost). Rust half (matrix.rs MATRIX_HTTP) NOT done.

Parked here rather than left dirty: she has redirected me to jobq as
prio 1, and uncommitted files migrate across a checkout.
2026-08-03 20:34:36 +02:00
müde
0d416df1e8 fix: shared-netns infra containers must not run their own firewall 2026-07-14 00:08:33 +02:00
müde
4a48ce5024 refactor: nix/host-modules + nix/agent-modules layout, update doc paths 2026-07-13 22:05:49 +02:00
Renamed from nix/modules/hive-matrix.nix (Browse further)