Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd5093c6f | ||
|
|
40e1ed2967 | ||
|
|
470d2ad845 | ||
|
|
600fa15afe | ||
|
|
0dc2e6b64f | ||
|
|
d337fec565 | ||
|
|
f2840612c0 |
12 changed files with 345 additions and 12 deletions
|
|
@ -15,6 +15,7 @@ Single nginx in front of every hyperhive web surface. Runs on the **host**, next
|
|||
| `matrix.<hive>/` | `matrix.<hive>` | fluffychat-web static | `matrix.gui.enable` |
|
||||
| `matrix.<hive>/config.json` | `matrix.<hive>` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` |
|
||||
| `auth.<swarm>/` | `auth.<swarm>` | authelia (`9091`) | `swarm.authelia.enable` |
|
||||
| `<swarm>/` | `<swarm>` | swarm-ui dist (static), behind an authelia subrequest | `swarm.ui.enable` |
|
||||
|
||||
The authelia vhost is declared only by the host that **runs** authelia, not by every hive that uses it — a client hive knows the swarm's `authelia.url` but must not answer for a name it doesn't serve. Its server name is exactly `swarm.authelia.domain`: authelia validates `authelia_url ⊂ session cookie domain` at startup, so a near-miss is a container that refuses to boot. It carries no `auth_basic` — the login page must not sit behind the login mechanism it replaces — and sets the four `X-Forwarded-{Proto,Host,Uri,For}` headers, since authelia decides by the *original* request rather than the hop it sees.
|
||||
|
||||
|
|
|
|||
|
|
@ -49,30 +49,51 @@ start. Skipping this step looks like a broken proxy.
|
|||
swarmctl user add mara --display-name Mara --email mara@example.com --group admins
|
||||
```
|
||||
|
||||
⚠️ **Keep `--group admins`.** It is not decoration: operator-only
|
||||
surfaces (the swarm UI below) are gated on that group, and an account
|
||||
without it authenticates successfully and is then refused — which reads
|
||||
like a broken login rather than a missing group.
|
||||
|
||||
Detail, including what the password is and why this stays manual:
|
||||
[`swarm/sso.md`](swarm/sso.md).
|
||||
|
||||
### 4 · Matrix
|
||||
### 4 · Swarm UI (only when `swarm.ui.enable`, on by default with the controller)
|
||||
|
||||
Nothing to run — it is served on the swarm apex
|
||||
(`https://<swarm.domain>/`) as soon as the host rebuilds. Two things
|
||||
decide whether you can actually open it:
|
||||
|
||||
- **You are in `admins`** (step 3). The gateway asks authelia whether
|
||||
you have a session; the rule that makes it mean *operator* wants the
|
||||
group. Without it you log in and still get bounced.
|
||||
- **The name resolves to this host.** It is published to the hive's own
|
||||
resolver and to `/etc/hosts` when `gateway.localHostsEntry` is on; from
|
||||
anywhere else it needs a real DNS record like any other public name.
|
||||
|
||||
Detail, including why reachability is deliberately not the access
|
||||
control: [`swarm/ui.md`](swarm/ui.md).
|
||||
|
||||
### 5 · Matrix
|
||||
|
||||
```bash
|
||||
# 4a. Ensure the hive-internal admin account exists first
|
||||
# 5a. Ensure the hive-internal admin account exists first
|
||||
hivectl matrix sync-admin
|
||||
|
||||
# 4b. Provision ruth's own matrix account
|
||||
# 5b. Provision ruth's own matrix account
|
||||
hivectl matrix create-user ruth
|
||||
|
||||
# 4c. Create a human matrix account
|
||||
# 5c. Create a human matrix account
|
||||
hivectl matrix create-user mara --password hunter2
|
||||
|
||||
# 4d. Invite the operator to the hive Space (and optionally to rooms)
|
||||
# 5d. Invite the operator to the hive Space (and optionally to rooms)
|
||||
hivectl matrix invite mara
|
||||
hivectl matrix invite @mara:yourserver --room '#hive-chat:yourserver'
|
||||
|
||||
# 4e. Promote the operator to homeserver admin if needed
|
||||
# 5e. Promote the operator to homeserver admin if needed
|
||||
hivectl matrix promote-user mara
|
||||
```
|
||||
|
||||
### 5 · Spawn sub-agents
|
||||
### 6 · Spawn sub-agents
|
||||
|
||||
Sub-agent creation goes through the approval queue — ruth proposes, the
|
||||
operator approves, the container builds. From ruth's own turn (inside
|
||||
|
|
@ -93,7 +114,7 @@ request_init_config(name: "iris")
|
|||
|
||||
See [`approvals.md`](approvals.md) for the full flow.
|
||||
|
||||
### 6 · Useful host commands
|
||||
### 7 · Useful host commands
|
||||
|
||||
```bash
|
||||
# Roster: all agents, status, rev, parent, pending reminders
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ Which secrets the SSO provider generates, which one has a reader in
|
|||
another container, and the three ways that one gets delivered:
|
||||
[`sso.md`](sso.md).
|
||||
|
||||
## Swarm UI
|
||||
|
||||
The operator-only web surface on the swarm apex, why reaching it needs
|
||||
the `admins` group rather than just a session, and the four sites a
|
||||
swarm service name has to be wired into: [`ui.md`](ui.md).
|
||||
|
||||
## The swarm's hive directory
|
||||
|
||||
```nix
|
||||
|
|
|
|||
87
docs/swarm/ui.md
Normal file
87
docs/swarm/ui.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Swarm UI
|
||||
|
||||
The swarm's own web surface, served by the gateway on the **swarm apex**
|
||||
(`services.hyperhive.swarm.domain`) and readable only by operators.
|
||||
|
||||
Distinct from the per-hive dashboard, which lives on the hive domain and
|
||||
answers for one host. This one is the view *across* hives.
|
||||
|
||||
## Enabling
|
||||
|
||||
```nix
|
||||
services.hyperhive.swarm.ui.enable = true; # defaults to swarm.controller.enable
|
||||
```
|
||||
|
||||
Derived from the controller rather than from `enableRequiredServices`:
|
||||
the UI is a view onto the controller's state and reaches it over that
|
||||
daemon's socket, so the host that runs the controller is the host that
|
||||
can serve the UI. A hive that merely *uses* a swarm has nothing to serve.
|
||||
|
||||
`swarm.ui.domain` defaults to the swarm apex and can be pinned, the same
|
||||
way `swarm.forge.domain` and `swarm.matrix.gatewayHost` can.
|
||||
|
||||
> The apex must differ from `services.hyperhive.domain`. The gateway's
|
||||
> default server already answers for the hive domain, and two vhosts
|
||||
> claiming one `server_name` do not error — nginx picks one — so this is
|
||||
> an assertion rather than a runtime surprise.
|
||||
|
||||
## 🔑 You must be in the `admins` group
|
||||
|
||||
**This is the step that separates "protected" from "locked out".** The
|
||||
vhost's `auth_request` asks authelia *"is there a session"*; the rule
|
||||
that makes it mean *"is this an operator"* is an `access_control` entry
|
||||
requiring `group:admins`. An account without that group authenticates
|
||||
fine and still gets bounced.
|
||||
|
||||
```sh
|
||||
swarmctl user add <you> --group admins
|
||||
```
|
||||
|
||||
`admins` deliberately, not a new word: [`../setup.md`](../setup.md) has
|
||||
told every operator to create exactly that group since the bootstrap step
|
||||
existed, so an account made by following the guide already passes. This
|
||||
is the first rule that *consumes* a group name — inventing a second one
|
||||
would have meant those accounts silently failing a check they were
|
||||
supposed to pass.
|
||||
|
||||
An account created without any group needs re-adding with the flag —
|
||||
`swarmctl` treats the existing entry as the canonical store, so the group
|
||||
is what changes.
|
||||
|
||||
Why a group and not a list of usernames: agents are getting authelia
|
||||
accounts of their own (matrix SSO), and *authenticated* would then
|
||||
include every agent in the hive. The group is the only thing standing
|
||||
between "an operator's page" and "anyone with a session".
|
||||
|
||||
## What it costs to be reachable
|
||||
|
||||
The apex is published to the hive's resolver like every other swarm
|
||||
service, so agent containers can resolve it. That is deliberate and it is
|
||||
not a hole: **reachability is not the access control here.** An agent
|
||||
that resolves the name and connects still has no operator session, and
|
||||
the subrequest denies it.
|
||||
|
||||
## Four wiring sites
|
||||
|
||||
Adding a swarm service name means touching all four. Missing one ships as
|
||||
a different flavour of "works from the host, broken from a container":
|
||||
|
||||
| site | file |
|
||||
| --- | --- |
|
||||
| vhost | `nix/host-modules/hive-gateway/vhosts.nix` |
|
||||
| **certificate name** | `nix/host-modules/swarm.nix` (`serviceDomains`) |
|
||||
| DNS record | `nix/host-modules/hive-gateway/dnsmasq.nix` |
|
||||
| local-dev hosts | `nix/host-modules/hive-gateway/default.nix` |
|
||||
|
||||
⚠️ The certificate one is the least obvious and the most visible when
|
||||
missed. `serviceDomains` is *both* the services sub-CA's
|
||||
`nameConstraints` set and the leaf's SAN list, and the apex is a
|
||||
**sibling** of `forge.<swarm>` / `chat.<swarm>` / `auth.<swarm>`, not a
|
||||
parent — no CA in the hierarchy issues for it implicitly. Left out, the
|
||||
vhost falls back to the hive leaf and the swarm's front page opens with a
|
||||
name mismatch.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [`sso.md`](sso.md) — the authelia instance itself, and the user store.
|
||||
- [`../gateway.md`](../gateway.md) — the full vhost map and TLS modes.
|
||||
|
|
@ -148,6 +148,9 @@
|
|||
services.hyperhive.swarm.controller.swarmctlPackage =
|
||||
lib.mkDefault
|
||||
self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl;
|
||||
services.hyperhive.swarm.ui.package =
|
||||
lib.mkDefault
|
||||
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-ui;
|
||||
services.hyperhive.gateway.swaggerUiTheme =
|
||||
lib.mkDefault
|
||||
self.packages.${pkgs.stdenv.hostPlatform.system}.swagger-ui-theme;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
./swarm-ca.nix
|
||||
./swarm-controller.nix
|
||||
./swarm-snapshot-store.nix
|
||||
./swarm-ui.nix
|
||||
./swarm-wireguard.nix
|
||||
./swarm.nix
|
||||
./swarm-peers-removed.nix
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ let
|
|||
swarmServiceDomains = config.services.hyperhive.swarm.serviceDomains;
|
||||
matrixCfg = config.services.hyperhive.swarm.matrix;
|
||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||
uiCfg = config.services.hyperhive.swarm.ui;
|
||||
forgeCfg = config.services.hyperhive.swarm.forge;
|
||||
networkCfg = config.services.hyperhive.network;
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ let
|
|||
forgeCfg
|
||||
matrixCfg
|
||||
autheliaCfg
|
||||
uiCfg
|
||||
hyperhiveDomain
|
||||
dashboardDist
|
||||
swaggerUiTheme
|
||||
|
|
@ -308,6 +310,7 @@ in
|
|||
forgeCfg
|
||||
matrixCfg
|
||||
autheliaCfg
|
||||
uiCfg
|
||||
hyperhiveDomain
|
||||
;
|
||||
};
|
||||
|
|
@ -332,6 +335,7 @@ in
|
|||
) config.services.hyperhive.swarm.forge.domain
|
||||
++ lib.optional (matrixCfg.enable && matrixCfg.gatewayHost != null) matrixCfg.gatewayHost
|
||||
++ lib.optional autheliaCfg.enable autheliaCfg.domain
|
||||
++ lib.optional uiCfg.enable uiCfg.domain
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
forgeCfg,
|
||||
matrixCfg,
|
||||
autheliaCfg,
|
||||
uiCfg,
|
||||
hyperhiveDomain,
|
||||
}:
|
||||
{
|
||||
|
|
@ -60,7 +61,16 @@
|
|||
++ lib.optional (
|
||||
matrixCfg.enable && matrixCfg.gatewayHost != null
|
||||
) "/${matrixCfg.gatewayHost}/${networkCfg.bridgeIp}"
|
||||
++ lib.optional autheliaCfg.enable "/${autheliaCfg.domain}/${networkCfg.bridgeIp}";
|
||||
++ lib.optional autheliaCfg.enable "/${autheliaCfg.domain}/${networkCfg.bridgeIp}"
|
||||
# The swarm UI's name is the swarm APEX by default — a sibling of
|
||||
# the three above, not a child of anything this resolver already
|
||||
# answers for, so the `/<hive domain>/` rule does not cover it.
|
||||
#
|
||||
# Published to agents deliberately (mara: publishing it is fine).
|
||||
# Reachability is not the access control here: the vhost's
|
||||
# `auth_request` + authelia's `group:operators` rule are, and an
|
||||
# agent that resolves the name still cannot open the page.
|
||||
++ lib.optional uiCfg.enable "/${uiCfg.domain}/${networkCfg.bridgeIp}";
|
||||
# DHCP pool covering all usable host addresses on the bridge
|
||||
# subnet — bounds computed by hive-network.nix from
|
||||
# bridgeIp/bridgePrefixLength. All containers (agents and service
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
forgeCfg,
|
||||
matrixCfg,
|
||||
autheliaCfg, # services.hyperhive.swarm.authelia
|
||||
uiCfg, # services.hyperhive.swarm.ui
|
||||
hyperhiveDomain,
|
||||
dashboardDist,
|
||||
swaggerUiTheme, # nix/packages/swagger-ui-theme.nix: has index.html + hyperhive-theme.css
|
||||
|
|
@ -171,6 +172,68 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
# Swarm UI vhost — the swarm's front page, on the swarm apex, and the
|
||||
# FIRST `auth_request` anywhere in this gateway (everything else is
|
||||
# `auth_basic` + htpasswd).
|
||||
#
|
||||
# ⚠️ `auth_request` answers "is there a session", not "is this an
|
||||
# operator". The operator-only part is authelia's `access_control`
|
||||
# rule (../swarm-authelia.nix) requiring `group:operators` — agents
|
||||
# are getting authelia accounts of their own, and without that rule a
|
||||
# session alone would open this page.
|
||||
#
|
||||
# ⚠️ Failure mode here is LOCKED OUT, not unprotected: a subrequest
|
||||
# that wrongly denies takes the whole UI away. That is the reason the
|
||||
# redirect target and the header set below are copied from a measured
|
||||
# source rather than from an example.
|
||||
swarmUiVhost = lib.optionalAttrs uiCfg.enable {
|
||||
"${uiCfg.domain}" = (vhostTlsFor uiCfg.domain) // {
|
||||
listen = vhostListen;
|
||||
extraConfig = securityHeaders;
|
||||
locations = {
|
||||
"/" = {
|
||||
root = "${uiCfg.package}";
|
||||
extraConfig = ''
|
||||
auth_request /__hive_authelia;
|
||||
# Captured BEFORE the error_page jump: inside the 401 handler
|
||||
# `$request_uri` is the internal one, so building the return
|
||||
# link there sends the operator back to the auth subrequest
|
||||
# instead of the page they asked for.
|
||||
auth_request_set $target_url $scheme://$http_host$request_uri;
|
||||
error_page 401 =302 https://${autheliaCfg.domain}/?rd=$target_url;
|
||||
# SPA: any path the bundle routes client-side is served the
|
||||
# entry document rather than a 404 from the filesystem.
|
||||
try_files $uri /index.html;
|
||||
'';
|
||||
};
|
||||
# The subrequest itself. `auth-request` is the implementation
|
||||
# name authelia exposes under `/api/authz/`; `/api/verify` is the
|
||||
# LEGACY path every older example shows.
|
||||
#
|
||||
# Header set measured against the pinned binary (4.39.20), not
|
||||
# copied: `X-Original-URL` and `X-Original-Method` are present as
|
||||
# literals and are what this implementation reads —
|
||||
# `X-Forwarded-Uri` does not appear in it at all, so sending it
|
||||
# would look like configuration and be dead weight.
|
||||
"= /__hive_authelia" = {
|
||||
proxyPass = "http://127.0.0.1:${toString autheliaCfg.port}/api/authz/auth-request";
|
||||
extraConfig = ''
|
||||
internal;
|
||||
# A subrequest carries no body, and forwarding one here makes
|
||||
# authelia read a payload it will never use.
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_set_header X-Original-Method $request_method;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Matrix sub-domain vhost. `server_name = matrixCfg.gatewayHost`.
|
||||
# `/_matrix/*` → tuwunel (CORS *, 50M body cap, 1h long-poll
|
||||
# timeout). `/` serves fluffychat or 404 if GUI off. nginx
|
||||
|
|
@ -458,5 +521,6 @@ in
|
|||
}
|
||||
// forgeVhost
|
||||
// autheliaVhost
|
||||
// matrixVhost;
|
||||
// matrixVhost
|
||||
// swarmUiVhost;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,20 @@ let
|
|||
hyperhiveCfg = config.services.hyperhive;
|
||||
hyperhiveDomain = hyperhiveCfg.domain;
|
||||
swarmDomain = hyperhiveCfg.swarm.domain;
|
||||
uiCfg = hyperhiveCfg.swarm.ui;
|
||||
|
||||
# Group an account must hold to reach operator-only surfaces. Named
|
||||
# here because this module writes the rule that enforces it and
|
||||
# `swarmctl user add --group <this>` is what grants it — the two must
|
||||
# agree, and one constant is how they stay agreeing.
|
||||
#
|
||||
# ⚠️ `admins` and not a new word, because `docs/setup.md` and
|
||||
# `docs/swarm/sso.md` have been telling every operator to create
|
||||
# exactly that group since the bootstrap step existed. This is the
|
||||
# first rule that CONSUMES a group name; picking a different one would
|
||||
# have meant every account created by following the guide silently
|
||||
# failing the check it was supposed to pass.
|
||||
operatorGroup = "admins";
|
||||
|
||||
# Upstream's `services.authelia.instances.<name>` derives the unit,
|
||||
# user, group and StateDirectory from the instance name
|
||||
|
|
@ -536,7 +550,26 @@ in
|
|||
|
||||
authentication_backend.file.path = cfg.usersFile;
|
||||
|
||||
access_control.default_policy = "one_factor";
|
||||
# ⚠️ `one_factor` as the DEFAULT means "any authenticated
|
||||
# user", which is authentication, not authorisation. The
|
||||
# swarm UI is operator-only, and agents are getting
|
||||
# authelia accounts of their own — so the day that lands,
|
||||
# a session alone would be enough to open it. The rule
|
||||
# below is what makes the distinction real; without it
|
||||
# the vhost's `auth_request` is a check nobody fails.
|
||||
#
|
||||
# The group is a constant rather than an option: it is the
|
||||
# value an operator types into `swarmctl user add --group`,
|
||||
# and a configurable name is one more way for the rule and
|
||||
# the account to disagree silently.
|
||||
access_control = {
|
||||
default_policy = "one_factor";
|
||||
rules = lib.optional uiCfg.enable {
|
||||
domain = uiCfg.domain;
|
||||
subject = [ "group:${operatorGroup}" ];
|
||||
policy = "one_factor";
|
||||
};
|
||||
};
|
||||
|
||||
# The cookie domain is the SWARM's domain, NOT authelia's
|
||||
# own host: the session cookie has to be sent to the apps
|
||||
|
|
|
|||
97
nix/host-modules/swarm-ui.nix
Normal file
97
nix/host-modules/swarm-ui.nix
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# The swarm-level web UI: a static bundle served by the gateway's nginx,
|
||||
# behind authelia. Distinct from the per-hive dashboard (hive-c0re's, on
|
||||
# the hive domain) — this one answers for the swarm apex and is the
|
||||
# operator's view across hives.
|
||||
#
|
||||
# Options only. The vhost itself is declared in hive-gateway/vhosts.nix
|
||||
# alongside forge/matrix/authelia: a service module says *how* it is
|
||||
# reached, the gateway says *whether this host serves it*.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.hyperhive.swarm.ui;
|
||||
swarmCfg = config.services.hyperhive.swarm;
|
||||
hiveDomain = config.services.hyperhive.domain;
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.swarm.ui = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = swarmCfg.controller.enable;
|
||||
defaultText = lib.literalExpression "services.hyperhive.swarm.controller.enable";
|
||||
example = true;
|
||||
description = ''
|
||||
Serve the swarm UI from this host.
|
||||
|
||||
Derived from `swarm.controller.enable` rather than from
|
||||
`enableRequiredServices`: the UI is a view onto the controller's
|
||||
state and reaches it over that daemon's unix socket, so the host
|
||||
that runs the controller is the host that can serve the UI. A
|
||||
hive that merely *uses* a swarm has nothing to serve here.
|
||||
'';
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if swarmCfg.domain == null then "swarm.invalid" else swarmCfg.domain;
|
||||
defaultText = lib.literalExpression "services.hyperhive.swarm.domain";
|
||||
example = "swarm.example.com";
|
||||
description = ''
|
||||
Host name the swarm UI answers on. Defaults to the swarm apex
|
||||
itself — the swarm's front page is the swarm's name.
|
||||
|
||||
An option rather than a hardcoded derivation so a hive can pin a
|
||||
different name, the same way `swarm.forge.domain` and
|
||||
`swarm.matrix.gatewayHost` can.
|
||||
|
||||
Total on a null swarm domain (`.invalid`, RFC 2606) so the
|
||||
required-domain assertion is what fires rather than a coercion
|
||||
error naming this option — same reasoning as
|
||||
`hive-network.nix`'s.
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
|
||||
description = ''
|
||||
Static build of the swarm UI. nginx serves this store path
|
||||
directly — there is no server-side component beyond the
|
||||
controller's own API.
|
||||
|
||||
Wired by default from this flake's own package set (see
|
||||
`flake.nix`), the same way `swarm.controller.package` is. There
|
||||
is deliberately **no overlay** in this project, so a
|
||||
`pkgs.swarm-ui` default here would name an attribute that does
|
||||
not exist on any real deployment.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.hyperhive.enable && cfg.enable) {
|
||||
assertions = [
|
||||
{
|
||||
# The `_` default server already answers for the hive domain
|
||||
# (dashboard, per-agent routes). A second vhost claiming the same
|
||||
# server_name is not an error to nginx — it picks one and logs a
|
||||
# conflict — so the failure would surface as "the dashboard is
|
||||
# sometimes the swarm UI", which is far harder to read than an
|
||||
# eval failure naming both options.
|
||||
assertion = cfg.domain != hiveDomain;
|
||||
message = ''
|
||||
services.hyperhive.swarm.ui.domain (${cfg.domain}) must differ
|
||||
from services.hyperhive.domain (${hiveDomain}) — the hive
|
||||
domain is already served by the gateway's default vhost
|
||||
(dashboard + agent routes), and two vhosts claiming one
|
||||
server_name silently resolve to whichever nginx picks.
|
||||
|
||||
Set services.hyperhive.swarm.domain to a name distinct from
|
||||
this hive's, or pin swarm.ui.domain explicitly.
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -39,7 +39,13 @@ let
|
|||
swarmCfg.forge.domain
|
||||
swarmCfg.matrix.gatewayHost
|
||||
swarmCfg.authelia.domain
|
||||
];
|
||||
]
|
||||
# The swarm UI's name is a SIBLING of the other three, not a parent of
|
||||
# them — the apex is as much a name needing a certificate as
|
||||
# `forge.<apex>` is, and no CA in the hierarchy issues for it
|
||||
# implicitly. Left out, its vhost falls back to the hive leaf and the
|
||||
# swarm's front page opens with a name mismatch.
|
||||
++ lib.optional swarmCfg.ui.enable swarmCfg.ui.domain;
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.swarm.hives = lib.mkOption {
|
||||
|
|
|
|||
Loading…
Reference in a new issue