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.
The option had a `http://localhost:3000` default, which is only ever
correct when the forge shares the caller's network namespace — inside an
agent's netns `localhost` is the agent, and the forge may well be on
another host. Making it *required* instead was worse: the flake's own
container configs are what hive-c0re extends per agent, so the value
they needed in order to evaluate became a second definition on every
agent and collided with the real one.
`null` resolves both. It is not a URL, so nothing can quietly talk to
the wrong machine, and it needs no placeholder anywhere: the bases
evaluate as they are, so nothing deployment-shaped sits on the config
agents inherit from. The units that would consume the URL — tea-login
and forge-avatar-sync — are simply not generated without one, making an
absent forge an absent integration rather than a misdirected one.
hive-forge-notify is unaffected: it reads HIVE_FORGE_URL from the
forwarded global environment, not from this option.
Verified:
agent-base/ruth evaluate with forge.url = null, zero failing assertions
bare base: tea_login_present = false, avatar_present = false,
notify_present = true
extended with a rendered URL: FORGE_URL=http://forge.real.test
Refs #2860
Making `hyperhive.forge.url` required broke `nix flake check`:
`nixosConfigurations.agent-base` and `.ruth` exist to typecheck the
agent modules and to pre-build the container closure, and nothing in
that path supplies a value — `forge.nix` reads the option
unconditionally for tea-login's `FORGE_URL`.
Real containers are unaffected: they are built from the generated meta
flake, where hive-c0re renders the option per agent from the host's
`HIVE_FORGE_URL` (meta.rs's `SERVICE_URL_OPTIONS`). They never evaluate
through `self.nixosConfigurations`, so this value cannot reach a
running agent.
Uses a `.invalid` host (RFC 2606, guaranteed not to resolve) rather
than a plausible loopback: if it ever did escape into a runtime path it
must fail at DNS instead of quietly connecting to whatever is listening
locally, which is the failure mode this issue exists to remove.
Verified without the build farm:
nix eval .#nixosConfigurations.<cfg>.config.assertions \
--apply 'l: builtins.filter (a: !a.assertion) l' # => []
nix eval --raw .#nixosConfigurations.<cfg>.config.systemd.services.tea-login.script
Refs #2860
hive-forge-notify grows a second binary, hive-github-notify. The two
share the notification half of the job — tolerant parse, classification,
formatting, dedupe, todo delivery — and nothing else: each binary owns
its host's protocol outright.
Two binaries rather than one multi-source daemon, and rather than a
cargo feature. A feature would unify across the workspace and cost every
crate its build cache. Two binaries keep the decision in nix: forge.nix
installs the forge unit, github.nix installs the github one under
hyperhive.github.enable, so a hive built without that module has no
github poller in its closure at all — GitHub access is separable (a
tier, a policy boundary), not merely switched off. Both binaries ship
from the existing derivation, so packages.nix is untouched.
The split is real at the code level too, not just at the unit level.
source.rs is a trait; the impls live in the binaries that use them, so
neither binary links the other's protocol code and the library names no
host at all. The forge-only assigned-issue rollup moves into the forge
binary for the same reason: it asks the forge what is assigned to this
agent, which is not a notification-protocol concern.
At runtime the github unit needs a PAT at <state>/github-token, the same
dashboard-provisioned token the gh wrapper and the git credential helper
already use. No PAT: it logs why and exits 0, which is why the unit is
Restart=on-failure and not always.
Forgejo's notifications API is modelled on GitHub's, so one tolerant
parse serves both — the differences (string thread ids, PullRequest vs
Pull) are absorbed by lenient deserializers rather than a second parse
path. Thread ids normalise to String at the parse boundary; they are
only ever opaque keys. Todo keys gain a per-source prefix so the two
hosts cannot collide, and the forge's is deliberately empty to keep
existing forge todo keys stable across the deploy that lands this.
The github loop honours the server's X-Poll-Interval, re-arming only
when the server asks for a slower cadence than ours; the hint is read
before the status check, because it arrives on error and empty pages too
and that is exactly when it matters. Reading the notification stream
needs the notifications scope on the PAT, which a token minted for push
access typically lacks; the failure mode is silence, so docs/github.md
says so explicitly.
`hyperhive.gui.enable = true` could not evaluate for any agent:
error: attribute 'hive-screen-mcp' missing
at nix/agent-modules/screen.nix:23:46
screen.nix is activated by `gui.enable` and reads
`config.hyperhive.packages.hive-screen-mcp`, but the flake's
`agentPackages` module — the thing that actually populates
`hyperhive.packages` from `self.packages.<system>` — never inherited
it. The package itself was fine (`nix/packages/default.nix` builds it,
and `nix/agent-modules/packages.nix` already documents
`hive-screen-mcp` as one of the keys it provides), so this was purely
the missing third wiring edit.
It went unnoticed because `hyperhive.packages` is a plain
`attrsOf package`: a missing key isn't a schema error, it only fails
where it's dereferenced. No agent on this hive currently enables the
GUI, so nothing ever dereferenced it.
Found when a config PR enabling the GUI for an agent failed its deploy
verify step.
The new daemon's package was added to `nix/packages/default.nix` and
referenced from the forge agent module, but not to the `inherit` list in
the flake's `agentPackages` module — so `hyperhive.packages` never gained
the attribute and every agent container failed to evaluate.
Also drop the crate's `[[bin]]` section: the binary name defaults to the
package name and the path to `src/main.rs`, so all of it was restating
cargo's defaults. The comment explaining why this is a separate process
moves to the top of the manifest, where it isn't attached to a section
that no longer exists.
mara: "no, do split packages for the separate daemons" — instead of
grouping all daemon/harness/MCP bins into one hyperhive-daemon
derivation, each bin (hive-c0re, hive-priv, hive-agent,
hive-agent-mcp, hive-agent-wake, hive-bash-daemon, hive-bash-mcp,
hive-matrix-daemon, hive-matrix-mcp, hive-metric) is now its own
named package + overlay output, matching the existing hivectl /
hive-forge split. harness-base.nix's ExecStart/command lines now
point at the specific pkgs.hive-* each ExecStart needs, and
environment.systemPackages only carries the two bins actually
PATH-looked-up in-container (hive-agent-wake, hive-metric) instead
of the whole bundle.
Agent containers pulled in `pkgs.hyperhive` (packages.default: daemonBins +
hivectlPkg + hiveForgePkg) via environment.systemPackages and every
harness ExecStart. That shipped hivectl (a host-admin CLI dialing the
host admin socket, unreachable from inside a container, wrapped with
wireguard-tools for `hivectl wg`) plus a redundant second hive-forge
copy (already provided separately via hive-forge-tools.nix) into every
single agent's closure.
Expose the existing daemonBins split as packages.<system>.daemonBins
(mara: 'there should be split nix outputs already, use those' — no new
grouping derivation) + a hyperhive-daemon overlay entry, and repoint
harness-base.nix's systemPackages entry and all agent-run ExecStarts
(hive-agent, hive-agent-mcp, hive-bash-daemon, hive-matrix-daemon) plus
the bash/matrix MCP bridge commands at it instead of the full bundle.
nix flake check: daemonBins + agent-base-toplevel + ruth-toplevel all
evaluate cleanly (docs derivation failure is a preexisting dirty-tree
issue, unrelated).
Each binary now has its own derivation (daemonBins for hive-c0re/hive-ag3nt/MCP
servers, hivectlPkg, hiveForgePkg), all sharing cargoArtifacts for a single
rustc compilation. packages.default = pkgs.symlinkJoin assembles them without
any additional compilation — no rustc is invoked more than once regardless of
which packages the operator builds together.
NixOS module consumers (pkgs.hyperhive = packages.default) are unaffected:
symlinkJoin exposes all binaries at the same /bin/ paths.
Operators who only want hivectl or hive-forge can now add:
inputs.hyperhive.packages.${system}.hivectl
inputs.hyperhive.packages.${system}.hive-forge
to their environment.systemPackages without pulling the full
workspace (hive-c0re daemon, hive-ag3nt harness, etc.) into PATH.
Both reuse the shared cargoArtifacts so there is no extra compile
cost when both packages and the default workspace are built together.
packages.hivectl carries the shell completions and the wireguard-tools
wrapper identical to what packages.default ships for hivectl.
packages.hive-forge is a lean CLI-only build.
Adds hive-xdg-icons package (nix/packages/hive-xdg-icons.nix) that
rasterizes the branding SVG to all standard hicolor sizes (16–256px)
and installs /usr/share/pixmaps/hyperhive.png as a flat fallback.
Ships .desktop entries for hive-c0re and hive-ag3nt with NoDisplay=true
so desktop environments can match running processes to their icon without
cluttering the application launcher.
Narrow drv input: only ./branding/hyperhive.svg, so unrelated source
changes don't bust the cache.
Wired into environment.systemPackages in hive-c0re.nix (host side)
so the icons are present wherever hive-c0re is deployed.
Closes#2228
One-time-setup convenience for the inter-hive WireGuard mesh
(services.hyperhive.swarm) so nobody has to remember the wg dance:
- hivectl wg init [--address X] — generate (if absent) the hive's
private key at /etc/wireguard/hive.key (0400, never clobbered),
derive + print the public key, and print the swarm.wireguard nix
snippet to enable the mesh.
- hivectl wg peer <domain> --pubkey --address [--endpoint] — print the
swarm.peers.<domain> nix snippet to add a remote hive.
- hivectl wg status — wrap wg show wg-hive.
Hybrid model per the design: the verb owns the imperative state (the
key file), the operator pastes the printed nix into host config (kept
in git) — nothing mutates declarative config behind their back.
hivectl-only (root host ops, like the gateway htpasswd verbs); no
priv/wire/c0re changes.
flake: wrap hivectl with wireguard-tools on PATH so wg resolves even
before the mesh config (which would otherwise pull it in) exists —
wg init is the first setup step. Add clippy.toml doc-valid-idents for
the WireGuard proper noun. Regenerate hivectl-cli.md.
Add a 'hivectl completions <shell>' subcommand (clap_complete) that
prints a completion script for bash/zsh/fish/elvish/powershell, generated
from hivectl's own clap command tree so it never drifts from the real
verbs/flags. The package build installs the bash/zsh/fish scripts via
installShellFiles, so an operator gets working completion automatically
once hivectl is on their profile with shell completion enabled.
Regenerated docs/tools/hivectl-cli.md for the new verb.
Replace the unfiltered hyperhiveFlake = "${self}" (handed to hive-c0re
as the flake path it builds agent containers from) with a named,
explicitly-filtered source derivation that drops files no nix or cargo
derivation reads: docs/, scripts/, and root-level *.md. Editing those no
longer changes the hyperhiveFlake store path, so it no longer forces a
rebuild of every agent container.
This is the per-package source-filter pattern (operator request on
#1114): each real source is a named filtered derivation used as src,
not an inline filter at the use site. Companion to the existing rust
cleanSrc (crane cargo-source filter); supersedes the inline nixSrc
approach in the older #1114 branch.
Safe: hive-c0re consumes hyperhiveFlake as a runtime path string and
builds packages/nixosConfigurations from it (nix build <path>#…), which
never forces the checks that reference ./docs — and no package, module,
or container config reads docs/scripts/root-md.
Part of the tracker-tag cleanup: the hive convention is prose, not
issue-tracker tags, in code. Reword the 21 tags in the nix tree
(flake.nix + the hive-c0re/ci/gateway/network modules) to describe
the thing they pointed at, preserving the context without the tag.
Comment-only — no eval or logic change. Validated with nix fmt
(no reformatting) and nix flake check --no-build (all checks
evaluate clean); the full build check was skipped locally because
the shared remote builder is degraded, so CI will exercise the
build derivations once the runner recovers.
The nixpkgs bump to clippy 0.1.95 / cargo 1.95.0 added + strengthened a
large batch of lints. CI denied ALL warnings (`-D warnings`) against the
`pedantic = warn` workspace lint, so the bump hard-failed `nix flake
check` workspace-wide with zero code changes — and would recur on every
future clippy bump.
Posture fix (the durable part): CI now runs
`-D warnings -A clippy::pedantic`, so the default/correctness/style lints
stay a hard gate while the "extra, opinionated" pedantic group is
advisory only (still `warn` for local `cargo clippy` via the workspace
lints table, just non-blocking in CI). `-A` rather than `-W` so the
group drop doesn't re-enable the specific pedantic lints the workspace
allows (e.g. `must_use_candidate`).
Also fixes the genuine DEFAULT/STYLE lints the bump surfaced across the
workspace (doc_lazy_continuation, collapsible_if, ptr_arg,
match_like_matches_macro, …) via `cargo clippy --fix` + manual stragglers
(`too_many_arguments` #[allow] on the host-config constructors), and
three tests that had rotted while the CI runner was offline (#1221):
- topology::top_level_agents_in_multi_root — hardcoded unsorted expected
- rebuild_queue::depends_on_evicted_dep_counts_as_resolved — needs
MAX_HISTORY_PER_KIND newer terminals to evict, not one
- coordinator::agent_paths doctest — illustrative pseudo-code, now `ignore`
Validated: clippy + formatting + cargo-test checks all pass.
Per mara's direction: both nixpkgs and nixpkgs-unstable are now
top-level meta flake inputs with explicit store-path URLs. Hyperhive
follows them rather than the other way around:
inputs.nixpkgs.url = "path:${pkgs.path}";
inputs.nixpkgs-unstable.url = "path:${nixpkgs-unstable}";
inputs.hyperhive.url = "...";
inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs";
inputs.hyperhive.inputs.nixpkgs-unstable.follows = "nixpkgs-unstable";
New NixOS host options (auto-set at build time, overridable):
services.hyperhive.c0re.nixpkgsFlake
default: "path:${pkgs.path}" — host's evaluated nixpkgs.
services.hyperhive.c0re.nixpkgsUnstableFlake
default: "path:${nixpkgs-unstable}" from hyperhive's flake.nix —
the channel that carries claude-code. Operators can override to
track a different unstable snapshot.
Legacy fallback (both args empty) preserved for backward compat.
Two new Rust tests cover the full-URL and fallback paths.
Replaces the earlier PAM+binary approach with nginx's built-in
`auth_basic` module. No new binary, no new systemd service, no PAM.
New option `services.hyperhive.gateway.auth`:
- `enable` — off by default
- `htpasswdFile` — host path to an htpasswd file (required when enable)
- `realm` — WWW-Authenticate realm string (default "hyperhive");
restricted to `strMatching "[^\"$]*"` to prevent nginx config injection
When enabled:
- the parent directory of `htpasswdFile` is bind-mounted read-only
into the gateway container at `/run/gateway-auth/`
- the `"/"` proxy location gets `auth_basic` + `auth_basic_user_file`
Create credentials: `htpasswd -Bc /path/to/file alice` (BCrypt).
See `docs/gateway.md` ("HTTP Basic auth") for the full setup guide.
Adds opt-in HTTP Basic auth to the hive-gateway backed by the host PAM
stack + group membership check.
New binary `hive-gateway-auth` (hive-c0re workspace):
- Axum HTTP service on 127.0.0.1:7002 (host loopback)
- Decodes Basic credentials, authenticates via pam_unix.so
- Checks membership in `hyperhive-operator` group (or custom)
- Returns 200 / 401 / 403; nginx `auth_request` consumes these
New options under `services.hyperhive.gateway.auth`:
- `enable` — off by default
- `port` — auth service port (default 7002)
- `realm` — WWW-Authenticate realm string (default "hyperhive")
- `group` — required host group (default "hyperhive-operator")
- `pamService` — PAM service name (default "hive-gateway")
Host-side NixOS wiring:
- `users.groups.hyperhive-operator` declared when default group used
- `/etc/pam.d/hive-gateway` emitted via `security.pam.services`
- `systemd.services.hive-gateway-auth` runs the auth binary as root
(needs /etc/shadow access for pam_unix.so)
Gateway container nginx wiring:
- `location = /__hive_gateway_auth` — internal proxy to auth service
- `auth_request /__hive_gateway_auth` on the `"/"` proxy location
- `@hive_auth_required` named location adds WWW-Authenticate: Basic
header on 401 so browsers display a login prompt
Workspace deps: pam = "0.8"; flake.nix: linux-pam added to
nativeBuildInputs so pkg-config can find libpam at build time.
Adds `services.hyperhive.ci` NixOS module that spins up a `hive-ci`
nixos-container running `gitea-actions-runner` against the hive-forge
Forgejo instance. Off by default; opt in with `ci.enable = true` after
generating a runner registration token in Forgejo.
Also adds `.forgejo/workflows/ci.yml` with four jobs: nix flake check,
formatting (nix fmt + cargo fmt), cargo test, and cargo clippy. Jobs
target the `hive-ci` runner label.
Container design mirrors hive-forge (shared host netns, non-ephemeral
state, loopback reach to forge). sandbox-fallback = true since nspawn
containers can't create user-namespaces for nix sandbox.
Closes#175.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per mara on #622 comment 7442:
> follow up moving scripts and css and stuff out of the nix file.
> can live in the same dir.
Layout:
nix/docs/
default.nix ← what was nix/docs.nix
style.css ← extracted from inline `styleCSS = '' ... ''`
`builtins.readFile ./style.css` loads the stylesheet at evaluation
time, so the rendered HTML stays byte-identical (CSS inlined into
each page's `<style>` block — verified). Future client-side scripts
can land at `nix/docs/script.js` with the same `builtins.readFile`
pattern.
Bonus: the docs.nix stub NixOS eval was still force-disabling
`hyperhive.{forge,matrix}.enable` on the pre-#615 namespace; updated
to `services.hyperhive.{forge,matrix}.enable` so `nix flake check`
passes against current main. (Same fix lives on PRs #619 + #620;
whichever lands first wins, the others rebase to a no-op.)
`flake.nix` references updated: `./nix/docs.nix` → `./nix/docs`.
Verified:
- `nix flake check --no-build` passes clean
- `nix build .#docs` produces 5-file bundle identical to pre-PR shape
- inline CSS still appears 3× per HTML page (one per index/host/agent)
Drops the hardcoded `nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"`
from the meta flake renderer. meta now declares:
nixpkgs.follows = "hyperhive/nixpkgs";
nixpkgs-unstable.follows = "hyperhive/nixpkgs-unstable";
so every agent-level `inputs.<X>.inputs.nixpkgs.follows = "nixpkgs"`
resolves transitively to hyperhive's own pin. One channel decision
in the whole tree (hyperhive/flake.nix line 5), no second source
to drift.
`closes #317` invariant still holds: `nixpkgs` is a single canonical
name in the meta tree, it just resolves through hyperhive instead
of being its own root input.
Also:
- bump hyperhive/flake.nix line 5: `nixos-25.11` → `nixos-26.05`,
flake.lock relocked
- flake_check.rs fixtures bumped cosmetically (synthetic data, not
shape-affecting)
- rename + rewrite `render_flake_declares_canonical_nixpkgs` →
`render_flake_aliases_nixpkgs_to_hyperhive`; asserts the new
follows-form AND the absence of any literal `nixpkgs.url`
- drop the now-redundant `render_flake_collapses_hyperhive_nixpkgs_via_follows`
test (the old indirection it covered no longer exists)
Closes#526.
Per [mara on PR #615 comment 7349](http://localhost:3000/hyperhive/hyperhive/pulls/615#issuecomment-7349):
> follow nix conventions, services.hyperhive it is. the earlier we
> change this, the less breakage.
Renames the entire host-side option tree under `services.hyperhive.*`:
- `services.hive-c0re.*` → `services.hyperhive.c0re.*`
- `hyperhive.enable` → `services.hyperhive.enable`
- `hyperhive.domain` → `services.hyperhive.domain`
- `hyperhive.forge.*` → `services.hyperhive.forge.*`
- `hyperhive.matrix.*` → `services.hyperhive.matrix.*`
Per mara's "earlier = less breakage", the previous `services.hive-c0re.enable`
deprecation alias is dropped. Operators get a clear eval error on the
old paths pointing at the rename. Single migration moment.
Per-agent options in `nix/templates/harness-base.nix` (`hyperhive.model`,
`hyperhive.allowedRecipients`, etc.) stay at `hyperhive.*` — they're
container-level config, not services in the NixOS sense.
Verified via `nix flake check --no-build` + an end-to-end NixOS eval
exercising every renamed path.
Follow-up needed: rust source comments referencing the old NixOS
option names (`hive-c0re/src/{meta,coordinator,main,dashboard}.rs`)
should be updated in a separate pure-rust PR to keep this one
strictly nix-only.
- Move options.services.hive-c0re → options.hyperhive.c0re
- Add options.hyperhive.enable to auto-enable c0re + subsystems
- Add deprecation alias for services.hive-c0re.enable (backward compat)
- Update doc references in README, flake.nix, docs, harness-base.nix
- Simplifies config: 'hyperhive.enable = true' now enables everything
Existing operator configs using services.hive-c0re.enable will
continue to work but emit a deprecation warning. Aligns the option
namespace with the existing hyperhive.* family (matrix, forge, domain).
fixes#612
argus review notes on #618:
- `walk` in `pickSubtrees` was leftover from an earlier traversal
design; `pick` does everything we need. drop it.
- `checks.docs` was re-importing `nix/docs.nix` independently of
`packages.docs`; the comment claimed they shared eval but they
didn't (nix's lazy eval + import caching made the *result*
identical, not the eval). switch to `inherit (self.packages.\${system}) docs;`
so the check is literally the package output, no second import.
Auto-generate CommonMark references for hyperhive's two NixOS module
surfaces via `pkgs.nixosOptionsDoc`:
- `packages.<system>.docs-host` — operator-facing options exposed by
`hyperhive.nixosModules.default` (`services.hive-c0re.*`,
`hyperhive.domain`, `hyperhive.forge.*`, `hyperhive.matrix.*`).
- `packages.<system>.docs-agent` — per-agent options declared in
`nix/templates/harness-base.nix` (model, allowedRecipients,
extraMcpServers, frontend, forge, matrix, gui, …).
- `packages.<system>.docs` — both pages plus a thin `README.md`
index, bundled for publishing.
Declaration links are rewritten to point at the forge source tree
instead of nix-store paths.
Host options come from a stubbed `nixosSystem` eval that force-disables
all hyperhive subsystems — only the *declarations* feed the doc
renderer, no heavy build inputs end up in the closure. Agent options
reuse `nixosConfigurations.agent-base.options` (already evaluated).
Also wired as `checks.<system>.docs` so CI fails fast on eval breakage.