nix: move the agent option namespace under services.hyperhive.agent

Every per-agent harness option lived at the top-level `hyperhive.*` while
the host tier has always been `services.hyperhive.*`. Move all 52 agent-tier
option leaves (33 top-level names across 16 modules) to
`services.hyperhive.agent.*`, repoint every read, and keep existing agent
configs evaluating through one `mkRenamedOptionModule` per old leaf path in
the new nix/agent-modules/renamed-options.nix.

The shims are per leaf rather than per namespace: `user`, `mcp`, `otel`,
`queue`, `docs`, `forge`, `frontend`, `github`, `gui`, `logs`, `matrix` and
`cargo` are plain attrsets of declarations, not submodule-typed options, so
a parent-path rename would not reach their children. Three read-only
options (`frontend.mergedDist`, `queue.clientIdFile`,
`queue.clientSecretFile`) deliberately get no shim — a rename contributes a
definition, which a read-only option refuses; the exclusions are commented
in place.

Refs #4473
This commit is contained in:
atlas 2026-09-17 19:29:17 +02:00
commit 3662eda440
21 changed files with 531 additions and 290 deletions

View file

@ -8,14 +8,14 @@
...
}:
let
userName = config.hyperhive.user.name;
userName = config.services.hyperhive.agent.user.name;
homeDir = "/home/${userName}";
# Same 512×512 rasterization of the agent icon the matrix avatar
# sync uses (./matrix.nix — identical derivation, same store path).
# Only forced when an icon is configured (the avatar-sync unit below
# is gated on `hyperhive.icon != null`).
# is gated on `services.hyperhive.agent.icon != null`).
iconPng = pkgs.runCommand "hive-agent-icon.png" { nativeBuildInputs = [ pkgs.librsvg ]; } ''
rsvg-convert -f png -w 512 -h 512 ${config.hyperhive.icon} -o $out
rsvg-convert -f png -w 512 -h 512 ${config.services.hyperhive.agent.icon} -o $out
'';
# git credential helper for the hive forge --- the exact shape
@ -39,7 +39,7 @@ let
'';
in
{
options.hyperhive.forge.url = lib.mkOption {
options.services.hyperhive.agent.forge.url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "http://forge.internal:3000";
@ -80,10 +80,10 @@ in
# is for, so reject it and name the option.
{
assertion =
config.hyperhive.forge.url == null
|| lib.hasPrefix "http://" config.hyperhive.forge.url
|| lib.hasPrefix "https://" config.hyperhive.forge.url;
message = "hyperhive.forge.url must be an http:// or https:// URL, or null for no forge (got: \"${toString config.hyperhive.forge.url}\")";
config.services.hyperhive.agent.forge.url == null
|| lib.hasPrefix "http://" config.services.hyperhive.agent.forge.url
|| lib.hasPrefix "https://" config.services.hyperhive.agent.forge.url;
message = "services.hyperhive.agent.forge.url must be an http:// or https:// URL, or null for no forge (got: \"${toString config.services.hyperhive.agent.forge.url}\")";
}
];
@ -95,9 +95,9 @@ in
# hive-forge <verb>: CLI wrapping common Forgejo REST API operations
# (view, pr, issue, comment, assign, close, labels, branches, etc.).
# The per-bin split package — narrow closure, no hivectl/wireguard.
config.hyperhive.packages.hive-forge
config.services.hyperhive.agent.packages.hive-forge
]
++ lib.optional (config.hyperhive.forge.url != null) gitCredHelper;
++ lib.optional (config.services.hyperhive.agent.forge.url != null) gitCredHelper;
# Wire the forge credential helper for `git push`, scoped to the forge
# this agent is configured for.
@ -135,9 +135,9 @@ in
# works for any agent whose personal `~/.gitconfig` names the helper by
# ABSOLUTE path — so this is masked exactly where it would be noticed,
# and bites a fresh agent that has no such file.
environment.etc."gitconfig" = lib.mkIf (config.hyperhive.forge.url != null) {
environment.etc."gitconfig" = lib.mkIf (config.services.hyperhive.agent.forge.url != null) {
text = ''
[credential "${lib.removeSuffix "/" config.hyperhive.forge.url}"]
[credential "${lib.removeSuffix "/" config.services.hyperhive.agent.forge.url}"]
helper = hive-forge
username = ${userName}
'';
@ -165,7 +165,7 @@ in
# and the agent's `forge-token` from under the second.
};
serviceConfig = {
ExecStart = "${config.hyperhive.packages.hive-forge-notify}/bin/hive-forge-notify";
ExecStart = "${config.services.hyperhive.agent.packages.hive-forge-notify}/bin/hive-forge-notify";
SyslogIdentifier = "hive-forge-notify";
# `on-failure`, NOT `always`: an agent with no forge account is a
# supported configuration, and the poller reports that by logging
@ -183,7 +183,7 @@ in
# docs/process/conventions.md::Best-effort oneshot services.
# Not generated at all when no forge is configured: an absent
# integration rather than one pointed at a guessed address.
systemd.services.tea-login = lib.mkIf (config.hyperhive.forge.url != null) {
systemd.services.tea-login = lib.mkIf (config.services.hyperhive.agent.forge.url != null) {
description = "configure tea CLI from hive-forge token (best-effort)";
wantedBy = [ "multi-user.target" ];
after = [ "local-fs.target" ];
@ -202,7 +202,7 @@ in
environment.AGENT_USER = userName;
script = ''
# No `set -e`: best-effort posture (see docs pointer above).
FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url}
FORGE_URL=${lib.escapeShellArg config.services.hyperhive.agent.forge.url}
# $HYPERHIVE_STATE_DIR is system-wide via the meta flake.
TOKEN_FILE="$HYPERHIVE_STATE_DIR/forge-token"
if [ ! -f "$TOKEN_FILE" ]; then
@ -266,13 +266,13 @@ in
# watches paths this unit has no business reacting to.
# The service reads `$HYPERHIVE_STATE_DIR/forge-token`; this is the same
# file, spelled the way `tea-login` above already spells it.
systemd.paths.forge-avatar-sync = lib.mkIf (config.hyperhive.icon != null) {
systemd.paths.forge-avatar-sync = lib.mkIf (config.services.hyperhive.agent.icon != null) {
description = "trigger forge-avatar-sync when forge-token appears";
wantedBy = [ "multi-user.target" ];
pathConfig.PathChanged = "/agents/${userName}/state/forge-token";
};
# One-shot: hyperhive.icon → Forgejo profile avatar. Shape contract:
# One-shot: services.hyperhive.agent.icon → Forgejo profile avatar. Shape contract:
# docs/process/conventions.md::Best-effort oneshot services.
# RemainAfterExit = false so the .path trigger above can re-fire
# this unit when the forge-token arrives after boot. The PNG is
@ -281,7 +281,8 @@ in
# and needs no librsvg at runtime — Forgejo's Go image library
# can't decode SVG, hence PNG.
systemd.services.forge-avatar-sync =
lib.mkIf (config.hyperhive.icon != null && config.hyperhive.forge.url != null)
lib.mkIf
(config.services.hyperhive.agent.icon != null && config.services.hyperhive.agent.forge.url != null)
{
description = "sync agent icon to Forgejo user avatar (best-effort)";
wantedBy = [ "multi-user.target" ];
@ -298,7 +299,7 @@ in
pkgs.jq
];
script = ''
FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url}
FORGE_URL=${lib.escapeShellArg config.services.hyperhive.agent.forge.url}
# $HYPERHIVE_STATE_DIR is set system-wide by the meta flake
# (systemd.globalEnvironment) to `/agents/<name>/state`.
TOKEN_FILE="$HYPERHIVE_STATE_DIR/forge-token"