hyperhive/nix/packages/default.nix
atlas 0db83c40a0 feat(#2642): a github.com notification poller alongside the forge one
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.
2026-07-31 17:23:18 +02:00

188 lines
8 KiB
Nix

# All flake package outputs. Imported per system from flake.nix; the
# shared rust build wiring (cleanSrc / cargoArtifacts /
# nativeBuildInputs) comes in via `rust` (see ../rust.nix).
{
pkgs,
craneLib,
rust,
self,
nixpkgs,
}:
let
inherit (pkgs) lib;
inherit (rust) cleanSrc cargoArtifacts nativeBuildInputs;
docsAttrs = import ../docs {
inherit pkgs self;
inherit (nixpkgs) lib;
inherit (nixpkgs.lib) nixosSystem;
};
# 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-matrix-daemon = "hyperhive per-agent matrix-sdk daemon (serves its MCP tools directly over streamable-http)";
hive-metric = "hyperhive agent-emitted custom metrics 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 `cargoArtifacts` 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.
workspaceBuild = craneLib.buildPackage {
src = cleanSrc;
inherit cargoArtifacts 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 `swarm.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 harness-base.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
// {
# 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. (`docs` below is the auto-generated
# nix-options reference, a different artifact.)
reference-docs = pkgs.callPackage ./reference-docs.nix { };
# 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;
};
# 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.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 + agent pages into one tree; the split
# outputs are useful when consumers only want one surface.
# All three 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-agent = docsAttrs.agent;
}