A swarm runs one homeserver and a homeserver has one appservice sender account, so "mint it once" is a property of the thing being minted rather than something a lock has to enforce. That is what makes this account the one to move first: no trigger route, no controller change and no agent list — a boot-time oneshot beside tuwunel is the whole mechanism. `swarm-matrix-minter` runs inside `containers.hive-matrix`, which already holds the appservice token: the rendered registration is bound in read-only because that is how tuwunel is handed it. What the container lacked was an identity of its own, so this adds one — a leaf from the store's CA with a grant of exactly one path, not the hive's leaf, which reads every secret in the store. Both ends of the credential ship here. The minter reads the path it publishes to before it touches the homeserver, and returning on a non-empty read IS the "only once"; `hive-c0re`'s `ensure_hive_user` reads the same path, authenticating with the hive name already in `HYPERHIVE_HIVE_NAME`. The existing mint-then-`M_USER_IN_USE`-login ladder stays as the fallback for a store that is empty, unconfigured or unreachable, which is every swarm deployed before this — so nothing needs backfilling and nothing breaks if the rest of the sequence never lands. The credential is not an admin credential, and is not named like one. It is the access token of the appservice registration's own `sender_localpart` — `@hive:<server_name>`, an account the homeserver creates for itself when it loads the registration. The store path is `swarm/services/matrix/sender-token`, the host path is `matrix/access-token`, and the homeserver no longer runs an `admin_execute` promotion for that account at boot. Everything the hive provisions with it — the Space, the chat room, their hierarchy and join rules, the invites — rides on being the creator of those rooms at power level 100, not on homeserver admin; there is no Synapse admin API here to need, tuwunel has none. Two operations do need an admin *sender* and therefore stop working: `hivectl matrix promote-user` and `hivectl matrix reset-password`, both `!admin …` messages into `#admins:<server>`, plus the password-reset recovery path that an agent with a lost password file falls back to. They are swarm-level operations and are left failing loudly rather than served by an over-privileged token every other call site would also carry. The sweep's own admin-rights check and self-repair go with them: an account that is deliberately not an admin has nothing to check. `ephemeral = false` stays, and hive root can still read the container's filesystem. Accepted: what this buys is identity separation — no hive *process* holds or reads the appservice token — not physical isolation. Refs #4345
289 lines
14 KiB
Nix
289 lines
14 KiB
Nix
# All flake package outputs. Imported per system from flake.nix; the
|
|
# shared rust build wiring (cleanSrc / cargoArtifactsBinOnly /
|
|
# nativeBuildInputs) comes in via `rust` (see ../rust.nix).
|
|
{
|
|
pkgs,
|
|
craneLib,
|
|
rust,
|
|
self,
|
|
nixpkgs,
|
|
}:
|
|
let
|
|
inherit (pkgs) lib;
|
|
inherit (rust) cleanSrc cargoArtifactsBinOnly nativeBuildInputs;
|
|
|
|
docsAttrs = import ../docs {
|
|
inherit pkgs self;
|
|
inherit (nixpkgs) lib;
|
|
inherit (nixpkgs.lib) nixosSystem;
|
|
};
|
|
|
|
# Plain vendored Swagger UI dist (./swagger-ui-dist.nix) + the
|
|
# hyperhive-themed overlay on top (./swagger-ui-theme.nix) — bound
|
|
# here (not just inline in the attrset below) so the theme
|
|
# derivation can take the dist derivation as an explicit input.
|
|
swagger-ui-dist = pkgs.callPackage ./swagger-ui-dist.nix { };
|
|
swagger-ui-theme = pkgs.callPackage ./swagger-ui-theme.nix { inherit swagger-ui-dist; };
|
|
|
|
# Every per-binary package: name → description. The single source of
|
|
# truth for the bin list — it drives the per-bin extractor packages
|
|
# and the `default` bundle, so adding a binary is one entry here.
|
|
daemonBins = {
|
|
hive-c0re = "hyperhive host coordinator daemon";
|
|
hive-priv = "hyperhive privileged root helper";
|
|
hive-agent = "hyperhive in-container agent harness serve loop";
|
|
hive-agent-mcp = "hyperhive agent-surface MCP server";
|
|
hive-bash-daemon = "hyperhive per-agent bash-task runner daemon (serves its MCP tools directly over streamable-http)";
|
|
hive-subagent-daemon = "hyperhive per-agent claude-subagent task runner daemon (serves its MCP tools directly over streamable-http)";
|
|
hive-matrix-daemon = "hyperhive per-agent matrix-sdk daemon (serves its MCP tools directly over streamable-http)";
|
|
hive-metric = "hyperhive agent-emitted custom metrics CLI";
|
|
# The read half of what `hive-metric` writes, from the same seat: an
|
|
# agent's CLI for the swarm log store. In this list rather than beside
|
|
# `swarmctl` below because it is run *by an agent, inside a container*,
|
|
# which is exactly what this list's per-bin packages are for — see
|
|
# `nix/agent-modules/logs.nix` for what puts it on PATH there.
|
|
swarm-logs = "hyperhive agent-facing swarm log store query CLI";
|
|
hive-screen-mcp = "hyperhive screen MCP bridge (screenshot + input for GUI agents)";
|
|
hive-forge = "hyperhive Forgejo CLI";
|
|
hive-forge-notify = "hyperhive per-agent Forgejo notification poller daemon";
|
|
hive-github-notify = "hyperhive per-agent github.com notification poller daemon";
|
|
};
|
|
|
|
# ONE compile of the whole workspace (every bin, sharing the
|
|
# prebuilt `cargoArtifactsBinOnly` dep cache). The per-bin packages below
|
|
# are cheap copy-extractors over this, so workspace lib crates
|
|
# (hive-sh4re, hive-claude, …) compile exactly once instead of once
|
|
# per bin derivation.
|
|
#
|
|
# Tests are kept in the separate `checks.cargo-test` derivation
|
|
# (carries the hyperhive-assets build input for the prompt-template
|
|
# assertions in hive-agent::prompt::tests). Keeping them out of this
|
|
# derivation means a prompt edit doesn't bust the cargo cache.
|
|
#
|
|
# …and the dep cache underneath is the one WITHOUT test targets. This
|
|
# derivation has never run a test — `doCheck = false` below says so —
|
|
# but it used to sit on a cache that compiled every dev-dependency and
|
|
# test harness anyway, work whose only consumers are the checks. A
|
|
# deploy paid for it on every toolchain or dependency change.
|
|
workspaceBuild = craneLib.buildPackage {
|
|
src = cleanSrc;
|
|
cargoArtifacts = cargoArtifactsBinOnly;
|
|
inherit nativeBuildInputs;
|
|
pname = "hyperhive-workspace";
|
|
version = "0.1.0";
|
|
doCheck = false;
|
|
};
|
|
|
|
# Per-bin extractor: COPIES one binary out of the workspace build.
|
|
# A copy, not a symlink — a symlink would keep the whole workspace
|
|
# output (and thus every other binary) in the consumer's runtime
|
|
# closure, defeating the point of the per-bin split. The copied
|
|
# binary's RPATH references only the libs it links, so nix's
|
|
# reference scan gives each package a narrow closure.
|
|
mkBinPackage =
|
|
bin: description:
|
|
pkgs.runCommand bin
|
|
{
|
|
meta = {
|
|
inherit description;
|
|
mainProgram = bin;
|
|
};
|
|
}
|
|
''
|
|
install -Dm755 ${workspaceBuild}/bin/${bin} $out/bin/${bin}
|
|
'';
|
|
|
|
# Operator CLI — extractor plus shell completions and the `wg`
|
|
# wrapper. Suitable for `nix profile install .#hivectl` /
|
|
# `environment.systemPackages = [ …packages.<system>.hivectl ]`
|
|
# when the operator only wants the admin CLI.
|
|
# Completions come from the binary's own `completions <shell>` verb
|
|
# (the single source of truth, so they never drift from the actual
|
|
# verbs). The wireguard-tools wrap makes `hivectl wg` subcommands
|
|
# work before `deploy.wireguard.enable` is set (wg init is the very
|
|
# first setup step). Completion generation runs before wrapProgram
|
|
# since wrapProgram renames the real binary.
|
|
hivectlPkg =
|
|
pkgs.runCommand "hivectl"
|
|
{
|
|
nativeBuildInputs = [
|
|
pkgs.installShellFiles
|
|
pkgs.makeWrapper
|
|
];
|
|
meta = {
|
|
description = "hyperhive operator CLI";
|
|
mainProgram = "hivectl";
|
|
};
|
|
}
|
|
''
|
|
install -Dm755 ${workspaceBuild}/bin/hivectl $out/bin/hivectl
|
|
installShellCompletion --cmd hivectl \
|
|
--bash <("$out/bin/hivectl" completions bash) \
|
|
--zsh <("$out/bin/hivectl" completions zsh) \
|
|
--fish <("$out/bin/hivectl" completions fish)
|
|
wrapProgram "$out/bin/hivectl" \
|
|
--prefix PATH : ${pkgs.wireguard-tools}/bin
|
|
'';
|
|
|
|
# Per-bin split packages. Agent containers depend on the individual
|
|
# bins they actually exec/PATH-need (see nix/agent-modules/packages.nix)
|
|
# instead of the `default` bundle — that keeps `hivectl` (dials the *host*
|
|
# admin socket, unreachable from inside a container, drags in
|
|
# `wireguard-tools`) and redundant binaries out of every agent's
|
|
# closure.
|
|
binPkgs = lib.mapAttrs mkBinPackage daemonBins // {
|
|
hivectl = hivectlPkg;
|
|
};
|
|
in
|
|
{
|
|
# All workspace binaries in one derivation — a symlinkJoin over the
|
|
# per-bin packages, pure assembly with no extra compilation. The
|
|
# host module's `services.hyperhive.c0re.package` and `nix build .#`
|
|
# both land here.
|
|
default = pkgs.symlinkJoin {
|
|
name = "hyperhive";
|
|
paths = lib.attrValues binPkgs;
|
|
};
|
|
}
|
|
// binPkgs
|
|
// {
|
|
# Swarm-level controller daemon. Deliberately NOT in `daemonBins`:
|
|
# that list is the core stack — the binaries hive-c0re and the agent
|
|
# harness are made of — and it drives the `default` bundle that
|
|
# `services.hyperhive.c0re.package` points at. This daemon is a
|
|
# separate swarm-scoped service with its own module and its own
|
|
# `package` option, and one hive in a swarm runs it, so folding it
|
|
# into the core bundle would put it in every hive's closure to no end.
|
|
# Uses the same per-bin extractor, just bound on its own.
|
|
swarm-controller = mkBinPackage "swarm-controller" "hyperhive swarm-level controller daemon";
|
|
|
|
# The queue's auth-callout responder. Out of `daemonBins` for the same
|
|
# reason as the two above and one more: it runs *inside* the swarm-nats
|
|
# container, not on the host, so it belongs in that container's closure
|
|
# rather than every hive's.
|
|
swarm-nats-auth = mkBinPackage "swarm-nats-auth" "hyperhive swarm queue auth-callout responder";
|
|
|
|
# The matrix admin credential's minter. Out of `daemonBins` for the same
|
|
# "runs *inside* a container, not on the host" reason as the responder
|
|
# above, and with a second one: putting it in the core bundle would place
|
|
# the binary that reads the appservice token on every hive's filesystem,
|
|
# which is the arrangement it exists to end.
|
|
swarm-matrix-minter = mkBinPackage "swarm-matrix-minter" "hyperhive matrix admin-credential minter";
|
|
|
|
# The only process allowed to write swarm-authelia's users database —
|
|
# same "runs *inside* a container, not on the host" placement as
|
|
# `swarm-nats-auth` above (this one lives in `swarm-authelia`'s
|
|
# container, as authelia's own user, not the host's closure).
|
|
swarm-authelia-bridge = mkBinPackage "swarm-authelia-bridge" "hyperhive swarm-authelia users-database write bridge";
|
|
|
|
# The swarm operator's CLI, out of `daemonBins` for the same reason as
|
|
# the daemon above: it is installed by the swarm-controller module on
|
|
# the one host that runs the controller, and belongs in that hive's
|
|
# closure only. Kept a separate derivation rather than a second binary
|
|
# in the daemon's package so a hive can pin one without the other.
|
|
#
|
|
# Not `mkBinPackage`, because of the completions: they come from the
|
|
# binary's own `completions <shell>` verb, which walks the live clap
|
|
# tree, so they cannot drift from the actual verbs. Same shape as
|
|
# `hivectlPkg` above.
|
|
swarmctl =
|
|
pkgs.runCommand "swarmctl"
|
|
{
|
|
nativeBuildInputs = [ pkgs.installShellFiles ];
|
|
meta = {
|
|
description = "hyperhive swarm-level operator CLI";
|
|
mainProgram = "swarmctl";
|
|
};
|
|
}
|
|
''
|
|
install -Dm755 ${workspaceBuild}/bin/swarmctl $out/bin/swarmctl
|
|
installShellCompletion --cmd swarmctl \
|
|
--bash <("$out/bin/swarmctl" completions bash) \
|
|
--zsh <("$out/bin/swarmctl" completions zsh) \
|
|
--fish <("$out/bin/swarmctl" completions fish)
|
|
'';
|
|
|
|
# Static build of the swarm-level UI shell — see ./swarm-ui.nix. Same
|
|
# "swarm-scoped, not core-bundle" reasoning as swarm-controller/swarmctl
|
|
# above, kept a separate package for the same reason: not every hive
|
|
# runs the swarm controller, so this shouldn't ride along in
|
|
# `packages.default`'s closure.
|
|
swarm-ui = pkgs.callPackage ./swarm-ui.nix {
|
|
branding-svg = ../../branding/hyperhive.svg;
|
|
branding-svg-maskable = ../../branding/hyperhive-maskable.svg;
|
|
};
|
|
|
|
# Bundled browser assets — see ./frontend.nix. Output is
|
|
# $out/{dashboard,agent}/ which the Rust binaries serve via
|
|
# tower_http::ServeDir.
|
|
frontend = pkgs.callPackage ./frontend.nix {
|
|
branding-svg = ../../branding/hyperhive.svg;
|
|
};
|
|
# Static runtime assets the rust binaries read via
|
|
# `hive_sh4re::assets::*`: branding/* + prompts/*, plus the
|
|
# rendered agent-configs.png. Split out of the rust derivation so
|
|
# a tweak to e.g. system.md doesn't bust the cargo cache. Build
|
|
# input of the `cargo-test` check but NOT of `packages.default`,
|
|
# so the binary derivation stays cached when a prompt edit ripples
|
|
# through.
|
|
assets = pkgs.callPackage ./assets.nix { };
|
|
# hyperhive-authored Claude Code plugin marketplace (source tree at
|
|
# ../../claude-plugins) — a local-path marketplace spliced into the
|
|
# default `hyperhive.claudeMarketplaces`/`claudePlugins` lists. See
|
|
# ./claude-plugins.nix for the "why a store path, not a repo" rationale.
|
|
claude-plugins = pkgs.callPackage ./claude-plugins.nix { };
|
|
# The repo docs/ markdown tree as a standalone derivation —
|
|
# agents read it in-container (added as a claude additional
|
|
# directory) and the website repo reuses it as a flake input,
|
|
# neither of which needs the branding/prompt assets. See
|
|
# ./reference-docs.nix. `self` is needed to read each workspace
|
|
# crate's own README.md into the virtual `crates/` subdir;
|
|
# `docsAttrs.bundle` (the same tree `docs` below exposes standalone)
|
|
# is projected into the virtual `options/` subdir the same way.
|
|
reference-docs = pkgs.callPackage ./reference-docs.nix {
|
|
inherit self;
|
|
optionsMd = docsAttrs.bundle;
|
|
};
|
|
# XDG icon set + .desktop entries for hyperhive processes.
|
|
# Narrow input: only the branding SVG, so unrelated source changes
|
|
# don't bust this derivation's cache.
|
|
xdg-icons = pkgs.callPackage ./hive-xdg-icons.nix {
|
|
hyperhiveSvg = ../../branding/hyperhive.svg;
|
|
};
|
|
# Swagger UI: plain vendored dist + the hyperhive-themed overlay —
|
|
# see ./swagger-ui-dist.nix / ./swagger-ui-theme.nix (both computed
|
|
# above, in `let`). The gateway serves `swagger-ui-theme`'s full
|
|
# tree straight from the store at /api/docs/; a theme tweak is a
|
|
# gateway config change, not a hive-c0re rebuild. `swagger-ui-dist`
|
|
# is exposed too since it's independently useful/inspectable.
|
|
inherit swagger-ui-dist swagger-ui-theme;
|
|
|
|
# Pre-built per-container system closures. Exposed as packages
|
|
# so operators can `nix build .#agent-base-toplevel` (or wire
|
|
# them into their host system closure via the
|
|
# `preBuildAgentTemplates` option on the hive-c0re module —
|
|
# see nix/host-modules/hive-c0re/options.nix). Speeds up the first agent
|
|
# spawn dramatically because the heavy lifting (nixpkgs +
|
|
# claude-code + hive-agent binary) is already in the store
|
|
# when the meta evaluator goes to build the container.
|
|
#
|
|
# nixosConfigurations are pinned to x86_64-linux (nixos-
|
|
# containers only run native arch), so these toplevels are
|
|
# only useful on an x86_64-linux host — flake check across
|
|
# systems still tolerates evaluating them on aarch64 because
|
|
# they're plain derivations, but `nix build` from a non-x86
|
|
# host would only succeed via a remote x86 builder.
|
|
agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel;
|
|
ruth-toplevel = self.nixosConfigurations.ruth.config.system.build.toplevel;
|
|
|
|
# Auto-generated nix options reference for hyperhive.
|
|
# `docs` bundles host + swarm + deploy + agent pages into one tree;
|
|
# the split outputs are useful when consumers only want one surface.
|
|
# All five are pure markdown — no rust or frontend deps in
|
|
# the closure, so `nix build .#docs` is cheap.
|
|
docs = docsAttrs.bundle;
|
|
docs-host = docsAttrs.host;
|
|
docs-swarm = docsAttrs.swarm;
|
|
docs-deploy = docsAttrs.deploy;
|
|
docs-agent = docsAttrs.agent;
|
|
}
|