Commit graph hyperhive/nix
Author SHA1 Message Date
atlas
e5da9654f1 feat: add hyperhive.availableModels option for configurable model picker
Adds a per-agent hyperhive.availableModels list option (default
[ haiku sonnet opus ]) rendered into the HIVE_AVAILABLE_MODELS env var
(comma-separated) so the per-agent web UI model quick-picker lists
exactly the configured models instead of a hardcoded set. Operators set
a shared default hive-wide or narrow it per-agent.

An assertion guards that hyperhive.model is present in the list so the
picker can always offer the model the agent is actually running.
2026-06-05 13:47:33 +02:00
atlas
4f446269ae fix: re-run hive-ci-prefetch on every container restart via partOf
The hive-ci-prefetch oneshot has RemainAfterExit=true and is wired to the
container only via wantedBy + before. Once it runs successfully it stays
'active (exited)' indefinitely, so systemd skips it on subsequent container
restarts. The runner-token file it wrote is never refreshed.

This breaks the runner after its first registration: the token written on
the first successful boot is either a placeholder (forge-core-token wasn't
ready yet) or a registration token that has since been consumed/rotated.
On the next container restart prefetch does not re-run, the stale token
persists, and the in-container register service fails with
'invalid_argument: runner registration token not found' — exactly the
symptom in the field (worked briefly, two orphan runners registered, then
permanently offline).

Add partOf = [ nixos-container@hive-ci.service ] so a container stop/restart
propagates to the prefetch unit, forcing it to re-run and fetch a fresh
registration token before the container comes back up. before= still orders
it ahead of the container start within the same transaction.
2026-06-05 00:39:13 +02:00
damocles
6c16d25039 fix(#1304) scope state/harness chowns to own agent dir in hive-agent-user-migrate 2026-06-04 21:04:12 +02:00
atlas
60adb5aac9 fix: chmod .claude dir 0755 in hive-agent-user-migrate activation script
ensure_claude_dir creates the dir as 0755 but cannot re-chmod after
hive-agent-user-migrate chowns it to the agent user (EPERM — non-owner).
The activation script runs as root and can always chmod it. Add an explicit
'chmod 755 $homeDir/.claude' after the existing chown so existing 0700
dirs from pre-fix containers are corrected on the next container boot.

Without this, all agents with pre-existing .claude dirs show 'needs login'
in the dashboard even with working sessions, because hive-core cannot list
the 0700 dir owned by a different user.
2026-06-04 20:48:39 +02:00
atlas
0a3a956d28 fix: add /var/lib/hyperhive to hive-priv ReadWritePaths
WriteAgentForgeToken and WriteAgentMatrixToken write agent state files
under /var/lib/hyperhive/agents/<name>/state/. The hive-priv service
has ProtectSystem=strict which makes all of /var read-only by default,
but /var/lib/hyperhive was missing from ReadWritePaths, causing EROFS
on every token write.

Closes #1274.
2026-06-04 15:35:46 +02:00
damocles
c2d023ba86 fix(#1269): add requires+after hive-c0re.socket to service unit 2026-06-04 15:16:39 +02:00
damocles
911dc5227b fix(#1263): repeat security headers in locations with their own add_header 2026-06-04 14:35:18 +02:00
damocles
41eb3f806c refactor: remove hyperhive.role option — there is only one role: agent 2026-06-04 14:31:44 +02:00
atlas
5ec1306c7b feat(gateway): assert hsts.enable requires TLS
Addresses mara's review: add a NixOS assertion that rejects
hsts.enable = true when no TLS mode is configured. HSTS over
plain HTTP is silently ignored by browsers; the assertion turns
a silent misconfiguration into a build-time error.
2026-06-04 13:50:22 +02:00
atlas
5264ef7d5e feat(gateway): add gateway.hsts.enable option, disabled by default
HSTS was unconditionally tied to hasTls. This is risky: enabling it on a
deployment that later loses TLS locks browsers out until max-age expires.

Add three options under services.hyperhive.gateway.hsts:
  enable          — bool, default false
  maxAge          — seconds, default 31536000 (1 year)
  includeSubDomains — bool, default true

HSTS header is now only emitted when hsts.enable = true.
2026-06-04 13:50:22 +02:00
atlas
18941848dc feat(gateway): add security headers to all vhosts
X-Frame-Options, X-Content-Type-Options, Referrer-Policy at server
scope on _, forge, and matrix vhosts. HSTS added when TLS is active.

nginx inheritance rule: locations with their own add_header (CORS API
endpoints like /.well-known/matrix/client, /_matrix/) are unaffected —
they already carry the headers they need. HTML-serving and proxy
locations pick the security headers up automatically.
2026-06-04 13:50:22 +02:00
atlas
644519f358 fix: skip matrix avatar upload when icon unchanged
matrix-avatar-sync ran on every boot (RemainAfterExit=false + path
trigger), uploading a fresh PNG each time. Every upload mints a new
mxc:// URI, which triggers a profile state event in every joined room
— resulting in timeline spam even when the avatar hasn't changed.

Fix: before uploading, compute sha256sum of /etc/hyperhive/icon.svg
and compare against the last-synced hash stored in
$HYPERHIVE_STATE_DIR/matrix-avatar-icon-hash. Skip the upload if the
hash matches. Write the hash after a successful avatar_url PUT so
subsequent boots are no-ops until the icon file changes.

Hash file lives in the agent's state dir — survives restart, cleared
on purge (so purge + re-provision gets a fresh upload). Delete to force
re-upload manually.

Closes #1231
2026-06-03 23:24:21 +02:00
atlas
dacd83f278 fix: wait for forge-core-token in prefetch instead of bailing early
On first boot (or after a wipe) hive-c0re writes forge-core-token only
after the forge container starts and the admin is provisioned. This
lags hive-c0re.service becoming active. The previous code bailed
immediately with TOKEN=placeholder if the token file was absent,
causing the runner to fail registration with 'token not found'.

Fix: merge both waits (core-token file appearance + forge API ready)
into a single 60s retry loop. The early-bail path is removed; the
script only exits cleanly if .runner is valid (writes placeholder)
or a fresh registration token is obtained.

When .runner exists but core-token is absent after 60s, we keep the
existing credentials (safe — the runner holds valid creds; next boot
will validate properly).

Closes #1224 (which tracks #1221).
2026-06-03 22:50:01 +02:00
atlas
9209094397 fix: move core-token out of hive-ci container (host-side prefetch service) 2026-06-03 22:18:53 +02:00
atlas
4d1ce6f313 fix: use path= (ExecSearchPath) to add nix to hive-ci runner PATH 2026-06-03 21:39:47 +02:00
damocles
d1fbb4aef8 feat(#1184): make agent CPU quota and memory limit configurable 2026-06-03 21:39:14 +02:00
atlas
4f80253101 fix: use lib.mkForce on hive-ci runner PATH to resolve conflicting definition 2026-06-03 21:37:27 +02:00
atlas
181535650b fix(#1199): add nix to hive-ci PATH for runner job execution
The gitea-actions-runner's host-scheme job processes use the
service's environment PATH, not the NixOS login-shell PATH.
Without this, 'nix flake check' and similar steps fail with
'nix: command not found'.

- add pkgs.nix to environment.systemPackages (ensures the binary
  is in /run/current-system/sw/bin)
- set explicit PATH on gitea-runner-hive service covering both
  /run/current-system/sw/bin and /nix/var/nix/profiles/default/bin
2026-06-03 21:25:05 +02:00
damocles
18e0e8fc2b rename bash tools: bash_run/bash_status → run/status (#1186) 2026-06-03 21:20:49 +02:00
müde
3bb45acd87 fix: give hive-bash-daemon PATH so it can spawn sh + agent tooling 2026-06-03 20:17:09 +02:00
müde
872d605b4e fix: RuntimeDirectoryPreserve=yes on bash+matrix daemons so they survive rebuild restarts 2026-06-03 19:53:08 +02:00
atlas
453ee9c44c fix(nix): merge duplicate hyperhive.extraMcpServers assignments in harness-base
bash and matrix MCP entries were both assigned to the same attribute in the
same attrset literal, causing nix evaluation to fail with "attribute already
defined". Merged both into a single lib.mkMerge list under one assignment.
2026-06-03 19:00:12 +02:00
damocles
f87a52f28f fix: address argus review yellows on #1158
- restore count in get_loose_ends: 'N local task(s):' instead of bare 'local task(s):'
- add cross-crate coupling comment to both mcp_loose_ends_dir() copies
- add comment in hive-bash-daemon service env explaining HYPERHIVE_HARNESS_DIR
  is already injected via systemd.globalEnvironment by the meta flake
2026-06-03 18:06:59 +02:00
damocles
e86160820a feat(#1106): split bash mcp into hive-bash-daemon + hive-bash-mcp bridge
- new hive-bash-mcp crate: daemon (subprocess runner, wake signals) +
  stdio bridge (mcp tools). mirrors hive-matrix-mcp architecture
- hive-ag3nt: remove bash_runner.rs and bash_run/bash_status mcp tools;
  get_loose_ends uses hive_bash_mcp:🏃:active_tasks() via crate dep
- harness-base.nix: add hive-bash-daemon systemd service + auto-inject
  bash extraMcpServer into every agent (socket: /run/hive-bash/socket)
2026-06-03 18:06:59 +02:00
damocles
9aa624d310 fix(#1159): set WorkingDirectory=/var/lib/hive-priv for hive-priv service
With ProtectSystem=strict (added in the previous commit), / is read-only
inside hive-priv. nix creates a temp result symlink in its cwd; without
WorkingDirectory that cwd is / (systemd default), causing:

  error: creating symlink /.tmp.tmp-...: Read-only file system

Point WorkingDirectory at the StateDirectory (/var/lib/hive-priv) which is
always writable under strict. Nix drops its temp symlink there instead.
2026-06-03 17:44:37 +02:00
atlas
0e3df82504 feat(#972): tighten hive-priv systemd sandbox — ProtectSystem=strict + ReadWritePaths
Replace ProtectSystem=false with ProtectSystem=strict now that privsep
is complete (issue #702 closed) and hive-c0re runs as the non-root
hive-core user.

ReadWritePaths carves out the six paths hive-priv must write to at
runtime; everything else is read-only:

  /etc/nixos-containers   — writes <container>.conf (bind-mounts,
                            network isolation, nspawn flags)
  /run/hive               — fallback socket bind when LISTEN_FDS absent
  /run/hive-agent         — chown/chmod per-agent socket directories
  /run/systemd            — container@ drop-ins (resource limits) and
                            machinectl/systemd-machined machine state
  /var/lib/nixos-containers — container rootfs (nixos-container script)
  /nix                    — nix store + profile updates during
                            container create/update

PrivateTmp=true (already set) keeps /tmp private.
StateDirectory=hive-priv (already set) keeps /var/lib/hive-priv writable
for nix's fetch/eval cache (HOME points there).

nix flake check --no-build passes.
2026-06-03 16:57:01 +02:00
atlas
d2298e183b fix(#1155): enable recommendedTlsSettings + recommendedGzipSettings in gateway nginx 2026-06-03 16:48:33 +02:00
atlas
fb93cbf5c2 feat: add tls.acme mode — nginx inside container manages Let's Encrypt
per mara's suggestion: instead of bind-mounting operator certs, let
nginx handle ACME directly inside the gateway container.

- tls.acme.enable: lets nginx obtain + renew via HTTP-01 challenge
- tls.acme.email: ACME account contact (required when enable=true)
- security.acme in container config when acme.enable
- hasTls includes acme.enable → https, httpsPort listen, firewall
- mutual exclusion assertions: acme vs selfSignedTls vs certDir
- docs/gateway.md: four-mode TLS table + ACME section

typical setup:
  selfSignedTls = false; openFirewall = true;
  tls.acme = { enable = true; email = "admin@example.com"; };
2026-06-03 16:46:52 +02:00
atlas
44122c66de feat(#594): gateway operator-cert TLS mode (tls.certDir)
add services.hyperhive.gateway.tls.certDir option: operators with a
CA-signed cert (Let's Encrypt, corporate CA) point at the ACME output
dir instead of using the auto-generated self-signed cert.

- tls.certDir: host path bind-mounted r/o at /run/hive-tls/ in gateway
- tls.certName / tls.keyName: filenames within certDir (default: cert.pem / key.pem, matches nixpkgs security.acme layout)
- hasTls = selfSignedTls || certDir != null: publicScheme=https in both cases
- assertion: selfSignedTls=true + certDir set together is an error
- openFirewall: httpsPort opened in both TLS modes
- docs/gateway.md: TLS modes table + operator-cert section
- docs updated in swarm.md peer config reference in the cert TLS section

when using operator cert, swarm peers can omit certFingerprint —
standard CA bundle handles trust automatically.
2026-06-03 16:46:52 +02:00
atlas
a2a96490d3 fix: assert gateway.enable when isolateContainers + forge.enable
isolated agents reach forge via http://forge.<domain> → nginx.
without the gateway there is nothing on port 80 to serve that
hostname. assert early rather than fail silently at runtime.

addresses argus yellow note on PR #1150.
2026-06-03 16:33:10 +02:00
atlas
806d0e4a61 fix: use forge domain URL + open 80/443 for isolated agents
when isolateContainers=true, isolated agents have dnsmasq as their
resolver — forge.<domain> resolves to bridgeIp. route HIVE_FORGE_URL
through nginx on port 80 instead of exposing the raw forge port.

- HIVE_FORGE_URL: http://<forge.domain> when isolated (nginx proxies)
- bridge firewall: open 80+443 for agents to reach nginx (gateway)
- remove forge-specific httpPort rule (no longer needed)
- update docs/gateway.md + docs/network.md

per mara's review comment on PR #1150.
2026-06-03 16:33:10 +02:00
atlas
c97120f016 fix: forge URL + firewall for isolateContainers=true
When containers run in private netns (isolateContainers=true), host
loopback is unreachable so HIVE_FORGE_URL=http://127.0.0.1:3000 breaks.

- nix/modules/hive-network.nix: when isolateContainers is on + forge
  is enabled, open forge.httpPort on the bridge interface so agents
  can reach forgejo at bridgeIp:httpPort (forgejo binds 0.0.0.0)
- nix/modules/hive-c0re.nix: HIVE_FORGE_URL switches to bridge IP
  when network.enable && isolateContainers; loopback path retained
  when isolateContainers=false
- docs/network.md: add Forge access + Forge URL rows to effects table
- docs/gateway.md: rewrite HIVE_FORGE_URL section for both modes
2026-06-03 16:33:10 +02:00
atlas
9e63c9c1d0 fix: wireguard address option default + clarify peer mesh inclusion doc 2026-06-03 15:19:16 +02:00
atlas
89609aaa6a feat(#569): wireguard inter-hive mesh option
Add opt-in WireGuard mesh support to services.hyperhive.swarm:

- swarm.peers.<domain>.wireguardPublicKey — peer's wg public key
- swarm.peers.<domain>.wireguardEndpoint  — peer's UDP endpoint (optional)
- swarm.peers.<domain>.wireguardAddress   — peer's mesh IP with prefix

- swarm.wireguard.enable           — bring up wg-hive interface
- swarm.wireguard.privateKeyFile   — path to host's wg private key
- swarm.wireguard.address          — this host's mesh IP/prefix
- swarm.wireguard.listenPort       — UDP listen port (default 51820)
- swarm.wireguard.persistentKeepalive — keepalive seconds (default 25)

When enabled, generates networking.wireguard.interfaces.wg-hive with
one peer entry per mesh-enabled swarm.peers entry. Opens listenPort
UDP on the host firewall. Adds wireguard_address to HYPERHIVE_PEERS
JSON so hive-c0re can use mesh IPs for intra-swarm routing.

Assertions guard against enable=true without privateKeyFile or address.

Also refactors networking.firewall.allowedTCPPortRanges from the
nested attrset form (which conflicted with the new allowedUDPPorts
line) to the per-attribute form.

docs/swarm.md: adds WireGuard setup section with key generation
commands, two-hive config example, NAT/keepalive notes.
2026-06-03 15:19:16 +02:00
atlas
b89c5f5334 network: fix stale assertion messages; move prose to docs/network.md 2026-06-03 11:19:29 +02:00
atlas
3db51deace network: default network.enable to services.hyperhive.enable 2026-06-03 11:19:29 +02:00
atlas
8e50ddf016 network: revert isolateContainers default to false 2026-06-03 11:19:29 +02:00
atlas
a141d157ba network: default isolateContainers to true 2026-06-03 11:19:29 +02:00
atlas
c1b40ef819 network: fix dead assertion + document HIVE_NETWORK_SUBNET host-address
Two argus review fixups:

1. Move isolateContainers assertion to an unconditional `lib.mkIf
   cfg.isolateContainers` arm. The prior placement inside
   `mkIf (enable && isolateContainers)` was dead code — the assertion
   could never fire because both flags were already true by the time the
   block activated. Now `isolateContainers=true; enable=false;` raises
   a NixOS assertion error at eval time.

2. Add comment on HIVE_NETWORK_SUBNET noting it carries the host-side
   bridge IP (e.g. "10.42.0.1/24"), not the canonical network address
   ("10.42.0.0/24"). Rust consumer must normalize via bitwise AND before
   subnet membership checks or address arithmetic.
2026-06-03 11:19:29 +02:00
atlas
39b4c65922 network: add isolateContainers option for #14 netns isolation
Adds `services.hyperhive.network.isolateContainers` (bool, default
false). When enabled alongside `network.enable`, activates:

- IP forwarding + NAT masquerade so isolated agents reach the internet
- nftables DROP rule blocking bridge-subnet → loopback (defence-in-depth
  against compromised agent reaching the c0re dashboard)
- `HIVE_NETWORK_ISOLATION`, `HIVE_NETWORK_BRIDGE`, `HIVE_NETWORK_SUBNET`
  injected into the hive-c0re service env; the Rust lifecycle reads these
  to set `PRIVATE_NETWORK`, `LOCAL_ADDRESS`, and `HOST_BRIDGE` in each
  agent container's conf

Config block rewritten as `lib.mkMerge [...]` — the prior `lib.mkIf //
lib.mkIf` pattern was invalid nix (mkIf returns a tagged value, not an
attrset; // on it is a type error). See docs/network.md for full design.
2026-06-03 11:19:29 +02:00
müde
d96a924d77 fix(#702): set git safe.directory for cross-user repo reads
Privsep splits repo ownership: hive-priv (root) fetches the hive-core-owned
meta/applied repos via nix, and hive-c0re (hive-core) fetches the agent-owned
proposed-config repos. git/libgit2's dubious-ownership guard rejects both
('repository path is not owned by current user'), failing every rebuild.
Install a root-trusted gitconfig with safe.directory=* in each service's
HOME; both already have read access — this only satisfies the guard.
2026-06-03 00:52:42 +02:00
müde
40be58b0b9 fix(#702): heal proposed-config ownership in agent-user-migrate
The migration chown stomped agents/<name>/config (the proposed-config repo)
to hive-core like everything else, but hive-agent-user-migrate only re-chowned
state/harness/.claude on boot, leaving config core-owned. config is owned by
the editing parent/manager agent (core only pulls from it), so chown it to
this user too. RO self-mount in an agent's own container fails harmlessly.
2026-06-03 00:37:55 +02:00
müde
d272803d58 fix(#702): spare agent creds from migration chown; give nix a writable HOME
Two privsep follow-ups, both in the hive-c0re service:

1. The migration 'chown -R hive-core /var/lib/hyperhive' (which only
   started running once ExecStartPre stopped failing 203/EXEC) stomped every
   agent's bind-mounted creds — agents/<name>/{claude,state,harness,config}
   are owned by the per-agent/manager users — logging all agents out with no
   way back in. Scope it to everything *except* agents/, plus the agents/
   dir node itself so c0re can still create new per-agent subdirs. Each
   container's hive-agent-user-migrate activation chowns the contents back.

2. nix (prebuild 'nix build', flake-check, meta eval in c0re; nixos-container
   update->nix in priv) writes its cache under $HOME/.cache. Both services
   run as users with no home -> HOME=/var/empty (unwritable) -> Lix cache
   init fails, rebuilds error out. Set HOME to each service's StateDirectory
   (adding one for hive-priv).
2026-06-03 00:33:15 +02:00
müde
e7b6896e4c fix(#702): give hive-priv a PATH; route forge/matrix list through priv
hive-priv had no PATH, so the nixos-container it runs for every container
op (incl. lifecycle::list) failed ENOENT. build_all swallows that into an
empty list — the 'no managed containers' symptom. Give the helper the
minimal set nixos-container shells out to (nixos-container, nix, util-linux,
e2fsprogs) on top of the systemd/coreutils/findutils already in the unit
PATH, rather than the whole system profile.

Also route forge/matrix is_present() through priv_client::list_containers
instead of spawning nixos-container directly from unprivileged hive-c0re.
2026-06-03 00:05:17 +02:00
müde
1e4066b9cb fix(#702): hive-c0re ExecStartPre fails 203/EXEC — coreutils has no sh
The state-migration chown invoked ${pkgs.coreutils}/bin/sh, which does not
exist (coreutils ships chown, not sh), so ExecStartPre exited 203/EXEC on
every boot. hive-c0re hit its start limit and never came up, so the gateway
returned 502 Bad Gateway. Call chown directly with the +- prefix (run as
root, tolerate failure) instead of going through a shell for '|| true'.
2026-06-02 23:49:51 +02:00
damocles
0bec3af933 feat(#702): drop hive-c0re from root to hive-core user (privsep phase 2) 2026-06-02 22:45:12 +02:00
damocles
89665b94de feat(#1014): rename manager agent root→ruth across all crates + frontend 2026-06-02 18:18:06 +02:00
atlas
ed6f40ba40 fix(#1061): suppress tuwunel default displayname suffix
Tuwunel 1.6.1 appends " 💕" to every new user's display name via the
new_user_displayname_suffix config option. Set it to empty string so
agent and operator matrix accounts get clean names.

Existing users (atlas, mara, root) need a one-time display name reset.
Atlas's was already corrected via the client API.
2026-06-02 11:18:38 +02:00
damocles
4ee7967e2a deprecate allowedBashPatterns with warning instead of hard removal 2026-06-02 09:28:14 +02:00
damocles
88f22065b0 feat(#1058): disallow built-in Bash; remove allowedBashPatterns; prompt sweep 2026-06-02 01:14:24 +02:00