Watch
0
0
Fork
You've already forked hyperhive
0

forge: external forge accounts live in swarm bao; the agent fetches them itself

An operator now links an agent's external forge account (label, base URL,
token) in the swarm UI. swarm-controller stores it at
swarm/agents/<agent>/forge/<label>. There is no index: the store's
listing of the agent's forge/ directory is the set of accounts.

In the agent, hive-agent-forge-accounts (oneshot + 2-minute timer, as
the agent user, under its own store certificate) lists
swarm/agents/<agent>/forge/ with the `list` #4866 grants an agent on its
own metadata subtree, reads each account, and writes
<state>/forge-<label>-token and forge-<label>.json in the names and shape
hive-forge -f already reads. An empty listing (a 404, which `bao kv list
-format=json` answers with `{}` and an empty stderr) is zero accounts; a
denial or an unreachable store fails the unit. It never deletes: files
for labels not listed, including ones the hive wrote, stay as they are.

Removed: the dashboard FORGES tab (credentials.js/html section and its
CSS), hive-c0re's extra_forges.rs and its routes, priv_client's
extra-forge calls, and hive-priv's WriteAgentExtraForgeAccount /
DeleteAgentExtraForgeAccount with their helpers. The GITHUB tab and
WriteAgentGithubToken stay.

Also: persistence.md's matrix avatar note names the exit-75 restart on a
changed account listing, not the dashboard, as what brings a linked
account up.

Refs #4348
This commit is contained in:
atlas 2026-10-01 17:50:24 +02:00
commit 2c7e586f47
27 changed files with 815 additions and 748 deletions

View file

@ -42,6 +42,7 @@ let
};
fetchUnit = machine: machine.systemd.services.hive-agent-forge-token;
accountsUnit = machine: machine.systemd.services.hive-agent-forge-accounts;
tokenFile = machine: machine.services.hyperhive.agent.forge.tokenFile;
in
let
@ -164,6 +165,60 @@ let
&& !(agentForgeNoBao.systemd.services ? tea-login)
&& !(builtins.elem pkgs.tea agentForgeBao.environment.systemPackages);
}
{
name = "an agent with a store address fetches its external forge accounts, and one without does not";
ok =
agentForgeBao.systemd.services ? hive-agent-forge-accounts
&& agentForgeBao.systemd.timers ? hive-agent-forge-accounts
&& !(agentForgeNoBao.systemd.services ? hive-agent-forge-accounts)
&& !(agentForgeNoBao.systemd.timers ? hive-agent-forge-accounts);
}
{
# The nix half of `swarm_secret_client::forge::{accounts_dir,account_path}`,
# and the two file names `hive-forge -f` reads.
name = "the account fetch lists the agent's own accounts and reads each into hive-forge's files";
ok =
let
name = agentForgeBao.services.hyperhive.agent.user.name;
s = (accountsUnit agentForgeBao).script;
in
lib.hasInfix "bao kv list -format=json secret/swarm/agents/${name}/forge >" s
&& !(lib.hasInfix "/index/" s)
&& lib.hasInfix "path=\"secret/swarm/agents/${name}/forge/$label\"" s
&& lib.hasInfix "/agents/${name}/state" s
&& lib.hasInfix "/forge-$label-token" s
&& lib.hasInfix "/forge-$label.json" s
&& lib.hasInfix "{base_url: (.data.data.url | strings)}" s;
}
{
# The agent user owns its state dir (./user.nix), and the files keep
# the `0600` they have always had.
name = "the account fetch runs as the agent, with its own store identity";
ok =
let
u = accountsUnit agentForgeBao;
name = agentForgeBao.services.hyperhive.agent.user.name;
in
u.serviceConfig.User == name
&& u.serviceConfig.UMask == "0077"
&& builtins.elem "hive-agent-bao-cert" u.serviceConfig.LoadCredential
&& u.environment.BAO_CLIENT_CERT == "%d/hive-agent-bao-cert";
}
{
# Files a hive wrote keep working until the operator re-links them.
name = "the account fetch never deletes a state-dir file it did not stage";
ok =
let
s = (accountsUnit agentForgeBao).script;
in
!(lib.hasInfix "rm -f \"$token\"" s)
&& !(lib.hasInfix "rm -f \"$sidecar\"" s)
&& !(lib.hasInfix "forge-*" s);
}
{
name = "the account fetch re-runs every two minutes";
ok = agentForgeBao.systemd.timers.hive-agent-forge-accounts.timerConfig.OnUnitInactiveSec == "2min";
}
];
in
runGroup "agent-forge-bao" cases