deploy: split the homeserver's host decisions out of swarm.matrix

`swarm.*` is what a hive needs to be a *client* of the swarm. For the
homeserver that is what it IS from anywhere: its package, the name it
answers to, the ports and URLs it is reached on, and the client id it is
registered under. Whether it is exposed, which peers it trusts, how large
a request it accepts and where its host-local secrets sit are decisions
of the machine running it, so openFirewall, trustedServers,
maxRequestSize, registrationTokenFile, gui.enable and
sso.clientSecretFile move to `deploy.matrix.*`.

Two sub-blocks split rather than moving whole, on their own evidence.
`gui.enable` is whether THIS host serves the web client; `gui.package` is
which client, an artifact identity, and stays. `sso.clientSecretFile` is a
path on one host; `clientId` must match the id in authelia's register, so
it is swarm-wide. Each half now points at the other, because the rendered
docs put them on separate pages.

hive-gateway passed the whole `swarm.matrix` attrset into vhosts.nix, so
that file read a moving option through an argument with no option path
anywhere in it. It now takes `matrixDeployCfg` beside `matrixCfg` — the
only shape that carries a split namespace across that boundary.

While there: vhosts.nix read `matrixCfg.enable`, which has been a rename
alias for `deploy.matrix.enable` since the enable moved. Reading it made
the module system print `Obsolete option services.hyperhive.swarm.matrix.
enable is used` on EVERY evaluation of every host — a deprecation warning
no operator could silence, because the config tripping it was ours. That
shim lives in hive-matrix.nix rather than in this file's table, which is
why deploy.nix's header claim to be their single home is now qualified
in the new block's comment.

glue-matrix-bao-token.nix read the registration token through its own
`matrixCfg` alias; with that read repointed, the binding had no reader
left, so it goes, and the comment naming it is reworded.

module-eval gains a case configuring a hive through all six OLD paths and
asserting two rendered effects — the host firewall's port list and the
container's bind-mount table — because the new paths evaluate fine
without the shims. `gui.enable` is set to the opposite of its default so
the definition has to land rather than agreeing with it by accident.
This commit is contained in:
atlas 2026-09-07 11:23:47 +02:00 committed by mara
commit 7003d14d2c
11 changed files with 214 additions and 133 deletions

View file

@ -315,5 +315,5 @@ an error.
A surface has no URL when it isn't browser-reachable: `home` needs
`services.hyperhive.domain`; `forge` needs
`services.hyperhive.deploy.forgejo.behindGateway = true`; `matrix` needs
`services.hyperhive.swarm.matrix.gui.enable = true`. In those cases the command
`services.hyperhive.deploy.matrix.gui.enable = true`. In those cases the command
exits with a hint naming the option to set.

View file

@ -627,7 +627,7 @@ A static matrix web client (default `pkgs.fluffychat-web` rebuilt
with `--base-href /matrix/`, swappable via
`services.hyperhive.swarm.matrix.gui.package`) served by the hive-gateway
nginx container at `/matrix/` when
`services.hyperhive.swarm.matrix.gui.enable` is on (defaults to
`services.hyperhive.deploy.matrix.gui.enable` is on (defaults to
`matrix.enable`). c0re signals availability via the
`HIVE_MATRIX_GUI_ENABLED` env var → `state.matrix_gui_enabled` in
`/api/state`; the gateway does the actual static serving.

View file

@ -75,7 +75,7 @@ pub(super) struct StateSnapshot {
forge_present: bool,
/// Whether the matrix GUI is reachable at `/matrix/`. Sourced from
/// `HIVE_MATRIX_GUI_ENABLED` env var (set by the c0re NixOS module
/// when `services.hyperhive.swarm.matrix.gui.enable` is on). The gateway
/// when `services.hyperhive.deploy.matrix.gui.enable` is on). The gateway
/// (`nix/host-modules/hive-gateway/vhosts.nix`) does the actual `/matrix/` static serving;
/// this flag is just an availability signal for iris's dashboard
/// chrome so the `M4TR1X →` tab doesn't flash when the GUI is off.

View file

@ -30,7 +30,7 @@ pub(crate) async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> {
),
OpenTarget::Matrix => (
urls.matrix,
"the matrix GUI URL needs `services.hyperhive.swarm.matrix.gui.enable = true`",
"the matrix GUI URL needs `services.hyperhive.deploy.matrix.gui.enable = true`",
),
};
let url = url.with_context(|| format!("no URL available for this surface — {hint}"))?;

View file

@ -130,6 +130,37 @@ in
[ "services" "hyperhive" "deploy" "forgejo" "hostSwarmControllerTokenFile" ]
)
# The homeserver, split the same way. `swarm.matrix` keeps what it IS to
# every hive — its package, the name it answers to, the ports and URLs it
# is reached on, the client id it is registered under. These six are what
# the host running it decides. ⚠️ `enable` is NOT here: it was renamed
# earlier and its shim lives in ./hive-matrix.nix, so that file is the one
# to check before assuming this table is the whole matrix story.
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "openFirewall" ]
[ "services" "hyperhive" "deploy" "matrix" "openFirewall" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "trustedServers" ]
[ "services" "hyperhive" "deploy" "matrix" "trustedServers" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "maxRequestSize" ]
[ "services" "hyperhive" "deploy" "matrix" "maxRequestSize" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "registrationTokenFile" ]
[ "services" "hyperhive" "deploy" "matrix" "registrationTokenFile" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "gui" "enable" ]
[ "services" "hyperhive" "deploy" "matrix" "gui" "enable" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "sso" "clientSecretFile" ]
[ "services" "hyperhive" "deploy" "matrix" "sso" "clientSecretFile" ]
)
# Retention is read only where the container is defined, so it is a
# decision of the host running the store rather than something the swarm
# agrees on. The two stores keep everything else — package, domain, port

View file

@ -31,7 +31,6 @@ let
hyperhiveCfg = config.services.hyperhive;
deployCfg = hyperhiveCfg.deploy;
baoCfg = hyperhiveCfg.swarm.bao;
matrixCfg = hyperhiveCfg.swarm.matrix;
baoDeploy = deployCfg.bao;
@ -48,8 +47,9 @@ let
# A literal, not an option — ./hive-matrix.nix names its container
# `containers.hive-matrix` directly and declares no `machine` to derive it
# from, which the trust-bundle call in that file already says out loud.
# ⚠️ `matrixCfg.machine` parses fine and fails at module-system resolution,
# so this is the kind of mistake only reading the target module catches.
# ⚠️ A `swarm.matrix.machine` read parses fine and fails at module-system
# resolution, so this is the kind of mistake only reading the target module
# catches.
matrixMachine = "hive-matrix";
in
{
@ -115,8 +115,8 @@ in
fi
umask 077
printf '%s\n' "$token" > ${lib.escapeShellArg (toString matrixCfg.registrationTokenFile)}
chmod 0600 ${lib.escapeShellArg (toString matrixCfg.registrationTokenFile)}
printf '%s\n' "$token" > ${lib.escapeShellArg (toString deployCfg.matrix.registrationTokenFile)}
chmod 0600 ${lib.escapeShellArg (toString deployCfg.matrix.registrationTokenFile)}
'';
};
};

View file

@ -200,7 +200,7 @@ in
# one.
HIVE_MATRIX_API_URL = config.services.hyperhive.swarm.matrix.apiUrl;
}
// lib.optionalAttrs config.services.hyperhive.swarm.matrix.gui.enable {
// lib.optionalAttrs config.services.hyperhive.deploy.matrix.gui.enable {
# Availability flags read by the dashboard's `/api/state`.
# Matrix GUI lives entirely on the gateway nginx (matrix tab
# only shows when both flags are on). Gateway routing detail:
@ -229,7 +229,7 @@ in
//
lib.optionalAttrs
(
config.services.hyperhive.swarm.matrix.gui.enable
config.services.hyperhive.deploy.matrix.gui.enable
&& config.services.hyperhive.swarm.matrix.gatewayHost != null
)
{

View file

@ -22,6 +22,7 @@ let
# same list rather than each deciding what "a swarm service" means.
swarmServiceDomains = config.services.hyperhive.swarm.serviceDomains;
matrixCfg = config.services.hyperhive.swarm.matrix;
matrixDeployCfg = config.services.hyperhive.deploy.matrix;
networkCfg = config.services.hyperhive.network;
# Every vhost claiming `default_server`, ours and the operator's
@ -104,6 +105,7 @@ let
cfg
errorPages
matrixCfg
matrixDeployCfg
hyperhiveDomain
dashboardDist
swaggerUiTheme

View file

@ -15,7 +15,8 @@
{
lib,
cfg, # services.hyperhive.gateway
matrixCfg,
matrixCfg, # services.hyperhive.swarm.matrix — what the homeserver IS
matrixDeployCfg, # services.hyperhive.deploy.matrix — what THIS host runs
hyperhiveDomain,
dashboardDist,
swaggerUiTheme, # nix/packages/swagger-ui-theme.nix: has index.html + hyperhive-theme.css
@ -44,7 +45,8 @@ let
# `<hive>/matrix/*` → 301 → `matrix.<hive>/$1` (legacy deep-link
# shim during the fluffychat sub-domain move). See `docs/networking/gateway.md`.
matrixRedirectLocations =
lib.optionalAttrs (matrixCfg.enable && matrixCfg.gui.enable && matrixCfg.gatewayHost != null)
lib.optionalAttrs
(matrixDeployCfg.enable && matrixDeployCfg.gui.enable && matrixCfg.gatewayHost != null)
(
let
target = "${publicScheme}://${matrixCfg.gatewayHost}${publicPortSuffix}";
@ -62,7 +64,7 @@ let
# clients at `matrixCfg.gatewayHost` when set; falls back to direct
# `<hive>:<httpPort>`. CORS `*` per matrix spec. The `m.server`
# port-8448 carve-out is documented inline. See `docs/networking/gateway.md`.
wellKnownLocations = lib.optionalAttrs matrixCfg.enable (
wellKnownLocations = lib.optionalAttrs matrixDeployCfg.enable (
let
clientBaseUrl =
if matrixCfg.gatewayHost != null then

View file

@ -178,7 +178,7 @@ in
Removed rather than defaulted to true so a config that turned it
OFF fails here, where the line is, instead of silently gaining a
login flow on the next rebuild. Drop the line; if it was false,
set services.hyperhive.swarm.matrix.sso.clientSecretFile and
set services.hyperhive.deploy.matrix.sso.clientSecretFile and
services.hyperhive.swarm.authelia.url as the assertions describe.
'')
];
@ -312,6 +312,91 @@ in
'';
};
allowEncryption = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Server-side switch for matrix end-to-end encryption sets
tuwunel's `allow_encryption`. Off by default: on the hive-internal
homeserver the operator already controls the transport, so server
E2EE adds key-management overhead (cross-signing, device
verification, undecryptable-message recovery) without a clear
threat-model win for the common single-hive case. Turn on when
agents join encrypted rooms on external / federated homeservers,
or when the operator wants message contents opaque to the
homeserver admin. Independent of the agent matrix client, which
always supports decryption so it can read encrypted rooms it is
invited to regardless of this flag; this option only governs
whether THIS homeserver permits room encryption.
'';
};
gui = {
# Whether this host serves it is `deploy.matrix.gui.enable`.
package = lib.mkOption {
type = lib.types.package;
default = fluffychat-web-fixed;
defaultText = lib.literalMD ''
`pkgs.fluffychat-web` with a `postInstall` patch that adds
the three files `flutter341.buildFlutterApplication` skips.
'';
description = ''
Static web client dist served at `matrix.<hive>/`. Override
to swap fluffychat for hydrogen-web, cinny, element-web, or
an out-of-tree dist any replacement is mounted at the
sub-domain root with the upstream-default `<base href "/">`,
no sub-path gymnastics needed.
'';
};
};
# This homeserver always delegates login to the swarm's authelia, as
# an OIDC relying party — matrix SSO (`m.login.sso`), offered
# alongside password login. No toggle: a homeserver in a swarm is a
# client of that swarm's identity provider.
#
# ⚠️ Not to be confused with tuwunel's `oidc_*` settings, which point
# the other way: those make this homeserver an *authorization server*
# for matrix clients. This family makes it a *client* of an external
# identity provider. The two share the protocol's name and answer
# opposite questions.
#
# This **adds** a way in. Password login keeps working: an identity
# provider that can take the homeserver offline when it hiccups is a
# worse homeserver than one with two ways in — which is also what
# makes always-on safe. Making authelia the *only* path is a
# separate, reversible switch (tuwunel's `login_with_password`),
# deliberately not folded in here.
#
# ⚠️ Matrix SSO lives **inside** the homeserver, never behind a
# forward-auth proxy: the client-server API is spoken by non-browser
# clients holding matrix access tokens — every agent's own
# `hive-matrix-daemon` — plus federation, and a proxy in front of
# `/_matrix/` breaks all of it.
sso = {
clientId = lib.mkOption {
type = lib.types.str;
default = "tuwunel";
description = ''
OAuth2 client id this homeserver identifies itself with. Must
match the `id` of the corresponding entry in
`services.hyperhive.swarm.authelia.oidc.clients`.
The secret it pairs with is a host path, so it lives at
`services.hyperhive.deploy.matrix.sso.clientSecretFile`.
'';
};
};
};
# What stays above is what the homeserver IS from any hive's point of view:
# its package, the name it answers to, the ports and URLs it is reached on,
# and the client id it is registered under. What lives here is what the host
# running it decides — whether it is exposed, which peers it trusts, how large
# a request it accepts, and where its host-local secrets sit. Same rule as
# ./swarm-victorialogs.nix; `enable` already lives in ./deploy.nix, which also
# carries the renames.
options.services.hyperhive.deploy.matrix = {
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
@ -327,7 +412,7 @@ in
**Breaking change**: this used to default to `true`. If you
relied on the old default for external reach, add
`services.hyperhive.swarm.matrix.openFirewall = true;` to your host
`services.hyperhive.deploy.matrix.openFirewall = true;` to your host
config before rebuilding.
Note: federation (the matrix-spec well-known port 8448) is
@ -381,112 +466,43 @@ in
'';
};
allowEncryption = lib.mkOption {
gui.enable = lib.mkOption {
type = lib.types.bool;
default = false;
default = deployCfg.matrix.enable;
defaultText = lib.literalExpression "config.services.hyperhive.deploy.matrix.enable";
description = ''
Server-side switch for matrix end-to-end encryption sets
tuwunel's `allow_encryption`. Off by default: on the hive-internal
homeserver the operator already controls the transport, so server
E2EE adds key-management overhead (cross-signing, device
verification, undecryptable-message recovery) without a clear
threat-model win for the common single-hive case. Turn on when
agents join encrypted rooms on external / federated homeservers,
or when the operator wants message contents opaque to the
homeserver admin. Independent of the agent matrix client, which
always supports decryption so it can read encrypted rooms it is
invited to regardless of this flag; this option only governs
whether THIS homeserver permits room encryption.
Serve a matrix web client at `matrix.''${services.hyperhive.domain}/`.
Requires `swarm.matrix.gatewayHost != null` (default `matrix.<hive>`
when hive-domain set); the gateway itself always runs. When
off, the dashboard's `M4TR1X ` tab is hidden. See
`docs/networking/gateway.md` for the discovery flow that lets clients
auto-find the sub-domain. The client build itself is
`swarm.matrix.gui.package` which client, as opposed to whether
this host serves it.
'';
};
gui = {
enable = lib.mkOption {
type = lib.types.bool;
default = deployCfg.matrix.enable;
defaultText = lib.literalExpression "config.services.hyperhive.deploy.matrix.enable";
description = ''
Serve a matrix web client at `matrix.''${services.hyperhive.domain}/`.
Requires `matrix.gatewayHost != null` (default `matrix.<hive>`
when hive-domain set); the gateway itself always runs. When
off, the dashboard's `M4TR1X ` tab is hidden. See
`docs/networking/gateway.md` for the discovery flow that lets clients
auto-find the sub-domain.
'';
};
sso.clientSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/var/lib/tuwunel-oidc/tuwunel.secret";
description = ''
Path **inside the matrix container** holding the client
secret's plaintext.
package = lib.mkOption {
type = lib.types.package;
default = fluffychat-web-fixed;
defaultText = lib.literalMD ''
`pkgs.fluffychat-web` with a `postInstall` patch that adds
the three files `flutter341.buildFlutterApplication` skips.
'';
description = ''
Static web client dist served at `matrix.<hive>/`. Override
to swap fluffychat for hydrogen-web, cinny, element-web, or
an out-of-tree dist any replacement is mounted at the
sub-domain root with the upstream-default `<base href "/">`,
no sub-path gymnastics needed.
'';
};
};
A path, never a value: an OIDC client secret has two holders
in two containers (authelia keeps a hash, this homeserver
needs the plaintext), and a literal written here would be
rendered into the world-readable nix store.
# This homeserver always delegates login to the swarm's authelia, as
# an OIDC relying party — matrix SSO (`m.login.sso`), offered
# alongside password login. No toggle: a homeserver in a swarm is a
# client of that swarm's identity provider.
#
# ⚠️ Not to be confused with tuwunel's `oidc_*` settings, which point
# the other way: those make this homeserver an *authorization server*
# for matrix clients. This family makes it a *client* of an external
# identity provider. The two share the protocol's name and answer
# opposite questions.
#
# This **adds** a way in. Password login keeps working: an identity
# provider that can take the homeserver offline when it hiccups is a
# worse homeserver than one with two ways in — which is also what
# makes always-on safe. Making authelia the *only* path is a
# separate, reversible switch (tuwunel's `login_with_password`),
# deliberately not folded in here.
#
# ⚠️ Matrix SSO lives **inside** the homeserver, never behind a
# forward-auth proxy: the client-server API is spoken by non-browser
# clients holding matrix access tokens — every agent's own
# `hive-matrix-daemon` — plus federation, and a proxy in front of
# `/_matrix/` breaks all of it.
sso = {
clientId = lib.mkOption {
type = lib.types.str;
default = "tuwunel";
description = ''
OAuth2 client id this homeserver identifies itself with. Must
match the `id` of the corresponding entry in
`services.hyperhive.swarm.authelia.oidc.clients`.
'';
};
clientSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/var/lib/tuwunel-oidc/tuwunel.secret";
description = ''
Path **inside the matrix container** holding the client
secret's plaintext.
A path, never a value: an OIDC client secret has two holders
in two containers (authelia keeps a hash, this homeserver
needs the plaintext), and a literal written here would be
rendered into the world-readable nix store.
Required when `enable` is set deliberately no fallback. A
homeserver that boots with SSO half-configured is worse than
one that fails to evaluate: 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.
'';
};
Deliberately no fallback a homeserver that boots with SSO
half-configured is worse than one that fails to evaluate:
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. The id this pairs with is
`swarm.matrix.sso.clientId`, which is swarm-wide because it must
match authelia's register.
'';
};
};
@ -520,7 +536,7 @@ in
# page). See `services.hyperhive.swarm.controller.links`'s
# description.
services.hyperhive.swarm.controller.links =
lib.optional (cfg.gatewayHost != null && cfg.gui.enable)
lib.optional (cfg.gatewayHost != null && deployCfg.matrix.gui.enable)
{
label = "Matrix";
icon = "💬";
@ -534,7 +550,7 @@ in
# contributes instead of replacing it.
#
# The dashboard needs no equivalent — it routes by path.
services.nginx.appendHttpConfig = lib.optionalString cfg.gui.enable ''
services.nginx.appendHttpConfig = lib.optionalString deployCfg.matrix.gui.enable ''
map $http_accept $matrix_spa_target {
default "/__matrix_spa_no_html_fallback";
"~*text/html" "/index.html";
@ -569,7 +585,7 @@ in
'';
};
}
// lib.optionalAttrs cfg.gui.enable {
// lib.optionalAttrs deployCfg.matrix.gui.enable {
# fluffychat at sub-domain root, SPA-fallback via the
# Accept-header `$matrix_spa_target` map above.
"/" = {
@ -588,7 +604,7 @@ in
'';
};
}
// lib.optionalAttrs (!cfg.gui.enable) {
// lib.optionalAttrs (!deployCfg.matrix.gui.enable) {
"/" = {
return = "404";
};
@ -618,10 +634,10 @@ in
# Fail at EVAL, not at boot. tuwunel reads its identity providers
# from the config file, so a half-configured one does not hide a
# login button — it can stop the homeserver from starting at all.
assertion = cfg.sso.clientSecretFile != null;
assertion = deployCfg.matrix.sso.clientSecretFile != null;
message = ''
This homeserver's SSO login flow requires
services.hyperhive.swarm.matrix.sso.clientSecretFile the path
services.hyperhive.deploy.matrix.sso.clientSecretFile the path
(inside the matrix container) holding the OIDC client secret's
plaintext.
@ -683,7 +699,7 @@ in
# Same case, same reasoning: this host minted the secret, so it can say
# where the homeserver will find it.
services.hyperhive.swarm.matrix.sso.clientSecretFile = lib.mkIf ssoLocal (
services.hyperhive.deploy.matrix.sso.clientSecretFile = lib.mkIf ssoLocal (
lib.mkDefault matrixSecretPath
);
@ -729,7 +745,7 @@ in
set -euo pipefail
src=${lib.escapeShellArg "${autheliaCfg.hostClientSecretDir}/${cfg.sso.clientId}.secret"}
dst=${lib.escapeShellArg "/var/lib/nixos-containers/hive-matrix${toString cfg.sso.clientSecretFile}"}
dst=${lib.escapeShellArg "/var/lib/nixos-containers/hive-matrix${toString deployCfg.matrix.sso.clientSecretFile}"}
# authelia's container is up, but its first-boot generator may
# still be minting. Bounded wait, then fail: a silent skip here
@ -811,7 +827,7 @@ in
);
system.activationScripts.hive-matrix-register-token = lib.stringAfter [ "var" ] ''
tokenFile=${lib.escapeShellArg (toString cfg.registrationTokenFile)}
tokenFile=${lib.escapeShellArg (toString deployCfg.matrix.registrationTokenFile)}
if [ ! -s "$tokenFile" ]; then
mkdir -p "$(dirname "$tokenFile")"
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$tokenFile"
@ -833,8 +849,8 @@ in
# Read-only bind of the host-managed registration token; tuwunel
# reads it via systemd LoadCredential below (not directly).
bindMounts = {
${cfg.registrationTokenFile} = {
hostPath = cfg.registrationTokenFile;
${deployCfg.matrix.registrationTokenFile} = {
hostPath = deployCfg.matrix.registrationTokenFile;
isReadOnly = true;
};
}
@ -923,11 +939,11 @@ in
# `address` + `port` are upstream `listOf` — wrap singles.
address = [ "0.0.0.0" ];
port = [ cfg.httpPort ];
max_request_size = cfg.maxRequestSize;
max_request_size = deployCfg.matrix.maxRequestSize;
# Federation enabled at the protocol level; empty
# trustedServers keeps it effectively closed.
allow_federation = true;
trusted_servers = cfg.trustedServers;
trusted_servers = deployCfg.matrix.trustedServers;
# Token-gated registration. The absent
# `yes_i_am_very_very_sure_…_open_registration_…` flag
# keeps the server closed to anyone without the token.
@ -995,18 +1011,18 @@ in
# host-side chown :tuwunel / GID-pin gymnastics needed.
# See `man systemd.exec` → LoadCredential.
systemd.services.tuwunel.serviceConfig.LoadCredential = [
"registration_token:${toString cfg.registrationTokenFile}"
"registration_token:${toString deployCfg.matrix.registrationTokenFile}"
# Same mechanism, second secret. tuwunel re-reads this file on
# every OAuth exchange, not just at startup, so it has to
# outlive the unit's start — a credentials path does.
"oidc_client_secret:${toString cfg.sso.clientSecretFile}"
"oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}"
];
environment.systemPackages = [ cfg.package ];
};
};
networking.firewall = lib.mkIf cfg.openFirewall {
networking.firewall = lib.mkIf deployCfg.matrix.openFirewall {
allowedTCPPorts = [
cfg.httpPort
];

View file

@ -89,6 +89,21 @@ let
];
};
# The homeserver's turn to split. All six host-side options are set through
# their pre-rename paths — including `gui.enable`, whose value is deliberately
# the opposite of its default so the definition has to actually land. All six
# so that dropping any single shim entry fails the eval, not just the two the
# assertion reads.
matrixOldPath = hive {
deploy.matrix.enable = true;
swarm.matrix.openFirewall = true;
swarm.matrix.trustedServers = [ "matrix.example.invalid" ];
swarm.matrix.maxRequestSize = 31457280;
swarm.matrix.registrationTokenFile = "/etc/matrix/register.token";
swarm.matrix.gui.enable = false;
swarm.matrix.sso.clientSecretFile = "/etc/matrix/oidc.secret";
};
baoPkcs11 = hive {
deploy.bao.enable = true;
deploy.bao.seal = "pkcs11";
@ -236,6 +251,21 @@ let
in
builtins.elem 3000 ports && builtins.any (m: m.dest == "mirrors/tool") seeded;
}
{
# Third split, and the one whose readers were hardest to see: the
# registration token is read only through a `let` alias in another
# module, so no full path names it anywhere. Both arms read a rendered
# effect — the host firewall and the container's bind-mount table — so a
# rename that resolves but stops reaching the module still fails.
name = "a config written against the pre-rename matrix paths still opens the port and mounts the token";
ok =
let
ports = matrixOldPath.networking.firewall.allowedTCPPorts;
httpPort = matrixOldPath.services.hyperhive.swarm.matrix.httpPort;
in
builtins.elem httpPort ports
&& matrixOldPath.containers.hive-matrix.bindMounts ? "/etc/matrix/register.token";
}
{
# The gateway's per-name issuer choice. If this ever collapses to a
# constant, every swarm-service vhost serves a certificate its CA