refactor(#2015): drop redundant domain != null checks (domain is required) + update docs
This commit is contained in:
parent
816d387c65
commit
c8ae3371e1
6 changed files with 88 additions and 130 deletions
|
|
@ -280,21 +280,18 @@ dashboard reach by design — the surface is privileged (approve /
|
|||
deny / destroy) and must not be exposed without a real reverse
|
||||
proxy in front.
|
||||
|
||||
## `HIVE_FORGE_URL`: domain via gateway for isolated agents, loopback for shared-netns
|
||||
## `HIVE_FORGE_URL`: agents reach the forge via the gateway by domain
|
||||
|
||||
Agents poll `HIVE_FORGE_URL` for Forgejo notifications + run all
|
||||
`hive-forge` calls against it. `hive-c0re.nix` sets this based on the
|
||||
network isolation mode:
|
||||
|
||||
- **`network.isolateContainers = true`**: agents run in private netns and
|
||||
get the bridge dnsmasq as their resolver. `HIVE_FORGE_URL` is set to
|
||||
`http://<forge.domain>` (default `forge.<hive-domain>`). Agents resolve
|
||||
the hostname via dnsmasq → bridge IP, then reach nginx on port 80 (bridge
|
||||
firewall opens 80+443 when isolation is on). nginx proxies to forgejo — the
|
||||
same path an operator browser takes, no raw port exposure needed.
|
||||
- **`network.isolateContainers = false`** (default): agents share the host's
|
||||
network namespace, so loopback reaches forgejo directly. `HIVE_FORGE_URL`
|
||||
is `http://127.0.0.1:<forge.httpPort>`.
|
||||
`hive-forge` calls against it. Network isolation is always on (the
|
||||
shared-netns mode was removed), so agents run in a private netns and
|
||||
can never reach the host's loopback. `hive-c0re.nix` sets
|
||||
`HIVE_FORGE_URL` to `http://<forge.domain>` (default
|
||||
`forge.<hive-domain>`; `services.hyperhive.domain` is required). Agents
|
||||
get the bridge dnsmasq as their resolver, resolve the hostname →
|
||||
bridge IP, then reach nginx on port 80 (the bridge firewall opens
|
||||
80+443). nginx proxies to forgejo — the same path an operator browser
|
||||
takes, no raw port exposure needed.
|
||||
|
||||
## hive-forge container shape
|
||||
|
||||
|
|
@ -306,10 +303,13 @@ the operator already runs on the host — separate systemd namespace,
|
|||
separate state dir, separate port unless the operator deliberately
|
||||
collides.
|
||||
|
||||
Container shares the host network namespace
|
||||
(`privateNetwork = false`) so agents reach the forge at
|
||||
`http://localhost:<httpPort>` without extra plumbing — nixos-container
|
||||
is here for state + systemd-unit isolation, not network isolation.
|
||||
The forge container shares the host network namespace
|
||||
(`privateNetwork = false`), so forgejo's listeners look like a
|
||||
host-side service — nixos-container is here for state + systemd-unit
|
||||
isolation, not network isolation. Note this is the FORGE container;
|
||||
agent containers are network-isolated and reach the forge through the
|
||||
gateway by `forge.<domain>` (see `HIVE_FORGE_URL` above), not via the
|
||||
host's loopback.
|
||||
|
||||
State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/`
|
||||
and survives container restart / host reboot. To wipe, destroy the
|
||||
|
|
@ -336,9 +336,9 @@ via `-p 2222`). Port 22 is left alone on the host for openssh.
|
|||
|
||||
`openFirewall` (default **false**) controls whether `httpPort` and
|
||||
`sshPort` are opened in the host firewall. Off by default (secure by
|
||||
default): every agent container reaches Forgejo at `localhost:<httpPort>`
|
||||
via the shared host netns without a firewall hole. Flip to `true` when
|
||||
you need:
|
||||
default): agents reach Forgejo through the gateway (`forge.<domain>` on
|
||||
the bridge), not the raw port, so no firewall hole is needed. Flip to
|
||||
`true` when you need:
|
||||
- The operator's browser to reach `http://<host>:<httpPort>/` directly
|
||||
(not behind the gateway).
|
||||
- External git clients that push/pull via SSH directly to the host.
|
||||
|
|
|
|||
|
|
@ -112,8 +112,9 @@ in
|
|||
# The forge is mandatory — hive-c0re mirrors every agent's applied
|
||||
# config repo into it and it's the canonical store for the meta flake
|
||||
# + `internal/*` repos, so there's no enable toggle; it deploys with
|
||||
# hyperhive itself. hive-matrix is opt-in (off by default) and asserts
|
||||
# that `services.hyperhive.domain` is set before it can be enabled.
|
||||
# hyperhive itself. hive-matrix is opt-in (off by default). All
|
||||
# subsystems rely on `services.hyperhive.domain`, which is required
|
||||
# (asserted in hive-network.nix) whenever hyperhive is enabled.
|
||||
imports = [
|
||||
./hive-ci.nix
|
||||
./hive-forge.nix
|
||||
|
|
@ -129,8 +130,8 @@ in
|
|||
|
||||
# Canonical hive DNS domain shared by every subsystem that needs a
|
||||
# stable hostname. Typed nullOr (default null) so the option always
|
||||
# exists, but it's REQUIRED whenever hyperhive is enabled — a config
|
||||
# assertion (see the `config` block) fails eval when it's unset, since
|
||||
# exists, but it's REQUIRED whenever hyperhive is enabled — an
|
||||
# assertion in hive-network.nix fails eval when it's unset, since
|
||||
# matrix bakes it in on first boot and the gateway/forge/agent URLs all
|
||||
# derive from it (no safe default). Full identity-surface
|
||||
# context (HYPERHIVE_HIVE_DOMAIN / HIVE_NAME / SWARM_NAME env-var
|
||||
|
|
@ -831,51 +832,36 @@ in
|
|||
config.services.hyperhive.swarm.wireguard.listenPort
|
||||
];
|
||||
|
||||
assertions = [
|
||||
{
|
||||
# `domain` has no safe default: it's baked into the matrix server
|
||||
# (tuwunnel) on first boot and drives the gateway/forge/agent URLs.
|
||||
# Under mandatory network isolation agents can only reach the forge
|
||||
# via `forge.<domain>` on the bridge — with no domain there's no
|
||||
# reachable name. Changing it later is destructive
|
||||
# (deletes Matrix history), so it must be set before first boot.
|
||||
assertion = config.services.hyperhive.domain != null;
|
||||
message = ''
|
||||
services.hyperhive.domain must be set (e.g. "darkest.space").
|
||||
It's baked into the matrix server on first boot and into the
|
||||
gateway / forge / agent URLs, so hyperhive needs a stable domain
|
||||
and there is no safe default. Agents reach the forge via
|
||||
forge.<domain> on the bridge network, so without it the forge is
|
||||
unreachable from containers. Changing the domain after first boot
|
||||
is destructive (deletes Matrix history) — pick it before initial
|
||||
setup. See docs/gateway.md and docs/matrix.md.
|
||||
'';
|
||||
}
|
||||
]
|
||||
++ lib.optionals config.services.hyperhive.swarm.wireguard.enable [
|
||||
{
|
||||
assertion = config.services.hyperhive.swarm.wireguard.privateKeyFile != null;
|
||||
message = ''
|
||||
services.hyperhive.swarm.wireguard.enable requires
|
||||
services.hyperhive.swarm.wireguard.privateKeyFile to be set.
|
||||
Generate a key: wg genkey > /etc/wireguard/hive.key
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = config.services.hyperhive.swarm.wireguard.address != "";
|
||||
message = ''
|
||||
services.hyperhive.swarm.wireguard.enable requires
|
||||
services.hyperhive.swarm.wireguard.address to be set
|
||||
(e.g. "10.100.0.1/24").
|
||||
'';
|
||||
}
|
||||
]
|
||||
++ lib.optionals config.services.hyperhive.otel.enable [
|
||||
{
|
||||
assertion = config.services.hyperhive.otel.endpoint != "";
|
||||
message = "services.hyperhive.otel.enable is true but services.hyperhive.otel.endpoint is empty.";
|
||||
}
|
||||
];
|
||||
# NB: `services.hyperhive.domain` is required when hyperhive is
|
||||
# enabled — the canonical assertion lives in `hive-network.nix` (the
|
||||
# hive resolver is authoritative for `<domain>` and agents reach the
|
||||
# forge/matrix through the gateway by it). So everything below can
|
||||
# treat `config.services.hyperhive.domain` as non-null.
|
||||
assertions =
|
||||
lib.optionals config.services.hyperhive.swarm.wireguard.enable [
|
||||
{
|
||||
assertion = config.services.hyperhive.swarm.wireguard.privateKeyFile != null;
|
||||
message = ''
|
||||
services.hyperhive.swarm.wireguard.enable requires
|
||||
services.hyperhive.swarm.wireguard.privateKeyFile to be set.
|
||||
Generate a key: wg genkey > /etc/wireguard/hive.key
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = config.services.hyperhive.swarm.wireguard.address != "";
|
||||
message = ''
|
||||
services.hyperhive.swarm.wireguard.enable requires
|
||||
services.hyperhive.swarm.wireguard.address to be set
|
||||
(e.g. "10.100.0.1/24").
|
||||
'';
|
||||
}
|
||||
]
|
||||
++ lib.optionals config.services.hyperhive.otel.enable [
|
||||
{
|
||||
assertion = config.services.hyperhive.otel.endpoint != "";
|
||||
message = "services.hyperhive.otel.enable is true but services.hyperhive.otel.endpoint is empty.";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.hive-c0re = {
|
||||
description = "hyperhive coordinator daemon";
|
||||
|
|
@ -920,10 +906,11 @@ in
|
|||
# behaviour (root auto-managed); true makes the sweep a no-op.
|
||||
HYPERHIVE_RUTHLESS = lib.boolToString config.services.hyperhive.ruthless;
|
||||
}
|
||||
// lib.optionalAttrs (config.services.hyperhive.domain != null) {
|
||||
// {
|
||||
# Identity env vars threaded into c0re's own service env and
|
||||
# forwarded by meta.rs into every sub-agent's harness env —
|
||||
# full chain in docs/conventions.md::Hive identity.
|
||||
# full chain in docs/conventions.md::Hive identity. `domain` is
|
||||
# required (asserted in hive-network.nix), so it's always set.
|
||||
HYPERHIVE_HIVE_DOMAIN = config.services.hyperhive.domain;
|
||||
}
|
||||
// lib.optionalAttrs (config.services.hyperhive.hiveName != null) {
|
||||
|
|
|
|||
|
|
@ -69,13 +69,8 @@ in
|
|||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if hyperhiveDomain != null then "forge.${hyperhiveDomain}" else "localhost";
|
||||
defaultText = lib.literalExpression ''
|
||||
if services.hyperhive.domain != null then
|
||||
"forge.''${services.hyperhive.domain}"
|
||||
else
|
||||
"localhost"
|
||||
'';
|
||||
default = "forge.${hyperhiveDomain}";
|
||||
defaultText = lib.literalExpression ''"forge.''${services.hyperhive.domain}"'';
|
||||
example = "git.example.com";
|
||||
description = ''
|
||||
Public hostname for the forge. Doubles as both the forgejo
|
||||
|
|
@ -83,10 +78,10 @@ in
|
|||
gateway vhost server-name when `behindGateway = true`
|
||||
(sub-domain routing — see `docs/gateway.md`).
|
||||
|
||||
Defaults to `forge.''${services.hyperhive.domain}` when the
|
||||
hive-domain is set (idiomatic sub-domain shape — `forge`
|
||||
labelled under the hive's bare domain), falling back to
|
||||
`localhost` otherwise (direct-on-port behaviour).
|
||||
Defaults to `forge.''${services.hyperhive.domain}` (idiomatic
|
||||
sub-domain shape — `forge` labelled under the hive's bare
|
||||
domain). `services.hyperhive.domain` is required, so there's
|
||||
always a domain to derive from.
|
||||
|
||||
Set to a full hostname (`git.example.com`,
|
||||
`forge.internal.lan`, etc.) for a bespoke vhost shape — the
|
||||
|
|
@ -199,18 +194,17 @@ in
|
|||
{
|
||||
# `cfg.domain` can't be empty — would render `.<hive>` shaped
|
||||
# garbage as both server_name (nginx wildcard catch-all) and
|
||||
# /etc/hosts entry (invalid). Default rejects this case (lands
|
||||
# `"localhost"` when hive-domain is unset), but operator-set
|
||||
# empty strings should fail loud.
|
||||
# /etc/hosts entry (invalid). The default derives a non-empty
|
||||
# `forge.<domain>`, but an operator-set empty string should fail
|
||||
# loud.
|
||||
assertion = cfg.domain != "";
|
||||
message = ''
|
||||
services.hyperhive.forge.domain = "" is rejected. The
|
||||
rendered URLs would be invalid (nginx wildcard catch-all
|
||||
for an empty server_name, /etc/hosts rejects empty entries).
|
||||
Either leave at default (auto-derives to
|
||||
"forge.<services.hyperhive.domain>" when set, else
|
||||
"localhost"), or set a non-empty hostname like "forge.example.com"
|
||||
or "git.internal".
|
||||
"forge.<services.hyperhive.domain>"), or set a non-empty
|
||||
hostname like "forge.example.com" or "git.internal".
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -385,14 +385,6 @@ in
|
|||
|
||||
config = lib.mkIf config.services.hyperhive.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !cfg.localHostsEntry || hyperhiveDomain != null;
|
||||
message = ''
|
||||
services.hyperhive.gateway.localHostsEntry = true requires
|
||||
services.hyperhive.domain to be set. Either pin a hostname
|
||||
or leave `localHostsEntry` at its default of false.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.tls.acme.enable && cfg.tls.certDir != null);
|
||||
message = ''
|
||||
|
|
@ -632,10 +624,11 @@ in
|
|||
'';
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (hyperhiveDomain != null) {
|
||||
// {
|
||||
# FluffyChat boot-config pre-fill so the client's
|
||||
# `.well-known/matrix/client` lookup hits the
|
||||
# right delegation endpoint.
|
||||
# right delegation endpoint. `domain` is required, so
|
||||
# this is always present.
|
||||
"= /config.json" = {
|
||||
extraConfig = ''
|
||||
default_type application/json;
|
||||
|
|
@ -678,7 +671,7 @@ in
|
|||
# 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/gateway.md`.
|
||||
wellKnownLocations = lib.optionalAttrs (matrixCfg.enable && hyperhiveDomain != null) (
|
||||
wellKnownLocations = lib.optionalAttrs matrixCfg.enable (
|
||||
let
|
||||
clientBaseUrl =
|
||||
if matrixCfg.gatewayHost != null then
|
||||
|
|
@ -1020,7 +1013,7 @@ in
|
|||
# sub-domain modules that are on. `lib.unique` dedupes if any
|
||||
# sub-domain happens to equal another. See `docs/gateway.md`
|
||||
# ("Local dev").
|
||||
networking.hosts = lib.mkIf (cfg.localHostsEntry && hyperhiveDomain != null) {
|
||||
networking.hosts = lib.mkIf cfg.localHostsEntry {
|
||||
"127.0.0.1" = lib.unique (
|
||||
[ hyperhiveDomain ]
|
||||
++ lib.optional (config.services.hyperhive.forge.behindGateway or false
|
||||
|
|
|
|||
|
|
@ -149,13 +149,8 @@ in
|
|||
|
||||
gatewayHost = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = if hyperhiveDomain != null then "matrix.${hyperhiveDomain}" else null;
|
||||
defaultText = lib.literalExpression ''
|
||||
if services.hyperhive.domain != null then
|
||||
"matrix.''${services.hyperhive.domain}"
|
||||
else
|
||||
null
|
||||
'';
|
||||
default = "matrix.${hyperhiveDomain}";
|
||||
defaultText = lib.literalExpression ''"matrix.''${services.hyperhive.domain}"'';
|
||||
example = "matrix.example.com";
|
||||
description = ''
|
||||
Public hostname for the matrix homeserver behind the gateway.
|
||||
|
|
@ -294,25 +289,13 @@ in
|
|||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# serverName must exist (irrevocably embedded in user/room IDs);
|
||||
# gatewayHost may not be "" (same footgun as forge.domain —
|
||||
# nginx rejects an empty server_name). docs/matrix.md::Assertion
|
||||
# rationale.
|
||||
# `serverName` is irrevocably embedded in user/room IDs; it derives
|
||||
# from `services.hyperhive.domain` (required, asserted in
|
||||
# hive-network.nix) when not set explicitly, so no separate
|
||||
# domain/serverName assertion is needed here. gatewayHost may not be
|
||||
# "" (same footgun as forge.domain — nginx rejects an empty
|
||||
# server_name). docs/matrix.md::Assertion rationale.
|
||||
assertions = [
|
||||
{
|
||||
assertion = hyperhiveDomain != null || cfg.serverName != null;
|
||||
message = ''
|
||||
services.hyperhive.matrix.enable = true requires either:
|
||||
- services.hyperhive.domain set to your host's canonical domain
|
||||
(recommended; shared with forge / dashboard), or
|
||||
- services.hyperhive.matrix.serverName set explicitly.
|
||||
|
||||
The matrix server_name is embedded into every user ID and
|
||||
room ID on this homeserver — it cannot be changed later
|
||||
without losing every account and chat history. Pick a
|
||||
stable hostname before enabling.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.gatewayHost == null || cfg.gatewayHost != "";
|
||||
message = ''
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ let
|
|||
|
||||
# The host-managed hive CA is the trust anchor for self-signed mode.
|
||||
# It is only stood up when the gateway actually serves a self-signed
|
||||
# cert: a domain must be set (the leaf SANs derive from it) and the
|
||||
# gateway must be in self-signed mode. The self-signed condition is the
|
||||
# gateway module's single source of truth (`gateway.useSelfSigned`):
|
||||
# true when neither an operator cert (`tls.certDir`) nor ACME is set.
|
||||
active = hyperhiveCfg.enable && gatewayCfg.useSelfSigned && domain != null;
|
||||
# cert: the gateway must be in self-signed mode. `domain` is required
|
||||
# (asserted in hive-network.nix), so the leaf SANs always have a
|
||||
# domain to derive from. The self-signed condition is the gateway
|
||||
# module's single source of truth (`gateway.useSelfSigned`): true when
|
||||
# neither an operator cert (`tls.certDir`) nor ACME is set.
|
||||
active = hyperhiveCfg.enable && gatewayCfg.useSelfSigned;
|
||||
in
|
||||
{
|
||||
# Host-side TLS trust root for the self-signed gateway mode.
|
||||
|
|
|
|||
Loading…
Reference in a new issue