Commit graph hyperhive/nix/host-modules/hive-gateway
Author SHA1 Message Date
atlas
cf4a60b837 fix(nix): a missing swarm-services leaf must not kill the whole gateway
main is already unborked -- the constraint-syntax fix landed directly in
31e47632 and the hive-tls-ca ordering race in b5066ad0. What is left is
the reason the syntax bug became an OUTAGE rather than a warning, and it
is independent of both.

nginx refuses to load a config naming a cert file that does not exist:
`cannot load certificate ... no such file` fails the pre-start test, so
the vhost does not degrade -- the ENTIRE proxy dies and restart-loops
into start-limit-hit, taking the forge, the dashboard and matrix with
it. Signing tolerated the leaf's absence and the container import
tolerated it; the vhost reference did not, and that is the one that
decides whether anything serves.

Absence stays a normal state here: the leaf only exists where the swarm
CA is autoconfigured. So the import now installs the HIVE leaf at the
services path instead of removing the file. That is a name mismatch on
those vhosts -- browsers warn, strict clients refuse -- and every other
name keeps working. A bad certificate is a bad afternoon; a dead gateway
is a dead hive.

Also keeps the comment explaining why every nameConstraints entry needs
its own `permitted;` prefix, so the next reader doesn't re-derive it
from an outage.
2026-08-06 00:30:22 +02:00
atlas
11b8140981 feat(nix): serve swarm-service vhosts the swarm-services leaf
nginx already carried TLS per vhost, so this is a cert choice rather
than a restructure: a vhost whose name this hive's CA cannot sign
gets the swarm-services pair, and every other vhost keeps the hive
pair.

Which vhosts those are is not decided here. Both the sub-CA's name
constraints and this choice read swarm.serviceDomains, so "a swarm
service" means one thing in one place -- a vhost served a cert its
issuer is constrained out of would fail at TLS, and the two lists
drifting is the only way to get there.

Only in self-signed mode. With ACME or an operator cert there is a
single issuer that already covers every name, and a second pair would
be a cert nobody asked for.

The container import copies the pair only when the host issued one,
and removes a stale copy otherwise: the leaf exists only where the
swarm CA is autoconfigured, so absent is a normal state and a
leftover from a host that stopped issuing it is not.
2026-08-05 22:43:03 +02:00
atlas
433b294099 refactor(nix): swarm.peers becomes swarm.hives, a directory of every hive
One attrset describing every hive in the swarm including this one,
identical on every host, with hiveName selecting which entry is us.
"My peers" is derived (swarm.peerHives) rather than declared.

Every field in the old per-host peer list was intrinsic to the hive it
described, never to the pair -- so the list was a directory each host
kept its own copy of. Beyond the deduplication it removes a bug class:
two hosts could hold different endpoints for the same third hive with
nothing to detect the disagreement.

Drops the per-hive caCert. Trust inside a swarm derives from the swarm
root, which every hive chains to. What that genuinely removes is
trusting a hive whose root this swarm does not own -- a cross-swarm
problem that wants a mechanism of its own, not a field that happened to
work.

The matrix container's certificateFiles block goes with it and could
NOT be migrated: that list is read at build time and the swarm root is
a runtime file (its key must never enter the store), so there is no
build-time name to put there. caCert being a nix path was precisely
what made it the build-time distribution channel. Agents are unaffected
-- hive-tls folds the root into the hive trust bundle and the meta
renderer embeds that one file. Tracked separately.

Migration is an assertion plus warnings, not a rename: hives is peers
union {self}, and the set gains a member no existing config has written
down. A rename migrates a name and a default can re-root a meaning;
neither can conjure a new member. The warning explains, the self-entry
assertion stops the build.
2026-08-05 20:44:16 +02:00
atlas
fcdff04b23 refactor(nix): move the matrix host options under services.hyperhive.swarm
Second slice of the swarm-global service consolidation, same shape as
the forge move: the operator-facing host options become
services.hyperhive.swarm.matrix.*, and one mkRenamedOptionModule on the
namespace carries the whole subtree (nested gui.* included), so there is
no leaf list to forget an entry from.

The rename lives in hive-matrix.nix, the module that declares the
options, so each service's migration stays independent of its siblings.

The per-agent hyperhive.matrix.{enable,url} and hyperhive.matrixAccounts
are a different namespace -- a client pointer at the service, not the
service -- and deliberately do not move.
2026-08-05 13:45:09 +02:00
atlas
38c222fadd feat(nix): move the forge host options under services.hyperhive.swarm
Forgejo is a swarm-global service, so its operator-facing host options
move to services.hyperhive.swarm.forge (and .swarm.forge.ci) as the
first of the namespace consolidation.

Existing hive configs keep evaluating: swarm-renames.nix maps every
moved leaf with mkRenamedOptionModule, which also emits a deprecation
warning naming both the old and new path, so an operator is told what to
rename rather than discovering it from a failed eval.

The per-agent hyperhive.forge.url does NOT move. It is a client pointer
at whatever forge an agent talks to - it shares a word with the service
and nothing else, and the two are already documented as separate option
surfaces.

Verified by evaluating the host module, since no Rust gate evaluates
nix: setting the old paths and reading the new ones yields the values
(httpPort 3999, ci.concurrency 7), and config.warnings carries the
rename notice.
2026-08-05 03:44:53 +02:00
atlas
3fc1588e83 fix: declare the agent socket dir's owner in tmpfiles, not by chown after
/run/hive-agent/<name> was 0777 root root in steady state, not just during
first spawn. A directory without the sticky bit lets any user unlink files
in it, and the gateway container has all of /run/hive-agent bind-mounted
in, so anything that could reach the path could delete an agent's
agent.sock, bind its own, and receive that agent's todos from hive-c0re.

Two mechanisms were writing the dir and undoing each other: the tmpfiles.d
entry wrote 0777 root root, then hive-c0re round-tripped through hive-priv's
ChownSocketDir to narrow it. `d` re-asserts mode and owner on every apply
and the file is regenerated on any agent's spawn or destroy, so every such
event reset every agent's dir back to world-writable.

SyncAgentTmpfiles now carries each agent's container uid/gid and the entry
declares the answer: 0751 <uid> <gid>. Three principals need the dir and no
two share a group -- the harness binds its sockets (owner rwx), hive-c0re
dials agent.sock and the gateway's nginx dials web.sock (both only need
traverse, and both sockets are already 0666).

Deletes ChownSocketDir and ChmodSocketDir, both priv_client wrappers, the
either/or in host_config with its two swallowed warn!s, and the now-dead
socket_dir_path -- two verbs off the privileged helper's surface and one
round-trip off every agent spawn.

Also makes the two tmpfiles rules for /run/hive-agent itself agree: the
gateway module said hive-core, the generated file said root, and which won
depended on the order systemd read them in.
2026-08-04 01:00:48 +02:00
iris
9efe6f64b6 move swaggerUiTheme nix option out of c0re's namespace
services.hyperhive.c0re.swaggerUiTheme -> services.hyperhive.gateway.swaggerUiTheme.
hive-c0re has no relationship to this option (it's consumed entirely
by the gateway's nginx config) since the previous two commits moved
the theme files out of hive-c0re/ and gateway to hosting the whole
dist itself — the option shouldn't live in c0re's namespace either.

Pure option move: default wiring in flake.nix now sets
services.hyperhive.gateway.swaggerUiTheme instead of the c0re one;
hive-gateway/default.nix reads it via the module's own already-bound
cfg (services.hyperhive.gateway) instead of reaching into c0re's.

Verified: nix build .#swagger-ui-theme unaffected (same store path,
pure option-plumbing change, no content change); a full nixosSystem
eval of nixosModules.default resolves the option at its new path and
confirms the gateway's /api/docs/ location still aliases the correct
store path.
2026-08-02 21:24:57 +02:00
iris
1bc9c18504 gateway: nginx-hosts the full Swagger UI dist, core drops the fallback
Extends the theme-only alias into the full shape mara asked for on the
PR thread:

1. nix/packages/swagger-ui-dist.nix — plain vendored Swagger UI 5.17.14
   dist, sourced directly from the swagger-ui-dist npm package (same
   release the Rust utoipa-swagger-ui-vendored crate ships, verified
   via matching gitHead commit) rather than through Cargo.lock/cargo.
2. nix/packages/swagger-ui-theme.nix — overlays our 3 override files
   (index.html, hyperhive-theme.css, and now swagger-initializer.js)
   onto (1).
3. vhosts.nix's swaggerUiLocations now prefix-matches the whole
   /api/docs/ tree (not just 2 exact-match files) straight from (2),
   plus a `= /api/docs` redirect shim since hive-c0re's own redirect
   is going away too. /api/openapi.json (outside this prefix) keeps
   proxying to c0re unchanged — that's the one thing that stays
   dynamic.

New file swagger-initializer.js needed hand-verification: the plain
vendored copy hardcodes the swagger.io petstore demo URL.
utoipa-swagger-ui normally rewrites it per-request from a {{config}}
placeholder its own build.rs injects — since hive-c0re won't be
serving this file at all once its SwaggerUi mount is removed, that
rewrite has to be baked in statically here instead. Derived by
actually running build.rs's own two transforms (strip the default
layout: line, splice the Config JSON in place of the url/deepLinking
block) against the real vendored file, not typed from scratch —
verified byte-for-byte against what format_config() would produce for
hive-c0re's actual single-URL config, and checked with node --check.

Coordinated with damocles: he's taking the corresponding hive-c0re
side (drop the utoipa-swagger-ui dependency + SwaggerUi::new(...)
mount, keep only the plain /api/openapi.json route) once this lands.

Verified: nix fmt clean; nix build .#swagger-ui-theme succeeds, output
byte-matches the checked-in override files and node --check passes on
swagger-initializer.js; a full nixosSystem eval of nixosModules.default
resolves both new locations (/api/docs/ aliased to the right store
path, = /api/docs redirecting) with auth threaded through.
2026-08-02 21:24:57 +02:00
damocles
de1659d10e hive-gateway: serve the swagger-ui theme via nginx alias, not a c0re build hook 2026-08-02 21:24:57 +02:00
damocles
b39bf67cb3 add /health/live and /health/ready hive-wide health endpoints 2026-07-31 21:01:23 +02:00
atlas
2af8c2d17d fix(gateway): resync the gateway's resolv.conf when the host's changes
The gateway container's /etc/resolv.conf is a one-shot copy: nixos-container
cps it in from the host in its start script, and nspawn's --resolv-conf=auto
copies (not binds) for a writable host-netns container. systemd-nspawn(1)
states the consequence outright — "no further propagation of configuration is
generally done after the one-time early initialization (this is because the
file is usually updated through copying and renaming)".

dnsmasq has no explicit upstream and follows that file, so a host network
change strands it on a resolver that no longer answers and every non-hive
lookup from every agent hangs. Agents' own resolvers point at the static
bridge IP and never go stale, which is why the symptom presents as "the
gateway needs a kick".

Add a host-side hive-gateway-resolv path unit watching /etc/resolv.conf.
On change it machinectl copy-to's the file into the container and reloads
dnsmasq — ExecReload is kill -HUP, so upstreams are re-read and the cache
flushed without dropping anything; nginx never notices.

- watched from the HOST: a rename on the host doesn't cross the nspawn mount
  namespace, so an in-container path unit can't see it (same reason c0re
  reloads nginx from the host side)
- copy, not a file bind-mount: openresolv renames over the file, so a bind
  would pin the first inode forever — strictly worse than today
- machinectl copy-to writes through the container's own mount namespace, so
  this holds regardless of how the container assembles /etc
- armed Before=network-pre.target so the boot's first DHCP write is caught,
  and re-run on gateway start for changes made while it was down
- a host file with no nameserver line is skipped, not pushed, so a
  mid-rewrite snapshot can't blank hive DNS
- deliberately no fallback server=: dnsmasq queries all known upstreams in
  parallel, so a hardcoded public resolver would take a share of normal
  traffic rather than only covering the gap
2026-07-26 18:01:34 +02:00
müde
0d416df1e8 fix: shared-netns infra containers must not run their own firewall 2026-07-14 00:08:33 +02:00
müde
935e967718 feat: hive DNS always follows the host resolver 2026-07-13 22:09:53 +02:00
müde
4a48ce5024 refactor: nix/host-modules + nix/agent-modules layout, update doc paths 2026-07-13 22:05:49 +02:00