nix/templates: actually collapse agent-base + manager to role shims (#671 fixup)

The merge of #676 (commit 0951cd1) landed the role-driven harness service
in `harness-base.nix` but the rebase resolution accidentally kept the
legacy `systemd.services.hive-ag3nt` / `hive-m1nd` blocks in
agent-base.nix and manager.nix. Module merging silently accepts the
duplicate definitions because they evaluate to identical attrs — but
the whole point of #671 was to single-source the systemd unit + manager
forge defaults.

Collapses both templates to bare role-setters as originally intended:

    { ... }: {
      imports = [ ./harness-base.nix ];
      hyperhive.role = "agent";  # or "manager"
    }

Verified post-collapse:
- `nixosConfigurations.agent-base.config.systemd.services.hive-ag3nt
  .serviceConfig.ExecStart` -> `.../bin/hive-ag3nt serve`
- `nixosConfigurations.manager.config.systemd.services.hive-m1nd
  .serviceConfig.ExecStart` -> `.../bin/hive-m1nd serve`
- `agent-base` `.path` is `[ /run/wrappers/bin /run/current-system/sw ... ]`
- `manager` `.environment.HIVE_PORT` is `"8000"`

Follow-up to #671 (#676). No behaviour change — the duplicate
definitions were merging to the same values; this just deletes the
redundant copies so `harness-base.nix` is the true single source.
This commit is contained in:
atlas 2026-05-31 00:07:55 +02:00
commit d23fd8847d
2 changed files with 18 additions and 133 deletions

View file

@ -1,70 +1,11 @@
{ pkgs, config, ... }:
let
userName = config.hyperhive.user.name;
in
{ ... }:
{
imports = [ ./harness-base.nix ];
systemd.services.hive-ag3nt = {
description = "hive-ag3nt harness";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
# systemd units get a minimal PATH by default and don't inherit
# `environment.systemPackages`. Pointing at `/run/current-system/sw`
# gives the harness (and any tools claude shells out to via Bash)
# access to everything declared in `systemPackages` — including
# anything an agent adds to its own `agent.nix` — without having to
# touch the service definition.
#
# `/run/wrappers/bin` is prepended so the `security.wrappers`
# setuid shims (notably `sudo`) resolve before the bare nix-store
# binaries in `/run/current-system/sw/bin`. Post-#658 the harness
# runs as the per-agent user — without the wrapper dir on PATH,
# `sudo` resolves to the un-setuid nix-store binary and refuses
# with "must be owned by uid 0 and have the setuid bit set" even
# when `hyperhive.user.passwordlessSudo = true` is configured.
path = [
"/run/wrappers/bin"
"/run/current-system/sw"
];
environment = {
SHELL = "${pkgs.bashInteractive}/bin/bash";
# `HOME` defaults to `/` for systemd services without a User=
# set. With #658 the harness runs as the agent user — set HOME
# explicitly so claude (which the harness spawns) finds its
# `~/.claude/` session dir at the bind-mounted location.
HOME = "/home/${userName}";
# Path to the merged agent static dist. The harness serves this
# via `tower_http::ServeDir` for any request it doesn't route to
# an API endpoint. `mergedDist` is the agent-default dist with
# `hyperhive.frontend.extraFiles` layered on top — both come
# from harness-base.nix.
HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}";
# Static runtime assets (branding + claude prompts). Set on the
# unit directly — `environment.variables` in harness-base.nix only
# populates /etc/profile, which systemd services don't inherit.
HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive";
};
serviceConfig = {
ExecStart = "${pkgs.hyperhive}/bin/hive-ag3nt serve";
Restart = "on-failure";
RestartSec = 2;
# `/run/hive-config/` is a per-service runtime dir owned by
# the agent user (`User=` below), auto-cleared by systemd on
# stop. The harness writes its regenerated
# claude-{mcp-config,settings,system-prompt} files there
# (see `paths::config_dir`). Kept separate from `/run/hive`
# — that bind comes in root-owned from the host and holds
# hive-c0re's `mcp.sock` we only connect to (#658 fixup).
RuntimeDirectory = "hive-config";
# Run the harness as the per-agent user (#658). claude itself
# spawned by the harness then runs as that user too — drops
# root inside the container while sudo (`NOPASSWD: ALL` by
# default, see harness-base.nix `hyperhive.user.passwordlessSudo`)
# keeps the previous root-by-default surface available
# explicitly for tools that need it.
User = userName;
Group = userName;
};
};
# Sub-agent role: the role-driven `systemd.services.hive-ag3nt` plus
# the default forge notification surface live in `harness-base.nix`.
# This file is the bare entry-point referenced from `flake.nix`
# (`nixosConfigurations.agent-base`) and the meta-flake's
# `applied/<name>/flake.nix` for sub-agent containers.
hyperhive.role = "agent";
}

View file

@ -1,71 +1,15 @@
{ pkgs, config, ... }:
let
userName = config.hyperhive.user.name;
in
{ ... }:
{
imports = [ ./harness-base.nix ];
# Manager auto-unsubscribes from repo watches and skips the subscription/
# participation firehose — only direct mentions, reviews, and assignments
# land in the inbox. Sub-agents default to keepSubscriptions=true and
# skipNotifyReasons=[].
hyperhive.forge.keepSubscriptions = false;
hyperhive.forge.skipNotifyReasons = [
"subscribed"
"participating"
];
# HIVE_PORT/HIVE_LABEL/gitconfig are also injected by the generated
# `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`); the values
# here are the base config so the container stays sensible if anyone
# ever evaluates `nixosConfigurations.manager` standalone.
systemd.services.hive-m1nd = {
description = "hive-m1nd manager harness";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
HIVE_PORT = "8000";
HIVE_LABEL = "hm1nd";
SHELL = "${pkgs.bashInteractive}/bin/bash";
# `HOME` set explicitly so claude finds `~/.claude/` at the
# bind-mounted location after #658 (User= drop from root).
HOME = "/home/${userName}";
# Manager runs the same hive-m1nd harness binary that serves
# the per-agent web UI; point it at the merged agent static dist
# (same shape as for sub-agents).
HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}";
# Static runtime assets (branding + claude prompts). Set on the
# unit directly — `environment.variables` in harness-base.nix only
# populates /etc/profile, which systemd services don't inherit.
HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive";
};
# See note in agent-base.nix for the rationale on both entries —
# `/run/wrappers/bin` so the setuid sudo shim resolves before the
# bare nix-store binary (#658 fixup), `/run/current-system/sw`
# so the harness PATH tracks `environment.systemPackages`.
path = [
"/run/wrappers/bin"
"/run/current-system/sw"
];
serviceConfig = {
ExecStart = "${pkgs.hyperhive}/bin/hive-m1nd serve";
Restart = "on-failure";
RestartSec = 2;
# `/run/hive-config/` is a per-service runtime dir owned by
# the agent user (`User=` below), auto-cleared by systemd on
# stop. The harness writes its regenerated
# claude-{mcp-config,settings,system-prompt} files there
# (see `paths::config_dir`). Kept separate from `/run/hive`
# — that bind comes in root-owned from the host and holds
# hive-c0re's `mcp.sock` we only connect to (#658 fixup).
RuntimeDirectory = "hive-config";
# Same drop-from-root as agent-base.nix (#658). Manager
# interactions with the host (rebuild approvals, config
# writes) still happen via the dedicated unix sockets
# bind-mounted from hive-c0re — those don't need root
# inside the container.
User = userName;
Group = userName;
};
};
# Manager role: the role-driven `systemd.services.hive-m1nd` plus
# the manager-only forge defaults (`keepSubscriptions = false`,
# `skipNotifyReasons = [ "subscribed" "participating" ]`) live in
# `harness-base.nix` under `lib.mkIf (config.hyperhive.role ==
# "manager")`. This file is the bare entry-point referenced from
# `flake.nix` (`nixosConfigurations.manager`) and the meta-flake's
# `applied/hm1nd/flake.nix`. HIVE_PORT / HIVE_LABEL are injected by
# the meta-flake at deploy time and have manager-only standalone-eval
# fallbacks in `harness-base.nix`.
hyperhive.role = "manager";
}