Watch
0
0
Fork
You've already forked hyperhive
0

agents: pull the forge token from bao; drop tea-login

forge-token.nix fetches swarm/agents/<agent>/forge-token under the
agent's own store identity into /run/hive-agent-forge-token/token, and
re-fetches on a timer so a rotation lands. hive-forge, the git
credential helper, hive-forge-notify, forge-avatar-sync and the web UI
read that file first and fall back to <state>/forge-token.

tea-login is deleted: it copied the token into ~/.config/tea, which
docs/swarm/credentials.md forbids for a store secret. hive-forge covers
the same verbs. swarmctl gains agent mint-forge-token.

Refs #3782
This commit is contained in:
atlas 2026-09-24 16:35:40 +02:00 • committed by mara
commit dd32a395f7
16 changed files with 501 additions and 156 deletions

View file

@ -37,23 +37,17 @@ async fn forgejo_loop(state_dir: String, socket: std::path::PathBuf) {
}
};
let token_path = format!("{state_dir}/forge-token");
// Retry reading the token to handle races where hive-priv provisions
// it after the harness starts, or where a parent-container chown
// briefly makes the file unreadable.
let token = {
let token_paths = hive_forge_notify::forge_token_paths(&state_dir);
// Retry reading the token to handle races where the agent's fetch of
// it from the swarm secret store lands after this unit starts, or where
// a parent-container chown briefly makes the file unreadable.
let mut token = {
let mut attempts = 0u32;
loop {
match tokio::fs::read_to_string(&token_path).await {
Ok(t) => {
let t = t.trim().to_owned();
if !t.is_empty() {
break t;
}
debug!("forge_notify: empty forge token at {token_path}");
}
Err(e) => debug!("forge_notify: cannot read token at {token_path}: {e}"),
if let Some(t) = hive_forge_notify::read_first_token(&token_paths) {
break t;
}
debug!(?token_paths, "forge_notify: no forge token yet");
attempts += 1;
if attempts >= TOKEN_RETRY_MAX {
debug!(
@ -65,7 +59,7 @@ async fn forgejo_loop(state_dir: String, socket: std::path::PathBuf) {
}
};
let Some(source) = ForgejoSource::new(&forge_url, &token) else {
let Some(mut source) = ForgejoSource::new(&forge_url, &token) else {
return;
};
@ -111,6 +105,18 @@ async fn forgejo_loop(state_dir: String, socket: std::path::PathBuf) {
loop {
interval.tick().await;
// The swarm rotates the token when it goes stale, and the agent's
// fetch replaces the file; pick the new one up rather than polling
// with a revoked token for the rest of this process's life.
if let Some(t) = hive_forge_notify::read_first_token(&token_paths)
&& t != token
&& let Some(s) = ForgejoSource::new(&forge_url, &t)
{
info!("forge_notify: forge token changed on disk; using the new one");
token = t;
source = s;
own_login.clear();
}
if own_login.is_empty() {
own_login = resolve_own_login(&client, &source).await;
if !own_login.is_empty() {