diff --git a/docs/networking/gateway.md b/docs/networking/gateway.md index c500b4a8..20a6f16d 100644 --- a/docs/networking/gateway.md +++ b/docs/networking/gateway.md @@ -10,7 +10,7 @@ This host's nginx fronts the hyperhive web surfaces running on it — next to hi | `/agent//` | `_` | per-agent harness (UDS or TCP) | `agents.conf` (runtime-generated) | | `/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null` | | `/matrix/` (deprecated) | `_` | 301 → `chat./` | `matrix.gui.enable` | -| `forge./` | `forge.` | forgejo (`3000`) | `deploy.forgejo.behindGateway` | +| `forge./` | `forge.` | forgejo (`3000`) | `deploy.forgejo` | | `chat./_matrix/*` | `chat.` | tuwunel (`8008`) | `matrix.gatewayHost != null` | | `chat./` | `chat.` | fluffychat-web static | `matrix.gui.enable` | | `chat./config.json` | `chat.` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` | @@ -66,7 +66,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`: - `` → `127.0.0.1` -- `forge.` → `127.0.0.1` (when deploy.forgejo.behindGateway) +- `forge.` → `127.0.0.1` (when deploy.forgejo) - `chat.` → `127.0.0.1` (when matrix.gatewayHost set) - `auth.` → `127.0.0.1` (when deploy.authelia) @@ -165,9 +165,8 @@ true; the `false` branch stays as a defensive fallback for the env being unset. Three render sites flip together: the primary agent-name link, the favicon fetch (`/icon`), and the nav-strip `container`-kind links from -`DashboardState.links` (`GET /api/dashboard-state`). `forge`-kind nav-strip links still -resolve against `http://:3000` (separate sub-domain transition -tracked by `deploy.forgejo.behindGateway`); `external`-kind links are +`DashboardState.links` (`GET /api/dashboard-state`). `forge`-kind nav-strip links +resolve against `forge_public_url`, and the dashboard hides them when it's unset; `external`-kind links are already absolute. See `docs/web-ui/dashboard.md::Container row` for the frontend-side derivation. @@ -385,9 +384,8 @@ the bridge), not the raw port, so no firewall hole is needed. Flip to - External git clients that push/pull via SSH directly to the host. -Forgejo served through the gateway (`deploy.forgejo.behindGateway = true`) does -not need `openFirewall` — the gateway's own `openFirewall` option covers -that path. +Forgejo's gateway vhost doesn't need `openFirewall` — the gateway's own +`openFirewall` option covers that path. ### `rootUrl` override @@ -396,17 +394,9 @@ services.hyperhive.swarm.forge.rootUrl = "https://forge.example.com/"; ``` `rootUrl` (default **null**) overrides the Forgejo `ROOT_URL` that's -autoderived from `forge.domain` + gateway state. The autoderivation -covers most cases: - -| Shape | Autoderived `ROOT_URL` | -|---|---| -| `deploy.forgejo.behindGateway = true` | `https:///` (port suffix omitted when `gateway.httpsPort == 443`) | -| `deploy.forgejo.behindGateway = false` | `http://:/` | - -The gateway always terminates TLS, so the `behindGateway = true` case is -always advertised over `https://`; only the direct (`behindGateway = -false`) shape stays `http://`. Set `rootUrl` explicitly when +autoderived as `https:///`, with `:` +appended when that port isn't 443. The gateway always terminates TLS, so +the forge is always advertised over `https://`. Set `rootUrl` explicitly when `forge.domain` resolves differently from the public URL, or for a genuinely bespoke shape (for example an external reverse proxy on a different host/path). Must end with `/` (Forgejo requirement; an assertion diff --git a/docs/scheduler/ci.md b/docs/scheduler/ci.md index 450c5d70..56dd7e1b 100644 --- a/docs/scheduler/ci.md +++ b/docs/scheduler/ci.md @@ -119,7 +119,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://` (resolved to the bridge IP via `networking.extraHosts`). It can't 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`. +- **Private netns, bridge-attached**: the container runs in its own network namespace (`privateNetwork = true`, `hostBridge`) and reaches hive-forge through the gateway at `http://` (resolved to the bridge IP via `networking.extraHosts`). It can't reach host-loopback services — the core dashboard at `127.0.0.1:7000` and the raw forge port are unreachable from CI. - **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. diff --git a/docs/tools/hivectl.md b/docs/tools/hivectl.md index 9834a841..af34771e 100644 --- a/docs/tools/hivectl.md +++ b/docs/tools/hivectl.md @@ -272,6 +272,6 @@ note, not an error. A surface has no URL when it isn't browser-reachable: `home` needs `services.hyperhive.domain`; `forge` needs -`services.hyperhive.deploy.forgejo.behindGateway = true`; `matrix` needs +`services.hyperhive.swarm.forge.publicUrl` (set by default); `matrix` needs `services.hyperhive.deploy.matrix.gui.enable = true`. In those cases the command exits with a hint naming the option to set. diff --git a/hive-c0re/src/dashboard/state_snapshot.rs b/hive-c0re/src/dashboard/state_snapshot.rs index 7e2d816c..1d48e896 100644 --- a/hive-c0re/src/dashboard/state_snapshot.rs +++ b/hive-c0re/src/dashboard/state_snapshot.rs @@ -94,8 +94,7 @@ pub(super) struct StateSnapshot { /// `"https://forge.pr1ma.darkest.space"`). Sourced from the /// `HIVE_FORGE_PUBLIC_URL` env var, which the c0re NixOS module /// 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** + /// the gateway vhost URL). `None` when absent — the frontend **hides** /// forge links rather than guessing `http://:3000`, /// which is only right by accident on deployments that aren't /// plain localhost. diff --git a/hive-host-sock/src/lib.rs b/hive-host-sock/src/lib.rs index 700210ac..a2099491 100644 --- a/hive-host-sock/src/lib.rs +++ b/hive-host-sock/src/lib.rs @@ -451,7 +451,7 @@ impl LifecycleScope { /// This hive's canonical domain plus the browser-facing URLs for its /// web surfaces — the `Urls` request result. Every field is `None` when /// the corresponding surface can't be reached from a browser (domain -/// unset, forge not behind the gateway, matrix GUI disabled), so the CLI +/// unset, forge `publicUrl` null, matrix GUI disabled), so the CLI /// can give a precise hint instead of opening a dead link. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HiveUrls { @@ -461,8 +461,8 @@ pub struct HiveUrls { /// Operator dashboard root (`https:///`). #[serde(default, skip_serializing_if = "Option::is_none")] pub home: Option, - /// Forge browser URL (`HIVE_FORGE_PUBLIC_URL`) — only the - /// behind-gateway public URL; `None` on direct-port forge deploys. + /// Forge browser URL (`HIVE_FORGE_PUBLIC_URL`) — `None` when + /// `swarm.forge.publicUrl` is `null`. #[serde(default, skip_serializing_if = "Option::is_none")] pub forge: Option, /// Matrix GUI (fluffychat) browser URL — `None` when the matrix GUI diff --git a/hivectl/src/open.rs b/hivectl/src/open.rs index 4ebf7a56..1a50062a 100644 --- a/hivectl/src/open.rs +++ b/hivectl/src/open.rs @@ -26,7 +26,7 @@ pub(crate) async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> { ), OpenTarget::Forge => ( urls.forge, - "the public forge URL needs `services.hyperhive.deploy.forgejo.behindGateway = true`", + "the public forge URL needs `services.hyperhive.swarm.forge.publicUrl` to be set", ), OpenTarget::Matrix => ( urls.matrix, diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index acc2f4a1..b02f3f89 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -209,7 +209,7 @@ in # The rest of the forge split. What stays under `swarm.forge` is what the # forge IS from any hive's point of view — 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. Its package moved as well, + # four are what the host running it decides. Its package moved as well, # further down with the other `*.package` moves. `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. @@ -218,10 +218,6 @@ in # 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" ] diff --git a/nix/host-modules/hive-c0re/environment.nix b/nix/host-modules/hive-c0re/environment.nix index 329d3b09..c16f8fa0 100644 --- a/nix/host-modules/hive-c0re/environment.nix +++ b/nix/host-modules/hive-c0re/environment.nix @@ -210,8 +210,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 `deploy.forgejo.behindGateway = true` and `null` - # otherwise — see that option's doc for the "hide, don't guess" rationale. + # gateway vhost URL — 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; diff --git a/nix/host-modules/hive-ci.nix b/nix/host-modules/hive-ci.nix index 74e3ad71..d75b4d90 100644 --- a/nix/host-modules/hive-ci.nix +++ b/nix/host-modules/hive-ci.nix @@ -52,8 +52,7 @@ let # 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 `deploy.forgejo.behindGateway = true` (asserted in the - # config block below). See docs/networking/network.md. + # See docs/networking/network.md. privateNetwork = true; in { @@ -161,22 +160,7 @@ in }; config = lib.mkIf cfg.enable { - # `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 = forgeDeployCfg.behindGateway; - message = '' - services.hyperhive.deploy.forgejo.ci.enable requires - 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 is the default). - ''; - } { # The runner reaches the forge through THIS host's gateway, and # hive-c0re registers it through the local forge container; neither diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index 1c778073..04b0a39d 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -58,26 +58,20 @@ let caTrust = import ../lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; }; - # ROOT_URL forgejo advertises in clone links + outbound URLs. When - # served behind the gateway, `cfg.domain` doubles as both the - # forgejo `DOMAIN` setting AND the gateway vhost server-name, so - # ROOT_URL just uses it directly. The gateway always terminates TLS - # (self-signed is the implicit floor when neither `tls.certDir` nor - # ACME is configured), so behind the gateway the forge is always - # advertised over `https` on `httpsPort` — the canonical 443 elides - # the port suffix. When direct (`behindGateway = false`), keep the - # host:httpPort shape so direct browser access still produces correct - # links. Operators can still override via `cfg.rootUrl` for bespoke - # shapes. Both bindings are duplicated in ./service.nix, which builds - # `sso.redirectUri` from them; keep the two equal. + # ROOT_URL forgejo advertises in clone links + outbound URLs. + # `cfg.domain` doubles as both the forgejo `DOMAIN` setting AND the + # gateway vhost server-name, so ROOT_URL just uses it directly. The + # gateway always terminates TLS (self-signed is the implicit floor when + # neither `tls.certDir` nor ACME is configured), so the forge is always + # advertised over `https` on `httpsPort` — the canonical 443 elides the + # port suffix. Operators can still override via `cfg.rootUrl` for + # bespoke shapes. Both bindings are duplicated in ./service.nix, which + # builds `sso.redirectUri` from them; keep the two equal. defaultRootUrl = - if deployCfg.forgejo.behindGateway then - let - portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; - in - "https://${cfg.domain}${portSuffix}/" - else - "http://${cfg.domain}:${toString cfg.httpPort}/"; + let + portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; + in + "https://${cfg.domain}${portSuffix}/"; effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; # When CI is enabled, the runner needs `actions/checkout` resolvable @@ -136,35 +130,6 @@ in ''; }; - behindGateway = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Serve forgejo through the hive-gateway nginx as a sub-domain - vhost (`server_name = cfg.domain`) instead of directly on - `httpPort` (sub-domain routing — see `docs/networking/gateway.md`). - - When `true`: - - The gateway adds a `server { server_name = ''${cfg.domain}; }` - block that proxies all `/` → `http://127.0.0.1:''${httpPort}/`. - - Forgejo's `ROOT_URL` flips to `http(s)://''${cfg.domain}/` - (sub-domain root, no port suffix when gateway is on 80). - - `gateway.localHostsEntry = true` extends `/etc/hosts` to - include `cfg.domain → 127.0.0.1` for local dev. - - Defaults to `true` (the gateway always runs alongside - hyperhive, so forge auto-routes through it). Set `false` - explicitly to keep forge on the direct port even though the - gateway is running (e.g. an external git client that doesn't - traverse the gateway). - - Sub-domain routing is the preferred shape for forge + matrix - (both are external standard apps with sub-domain-native config - defaults). Per-agent UIs stay on sub-path (`/agent//`) - because they're hyperhive-internal + already base-path-aware. - ''; - }; - openFirewall = lib.mkOption { type = lib.types.bool; default = false; @@ -281,49 +246,38 @@ in # the gateway supplies the primitives (`lib.listen`, `lib.tlsFor`, # `lib.securityHeaders`) and never needs to know this service by # name. - # - # 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 deployCfg.forgejo.behindGateway cfg.domain; - services.hyperhive.gateway.enable = lib.mkIf deployCfg.forgejo.behindGateway (lib.mkDefault true); + services.hyperhive.gateway.localNames = [ cfg.domain ]; + services.hyperhive.gateway.enable = lib.mkDefault true; - # Not conditional on `behindGateway`: the forge container resolves the - # rest of the hive through dnsmasq whoever fronts it. + # The forge container resolves the rest of the hive through dnsmasq. services.hyperhive.gateway.dns.enable = lib.mkDefault true; - # 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 + # This swarm-ui quick-links entry. See # `services.hyperhive.swarm.controller.links`'s description. - services.hyperhive.swarm.controller.links = lib.optional deployCfg.forgejo.behindGateway { - label = "Forge"; - icon = "⚒"; - url = "https://${cfg.domain}/"; - }; + services.hyperhive.swarm.controller.links = [ + { + label = "Forge"; + icon = "⚒"; + url = "https://${cfg.domain}/"; + } + ]; # The metrics endpoint, declared once: the collector both scrapes this - # URL and derives from it the audience its token is minted for. Same - # `behindGateway` guard, and for a stronger reason than the two above: - # with it off there is no `= /metrics` location and no `auth_request` - # in front of it, so the URL this names does not exist to be scraped - # or authorised. + # URL and derives from it the audience its token is minted for. # # ⚠️ Written as the exact URL a collector requests, because that is # 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 deployCfg.forgejo.behindGateway - { - forgejo = "https://${cfg.domain}/metrics"; - }; + services.hyperhive.swarm.otel.publishedScrapeTargets = { + forgejo = "https://${cfg.domain}/metrics"; + }; # `server_name = forge.domain`, proxies all `/` → forgejo. Tuned for # 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 deployCfg.forgejo.behindGateway { + services.nginx.virtualHosts = { "${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // { listen = gatewayCfg.lib.listen; extraConfig = gatewayCfg.lib.securityHeaders; @@ -600,21 +554,17 @@ in DEFAULT_PRIVATE = "private"; }; # Not an option: a swarm-integrated, auto-deployed forge - # always has metrics. Tied to `behindGateway` because that - # IS the swarm-integrated shape — it is the condition under - # which the protected `= /metrics` location below exists. - # Serving the endpoint without that location would put it on - # a listener `openFirewall` can expose, with nothing in - # front of it. + # always has metrics, behind the protected `= /metrics` + # location on its gateway vhost. # # No `TOKEN` here on purpose. Forgejo can guard this itself # with a static bearer, but the swarm authenticates the # 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 = deployCfg.forgejo.behindGateway; - # The two per-dimension breakdowns, on the same condition as - # the endpoint itself: `gitea_issues_by_label{label=…}` and + metrics.ENABLED = true; + # The two per-dimension breakdowns, alongside the endpoint + # itself: `gitea_issues_by_label{label=…}` and # `gitea_issues_by_repository{repository=…}`. Off by default # upstream because they are the only metrics here whose series # count grows with the CONTENT of the forge rather than with @@ -631,8 +581,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 = deployCfg.forgejo.behindGateway; - metrics.ENABLED_ISSUE_BY_REPOSITORY = deployCfg.forgejo.behindGateway; + metrics.ENABLED_ISSUE_BY_LABEL = true; + metrics.ENABLED_ISSUE_BY_REPOSITORY = true; # Repo migrations / pull-mirrors fetch from the source # URL *inside* Forgejo. hyperhive code is synced from # `localhost` (and the host LAN), which Forgejo's diff --git a/nix/host-modules/hive-forge/service.nix b/nix/host-modules/hive-forge/service.nix index f1dd97cd..afdbe83a 100644 --- a/nix/host-modules/hive-forge/service.nix +++ b/nix/host-modules/hive-forge/service.nix @@ -10,7 +10,6 @@ let cfg = config.services.hyperhive.swarm.forge; gatewayCfg = config.services.hyperhive.gateway; swarmDomain = config.services.hyperhive.swarm.domain; - deployCfg = config.services.hyperhive.deploy; # Forgejo's name for the login source. Duplicated in ./default.nix, which # registers the source under it. @@ -24,13 +23,10 @@ let # Forgejo's `ROOT_URL`, duplicated from ./default.nix, which documents its # shape. defaultRootUrl = - if deployCfg.forgejo.behindGateway then - let - portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; - in - "https://${cfg.domain}${portSuffix}/" - else - "http://${cfg.domain}:${toString cfg.httpPort}/"; + let + portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; + in + "https://${cfg.domain}${portSuffix}/"; effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; in { @@ -94,8 +90,8 @@ 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 `deploy.forgejo.behindGateway = true` - (sub-domain routing — see `docs/networking/gateway.md`). + gateway vhost server-name (sub-domain routing — see + `docs/networking/gateway.md`). Defaults to `forge.''${services.hyperhive.swarm.domain}` — the swarm's domain, not this hive's, because a swarm runs **one** @@ -116,10 +112,8 @@ in publicUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; - default = if deployCfg.forgejo.behindGateway then "https://${cfg.domain}" else null; - defaultText = lib.literalExpression '' - if behindGateway then "https://''${domain}" else null - ''; + default = "https://${cfg.domain}"; + defaultText = lib.literalExpression ''"https://''${domain}"''; example = "https://forge.example.com"; description = '' Browser-facing forge URL the dashboard uses to build clickable @@ -127,20 +121,12 @@ 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 `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 - `docs/web-ui/dashboard.md::H0M3 page` for the rationale (a - link built from the operator's own browser hostname + a - container port is only an accident away from wrong on any - deployment that isn't plain localhost). - - **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://:''${toString cfg.httpPort}` - for an all-LAN deployment) — leaving it unset there means the - dashboard's forge links are simply absent, not broken. + Defaults to `https://''${cfg.domain}`, the gateway vhost. When + `null`, the dashboard **hides** forge links rather than + guessing one — see `docs/web-ui/dashboard.md::H0M3 page` for + the rationale (a link built from the operator's own browser + hostname + a container port is only an accident away from + wrong on any deployment that isn't plain localhost). ''; }; @@ -150,20 +136,15 @@ in 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: + (default), `ROOT_URL` is `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 `:`. - - `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 `:`. - - `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. + Set this only 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. ''; }; @@ -202,11 +183,10 @@ in format. ⚠️ With {option}`services.hyperhive.swarm.forge.rootUrl` unset, - `ROOT_URL` follows - {option}`services.hyperhive.deploy.forgejo.behindGateway` and - the gateway's `httpsPort`, which are per-host. An authelia host - that is not the forge's host renders the forge's callback only - if the two agree on them; set `rootUrl` if they do not. + `ROOT_URL` follows the gateway's `httpsPort`, which is + per-host. An authelia host that is not the forge's host renders + the forge's callback only if the two agree on it; set `rootUrl` + if they do not. ''; }; # The secret half is a path on the host that runs the forge, so it diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 3d906ee9..f42a971f 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -1060,7 +1060,7 @@ in # Denied or client-scoped depending on whether a # collector is registered — see `metricsRule` above, # which is where the reasoning for both halves lives. - lib.optional deployCfg.forgejo.behindGateway metricsRule + [ metricsRule ] # Also guarded on the domain being set: without it a null # apex would render a rule matching the string "null". ++ lib.optional (deployCfg.swarm-ui.enable && swarmDomain != null) { diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index 4ec25819..07029155 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -52,10 +52,6 @@ let deploy.forgejo.ci.enable = true; }; - # A hive whose one gateway-published swarm service sits on another host: - # every swarm name still configured, not one of them served here. - forgeElsewhere = hive { deploy.forgejo.behindGateway = false; }; - # A priority collision is a property of the *option*, not # of the merged value's interior — nix throws the moment the value is # demanded at all, so `seq`-ing each `serviceConfig` value to WHNF is @@ -72,31 +68,12 @@ let builtins.foldl' (acc: v: builtins.seq v acc) true vals; cases = [ { - # Both halves matter. The equality is the "no longer consults the central - # toggle" half; the literal is the "and still renders what it always - # did" half, which an equality on its own would let drift to `false` in - # lockstep. - name = "the forge's behindGateway default is true regardless of the central toggle"; - ok = - bare.services.hyperhive.deploy.forgejo.behindGateway == true - && centralToggleOff.services.hyperhive.deploy.forgejo.behindGateway == true; - } - { - # Downstream of the one above — publicUrl reads `behindGateway`, so it - # tracked the central toggle transitively as well as directly. The domain - # is the stub's swarm domain, which both fixtures share. - name = "the forge's publicUrl default follows behindGateway alone, not the central toggle"; + # The domain is the stub's swarm domain, which both fixtures share. + name = "the forge's publicUrl default does not consult the central toggle"; ok = bare.services.hyperhive.swarm.forge.publicUrl == "https://forge.t.local" && centralToggleOff.services.hyperhive.swarm.forge.publicUrl == "https://forge.t.local"; } - { - # And that it still tracks `behindGateway` at all: without this arm the - # case above passes just as well for a default hardcoded to the URL. - name = "the forge's publicUrl default is still null with behindGateway off"; - ok = - (hive { deploy.forgejo.behindGateway = false; }).services.hyperhive.swarm.forge.publicUrl == null; - } { # The controller's token path follows where the forge runs # (./forge-placement.nix), never the central toggle: a forge host with @@ -226,9 +203,9 @@ let # vhosts are the hive leaf's, which this host still signs. name = "a host fronting none of the swarm's service names requests no services leaf"; ok = - forgeElsewhere.services.hyperhive.swarm.localServiceDomains == [ ] - && forgeElsewhere.services.hyperhive.swarm.serviceDomains != [ ] - && lib.hasInfix "want_svc=0" forgeElsewhere.systemd.services.swarm-services-cert.script; + bare.services.hyperhive.swarm.localServiceDomains == [ ] + && bare.services.hyperhive.swarm.serviceDomains != [ ] + && lib.hasInfix "want_svc=0" bare.systemd.services.swarm-services-cert.script; } { # nixos asserts when a vhost declares both, so this is also a