Compare commits

..
8 changed files with 71 additions and 180 deletions

View file

@ -184,7 +184,7 @@ services.hyperhive.gateway = {
nginx inside the gateway container obtains and auto-renews certs via the ACME HTTP-01 challenge on `port` (default 80). The gateway container shares the host network namespace (`privateNetwork = false`) so outbound ACME requests work without any extra routing. Certs are stored inside the container's persistent state dir (`/var/lib/acme/` inside `hive-gateway`; survives restarts because `ephemeral = false`). nginx inside the gateway container obtains and auto-renews certs via the ACME HTTP-01 challenge on `port` (default 80). The gateway container shares the host network namespace (`privateNetwork = false`) so outbound ACME requests work without any extra routing. Certs are stored inside the container's persistent state dir (`/var/lib/acme/` inside `hive-gateway`; survives restarts because `ephemeral = false`).
**Requirements**: `services.hyperhive.domain` must be publicly DNS-resolvable to this host, and `openFirewall = true` so Let's Encrypt can reach `/.well-known/acme-challenge/`. Each active vhost (main domain, `forge.<swarm-domain>`, `chat.<swarm-domain>`) gets its own cert via separate ACME challenges — the swarm services default to names under `services.hyperhive.swarm.domain`, so **every one of those names must resolve to this host too**, not just the hive's own. **Requirements**: `services.hyperhive.domain` must be publicly DNS-resolvable to this host, and `openFirewall = true` so Let's Encrypt can reach `/.well-known/acme-challenge/`. Each active vhost (main domain, `forge.<domain>`, `matrix.<domain>`) gets its own cert via separate ACME challenges.
Mutual exclusion: `tls.certDir` set together with `tls.acme.enable = true` fails an assertion — pick one external TLS source (or neither, for the self-signed default). Mutual exclusion: `tls.certDir` set together with `tls.acme.enable = true` fails an assertion — pick one external TLS source (or neither, for the self-signed default).
@ -308,8 +308,8 @@ The forge container shares the host network namespace
host-side service — nixos-container is here for state + systemd-unit host-side service — nixos-container is here for state + systemd-unit
isolation, not network isolation. Note this is the FORGE container; isolation, not network isolation. Note this is the FORGE container;
agent containers are network-isolated and reach the forge through the agent containers are network-isolated and reach the forge through the
gateway by `forge.<swarm-domain>` (see `HIVE_FORGE_URL` above), not via gateway by `forge.<domain>` (see `HIVE_FORGE_URL` above), not via the
the host's loopback. host's loopback.
State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/` State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/`
and survives container restart / host reboot. To wipe, destroy the and survives container restart / host reboot. To wipe, destroy the
@ -336,7 +336,7 @@ via `-p 2222`). Port 22 is left alone on the host for openssh.
`openFirewall` (default **false**) controls whether `httpPort` and `openFirewall` (default **false**) controls whether `httpPort` and
`sshPort` are opened in the host firewall. Off by default (secure by `sshPort` are opened in the host firewall. Off by default (secure by
default): agents reach Forgejo through the gateway (`forge.<swarm-domain>` on default): agents reach Forgejo through the gateway (`forge.<domain>` on
the bridge), not the raw port, so no firewall hole is needed. Flip to the bridge), not the raw port, so no firewall hole is needed. Flip to
`true` when you need: `true` when you need:
- The operator's browser to reach `http://<host>:<httpPort>/` directly - The operator's browser to reach `http://<host>:<httpPort>/` directly
@ -542,7 +542,7 @@ ordering, preventing the subrequest from looping back through
## Security headers ## Security headers
The following headers are emitted at server scope on every gateway The following headers are emitted at server scope on every gateway
vhost (`_`, `forge.<swarm-domain>`, `chat.<swarm-domain>`): vhost (`_`, `forge.<domain>`, `matrix.<domain>`):
| Header | Value | | Header | Value |
|--------|-------| |--------|-------|

View file

@ -18,7 +18,7 @@ Same shape as [`gateway.md::hive-forge container shape`](gateway.md):
separate state dir. separate state dir.
- Container shares the host network namespace - Container shares the host network namespace
(`privateNetwork = false`) for state + systemd-unit isolation. Agents (`privateNetwork = false`) for state + systemd-unit isolation. Agents
reach the homeserver at `chat.<swarm-domain>` via the gateway (agents reach the homeserver at `matrix.<domain>` via the gateway (agents
run in private netns and can't access host loopback directly). run in private netns and can't access host loopback directly).
- Persistent state at - Persistent state at
`/var/lib/nixos-containers/hive-matrix/var/lib/matrix-tuwunel/` `/var/lib/nixos-containers/hive-matrix/var/lib/matrix-tuwunel/`
@ -33,45 +33,27 @@ Two distinct hostnames:
*irrevocably* in every `@user:<server_name>` and `!room:<server_name>` *irrevocably* in every `@user:<server_name>` and `!room:<server_name>`
identifier minted on this homeserver. Cannot be changed later identifier minted on this homeserver. Cannot be changed later
without abandoning every account and chat history. Defaults to the without abandoning every account and chat history. Defaults to the
bare `services.hyperhive.swarm.domain`; clients auto-discover the bare `services.hyperhive.domain`; clients auto-discover the actual
actual API endpoint via the `.well-known/matrix/{client,server}` API endpoint via the `.well-known/matrix/{client,server}` routes
routes the gateway serves at that domain. the hive-gateway serves at that domain.
- **`gatewayHost`** — the API listener hostname, where the gateway's - **`gatewayHost`** — the API listener hostname, where the gateway's
matrix vhost proxies `/_matrix/*` to tuwunel. Defaults to matrix vhost proxies `/_matrix/*` to tuwunel. Defaults to
`chat.<services.hyperhive.swarm.domain>`. Set to `null` to skip the `matrix.<services.hyperhive.domain>` (sub-domain shape). Set to
gateway vhost (tuwunel stays direct on `httpPort`). `null` to skip the gateway vhost (tuwunel stays direct on
`httpPort`).
Both now default under the **swarm** domain, because a swarm runs one **Breaking change**: `serverName` used to default to
homeserver: tying its identity to a single hive's domain would make `matrix.${services.hyperhive.domain}`. Existing homeservers must set
relocating the container between hives look like a different the option explicitly to preserve their existing user / room IDs
homeserver. before rebuilding. The default flipped because the bare hive-domain
makes for cleaner matrix IDs and `.well-known` delegation hides the
⚠️ **They are still not interchangeable, and the difference is the sub-domain from the user-facing identifier.
cost of changing one.** `gatewayHost` is a routing detail clients
rediscover through `.well-known`, so it is safe to move on a running
deployment. `serverName` is baked into every user and room id, so
adopting a new one does **not** rename the existing users and rooms —
it strands them, because their ids still name a homeserver that no
longer answers.
**Breaking change — pin `serverName` before rebuilding.** Its default
has now moved twice: from `matrix.${services.hyperhive.domain}`, to
the bare hive domain, and now to the swarm domain. Any homeserver that
has already minted ids must name the value it minted them under:
```nix
services.hyperhive.swarm.matrix = {
# whichever this deployment already uses
serverName = config.services.hyperhive.domain;
gatewayHost = "matrix.${config.services.hyperhive.domain}";
};
```
## Default-closed firewall ## Default-closed firewall
`openFirewall` defaults to `false` (secure-by-default): the host `openFirewall` defaults to `false` (secure-by-default): the host
reaches the homeserver on loopback, and agent containers reach it reaches the homeserver on loopback, and agent containers reach it
at `chat.<swarm-domain>` via the gateway — so the firewall hole only at `matrix.<domain>` via the gateway — so the firewall hole only
matters for access from *outside* the host. Flip to `true` when matters for access from *outside* the host. Flip to `true` when
announcing the homeserver to other hives or when an external matrix announcing the homeserver to other hives or when an external matrix
client needs to reach the client-server API directly. client needs to reach the client-server API directly.

View file

@ -46,7 +46,7 @@ untouched by any of it.
| container | netns | IPv4 | listens / reached via | | container | netns | IPv4 | listens / reached via |
| -------------- | ----------------------- | -------------------- | -------------------------------------------------------------------------------------------- | | -------------- | ----------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| `hive-gateway` | host (shared) | host addresses | nginx `:80`/`:443` (every vhost); dnsmasq `bridgeIp:53` + DHCP `:67` on the bridge | | `hive-gateway` | host (shared) | host addresses | nginx `:80`/`:443` (every vhost); dnsmasq `bridgeIp:53` + DHCP `:67` on the bridge |
| `hive-forge` | host (shared) | host addresses | forgejo `:3000` http, `:2222` git-ssh; fronted by the `forge.<swarm-domain>` vhost | | `hive-forge` | host (shared) | host addresses | forgejo `:3000` http, `:2222` git-ssh; fronted by the `forge.<domain>` vhost |
| `hive-matrix` | host (shared) | host addresses | tuwunel `:8008` (+ optional federation port); fronted by the matrix vhost | | `hive-matrix` | host (shared) | host addresses | tuwunel `:8008` (+ optional federation port); fronted by the matrix vhost |
| `hive-ci` | private, veth on bridge | DHCP pool | outbound only (runner → forge); no inbound surface | | `hive-ci` | private, veth on bridge | DHCP pool | outbound only (runner → forge); no inbound surface |
| `h-<agent>` | private, veth on bridge | DHCP pool | web UI via UDS `/run/hive-agent/<name>` → nginx sub-path; in-container UI port hashed 81008999 | | `h-<agent>` | private, veth on bridge | DHCP pool | web UI via UDS `/run/hive-agent/<name>` → nginx sub-path; in-container UI port hashed 81008999 |
@ -58,9 +58,8 @@ The flows, end to end:
- **DNS** — agents query `bridgeIp:53`; hive zones are answered - **DNS** — agents query `bridgeIp:53`; hive zones are answered
authoritatively with the bridge IP, everything else forwards to the authoritatively with the bridge IP, everything else forwards to the
host's resolvers (see *Resolver behaviour* below). host's resolvers (see *Resolver behaviour* below).
- **HTTP**`forge.` and `chat.` (under `swarm.domain`) plus the hive's - **HTTP**`forge.<domain>` / matrix / dashboard names all resolve
own dashboard name resolve to the bridge IP, land on nginx to the bridge IP, land on nginx `:80`/`:443`, and proxy to forgejo
`:80`/`:443`, and proxy to forgejo
`:3000`, tuwunel `:8008`, hive-c0re `127.0.0.1:7000`, or a per-agent `:3000`, tuwunel `:8008`, hive-c0re `127.0.0.1:7000`, or a per-agent
UI unix socket. UI unix socket.
- **Internet egress** — agent default route points at the bridge IP; - **Internet egress** — agent default route points at the bridge IP;

View file

@ -157,7 +157,7 @@ evaluated outside a hive.
**`hyperhive.matrix.url`** — homeserver URL used by **`hyperhive.matrix.url`** — homeserver URL used by
`hive-matrix-daemon` when connecting via the matrix-sdk. hive-c0re `hive-matrix-daemon` when connecting via the matrix-sdk. hive-c0re
writes it into every agent at deploy time as the gateway-routed writes it into every agent at deploy time as the gateway-routed
`chat.<swarm-domain>` URL, so isolated agents can reach the homeserver. `matrix.<domain>` URL, so isolated agents can reach the homeserver.
Override per-agent when an agent should talk to a different homeserver Override per-agent when an agent should talk to a different homeserver
— for example a remote hive's tuwunel reached over a VPN, or an — for example a remote hive's tuwunel reached over a VPN, or an
external Matrix server for a federation-only agent. external Matrix server for a federation-only agent.

View file

@ -692,7 +692,7 @@ in once with the in-host tuwunel homeserver URL
(`http://localhost:8008` or whatever the matrix module exposes). (`http://localhost:8008` or whatever the matrix module exposes).
The unified nginx-front re-root to The unified nginx-front re-root to
`https://chat.${hyperhive.swarm.domain}` + `.well-known/matrix/client` `https://matrix.${hyperhive.domain}` + `.well-known/matrix/client`
auto-discovery lives in `docs/gateway.md` (atlas's lane). auto-discovery lives in `docs/gateway.md` (atlas's lane).
## FL0W page (`/flow.html`) ## FL0W page (`/flow.html`)

View file

@ -8,7 +8,6 @@ let
cfg = config.services.hyperhive.swarm.forge; cfg = config.services.hyperhive.swarm.forge;
gatewayCfg = config.services.hyperhive.gateway; gatewayCfg = config.services.hyperhive.gateway;
hyperhiveDomain = config.services.hyperhive.domain; hyperhiveDomain = config.services.hyperhive.domain;
swarmDomain = config.services.hyperhive.swarm.domain;
tlsCfg = config.services.hyperhive.tls; tlsCfg = config.services.hyperhive.tls;
# Self-signed gateway TLS: forgejo (Go) validates outbound webhook # Self-signed gateway TLS: forgejo (Go) validates outbound webhook
@ -121,14 +120,10 @@ in
domain = lib.mkOption { domain = lib.mkOption {
type = lib.types.str; type = lib.types.str;
# Under the SWARM domain, not this hive's: a swarm runs one forge # Total on a null hive domain so the required-domain assertion in
# and every hive in it reaches the same host, so the name belongs
# to the swarm rather than to whichever hive happens to run it.
#
# Total on a null swarm domain so the required-domain assertion in
# hive-network.nix is the thing that fires; see the comment there. # hive-network.nix is the thing that fires; see the comment there.
default = if swarmDomain == null then "forge.invalid" else "forge.${swarmDomain}"; default = if hyperhiveDomain == null then "forge.invalid" else "forge.${hyperhiveDomain}";
defaultText = lib.literalExpression ''"forge.''${services.hyperhive.swarm.domain}"''; defaultText = lib.literalExpression ''"forge.''${services.hyperhive.domain}"'';
example = "git.example.com"; example = "git.example.com";
description = '' description = ''
Public hostname for the forge. Doubles as both the forgejo Public hostname for the forge. Doubles as both the forgejo
@ -136,16 +131,10 @@ in
gateway vhost server-name when `behindGateway = true` gateway vhost server-name when `behindGateway = true`
(sub-domain routing see `docs/gateway.md`). (sub-domain routing see `docs/gateway.md`).
Defaults to `forge.''${services.hyperhive.swarm.domain}` the Defaults to `forge.''${services.hyperhive.domain}` (idiomatic
swarm's domain, not this hive's, because a swarm runs **one** sub-domain shape `forge` labelled under the hive's bare
forge that every hive in it talks to. domain). `services.hyperhive.domain` is required, so there's
always a domain to derive from.
A deployment that was running before this moved keeps its
current name by pinning it here:
`forge.''${services.hyperhive.domain}`, which is exactly what
the old default rendered. Certificates follow either way: the
swarm-services sub-CA is name-constrained to the configured
names (see `./swarm-ca.nix`), not to a fixed tree.
Set to a full hostname (`git.example.com`, Set to a full hostname (`git.example.com`,
`forge.internal.lan`, etc.) for a bespoke vhost shape the `forge.internal.lan`, etc.) for a bespoke vhost shape the

View file

@ -7,29 +7,8 @@
let let
cfg = config.services.hyperhive.swarm.matrix; cfg = config.services.hyperhive.swarm.matrix;
networkCfg = config.services.hyperhive.network; networkCfg = config.services.hyperhive.network;
swarmDomain = config.services.hyperhive.swarm.domain; hyperhiveDomain = config.services.hyperhive.domain;
# Falls back to the SWARM domain: a swarm runs one homeserver, so its effectiveServerName = if cfg.serverName != null then cfg.serverName else hyperhiveDomain;
# identifier belongs to the swarm rather than to whichever hive happens
# to host it — otherwise moving the container between hives would look
# like a different homeserver.
#
# ⚠️ Changing this default is a BREAKING change in a way that moving
# `gatewayHost` was not: `serverName` is baked irrevocably into every
# user and room id, so a deployment that rebuilds onto a new one is a
# *different homeserver*, not a renamed one. Existing hives pin the old
# value explicitly (see the option's description); the default is what
# a fresh swarm gets.
#
# Total on a null swarm domain, deliberately: the required-domain
# assertion in hive-network.nix is what should fire, not a coercion
# error from an unrelated option interpolating null.
effectiveServerName =
if cfg.serverName != null then
cfg.serverName
else if swarmDomain != null then
swarmDomain
else
"invalid";
# fluffychat-web build fixes: nixpkgs's `flutter341.buildFlutterApplication` # fluffychat-web build fixes: nixpkgs's `flutter341.buildFlutterApplication`
# skips the dart web-worker compile + the emscripten native_imaging # skips the dart web-worker compile + the emscripten native_imaging
@ -152,32 +131,21 @@ in
Matrix `server_name` the host part of every user ID Matrix `server_name` the host part of every user ID
(`@argus:<server_name>`) and room ID minted on this (`@argus:<server_name>`) and room ID minted on this
homeserver. CRITICAL: must be stable from day one because homeserver. CRITICAL: must be stable from day one because
it's embedded irrevocably in the identifiers. it's embedded irrevocably in the identifiers. Defaults to
`services.hyperhive.domain` (the bare hive domain). Combined
Defaults to `services.hyperhive.swarm.domain` (the bare swarm with the `.well-known/matrix/{client,server}` routes the
domain), because **a swarm runs one homeserver** tying its hive-gateway serves at that domain, clients auto-discover the
identity to a single hive's domain would make relocating the
container between hives look like a different homeserver.
Combined with the `.well-known/matrix/{client,server}` routes
the gateway serves at that domain, clients auto-discover the
actual matrix endpoint without needing a subdomain. Override actual matrix endpoint without needing a subdomain. Override
here only if you need a different server_name shape (e.g. here only if you need a different server_name shape (e.g.
`matrix.<domain>` if you want the subdomain split, or
`chat.example.org` for a bespoke hostname). `chat.example.org` for a bespoke hostname).
**Breaking change, and the one on this page that cannot be **Breaking change**: this used to default to
undone by rebuilding.** This default has now moved twice from `matrix.''${services.hyperhive.domain}`. matrix IDs embed
`matrix.''${services.hyperhive.domain}`, then to the bare hive the server_name irrevocably, so existing homeservers must
domain, and now to the swarm domain. Every existing homeserver set `services.hyperhive.swarm.matrix.serverName = "matrix.''${services.hyperhive.domain}";`
must pin whichever value it already minted ids under, e.g. explicitly to preserve their existing user / room IDs
before rebuilding.
```nix
services.hyperhive.swarm.matrix.serverName =
config.services.hyperhive.domain; # or "matrix.''${…domain}"
```
before rebuilding. Adopting a new `server_name` does not rename
the old users and rooms it strands them, because their ids
still name a homeserver that no longer answers.
''; '';
}; };
@ -226,38 +194,23 @@ in
gatewayHost = lib.mkOption { gatewayHost = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
# `chat.` under the SWARM domain — both halves change: a swarm runs # Total on a null hive domain so the required-domain assertion in
# one homeserver, and the label follows the service rather than the
# protocol.
#
# Total on a null swarm domain so the required-domain assertion in
# hive-network.nix is the thing that fires; see the comment there. # hive-network.nix is the thing that fires; see the comment there.
default = if swarmDomain == null then "chat.invalid" else "chat.${swarmDomain}"; default = if hyperhiveDomain == null then "matrix.invalid" else "matrix.${hyperhiveDomain}";
defaultText = lib.literalExpression ''"chat.''${services.hyperhive.swarm.domain}"''; defaultText = lib.literalExpression ''"matrix.''${services.hyperhive.domain}"'';
example = "matrix.example.com"; example = "matrix.example.com";
description = '' description = ''
Public hostname for the matrix homeserver behind the gateway. Public hostname for the matrix homeserver behind the gateway.
Defaults to `chat.''${services.hyperhive.swarm.domain}` the Defaults to `matrix.''${services.hyperhive.domain}` (sub-domain
swarm's domain, because a swarm runs **one** homeserver. Set to shape see `docs/gateway.md`). Set to `null` to skip the
`null` to skip the gateway vhost (tuwunel stays direct on gateway vhost (tuwunel stays direct on `httpPort`). See
`httpPort`). See `docs/gateway.md` for the vhost map + matrix `docs/gateway.md` for the vhost map + matrix discovery flow,
discovery flow, and the federation port-8448 caveat at the and the federation port-8448 caveat at the bottom of that doc.
bottom of that doc.
**`gatewayHost` and `serverName` are different things, and Note: `gatewayHost` is the API listener hostname (where nginx
they carry very different costs.** `gatewayHost` is the API proxies `/_matrix/*`); `serverName` is the matrix-identifier
listener hostname (where nginx proxies `/_matrix/*`) and is domain embedded irrevocably in user/room IDs (default = bare
free to change: it is a routing detail clients rediscover hive-domain). The two are distinct.
through `.well-known`. `serverName` is the matrix-identifier
domain embedded **irrevocably** in every user and room id
adopting a new one is a different homeserver, not a rename.
Both defaults now sit under the swarm domain, but only this
one is safe to move on a running deployment.
A deployment that was running before this moved keeps its
current name by pinning
`matrix.''${services.hyperhive.domain}` here exactly what the
old default rendered.
''; '';
}; };

View file

@ -32,7 +32,6 @@ let
cfg = config.services.hyperhive.swarm.authelia; cfg = config.services.hyperhive.swarm.authelia;
hyperhiveCfg = config.services.hyperhive; hyperhiveCfg = config.services.hyperhive;
hyperhiveDomain = hyperhiveCfg.domain; hyperhiveDomain = hyperhiveCfg.domain;
swarmDomain = hyperhiveCfg.swarm.domain;
# Upstream's `services.authelia.instances.<name>` derives the unit, # Upstream's `services.authelia.instances.<name>` derives the unit,
# user, group and StateDirectory from the instance name # user, group and StateDirectory from the instance name
@ -42,16 +41,10 @@ let
unitName = "authelia-${instance}"; unitName = "authelia-${instance}";
stateDir = "/var/lib/${unitName}"; stateDir = "/var/lib/${unitName}";
# The SWARM's domain, because that is where the protected apps now live # Total on a null hive domain for the same reason the option defaults
# (`forge.<swarm>`, `chat.<swarm>`, `auth.<swarm>`). It moves in the
# same commit as `domain` below and cannot lag it: authelia validates
# `authelia_url ⊂ cookie domain` at STARTUP, so a half-move does not
# misbehave at login — it refuses to boot.
#
# Total on a null swarm domain for the same reason the option defaults
# below are: the required-domain assertion in hive-network.nix should # below are: the required-domain assertion in hive-network.nix should
# be what an operator sees, not a coercion error from here. # be what an operator sees, not a coercion error from here.
cookieDomain = if swarmDomain == null then "invalid" else swarmDomain; cookieDomain = if hyperhiveDomain == null then "invalid" else hyperhiveDomain;
in in
{ {
options.services.hyperhive.swarm.authelia = { options.services.hyperhive.swarm.authelia = {
@ -92,25 +85,15 @@ in
domain = lib.mkOption { domain = lib.mkOption {
type = lib.types.str; type = lib.types.str;
# Under the SWARM domain, like the forge and matrix: a swarm has one # Total on a null hive domain so the required-domain assertion in
# SSO provider, and the session cookie has to reach the swarm's
# services.
#
# Total on a null swarm domain so the required-domain assertion in
# hive-network.nix is the thing that fires; see the comment there. # hive-network.nix is the thing that fires; see the comment there.
default = if swarmDomain == null then "auth.invalid" else "auth.${swarmDomain}"; default = if hyperhiveDomain == null then "auth.invalid" else "auth.${hyperhiveDomain}";
defaultText = lib.literalExpression ''"auth.''${services.hyperhive.swarm.domain}"''; defaultText = lib.literalExpression ''"auth.''${services.hyperhive.domain}"'';
example = "login.example.com"; example = "login.example.com";
description = '' description = ''
Public hostname for the SSO provider the sub-domain shape the Public hostname for the SSO provider the sub-domain shape the
forge and matrix already use, under the swarm's domain because a forge and matrix already use. Doubles as the cookie domain's
swarm has **one** SSO provider. Must be the name browsers host, so it must be the name browsers actually visit.
actually visit: it is the `authelia_url` the session cookie is
validated against.
Unlike the forge and matrix names, this one carries **no
migration pin**: nothing depends on the previous
`auth.''${services.hyperhive.domain}` yet, so it moves outright.
''; '';
}; };
@ -165,13 +148,6 @@ in
{ {
system.stateVersion = "26.05"; system.stateVersion = "26.05";
# The authelia binary itself, so an operator who gets a shell
# in here can run `authelia crypto hash generate` to make a
# password for the users file. Without it the container runs
# authelia and cannot invoke it: the unit's ExecStart resolves
# through the store path, and nothing puts the CLI on PATH.
environment.systemPackages = [ cfg.package ];
# This container shares the host netns, so its own # This container shares the host netns, so its own
# firewall.service would rewrite the HOST ruleset at every # firewall.service would rewrite the HOST ruleset at every
# boot. The host firewall owns all filtering. # boot. The host firewall owns all filtering.
@ -265,22 +241,14 @@ in
access_control.default_policy = "one_factor"; access_control.default_policy = "one_factor";
# The cookie domain is the SWARM's domain, NOT authelia's # The cookie domain is the hive's domain, NOT authelia's
# own host: the session cookie has to be sent to the apps # own host: the session cookie has to be sent to the apps
# being protected (`forge.<swarm>`, `chat.<swarm>`), and a # being protected (`<hive>`, `forge.<hive>`,
# cookie scoped to `auth.<swarm>` reaches none of them. # `matrix.<hive>`), and a cookie scoped to `auth.<hive>`
# authelia enforces the relationship from the other side # reaches none of them. authelia enforces the relationship
# too — `authelia_url` must be a sub-domain of `domain`, so # from the other side too — `authelia_url` must be a
# setting both to the same host fails validation at startup # sub-domain of `domain`, so setting both to the same host
# rather than at first login. # fails validation at startup rather than at first login.
#
# ⚠️ Known and accepted consequence while a hive keeps a
# domain outside the swarm's tree: this cookie is NOT sent
# to that hive's own surfaces (its dashboard), so SSO
# covers the swarm's services and not the hive's. It
# resolves when the hive domain moves under the swarm
# domain; until then it is a scope limit, not a bug to
# chase.
session.cookies = [ session.cookies = [
{ {
domain = cookieDomain; domain = cookieDomain;