mara on #638: in the dashboard's inputs section, `nixpkgs` appeared
under an `agent-*` path instead of `hyperhive/nixpkgs` where the
operator expects it.
Root cause (post-#632 follows refactor):
- meta's top-level `nixpkgs.follows = "hyperhive/nixpkgs"` is a
`follows` chain, rendered in `flake.lock` as an array — the
`String` extractor in `walk_meta_inputs` correctly skips it (can't
`nix flake update` a follows alias).
- That left the root-level recursion to find `nixpkgs` only through
some other input's subtree.
- Recursion order was the BTreeMap's alphabetical key order, so
`agent-z` (or any agent starting with a letter before `h`) got
walked first and claimed `nixpkgs` at `agent-z/nixpkgs`. Hyperhive's
subsequent walk skipped `nixpkgs` (already visited).
Fix: sort `to_recurse` so hyperhive's subtree is descended first,
matching the same "hyperhive first, then alpha" priority
`read_meta_inputs` already uses for the final output ordering. Now
`nixpkgs` is claimed under `hyperhive/nixpkgs` regardless of which
agents the operator has spawned.
Added regression test covering the exact post-#632 lock shape
(`["hyperhive", "nixpkgs"]` follows array at root, agent-z
alphabetically before hyperhive). Asserts the emitted path is
`hyperhive/nixpkgs` and that `agent-z/nixpkgs` is NOT emitted (the
spanning-tree visited set guarantees one claim per node).
Closes#638.
Footer was still pointing at the old canonical at
`git.berlin.ccc.de/vinzenz/hyperhive` — that's the upstream URL
from before #524 moved the public mirror to `forge.darkest.space`.
README + docs already reference the new URL; this was the only
stale frontend pointer.
mara via triage on #619 (post-merge follow-up):
> "I thought all nixpkgs follow the one hyperhive was deployed
> with?! if not, thats what we should fix."
This is the fix. Flip the rendered meta flake from:
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
hyperhive.url = "...";
hyperhive.inputs.nixpkgs.follows = "nixpkgs";
hyperhive.inputs.nixpkgs-unstable.follows = "nixpkgs-unstable";
to:
hyperhive.url = "...";
nixpkgs.follows = "hyperhive/nixpkgs";
nixpkgs-unstable.follows = "hyperhive/nixpkgs-unstable";
Net effect: hyperhive's `flake.nix` is the single channel-pin
authority. Operators who want to slide the whole swarm onto a
different channel do it at the host level via
`inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"`, which makes
hyperhive's nixpkgs = the host's nixpkgs and cascades through every
agent transparently.
This is the same shape I shipped in #619 v1 (then reverted per mara
on comment 7354, then re-affirmed via triage). Going with the
re-affirmed direction.
Closes#317 invariant still satisfied — `nixpkgs` is a single
canonical name in the meta tree, just resolving through hyperhive
instead of being its own root input.
Tests:
- rename `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 `render_flake_collapses_hyperhive_nixpkgs_via_follows` (no
separate meta-level nixpkgs to collapse anymore)
mara on #630: host options page came up empty (template chrome with no
`<h2>` option headers).
Root cause: `pickSubtrees` filtered the host eval against the pre-#615
roots `[ "hyperhive" ]` and `[ "services" "hive-c0re" ]`. #615 moved
the whole host option tree under `services.hyperhive.*`; neither old
root matches anymore, so the filter silently produced an empty tree
and the rendered page degraded to just `<nav> + <main><h1></h1></main>
+ <footer>` chrome.
Fix: pick under `[ "services" "hyperhive" ]`. Agent options stay at
`[ "hyperhive" ]` — per-agent harness options weren't moved by #615.
Before: 100 lines, 0 `<h2>` headers
After: 661 lines, 33 `<h2>` headers covering:
services.hyperhive.{enable,domain,c0re.*,forge.*,matrix.*,gateway.*}
Closes#630.
Per #621 (filed as follow-up to #620 v0): when the gateway is on
(now the default), the c0re dashboard / manager / sub-agent direct
ports should NOT be open in the host firewall — the gateway nginx
is the sole external entry point, proxying to `127.0.0.1:7000` etc.
internally. Leaving them open in the firewall defeats the "single
front door" story.
Wraps the existing `allowedTCPPorts` + `allowedTCPPortRanges` blocks
in `lib.mkIf (!config.services.hyperhive.gateway.enable)`. Operators
who opt out of the gateway still get the direct ports opened so the
legacy `http://<host>:7000/` flow keeps working.
Verified via `nix eval`:
| gateway | allowedTCPPorts (host firewall) | allowedTCPPortRanges |
| --- | --- | --- |
| on | `[80 2222 3000]` (gateway + forge) | `[]` |
| off | `[2222 3000 7000 8000]` (forge + c0re + manager) | `[{from=8100; to=8999}]` (agents) |
Forge ports stay direct in both modes — `hive-forge.nix` opens them
independently and they're not proxied through the gateway (that's a
separate follow-up if wanted).
Closes#621.
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)
two stale spots in `nix/docs.nix` that #622 didn't catch:
- the stub NixOS eval was force-disabling `hyperhive.{forge,matrix}.enable`
on the old paths, which fail eval post-#615 (`The option `hyperhive'
does not exist`)
- the rendered index page text still listed the old namespace shape
both updated to use `services.hyperhive.*` consistently. necessary on
this branch for `nix flake check` to pass; the same fix lives on PR
a no-op.
mara on #619 comment 7354:
> agent flake needs the url actually so the configuring agent
> can eval against it
Reverts the meta.rs `nixpkgs.follows = "hyperhive/nixpkgs"` shape from
this PR's earlier commit. Restores the pre-PR rendered shape with the
concrete `nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"` baked
into meta + the `hyperhive.inputs.nixpkgs.follows = "nixpkgs"`
redirect, so a configuring agent (or manager pre-apply check) can
evaluate the rendered meta flake without having to resolve through
hyperhive first.
Net behaviour for this PR is now:
- `flake.nix` line 5 bumped 25.11 → 26.05 (kept)
- `meta.rs` literal bumped to match (channel-pin in two places stays
acknowledged as duplication — drift fixable later if needed)
- `flake_check.rs` test fixtures bumped cosmetically (kept)
- meta.rs tests restored to assert the concrete-URL shape
3 meta tests pass via `nix develop -c cargo test`.
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.
Trailing #615 + #620 rebase fix: `nix/docs.nix`'s stub NixOS eval still
referenced the old `hyperhive.{forge,matrix}.enable` paths that #615
moved under `services.hyperhive.*`. Update to match + also force-disable
the new `services.hyperhive.gateway.enable` so the docs eval doesn't
spawn the gateway container as part of `nix build .#docs`.
`packages.docs{,-host,-agent}` and `checks.docs` all evaluate cleanly
on the post-#615 / post-#620 shape verified via `nix flake check`.
Per mara's directive on #609: stand up a single nginx in its own
nixos-container, serve the matrix GUI static dist there, proxy
everything else to hive-c0re. v0 is HTTP-only; TLS / public-domain
shape lands in follow-ups.
New `nix/modules/hive-gateway.nix` declaring `containers.hive-gateway`
modelled on `hive-forge`:
- nixos-container running nginx, shares host netns
- `location /matrix/` → static-serves `hyperhive.matrix.gui.package`
(fluffychat-web by default) when `matrix.gui.enable` is true
- `location /` → proxy_pass to `127.0.0.1:${dashboardPort}` with
websocket + SSE upgrade headers + 1d read timeout
Options (`hyperhive.gateway.*`):
- `enable` (default `true`) — gateway on by default, opt out to bypass
- `port` (default `80`) — nginx listen port on the host
- `upstreamHost` / `upstreamPort` — c0re target, defaults to
`127.0.0.1:${services.hive-c0re.dashboardPort}`
- `openFirewall` (default `true`) — open the listen port
- `localHostsEntry` (default `false`) — when true, adds an
`/etc/hosts` entry mapping `hyperhive.domain` → `127.0.0.1` for
local-dev / test loops without real DNS (per mara's spec)
`hive-c0re.nix` updates: when gateway is enabled, skip wiring
`HIVE_MATRIX_GUI_DIR` (gateway owns `/matrix/` now). When gateway is
off, c0re's pre-existing matrix mount stays as the fallback.
README: short "Optional" block introducing the gateway + the
`localHostsEntry` knob.
```sh
nix flake check --no-build
nix build .#docs-host
```
End-to-end eval matrix:
| gateway.enable | matrix.gui.enable | c0re HIVE_MATRIX_GUI_DIR | gateway container |
| --- | --- | --- | --- |
| true (default) | true | unset (gateway serves) | present |
| true | false | unset | present, no /matrix |
| false | true | set (c0re serves) | absent |
| false | false | unset | absent |
- TLS termination — separate follow-up once mara picks a story
(self-signed-mkcert vs operator-provided certs)
- Per-agent UI routing (`/agent/<name>/`) — depends on agent base-path
support which is a frontend lift
- Subdomain routing for `matrix.${hyperhive.domain}` — same-origin
`/matrix/` is the v0 shape per mara ("leave everything else as is")
Closes part of #609 (matrix GUI re-rooting onto nginx); leaves the
issue open for the subdomain re-root + `.well-known/matrix/client`
piece once the multi-host story matures.
argus on #622 comment 7419 🟡:
> monospace h2 on index — the CSS styles `h2` with `ui-monospace`
> font (intended for option-name headings in the per-option pages).
> on the index page, "host options" / "agent options" / "Regenerate"
> headings also get monospace treatment. cosmetic; reads as
> intentional if not, easy to scope.
Index page now uses h3 for section headers, leaves h2 free for the
auto-generated option-name headings on host.html / agent.html where
the monospace styling is appropriate.
(Other argus nit — stale namespace text in indexHTML's option
listings — will be addressed when this branch rebases post-#615
merge, same as #620.)
mara on mara/internal-requests#8:
> nix/docs.nix currently only emits CommonMark via doc.optionsCommonMark;
> add HTML output alongside.
Renders host + agent option pages as standalone HTML using cmark-gfm
(stock nixpkgs, no pandoc). Each page is wrapped in a minimal
inline-CSS template — no external stylesheets, no second HTTP fetch.
New bundle layout (consumed by nginx at `hyperhive.darkest.space/options/`):
index.html — landing page with cross-links + regenerate snippet
host.html — services.hive-c0re.* / hyperhive.{domain,forge,matrix}.*
agent.html — hyperhive.{model,allowedRecipients,extraMcpServers,…}
host.md — same content, CommonMark source-of-truth
agent.md — same content, CommonMark source-of-truth
All asset paths inside the rendered HTML are relative (`./host.html`
etc.) per mara's spec — the bundle mounts at any URL prefix without
rebuild. Forge source links from `transformOptions` are preserved
as proper `<a href>` (verified: `forge.darkest.space/.../nix/...`).
`packages.<system>.docs` now emits HTML primarily; `docs-host` and
`docs-agent` outputs flip from .md to .html (the .md content is still
in the `docs` bundle for callers that want the source shape).
The native nixos-render-docs `options html` subcommand doesn't exist
(only `manpage` / `commonmark` / `asciidoc`). The `manual html` path
exists but needs a full manual structure for what we're treating as
two standalone pages — overkill. cmark-gfm over the existing
CommonMark output is the leanest path.
Verified:
nix flake check --no-build
nix build .#docs # bundled site (5 files)
nix build .#docs-host # standalone HTML page
nix build .#docs-agent # standalone HTML page
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.
Frontend half of #607 v0 — adds the `◆ M4TR1X ◆ →` page-link entry
to the dashboard tab strip between SCH3DUL3S and FL0W →. Same
`tab-link` class as FL0W → since /matrix/ is its own SPA, not an
in-place pane swap.
Hidden via the `hidden` attribute by default; tabs.js's `refreshState`
flips it based on `s.matrix_gui_enabled` (added in #610). When the
operator hasn't set `hyperhive.matrix.gui.enable = true` the snapshot
returns false and the tab strip doesn't surface a dead link.
docs/web-ui.md updated:
- chrome-header tab-strip list now includes M4TR1X → with the
hidden-when-disabled note
- new "M4TR1X page" section under FL0W explaining the same-origin
serve via hive-c0re ServeDir + the manual homeserver URL pick on
first login (with #609 tracking the post-#15 nginx-front re-root +
.well-known/matrix/client auto-discovery)
The N3W SCH3DUL3 vertical form is gone. The schedules table now
carries an always-visible inline create row at the bottom — fill the
cells, click + to POST /api/schedules, the new schedule appears
above on the next refresh.
Per-column inputs match the display semantics:
- `next` → `<input type="datetime-local">` (defaults to now+5min)
- `every` → 4 mini d/h/m/s number inputs (blank/all-zero = one-shot)
- `body` → `<textarea rows=1>` expanding to 4em on focus-within
so multi-line prompts still fit (closes mara's "prompt may still be
multi-line" requirement)
- per-agent columns → checkbox (whole cell clickable via padded label)
- actions → + submit + ⌫ reset (clears the carry without POSTing)
`newScheduleCarry` module-scope object survives the paintAtomic
re-render so mid-typed values stick across schedule-list refreshes
(same pattern as `scheduleEditCarry` from #474).
H2 collapses from "N3W SCH3DUL3" + "QU3U3D SCH3DUL3S" down to a
single "SCH3DUL3S" since the section now does both. Empty-state
branch on `renderSchedulesList` is gone — the table always renders
because the create row is always there.
`#schedule-new-section` div + `renderScheduleNewForm` /
`readScheduleFormCarry` / `submitNewSchedule` and the
`.schedule-new-form` CSS selector all removed. The edit-form path
(#474 `renderScheduleEditForm` colspan'd row on `✎` toggle) is
unchanged — "edit mode is inline in the table" already shipped
there, this PR just adds the same mental model for create.
Validations match the old form: empty targets / empty body /
invalid datetime / non-integer interval each surface an `alert()`.
The + button shows a spinner while the POST is in flight, then
clears the carry + triggers `refreshSchedules()`.