Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7c5067f79 | ||
|
|
b6df0b4afc | ||
|
|
81b9ddd189 | ||
|
|
7003d14d2c | ||
|
|
1db2ac26a7 | ||
|
|
368f5d82aa |
33 changed files with 875 additions and 455 deletions
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
Private Forgejo instance running in a nixos-container, used as the
|
||||
swarm's persistent code-collaboration surface (issues, PRs, reviews,
|
||||
attachments). Configured via `services.hyperhive.swarm.forge.*`. Container
|
||||
attachments). Configured via `services.hyperhive.swarm.forge.*` (what the
|
||||
forge IS to every hive — its package, names, ports, URLs) plus
|
||||
`services.hyperhive.deploy.forgejo.*` (what the host running it decides —
|
||||
how it is served, what it mirrors, where its host-local secrets sit).
|
||||
Container
|
||||
shape, ROOT_URL / sub-domain routing, and operator-vs-in-cluster URL
|
||||
handling live in [`docs/networking/gateway.md`](../networking/gateway.md); this file owns the
|
||||
per-agent integration story and the notification pump that wakes
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Single nginx in front of every hyperhive web surface. Runs on the **host**, next
|
|||
| `<hive>/agent/<name>/` | `_` | per-agent harness (UDS or TCP) | `agents.conf` (runtime-generated) |
|
||||
| `<hive>/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null` |
|
||||
| `<hive>/matrix/` (deprecated) | `_` | 301 → `chat.<swarm>/` | `matrix.gui.enable` |
|
||||
| `forge.<swarm>/` | `forge.<swarm>` | forgejo (`3000`) | `forge.behindGateway` |
|
||||
| `forge.<swarm>/` | `forge.<swarm>` | forgejo (`3000`) | `deploy.forgejo.behindGateway` |
|
||||
| `chat.<swarm>/_matrix/*` | `chat.<swarm>` | tuwunel (`8008`) | `matrix.gatewayHost != null` |
|
||||
| `chat.<swarm>/` | `chat.<swarm>` | fluffychat-web static | `matrix.gui.enable` |
|
||||
| `chat.<swarm>/config.json` | `chat.<swarm>` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` |
|
||||
|
|
@ -62,7 +62,7 @@ Each location carries a duplicated `auth_basic` block (separate locations don't
|
|||
`services.hyperhive.gateway.localHostsEntry = true` adds entries to the host's `/etc/hosts`:
|
||||
|
||||
- `<hive-domain>` → `127.0.0.1`
|
||||
- `forge.<swarm>` → `127.0.0.1` (when forge.behindGateway)
|
||||
- `forge.<swarm>` → `127.0.0.1` (when deploy.forgejo.behindGateway)
|
||||
- `chat.<swarm>` → `127.0.0.1` (when matrix.gatewayHost set)
|
||||
- `auth.<swarm>` → `127.0.0.1` (when deploy.authelia)
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ flip together: the primary agent-name link, the favicon fetch
|
|||
(`<url>/icon`), and the nav-strip `container`-kind links from
|
||||
`DashboardState.links` (`GET /api/dashboard-state`). `forge`-kind nav-strip links still
|
||||
resolve against `http://<host>:3000` (separate sub-domain transition
|
||||
tracked by `forge.behindGateway`); `external`-kind links are
|
||||
tracked by `deploy.forgejo.behindGateway`); `external`-kind links are
|
||||
already absolute. See `docs/web-ui/dashboard.md::Container row` for the
|
||||
frontend-side derivation.
|
||||
|
||||
|
|
@ -335,8 +335,10 @@ container.
|
|||
services.hyperhive.swarm.forge = {
|
||||
httpPort = 3000; # default — HTTP listener; outside hyperhive's 7000/8100-8999 range
|
||||
sshPort = 2222; # default — git-over-SSH; kept off 22 so it doesn't collide with the host openssh
|
||||
openFirewall = false; # default — expose httpPort + sshPort to the host firewall
|
||||
};
|
||||
# Which ports the forge answers on is swarm-wide; whether THIS host opens
|
||||
# them in its firewall is a deployment decision, so it lives under deploy.*.
|
||||
services.hyperhive.deploy.forgejo.openFirewall = false; # default
|
||||
```
|
||||
|
||||
`httpPort` (default **3000**) is the port Forgejo's HTTP server binds to.
|
||||
|
|
@ -357,7 +359,7 @@ the bridge), not the raw port, so no firewall hole is needed. Flip to
|
|||
(not behind the gateway).
|
||||
- External git clients that push/pull via SSH directly to the host.
|
||||
|
||||
Forgejo served through the gateway (`forge.behindGateway = true`) does
|
||||
Forgejo served through the gateway (`deploy.forgejo.behindGateway = true`) does
|
||||
not need `openFirewall` — the gateway's own `openFirewall` option covers
|
||||
that path.
|
||||
|
||||
|
|
@ -373,8 +375,8 @@ covers most cases:
|
|||
|
||||
| Shape | Auto-derived `ROOT_URL` |
|
||||
|---|---|
|
||||
| `behindGateway = true` | `https://<forge.domain>/` (port suffix omitted when `gateway.httpsPort == 443`) |
|
||||
| `behindGateway = false` | `http://<forge.domain>:<httpPort>/` |
|
||||
| `deploy.forgejo.behindGateway = true` | `https://<forge.domain>/` (port suffix omitted when `gateway.httpsPort == 443`) |
|
||||
| `deploy.forgejo.behindGateway = false` | `http://<forge.domain>:<httpPort>/` |
|
||||
|
||||
The gateway always terminates TLS, so the `behindGateway = true` case is
|
||||
always advertised over `https://`; only the direct (`behindGateway =
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ services.hyperhive.snapshotStore = {
|
|||
};
|
||||
|
||||
# The mesh is a hard requirement, and is asserted:
|
||||
services.hyperhive.swarm.wireguard = {
|
||||
services.hyperhive.deploy.wireguard = {
|
||||
enable = true;
|
||||
address = "10.100.0.9/24";
|
||||
privateKeyFile = "/etc/wireguard/hive.key";
|
||||
|
|
@ -37,7 +37,7 @@ The store host is a swarm member like any other: it gets an entry in
|
|||
`services.hyperhive.swarm.hives`, the same directory every host holds. See
|
||||
[swarm/](../swarm/README.md) for the mesh itself.
|
||||
|
||||
Note that the mesh is gated on `swarm.wireguard.enable`, **not** on
|
||||
Note that the mesh is gated on `deploy.wireguard.enable`, **not** on
|
||||
`c0re.enable` --- a store host runs no hive and would otherwise get no
|
||||
`wg-hive` interface at all.
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ build can't hold the runner's single slot indefinitely).
|
|||
|
||||
## Container design
|
||||
|
||||
- **Private netns, bridge-attached**: the container runs in its own network namespace (`privateNetwork = true`, `hostBridge`) and reaches hive-forge through the gateway at `http://<forge.domain>` (resolved to the bridge IP via `networking.extraHosts`). It cannot reach host-loopback services — the core dashboard at `127.0.0.1:7000` and the raw forge port are unreachable from CI. Requires `forge.behindGateway = true`.
|
||||
- **Private netns, bridge-attached**: the container runs in its own network namespace (`privateNetwork = true`, `hostBridge`) and reaches hive-forge through the gateway at `http://<forge.domain>` (resolved to the bridge IP via `networking.extraHosts`). It cannot reach host-loopback services — the core dashboard at `127.0.0.1:7000` and the raw forge port are unreachable from CI. Requires `deploy.forgejo.behindGateway = true`.
|
||||
- **Non-ephemeral**: runner credentials persist across restarts (written to container's stateDir on first registration, reused thereafter).
|
||||
- **Sandbox fallback**: nspawn containers can't create user-namespaces, so nix's sandboxing would always fail. Module sets `nix.settings.sandbox-fallback = true` in the container — nix builds run unsandboxed (safe because the container is already isolated). See `docs/process/gotchas.md`.
|
||||
- **Credential isolation**: the forge admin token (`forge-core-token`) never enters the container. hive-c0re holds it and performs all forge API calls (runner validation + registration-token mint, in `forge/ci_runner.rs`); via hive-priv it writes only the runner registration token to the host env-file `/run/hive-ci/runner-token`, which the container bind-mounts read-only.
|
||||
|
|
@ -150,10 +150,10 @@ forge provisioning sweep (orgs, agent accounts, etc.), so mirror
|
|||
seeding lives in the same place rather than a separate host-side unit.
|
||||
|
||||
**General-purpose mirrors**: you can pre-seed any external repo as a
|
||||
pull-mirror via `services.hyperhive.swarm.forge.mirrors`:
|
||||
pull-mirror via `services.hyperhive.deploy.forgejo.mirrors`:
|
||||
|
||||
```nix
|
||||
services.hyperhive.swarm.forge.mirrors = [
|
||||
services.hyperhive.deploy.forgejo.mirrors = [
|
||||
{ upstream = "https://github.com/actions/checkout"; dest = "actions/checkout"; }
|
||||
{ upstream = "https://github.com/example/tool"; dest = "mirrors/tool"; }
|
||||
];
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ evaluates cleanly points at a real machine that isn't the one you meant.
|
|||
issue. Until then, federation needs CA-issued certs (ACME). See
|
||||
`docs/integrations/matrix.md` for federation firewall + TLS requirements.
|
||||
|
||||
3. **WireGuard mesh** (optional) — `swarm.wireguard.enable` reads each
|
||||
3. **WireGuard mesh** (optional) — `deploy.wireguard.enable` reads each
|
||||
entry's `wireguardPublicKey`/`wireguardEndpoint`/`wireguardAddress`
|
||||
to configure `wg-hive`. See "WireGuard inter-hive mesh" below.
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ wg pubkey < /etc/wireguard/hive.key # → share this with peer operators
|
|||
```nix
|
||||
# hive A (pr1ma.example.com, mesh IP 10.100.0.1)
|
||||
services.hyperhive = {
|
||||
swarm.wireguard = {
|
||||
deploy.wireguard = {
|
||||
enable = true;
|
||||
privateKeyFile = "/etc/wireguard/hive.key";
|
||||
address = "10.100.0.1/24";
|
||||
|
|
@ -237,7 +237,7 @@ services.hyperhive = {
|
|||
|
||||
# hive B (edge.corp, mesh IP 10.100.0.2)
|
||||
services.hyperhive = {
|
||||
swarm.wireguard = {
|
||||
deploy.wireguard = {
|
||||
enable = true;
|
||||
privateKeyFile = "/etc/wireguard/hive.key";
|
||||
address = "10.100.0.2/24";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ than a convenience.
|
|||
|
||||
The rule is about what must stay secret, not about credentials generally.
|
||||
**Public material is a value**: a certificate, or a public nkey like
|
||||
`swarm.nats.calloutUserPublicKey`, is published to every client that connects,
|
||||
`deploy.nats.calloutUserPublicKey`, is published to every client that connects,
|
||||
so the store is a perfectly good place for it.
|
||||
|
||||
**The generator and the reader are usually in different containers.** They share
|
||||
|
|
@ -53,8 +53,8 @@ neither is a renaming of the other.
|
|||
| OIDC client secret, digest half | the same mint | `oidc-clients/<id>.digest` | authelia's own half; merged at runtime via `settingsFiles` |
|
||||
| the swarm collector's copy of its OIDC secret | `swarm-otel-oidc-secret.service` copies it from authelia's tree, **when authelia runs on this host** | `/var/lib/swarm-otel-oidc/<id>.secret` inside the `swarm-otel` container | operator provides the file and names it in `swarm.otel.clientSecretFile` — the collector need not share a host with authelia |
|
||||
| authelia subject store | `swarmctl` and `swarm-authelia-bridge` | `users.yml` — one file, read and written by both | `swarmctl`, on the host that runs authelia |
|
||||
| wireguard private key | **the operator** — `wg genkey` | whatever `swarm.wireguard.privateKeyFile` names | always operator-provided; nothing generates this for you |
|
||||
| queue auth-callout nkeys (user seed + account seed) | `swarm-nats-callout-keys` first-boot unit, when `nats.autoGenerateCallout` is set | `/var/lib/swarm-nats-callout/{callout-user,issuer}.seed`, `0600` | operator mints both with `nk` and names them in `nats.calloutUserSeedFile` / `nats.calloutIssuerSeedFile` |
|
||||
| wireguard private key | **the operator** — `wg genkey` | whatever `deploy.wireguard.privateKeyFile` names | always operator-provided; nothing generates this for you |
|
||||
| queue auth-callout nkeys (user seed + account seed) | `swarm-nats-callout-keys` first-boot unit, when `deploy.nats.autoGenerateCallout` is set | `/var/lib/swarm-nats-callout/{callout-user,issuer}.seed`, `0600` | operator mints both with `nk` and names them in `deploy.nats.calloutUserSeedFile` / `deploy.nats.calloutIssuerSeedFile` |
|
||||
| the secret store's own contents | openbao, on first `bao operator init` — **an operator action, not a unit** | inside the `swarm-bao` container, at its own `/var/lib/openbao`, kept across rebuilds by `ephemeral = false`. ⚠️ **Not a host path**: `nixos-container destroy swarm-bao` takes the raft data with it, so back up the container's tree, not `/var/lib/`. Only the store's TLS material (`/var/lib/swarm-bao-tls`) and its PKCS11 token (`/var/lib/swarm-bao-token`) are host-level | n/a — there is one store; a hive elsewhere is a _client_ of it and holds none of this |
|
||||
| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it is stated rather than inferred | n/a — only the host running the store seals anything |
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ not the definition of the case.
|
|||
|
||||
### Minting the queue's callout nkeys
|
||||
|
||||
`nats.autoGenerateCallout` mints both keypairs on the host before the queue
|
||||
`deploy.nats.autoGenerateCallout` mints both keypairs on the host before the queue
|
||||
starts. It is on by default only under `singleHostSwarm` — the one
|
||||
topology where the queue, its responder and the operator are the same person. On
|
||||
every other topology, mint them yourself:
|
||||
|
|
@ -102,8 +102,8 @@ nk -inkey callout-user.seed -pubout # → calloutUserPublicKey
|
|||
nk -inkey issuer.seed -pubout # → calloutIssuerPublicKey
|
||||
```
|
||||
|
||||
Keep both seeds at `0600` and name them in `calloutUserSeedFile` /
|
||||
`calloutIssuerSeedFile`. Possession of the **issuer** seed is the authority to
|
||||
Keep both seeds at `0600` and name them in `deploy.nats.calloutUserSeedFile` /
|
||||
`deploy.nats.calloutIssuerSeedFile`. Possession of the **issuer** seed is the authority to
|
||||
admit anyone to the queue, so it belongs wherever the responder runs and nowhere
|
||||
else.
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ See `retentionPeriod` below before leaving it at its default.
|
|||
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `deploy.victoriametrics.retentionPeriod` | Default `5y`. Lower it once you have measured how fast this swarm actually fills a disk — the default is deliberately generous because too-short silently discards history you cannot get back. |
|
||||
| `swarm.grafana.oidc.role` | Default `Admin` for everyone who logs in. Lower to `Viewer`/`Editor` if the swarm grows operators who should not be able to reconfigure Grafana. |
|
||||
| `swarm.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
||||
| `deploy.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
||||
|
||||
**Logging in.** Grafana is behind swarm SSO, so the accounts are the
|
||||
authelia ones — there is no separate Grafana password, and the local
|
||||
|
|
|
|||
|
|
@ -314,6 +314,6 @@ an error.
|
|||
|
||||
A surface has no URL when it isn't browser-reachable: `home` needs
|
||||
`services.hyperhive.domain`; `forge` needs
|
||||
`services.hyperhive.swarm.forge.behindGateway = true`; `matrix` needs
|
||||
`services.hyperhive.swarm.matrix.gui.enable = true`. In those cases the command
|
||||
`services.hyperhive.deploy.forgejo.behindGateway = true`; `matrix` needs
|
||||
`services.hyperhive.deploy.matrix.gui.enable = true`. In those cases the command
|
||||
exits with a hint naming the option to set.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -94,8 +94,8 @@ pub(super) struct StateSnapshot {
|
|||
/// Public URL of the forge (e.g.
|
||||
/// `"https://forge.pr1ma.darkest.space"`). Sourced from the
|
||||
/// `HIVE_FORGE_PUBLIC_URL` env var, which the c0re NixOS module
|
||||
/// sets from `services.hyperhive.forge.publicUrl` (defaults to
|
||||
/// the gateway vhost URL when `forge.behindGateway = true`,
|
||||
/// sets from `services.hyperhive.swarm.forge.publicUrl` (defaults to
|
||||
/// the gateway vhost URL when `deploy.forgejo.behindGateway = true`,
|
||||
/// `null` otherwise). `None` when absent — the frontend **hides**
|
||||
/// forge links rather than guessing `http://<hostname>:3000`,
|
||||
/// which is only right by accident on deployments that aren't
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ pub(crate) async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> {
|
|||
),
|
||||
OpenTarget::Forge => (
|
||||
urls.forge,
|
||||
"the public forge URL needs `services.hyperhive.forge.behindGateway = true`",
|
||||
"the public forge URL needs `services.hyperhive.deploy.forgejo.behindGateway = true`",
|
||||
),
|
||||
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}"))?;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use anyhow::{Context as _, Result, bail};
|
|||
use crate::util::query_hive_urls;
|
||||
|
||||
/// Host path of this hive's WireGuard private key (matches the
|
||||
/// `privateKeyFile` example in the swarm.wireguard nix options).
|
||||
/// `privateKeyFile` example in the deploy.wireguard nix options).
|
||||
const WG_KEY_PATH: &str = "/etc/wireguard/hive.key";
|
||||
/// The mesh interface name hive-c0re's nix module brings up.
|
||||
const WG_INTERFACE: &str = "wg-hive";
|
||||
|
|
@ -116,7 +116,7 @@ pub(crate) async fn wg_init(socket: &Path, address: Option<&str>) -> Result<()>
|
|||
println!("\nPublic key (share this with peer hives — they pass it to `hivectl wg peer`):");
|
||||
println!(" {pubkey}");
|
||||
println!("\nAdd to this hive's NixOS config:");
|
||||
println!(" services.hyperhive.swarm.wireguard = {{");
|
||||
println!(" services.hyperhive.deploy.wireguard = {{");
|
||||
println!(" enable = true;");
|
||||
println!(" privateKeyFile = \"{WG_KEY_PATH}\";");
|
||||
println!(" address = \"{addr}\";");
|
||||
|
|
@ -238,7 +238,7 @@ pub(crate) fn wg_status() -> Result<()> {
|
|||
if !status.success() {
|
||||
bail!(
|
||||
"`wg show {WG_INTERFACE}` failed — is the mesh enabled + up? \
|
||||
(services.hyperhive.swarm.wireguard.enable = true, then deploy)"
|
||||
(services.hyperhive.deploy.wireguard.enable = true, then deploy)"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -41,6 +41,22 @@ in
|
|||
[ "services" "hyperhive" "swarm" "grafana" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "socketDir" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "socketDir" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "datasourceUrl" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "datasourceUrl" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "logsDatasourceUrl" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "logsDatasourceUrl" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "plugins" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "plugins" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "victoriametrics" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "victoriametrics" "enable" ]
|
||||
|
|
@ -53,6 +69,22 @@ in
|
|||
[ "services" "hyperhive" "swarm" "controller" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "controller" "socketPath" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "socketPath" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "controller" "forgeTokenFile" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "forgeTokenFile" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "controller" "authBridgeUrl" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "authBridgeUrl" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "controller" "queue" "clientSecretFile" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "queue" "clientSecretFile" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "ui" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-ui" "enable" ]
|
||||
|
|
@ -98,6 +130,100 @@ in
|
|||
[ "services" "hyperhive" "deploy" "forgejo" "ci" "package" ]
|
||||
)
|
||||
|
||||
# The queue's callout identity. What stays under `swarm.nats` is what the
|
||||
# queue IS to every hive — the ports it answers on, the client id it is
|
||||
# registered under, the responder package; these five are what the host
|
||||
# running it decides.
|
||||
#
|
||||
# ⚠️ The two PUBLIC keys move with their seeds. A public key looks
|
||||
# swarm-wide, and the user key genuinely is published to every client that
|
||||
# connects — but peers RECEIVE it over the wire, they never configure it,
|
||||
# and splitting a keypair across two namespaces is worse than either
|
||||
# placement. `enable` moved earlier; its shim is the one above.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "nats" "autoGenerateCallout" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" "autoGenerateCallout" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "nats" "calloutUserPublicKey" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" "calloutUserPublicKey" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "nats" "calloutIssuerPublicKey" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" "calloutIssuerPublicKey" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "nats" "calloutUserSeedFile" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" "calloutUserSeedFile" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "nats" "calloutIssuerSeedFile" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" "calloutIssuerSeedFile" ]
|
||||
)
|
||||
|
||||
# The rest of the forge split. What stays under `swarm.forge` is what the
|
||||
# forge IS from any hive's point of view — its package, the names and ports
|
||||
# it answers on, the URLs it advertises, the client id it is registered
|
||||
# under; these five are what the host running it decides. `sso` splits
|
||||
# because its two halves are different facts: the client id must match the
|
||||
# entry in authelia's register, the secret is a path on this machine.
|
||||
#
|
||||
# `mirrors` renames in one entry rather than one per field: it is a single
|
||||
# option of a list-of-submodule type, so the rename carries its whole
|
||||
# value. The `ci` block above needs five because it is a plain attrset of
|
||||
# separate options, which is the case with no parent path to rename.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "forge" "behindGateway" ]
|
||||
[ "services" "hyperhive" "deploy" "forgejo" "behindGateway" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "forge" "openFirewall" ]
|
||||
[ "services" "hyperhive" "deploy" "forgejo" "openFirewall" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "forge" "mirrors" ]
|
||||
[ "services" "hyperhive" "deploy" "forgejo" "mirrors" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "forge" "sso" "clientSecretFile" ]
|
||||
[ "services" "hyperhive" "deploy" "forgejo" "sso" "clientSecretFile" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "forge" "hostSwarmControllerTokenFile" ]
|
||||
[ "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
|
||||
|
|
@ -148,6 +274,34 @@ in
|
|||
[ "services" "hyperhive" "tls" "leafValidityDays" ]
|
||||
[ "services" "hyperhive" "deploy" "hive-controller" "tls" "leafValidityDays" ]
|
||||
)
|
||||
|
||||
# The WireGuard mesh, whole. Unlike every rename above this one moves a
|
||||
# namespace rather than a toggle: nothing under it is a fact another hive
|
||||
# reads. A peer needs this host's `wireguardEndpoint` — the roster entry
|
||||
# in ./swarm.nix, which stays — and nothing about the interface this host
|
||||
# brings up. `listenPort` moves for the same reason and is easy to read
|
||||
# the other way: it is what this host *binds*, while the port a peer
|
||||
# *dials* is the one inside `wireguardEndpoint`.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "wireguard" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "wireguard" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "wireguard" "privateKeyFile" ]
|
||||
[ "services" "hyperhive" "deploy" "wireguard" "privateKeyFile" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "wireguard" "address" ]
|
||||
[ "services" "hyperhive" "deploy" "wireguard" "address" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "wireguard" "listenPort" ]
|
||||
[ "services" "hyperhive" "deploy" "wireguard" "listenPort" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "wireguard" "persistentKeepalive" ]
|
||||
[ "services" "hyperhive" "deploy" "wireguard" "persistentKeepalive" ]
|
||||
)
|
||||
];
|
||||
|
||||
# ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -220,8 +220,8 @@ in
|
|||
# breaks the moment the operator's browser hostname isn't the forge
|
||||
# host, e.g. through the gateway or a reverse proxy). Sourced from
|
||||
# `services.hyperhive.swarm.forge.publicUrl`, which itself defaults to the
|
||||
# gateway vhost URL when `behindGateway = true` and `null` otherwise
|
||||
# — see that option's doc for the "hide, don't guess" rationale.
|
||||
# gateway vhost URL when `deploy.forgejo.behindGateway = true` and `null`
|
||||
# otherwise — see that option's doc for the "hide, don't guess" rationale.
|
||||
# Absent here whenever `publicUrl` is `null`; the dashboard hides
|
||||
# forge links rather than emitting one it can't justify.
|
||||
HIVE_FORGE_PUBLIC_URL = config.services.hyperhive.swarm.forge.publicUrl;
|
||||
|
|
@ -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
|
||||
)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
let
|
||||
cfg = config.services.hyperhive.deploy.forgejo.ci;
|
||||
forgeCfg = config.services.hyperhive.swarm.forge;
|
||||
forgeDeployCfg = config.services.hyperhive.deploy.forgejo;
|
||||
gatewayCfg = config.services.hyperhive.gateway;
|
||||
networkCfg = config.services.hyperhive.network;
|
||||
tlsCfg = config.services.hyperhive.deploy.hive-controller.tls;
|
||||
|
|
@ -79,8 +80,8 @@ in
|
|||
example = true;
|
||||
description = ''
|
||||
Run a Forgejo Actions runner in a `hive-ci` nixos-container.
|
||||
Grouped under `services.hyperhive.swarm.forge` because the runner is
|
||||
tightly coupled to the forge instance it registers against.
|
||||
Grouped under `services.hyperhive.deploy.forgejo` because the runner
|
||||
is tightly coupled to the forge instance it registers against.
|
||||
Disabled by default; the internal forge it registers against is
|
||||
always present (mandatory), so enabling this is all that's needed.
|
||||
|
||||
|
|
@ -153,17 +154,17 @@ in
|
|||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# `forge.behindGateway = true` (the default) is required because the
|
||||
# CI container uses private networking and reaches the forge through
|
||||
# `deploy.forgejo.behindGateway = true` (the default) is required because
|
||||
# the CI container uses private networking and reaches the forge through
|
||||
# the gateway vhost. Without the gateway vhost there is no HTTP
|
||||
# listener for `forgeCfg.domain` on the bridge that the runner can
|
||||
# connect to.
|
||||
assertions = [
|
||||
{
|
||||
assertion = forgeCfg.behindGateway;
|
||||
assertion = forgeDeployCfg.behindGateway;
|
||||
message = ''
|
||||
services.hyperhive.deploy.forgejo.ci.enable requires
|
||||
services.hyperhive.swarm.forge.behindGateway = true.
|
||||
services.hyperhive.deploy.forgejo.behindGateway = true.
|
||||
The CI container runs with a private network namespace and
|
||||
reaches the forge through the gateway vhost on the bridge IP.
|
||||
Set behindGateway = true (it defaults to true alongside
|
||||
|
|
@ -216,8 +217,8 @@ in
|
|||
# Private network namespace, attached to the hive bridge so the
|
||||
# runner reaches the forge via the gateway — and cannot reach
|
||||
# host-loopback (127.0.0.1:7000 dashboard, raw forge port, etc.).
|
||||
# Requires `forge.behindGateway = true` (asserted in the options
|
||||
# block above). See docs/networking/network.md.
|
||||
# Requires `deploy.forgejo.behindGateway = true` (asserted in the
|
||||
# options block above). See docs/networking/network.md.
|
||||
privateNetwork = true;
|
||||
hostBridge = networkCfg.bridgeName;
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ let
|
|||
# links. Operators can still override via `cfg.rootUrl` for bespoke
|
||||
# shapes.
|
||||
defaultRootUrl =
|
||||
if cfg.behindGateway then
|
||||
if deployCfg.forgejo.behindGateway then
|
||||
let
|
||||
portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}";
|
||||
in
|
||||
|
|
@ -98,9 +98,9 @@ let
|
|||
# operator hasn't already declared that dest themselves (else CI-on +
|
||||
# an explicit `actions/checkout` entry would duplicate it).
|
||||
effectiveMirrors =
|
||||
cfg.mirrors
|
||||
deployCfg.forgejo.mirrors
|
||||
++ lib.optional (
|
||||
ciEnabled && !(lib.any (m: m.dest == actionCheckoutMirror.dest) cfg.mirrors)
|
||||
ciEnabled && !(lib.any (m: m.dest == actionCheckoutMirror.dest) deployCfg.forgejo.mirrors)
|
||||
) actionCheckoutMirror;
|
||||
in
|
||||
{
|
||||
|
|
@ -133,7 +133,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 provider on the next rebuild. Drop the line; if it was
|
||||
false, set services.hyperhive.swarm.forge.sso.clientSecretFile and
|
||||
false, set services.hyperhive.deploy.forgejo.sso.clientSecretFile and
|
||||
services.hyperhive.swarm.authelia.url as the assertions describe.
|
||||
'')
|
||||
];
|
||||
|
|
@ -178,7 +178,7 @@ in
|
|||
description = ''
|
||||
Public hostname for the forge. Doubles as both the forgejo
|
||||
`DOMAIN` setting (clone URLs forgejo advertises) AND the
|
||||
gateway vhost server-name when `behindGateway = true`
|
||||
gateway vhost server-name when `deploy.forgejo.behindGateway = true`
|
||||
(sub-domain routing — see `docs/networking/gateway.md`).
|
||||
|
||||
Defaults to `forge.''${services.hyperhive.swarm.domain}` — the
|
||||
|
|
@ -201,7 +201,10 @@ in
|
|||
publicUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default =
|
||||
if config.services.hyperhive.enable && cfg.behindGateway then "https://${cfg.domain}" else null;
|
||||
if config.services.hyperhive.enable && deployCfg.forgejo.behindGateway then
|
||||
"https://${cfg.domain}"
|
||||
else
|
||||
null;
|
||||
defaultText = lib.literalExpression ''
|
||||
if behindGateway then "https://''${domain}" else null
|
||||
'';
|
||||
|
|
@ -212,7 +215,7 @@ in
|
|||
the approval-queue's "review PR on forge" link) — sourced into
|
||||
every agent container + hive-c0re as `HIVE_FORGE_PUBLIC_URL`.
|
||||
|
||||
Defaults to `https://''${cfg.domain}` when `behindGateway =
|
||||
Defaults to `https://''${cfg.domain}` when `deploy.forgejo.behindGateway =
|
||||
true` (the gateway vhost is genuinely reachable at that URL)
|
||||
and `null` otherwise. When `null`, the dashboard **hides**
|
||||
forge links rather than guessing one — see
|
||||
|
|
@ -221,7 +224,7 @@ in
|
|||
container port is only an accident away from wrong on any
|
||||
deployment that isn't plain localhost).
|
||||
|
||||
**Set this explicitly if `behindGateway = false`** and the
|
||||
**Set this explicitly if `deploy.forgejo.behindGateway = false`** and the
|
||||
forge is still reachable at a stable URL you want linked from
|
||||
the dashboard (e.g. `http://<lan-host>:''${toString cfg.httpPort}`
|
||||
for an all-LAN deployment) — leaving it unset there means the
|
||||
|
|
@ -244,6 +247,60 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
rootUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "https://forge.example.com/";
|
||||
description = ''
|
||||
Override the auto-derived forgejo `ROOT_URL`. When `null`
|
||||
(default), `ROOT_URL` is derived from `cfg.domain` + gateway
|
||||
state, including the scheme:
|
||||
|
||||
- `deploy.forgejo.behindGateway = true` → `https://''${cfg.domain}/`. The gateway
|
||||
always terminates TLS (self-signed is the implicit floor when no
|
||||
`gateway.tls.certDir` / ACME is set), so the forge is always
|
||||
advertised over https. A non-canonical `gateway.httpsPort` is
|
||||
appended as `:<port>`.
|
||||
- `deploy.forgejo.behindGateway = false` → `http://''${cfg.domain}:''${cfg.httpPort}/`
|
||||
|
||||
The TLS scheme is derived automatically now, so you only need to
|
||||
set this for a genuinely bespoke shape (e.g. an external reverse
|
||||
proxy on a different host/path). Must end with `/` per forgejo's
|
||||
`ROOT_URL` contract.
|
||||
'';
|
||||
};
|
||||
|
||||
# The swarm's authelia is always registered as an OpenID Connect
|
||||
# login source here — there is no toggle, for the same reason the
|
||||
# forge itself has none.
|
||||
#
|
||||
# **Additive, never exclusive.** Forgejo keeps its local password
|
||||
# database and gains an extra "sign in with" button; this does not
|
||||
# disable local login. Deliberate: an identity provider that can take
|
||||
# the forge offline when it hiccups is a worse forge than one with
|
||||
# two ways in — which is also what makes always-on safe.
|
||||
sso = {
|
||||
clientId = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "forgejo";
|
||||
description = ''
|
||||
OAuth2 client id this forge identifies itself with. Must match
|
||||
the `id` of the corresponding entry in
|
||||
`services.hyperhive.swarm.authelia.oidc.clients`.
|
||||
'';
|
||||
};
|
||||
# The secret half is a path on the host that runs the forge, so it
|
||||
# lives under `deploy.forgejo.sso` — see the block below.
|
||||
};
|
||||
};
|
||||
|
||||
# What stays above is what the forge IS from any hive's point of view: its
|
||||
# package, the names and ports it answers on, the URLs it advertises, and
|
||||
# the client id it is registered under. What lives here is what the host
|
||||
# running it decides — how it is served, what it mirrors, and where its
|
||||
# host-local secrets sit. Same rule as ./swarm-victorialogs.nix, and the
|
||||
# renames are in ./deploy.nix with the rest.
|
||||
options.services.hyperhive.deploy.forgejo = {
|
||||
behindGateway = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.hyperhive.enable;
|
||||
|
|
@ -274,29 +331,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
rootUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "https://forge.example.com/";
|
||||
description = ''
|
||||
Override the auto-derived forgejo `ROOT_URL`. When `null`
|
||||
(default), `ROOT_URL` is derived from `cfg.domain` + gateway
|
||||
state, including the scheme:
|
||||
|
||||
- `behindGateway = true` → `https://''${cfg.domain}/`. The gateway
|
||||
always terminates TLS (self-signed is the implicit floor when no
|
||||
`gateway.tls.certDir` / ACME is set), so the forge is always
|
||||
advertised over https. A non-canonical `gateway.httpsPort` is
|
||||
appended as `:<port>`.
|
||||
- `behindGateway = false` → `http://''${cfg.domain}:''${cfg.httpPort}/`
|
||||
|
||||
The TLS scheme is derived automatically now, so you only need to
|
||||
set this for a genuinely bespoke shape (e.g. an external reverse
|
||||
proxy on a different host/path). Must end with `/` per forgejo's
|
||||
`ROOT_URL` contract.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
|
|
@ -312,8 +346,8 @@ in
|
|||
|
||||
**Breaking change**: this used to default to `true`. If you
|
||||
relied on the old default for external reach, add
|
||||
`services.hyperhive.swarm.forge.openFirewall = true;` to your host
|
||||
config before rebuilding.
|
||||
`services.hyperhive.deploy.forgejo.openFirewall = true;` to your
|
||||
host config before rebuilding.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -360,27 +394,10 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
# The swarm's authelia is always registered as an OpenID Connect
|
||||
# login source here — there is no toggle, for the same reason the
|
||||
# forge itself has none.
|
||||
#
|
||||
# **Additive, never exclusive.** Forgejo keeps its local password
|
||||
# database and gains an extra "sign in with" button; this does not
|
||||
# disable local login. Deliberate: an identity provider that can take
|
||||
# the forge offline when it hiccups is a worse forge than one with
|
||||
# two ways in — which is also what makes always-on safe.
|
||||
sso = {
|
||||
clientId = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "forgejo";
|
||||
description = ''
|
||||
OAuth2 client id this forge identifies itself with. Must match
|
||||
the `id` of the corresponding entry in
|
||||
`services.hyperhive.swarm.authelia.oidc.clients`.
|
||||
'';
|
||||
};
|
||||
|
||||
clientSecretFile = lib.mkOption {
|
||||
# The other half of the SSO pair: the client *id* is swarm-wide (it has
|
||||
# to match authelia's register), the secret is a path on this host. The
|
||||
# design note for the login source itself is with the id.
|
||||
sso.clientSecretFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/var/lib/hyperhive/forge-oidc-secret";
|
||||
|
|
@ -399,7 +416,6 @@ in
|
|||
eval error.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
hostSwarmControllerTokenFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -443,13 +459,13 @@ in
|
|||
# Both halves are gated on `behindGateway`: with it off the operator
|
||||
# fronts forgejo themselves, so this hive must neither claim the
|
||||
# vhost nor answer DNS for it.
|
||||
services.hyperhive.gateway.localNames = lib.optional cfg.behindGateway cfg.domain;
|
||||
services.hyperhive.gateway.localNames = lib.optional deployCfg.forgejo.behindGateway cfg.domain;
|
||||
|
||||
# This swarm-ui quick-links entry, same `behindGateway` guard as the
|
||||
# vhost/DNS name above — with it off, this host doesn't actually
|
||||
# serve `cfg.domain`, so linking to it would be dead. See
|
||||
# `services.hyperhive.swarm.controller.links`'s description.
|
||||
services.hyperhive.swarm.controller.links = lib.optional cfg.behindGateway {
|
||||
services.hyperhive.swarm.controller.links = lib.optional deployCfg.forgejo.behindGateway {
|
||||
label = "Forge";
|
||||
icon = "⚒";
|
||||
url = "https://${cfg.domain}/";
|
||||
|
|
@ -466,7 +482,9 @@ in
|
|||
# what authelia compares against — this string agreeing with the
|
||||
# `location` block above it is the whole mechanism. A near miss is a
|
||||
# correctly minted token refused at the target.
|
||||
services.hyperhive.swarm.otel.publishedScrapeTargets = lib.optionalAttrs cfg.behindGateway {
|
||||
services.hyperhive.swarm.otel.publishedScrapeTargets =
|
||||
lib.optionalAttrs deployCfg.forgejo.behindGateway
|
||||
{
|
||||
forgejo = "https://${cfg.domain}/metrics";
|
||||
};
|
||||
|
||||
|
|
@ -474,7 +492,7 @@ in
|
|||
# git: `client_max_body_size 1G`, `proxy_read_timeout 1h` (multi-GB
|
||||
# clones). SSH stays direct on `forge.sshPort`. See
|
||||
# `docs/networking/gateway.md`.
|
||||
services.nginx.virtualHosts = lib.optionalAttrs cfg.behindGateway {
|
||||
services.nginx.virtualHosts = lib.optionalAttrs deployCfg.forgejo.behindGateway {
|
||||
"${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
|
||||
listen = gatewayCfg.lib.listen;
|
||||
extraConfig = gatewayCfg.lib.securityHeaders;
|
||||
|
|
@ -543,10 +561,10 @@ in
|
|||
{
|
||||
# Fail at EVAL, not at boot. The alternative failure is a login
|
||||
# button that always 401s, three layers from the missing file.
|
||||
assertion = cfg.sso.clientSecretFile != null;
|
||||
assertion = deployCfg.forgejo.sso.clientSecretFile != null;
|
||||
message = ''
|
||||
The forge's SSO login source requires
|
||||
services.hyperhive.swarm.forge.sso.clientSecretFile — the path
|
||||
services.hyperhive.deploy.forgejo.sso.clientSecretFile — the path
|
||||
(inside the forge container) holding the OIDC client secret's
|
||||
plaintext.
|
||||
|
||||
|
|
@ -600,7 +618,7 @@ in
|
|||
# splits on the single slash to create the org + repo.
|
||||
assertion = lib.all (m: lib.length (lib.splitString "/" m.dest) == 2) effectiveMirrors;
|
||||
message = ''
|
||||
Every services.hyperhive.swarm.forge.mirrors[].dest must be exactly
|
||||
Every services.hyperhive.deploy.forgejo.mirrors[].dest must be exactly
|
||||
"<owner>/<repo>" (one slash). Got: ${lib.concatMapStringsSep ", " (m: m.dest) effectiveMirrors}
|
||||
'';
|
||||
}
|
||||
|
|
@ -618,7 +636,7 @@ in
|
|||
])
|
||||
) effectiveMirrors;
|
||||
message = ''
|
||||
services.hyperhive.swarm.forge.mirrors[].dest must not place a mirror
|
||||
services.hyperhive.deploy.forgejo.mirrors[].dest must not place a mirror
|
||||
in a hive-c0re-managed org (config / shared / agents / core) —
|
||||
those are provisioned by hive-c0re and a mirror there would
|
||||
collide. Use a dedicated org (e.g. "actions/checkout").
|
||||
|
|
@ -771,7 +789,7 @@ in
|
|||
# scraper at the gateway, so a second credential system per
|
||||
# service would buy nothing and would be the one that stops
|
||||
# getting rotated.
|
||||
metrics.ENABLED = cfg.behindGateway;
|
||||
metrics.ENABLED = deployCfg.forgejo.behindGateway;
|
||||
# The two per-dimension breakdowns, on the same condition as
|
||||
# the endpoint itself: `gitea_issues_by_label{label=…}` and
|
||||
# `gitea_issues_by_repository{repository=…}`. Off by default
|
||||
|
|
@ -790,8 +808,8 @@ in
|
|||
# forge that grew to thousands of repos would want this
|
||||
# revisited — that is a real trigger, unlike a time-based one:
|
||||
# `count(gitea_issues_by_repository)` answers it directly.
|
||||
metrics.ENABLED_ISSUE_BY_LABEL = cfg.behindGateway;
|
||||
metrics.ENABLED_ISSUE_BY_REPOSITORY = cfg.behindGateway;
|
||||
metrics.ENABLED_ISSUE_BY_LABEL = deployCfg.forgejo.behindGateway;
|
||||
metrics.ENABLED_ISSUE_BY_REPOSITORY = deployCfg.forgejo.behindGateway;
|
||||
# Repo migrations / pull-mirrors fetch from the source
|
||||
# URL *inside* Forgejo. hyperhive code is synced from
|
||||
# `localhost` (and the host LAN), which Forgejo's
|
||||
|
|
@ -1025,9 +1043,9 @@ in
|
|||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
secret=$(cat ${lib.escapeShellArg cfg.sso.clientSecretFile})
|
||||
secret=$(cat ${lib.escapeShellArg deployCfg.forgejo.sso.clientSecretFile})
|
||||
if [ -z "$secret" ]; then
|
||||
echo "empty OIDC client secret at ${cfg.sso.clientSecretFile}" >&2
|
||||
echo "empty OIDC client secret at ${deployCfg.forgejo.sso.clientSecretFile}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -1204,7 +1222,7 @@ in
|
|||
|
||||
# Same case, same reasoning: this host minted the secret, so it can
|
||||
# say where the forge will find it.
|
||||
services.hyperhive.swarm.forge.sso.clientSecretFile = lib.mkIf ssoLocal (
|
||||
services.hyperhive.deploy.forgejo.sso.clientSecretFile = lib.mkIf ssoLocal (
|
||||
lib.mkDefault forgeSecretPath
|
||||
);
|
||||
|
||||
|
|
@ -1298,7 +1316,7 @@ in
|
|||
set -euo pipefail
|
||||
|
||||
src=${lib.escapeShellArg "/var/lib/nixos-containers/hive-forge${swarmControllerTokenPath}"}
|
||||
dst=${lib.escapeShellArg cfg.hostSwarmControllerTokenFile}
|
||||
dst=${lib.escapeShellArg deployCfg.forgejo.hostSwarmControllerTokenFile}
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
[ -s "$src" ] && break
|
||||
|
|
@ -1313,7 +1331,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
networking.firewall = lib.mkIf deployCfg.forgejo.openFirewall {
|
||||
allowedTCPPorts = [
|
||||
cfg.httpPort
|
||||
cfg.sshPort
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,92 +466,23 @@ 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 = {
|
||||
enable = lib.mkOption {
|
||||
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>`
|
||||
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.
|
||||
auto-find the sub-domain. The client build itself is
|
||||
`swarm.matrix.gui.package` — which client, as opposed to whether
|
||||
this host serves it.
|
||||
'';
|
||||
};
|
||||
|
||||
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`.
|
||||
'';
|
||||
};
|
||||
|
||||
clientSecretFile = lib.mkOption {
|
||||
sso.clientSecretFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/var/lib/tuwunel-oidc/tuwunel.secret";
|
||||
|
|
@ -479,16 +495,16 @@ in
|
|||
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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf deployCfg.matrix.enable {
|
||||
# Matrix's own gateway surface: the sub-domain vhost, the name the
|
||||
|
|
@ -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
|
||||
];
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue