Commit graph

1,154 commits

Author SHA1 Message Date
damocles
4263fd25f5 fix: use rustdoc comments for manager-only variant markers 2026-06-01 11:20:10 +02:00
damocles
15617cef9a refactor: unify AgentRequest/Response + ManagerRequest/Response into Request/Response (#691) 2026-06-01 11:20:10 +02:00
atlas
2fd6de7bab fix: address argus review nits — tmpfiles dir comment, drop redundant chmod 2026-06-01 00:18:19 +02:00
atlas
f0bd572b35 fix: correct runner registration ordering — oneshot + tmpfiles
The nixpkgs gitea-actions-runner module uses tokenFile as a systemd
EnvironmentFile, loaded before any ExecStartPre. Our previous
ExecStartPre override was too late — file didn't exist yet.

Fix: tmpfiles pre-creates /run/hive-ci/runner-token with a placeholder
on every boot. A new hive-ci-register.service oneshot (gated on
ConditionPathExists=!/var/lib/gitea-runner/hive/.runner) fetches the
real token and overwrites before gitea-runner-hive.service starts,
wired via After/Wants. On subsequent boots the placeholder is harmless
— nixpkgs register step exits early when .runner already exists.

Closes #918.
2026-06-01 00:13:00 +02:00
iris
6f25ff7ee7 docs: add ci.md — hive-ci Forgejo Actions runner guide 2026-06-01 00:05:53 +02:00
damocles
d14df8b9a0 lifecycle: thread on_step callback through rebuild for accurate queue step labels 2026-06-01 00:05:09 +02:00
atlas
4df65deb20 fix: move gitea-actions-runner package to top-level option
Per-instance package option does not exist in the nixpkgs module.
Package is configured at services.gitea-actions-runner.package.
Fixes build failure on main. Closes #916.
2026-05-31 23:55:49 +02:00
damocles
52cfc3ea1c reshape swarm peers: domain-as-key, certFingerprint field 2026-05-31 23:55:34 +02:00
atlas
a8e8c50a30 refactor: move ci options to services.hyperhive.forge.ci 2026-05-31 23:52:57 +02:00
iris
803ea9d7f6 docs: fix swarm.md — peer list reaches agents via HYPERHIVE_PEERS env, not get_agent_meta 2026-05-31 23:51:31 +02:00
iris
b79ef005b5 docs: add swarm.md — multi-hive peer config guide 2026-05-31 23:51:31 +02:00
atlas
45cbf2eded fixup: trim systemPackages to git only
nix flake check pulls rust tools hermetically. curl/jq in preStart
use absolute store paths. nix is part of any NixOS system by default.
git is the only package genuinely needed at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:44:12 +02:00
atlas
5f79ff68f0 fixup: ci.yml — run on PR only, not every branch push
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:44:12 +02:00
atlas
61565e3c3a fixup: drop stale docs/ci.md forward-ref in module comment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:44:12 +02:00
atlas
0fa0e821d9 fixup: stateVersion 26.05, drop ubuntu labels, simplify CI workflow
- stateVersion: 25.11 → 26.05 (match hive-gateway + hive-matrix)
- default labels: drop ubuntu-latest/ubuntu-22.04 aliases — misleading
  since this isn't Ubuntu; hive-ci:host is the correct label
- ci.yml: collapse to single `nix flake check` job — flake checks.* already
  covers formatting (treefmt+rustfmt), cargo test, and clippy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:44:12 +02:00
atlas
bf350df442 fixup: hive-ci auto-registers runner via forge admin API
Removes the manual runnerTokenFile operator step. On first boot the
container fetches a runner registration token from Forgejo's admin
API using hive-c0re's existing admin token
(/var/lib/hyperhive/forge-core-token). A preStart script writes the
token to /run/hive-ci/runner-token; on subsequent boots it writes a
dummy (registered .runner creds take precedence anyway).

Operator bootstrap is now just `services.hyperhive.ci.enable = true`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:44:12 +02:00
atlas
aa7ebbcd3b feat: add hive-ci module — Forgejo Actions runner for CI
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>
2026-05-31 23:44:12 +02:00
iris
b6c5dcc397 docs: update P33RS tab wiring to match domain-as-key reshape 2026-05-31 23:36:32 +02:00
damocles
d266889263 fix: matrix registration token 0o640 -> 0o600 2026-05-31 23:35:00 +02:00
iris
2fb1b9eb5a docs: fix P33RS tab — correct nix option name and peer card icon 2026-05-31 23:30:56 +02:00
iris
8ba1947c3f docs: add P33RS tab to dashboard layout docs 2026-05-31 23:27:52 +02:00
iris
c353dc1893 fix: emit relative container link URLs from harness, update dashboard join
Container link URLs (/screen.html, /stats.html) were root-relative,
which works when the agent is accessed directly at :port/ but resolves
to the gateway root when accessed via /agent/<name>/.

Fix at the source: harness emits relative URLs (screen.html, stats.html)
and the dashboard joins containerBase + '/' + url. The agent page uses
the URL directly and relative resolution is now correct in both paths.
2026-05-31 23:24:39 +02:00
iris
7a8b8fe266 feat: P33RS tab — peer hive link cards in dashboard
Adds the P33RS tab to the dashboard. The tab is hidden by default and
only shown when at least one peer hive is configured in
`services.hyperhive.peers`. When visible, it renders a card per peer
with a link to that hive's dashboard.

Backend (HYPERHIVE_PEERS env var, PeerHiveView in StateSnapshot) is
wired by a separate PR. This commit is frontend only:
- index.html: P33RS tab strip entry + tab-pane-peers section
- tabs.js: renderPeerHives(), gate logic, 'peers' added to TABS array
2026-05-31 23:14:15 +02:00
atlas
d45b2f0293 chore: scrub issue/PR cookies from gateway + network nix modules and gateway_nginx.rs 2026-05-31 23:09:46 +02:00
damocles
4093f4fdb4 fixup: remove issue tags from code, use plain prose 2026-05-31 23:06:38 +02:00
damocles
348fb3792a feat(#589): swarm peers option + HYPERHIVE_PEERS env wire v0
nix: services.hyperhive.peers attrset-of-submodules option; serialises
to HYPERHIVE_PEERS JSON ([{label,domain}]); forwarded to containers via
FORWARDED_VARS. dashboard.rs: peer_hives: Vec<PeerHiveView> in
StateSnapshot, derived as {name:label, url:"http://domain/"}.
identity.rs: PeerHive struct + peers() accessor for agent-side use.
2026-05-31 23:06:38 +02:00
iris
cce85a6c1b fix(#540): use systemd.globalEnvironment instead of environment.variables
environment.variables writes to /etc/environment (PAM sessions only)
and is not visible to systemd service units. The correct path for
env vars that need to reach all systemd services is
systemd.globalEnvironment (sets DefaultEnvironment in systemd.conf),
which is the pattern established by #608 for HYPERHIVE_STATE_DIR.

Also update the inline comment reference from environment.variables
to systemd.globalEnvironment.
2026-05-31 22:59:46 +02:00
iris
8a97277f20 fix(#540): pin weston socket + inject WAYLAND_DISPLAY globally
Services started by systemd in a gui-enabled container didn't have
WAYLAND_DISPLAY set, so Wayland clients couldn't find the compositor.
libwayland would fall back to a headless display or error out, leaving
apps running invisibly while the VNC session showed a blank weston
desktop (the double-screen problem).

Fix in weston-vnc.nix:
- Pass --socket=wayland-0 to weston so the socket name is
  deterministic (weston normally picks any free wayland-N name).
- Set WAYLAND_DISPLAY=wayland-0 and XDG_RUNTIME_DIR=/run/user/0 as
  global environment.variables gated on hyperhive.gui.enable, so
  every service in the container inherits them automatically.
- Update gui.json to include wayland_display for tooling that reads it.

Update docs/gotchas.md with the rationale and pointer to #540.
2026-05-31 22:59:46 +02:00
atlas
3b76842853 gateway_nginx: fix stale module doc + clarify reload log (argus nits) 2026-05-31 22:42:52 +02:00
atlas
01d7c37af2 gateway: fix nginx reload — trigger from host via systemd-run (#889)
The #872 path-unit approach was silently broken: IN_MOVED_TO from an
atomic rename on the host does not propagate across the nspawn
mount-namespace boundary into the container, so the watcher inside
the gateway container never fired.

Fix: after each agents.conf write, c0re calls
`systemd-run --machine=hive-gateway -- nginx -s reload` from the host.
The reload is best-effort (logged on failure, not fatal).

Remove the now-unused `hive-gateway-agents-conf.path` +
`hive-gateway-nginx-reload.service` from the gateway container config.
Update docs/gateway.md + comments to reflect the host-side approach.
2026-05-31 22:42:52 +02:00
müde
21213be1b6 lifecycle: chown per-agent socket dir to the agent user
Replace the 0777 fallback with a chown to the in-container agent's
uid/gid (resolvable via agent_uid_gid since nspawn shares uids with
the host). Keeps the dir at the default 0755 mode and avoids
world-writability. Falls back to 0777 only when the agent uid is
unavailable (first-spawn race before /etc/passwd is rendered).
2026-05-31 22:37:35 +02:00
iris
30328a5184 fix(#888): http→https in forge_public_url doc comment example 2026-05-31 22:08:59 +02:00
iris
d3ca857659 fix(#888): use gateway forge URL in dashboard links
Dashboard links to the forge were hardcoded as http://<hostname>:3000,
which breaks when the operator accesses the dashboard through
hive-gateway (forge is served at forge.<domain> with no port).

- nix/modules/hive-c0re.nix: inject HIVE_FORGE_PUBLIC_URL when
  forge.behindGateway = true (e.g. http://forge.pr1ma.darkest.space)
- hive-c0re/src/dashboard.rs: expose forge_url: Option<String> in
  StateSnapshot, reading from HIVE_FORGE_PUBLIC_URL
- frontend/packages/dashboard/src/tabs.js: use state.forge_url when
  present; fall back to http://<hostname>:3000 for gateway-off /
  local-dev deploys
2026-05-31 22:04:52 +02:00
iris
61b9c23613 fix(#886 follow-up): restore docs/web-ui.md to thin index
The #886 merge accidentally reverted the #727/#885 split —
web-ui.md went from the 40-line index back to the 1344-line
monolithic file. The split files (docs/web-ui/{shape,dashboard,
agent}.md) are correct in main; only web-ui.md needs restoring.
2026-05-31 21:54:52 +02:00
iris
12985ade6d fix(#726): handle broadcast Lagged + clear on SSE reconnect
Distinguish RecvError::Lagged from Closed in the build-log stream
loop: Lagged continues (next recv delivers the full accumulated delta
via saved cursors), Closed returns (shutdown path).

Clear pre.textContent and reset cursor lengths in es.onerror when
readyState is CONNECTING so auto-reconnect doesn't double-append the
replay from cursor=0.
2026-05-31 21:48:31 +02:00
iris
050e130eba build-logs: SSE live streaming + raw download (#726 phase 4)
Backend (hive-c0re):
- build_logs.rs: add tokio::sync::broadcast::Sender<i64> to BuildLogs;
  append() and finish() notify subscribers after each sqlite write.
  Add BuildLogProgress struct and get_progress(id, stdout_cursor,
  stderr_cursor) for incremental delta reads.
- dashboard.rs: two new endpoints —
    GET /api/build-logs/id/{id}/stream  SSE; streams BuildLogFrame
      {stdout_append, stderr_append, status?, done} deltas until the
      build finishes or the browser disconnects. Backed by an mpsc
      channel task that watches the per-build broadcast notifications.
    GET /api/build-logs/id/{id}/raw    text/plain download with
      Content-Disposition: attachment; filename build-log-{agent}-{id}.txt

Frontend (dashboard):
- tabs.js: running builds (status === null) connect an EventSource to
  /stream and append lines live; "live" badge pulses amber while active,
  flips to ok/fail on done. Finished builds still use the JSON fetch path.
  Collapsing a running panel closes the EventSource; re-expanding
  reconnects. Adds a "⬇ raw" download link to every expanded row.
- dashboard.css: .build-logs-dl inline download link; .build-logs-live
  live pulse @keyframes animation.

Docs: web-ui.md updated for all three new endpoints + behaviour.
2026-05-31 21:48:31 +02:00
iris
c4ce91b61f build-logs viewer: add --crust var + aria-expanded on row buttons (argus nits) 2026-05-31 21:48:31 +02:00
iris
cc6227ccb5 dashboard: build-log side-panel viewer in agent card (#726 phase 3)
Adds a `↳ build logs · {agent}` drill-in to every agent card that
opens the side panel and fetches the last 10 build-log headers from
GET /api/build-logs/{agent}. Each row shows a status chip (ok/fail/
running), kind, age, and truncated cmdline. Clicking a row lazily
fetches the full stdout+stderr from GET /api/build-logs/id/{id} and
expands it inline as a scrollable pre.

CSS additions: .build-logs-{panel,toolbar,list,item,row-btn,...} plus
.badge-ok/.badge-fail/.badge-running status chips shared with future
uses. docs/web-ui.md updated with the new drill-in and the new badge
classes.
2026-05-31 21:48:31 +02:00
iris
65f385a7a0 fix(#804): spinner arc needs border-radius: 50% to travel a circle
The ::after spinner ring used border-radius: 11px on a ~90px element,
making it a rounded rectangle. The amber arc swept around a rectangular
path, reading as a spinning border side. border-radius: 50% makes it a
true circle and the arc sweeps smoothly like a classic loading spinner.
2026-05-31 21:47:03 +02:00
müde
8b238bbfaf agent UDS: chmod per-agent socket dir 0777 + log web_ui::serve errors
The harness runs as the non-root agent user; the per-agent
/run/hive-agent/<name>/ dir lands at 0755 root:root after
create_dir_all, so bind(2) of web.sock failed with EACCES. The error
was invisible because the web_ui::serve future was tokio::spawn'd with
its JoinHandle dropped — no log, no socket, agent looks unreachable
through the gateway.
2026-05-31 21:38:34 +02:00
iris
c7360cf0bb docs(#727): split docs/web-ui.md into shape / dashboard / agent
docs/web-ui.md (1315 lines) split into three sub-files:
- docs/web-ui/shape.md   — shared SPA skeleton, SSE multiplexing,
  Worker-death self-heal, terminal pane, listener bind, relative
  paths, atomic repaint, side panel
- docs/web-ui/dashboard.md — SW4RM/Y3R/SYST3M/SCH3DUL3S/S3TT1NGS
  tabs, container row, topology tree, selection bar, approval card,
  dashboard endpoints + event channel
- docs/web-ui/agent.md   — header, terminal, composer, inbox, live
  view, slash commands, per-agent endpoints, stats page

docs/web-ui.md replaced with a thin index linking all three.

Section anchors in docs (gateway.md, gotchas.md), Rust doc comments
(hive-ag3nt/src/web_ui.rs), and nix/templates/weston-vnc.nix updated
to point at the correct sub-file. README and CLAUDE.md file-map
updated with sub-file links. Inline // comments in frontend source
left unchanged (they reference the index which redirects to the right
sub-file).
2026-05-31 21:37:04 +02:00
iris
e1e5195081 dashboard + agent UI: surface hive_name + swarm_name in chrome (#701)
Damocles's backend (c41bf1b) landed HYPERHIVE_HIVE_NAME / _SWARM_NAME
env vars and identity.rs accessors. This commit wires them into the two
web surfaces:

hive-c0re/dashboard.rs:
  StateSnapshot gains `hive_name` + `swarm_name` (Option<String>),
  populated from the env vars the c0re NixOS module injects.

hive-ag3nt/web_ui.rs:
  Per-agent StateSnapshot gains the same two fields, populated from
  crate::identity::hive_name() / swarm_name().

Dashboard frontend (index.html + tabs.js):
  A `<p class="banner-thin" id="hive-identity">` sits above the tab
  strip in the chrome. refreshState() populates it with "swarm / hive"
  (or just "hive") when the fields are non-null, and updates
  document.title to "<swarm>/<hive> // h1ve-c0re". No change to
  chrome when both fields are null (backward compat).

Per-agent frontend (index.html + app.js + agent.css):
  A hidden `.agent-hive-label` span under the title row shows the
  swarm/hive label once setHeader() receives non-null names.
  document.title is updated to "<label> // <hive_name>" when hive_name
  is set, enabling browser-tab disambiguation when multiple hives are
  open in parallel. Styled as a small uppercase purple-dim sub-label.
2026-05-31 21:09:08 +02:00
damocles
fdd5fa2977 fix(#636): stop before kill in cold-start fallback (argus) 2026-05-31 21:00:35 +02:00
damocles
b3fde40ac8 fix(#636): fall back to kill+start when container start fails after rebuild 2026-05-31 21:00:02 +02:00
damocles
36c683138d feat(#704): add <children> recipient sentinel — fan-out to direct descendants 2026-05-31 20:59:20 +02:00
müde
7cc2690717 hive-c0re: pass build_logs the parent dir, not the broker.sqlite file
Every other Coordinator opener takes the broker.sqlite path and
derives its parent internally; BuildLogs alone wants a directory.
Passing the file path tripped create_dir_all on an existing file
(File exists os error 17) and blocked c0re boot.
2026-05-31 20:53:35 +02:00
damocles
252d8bf9af doc: add non-atomicity comment to emit_status RMW (argus note) 2026-05-31 20:32:13 +02:00
damocles
fce1f49f6a refactor(#838): consolidate harness state files into hyperhive-harness.json 2026-05-31 20:32:13 +02:00
iris
f6b3145349 dashboard: add GET /api/build-logs/{agent} + /id/{id} endpoints (#726 phase 2)
Wires the Phase 1 sqlite store into the dashboard HTTP layer so the
frontend can surface full build logs without hitting journald.

Two new read endpoints:
- GET /api/build-logs/{agent}?limit=N  — Vec<BuildLogHeader> JSON,
  newest first, default limit 10, server-side cap at 50.
- GET /api/build-logs/id/{id}          — BuildLogFull JSON (header +
  stdout + stderr), HTTP 404 on vacuum-reaped / unknown ids.

Agent-name validated ([a-z0-9_-], 1-63 chars) before the sqlite call.
Both handlers reach coord.build_logs (Arc<BuildLogs>) introduced in
the Phase 1 commit. Docs updated in docs/web-ui.md.
2026-05-31 20:31:07 +02:00
atlas
cbe8d25e25 dashboard.rs: update gateway routing comment to reflect agents.conf (#872) 2026-05-31 20:29:56 +02:00