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:
parent
97fb76ce99
commit
2c7e586f47
27 changed files with 815 additions and 748 deletions
|
|
@ -9,7 +9,7 @@ never `curl` it directly.
|
|||
|
||||
Reads credentials from the environment (`HIVE_FORGE_URL`,
|
||||
`HYPERHIVE_STATE_DIR`); `-f/--forge <label>` retargets a
|
||||
dashboard-provisioned external forge account instead. The active repo
|
||||
swarm-UI-linked external forge account instead. The active repo
|
||||
resolves `-r/--repo` > the `origin` remote of the cwd's git checkout >
|
||||
`HIVE_FORGE_REPO` (last-resort override, unset by default) > a hard
|
||||
error — see `client::Client::from_env`.
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ impl Client {
|
|||
/// `json_mode` comes from the global `--json` flag — per-verb
|
||||
/// output formatters key off it via `Client::json_mode`.
|
||||
/// `forge_label` (from the global `-f/--forge` flag) targets a
|
||||
/// dashboard-provisioned external forge account instead of the
|
||||
/// swarm-UI-linked external forge account instead of the
|
||||
/// internal forge — see [`resolve_credentials`].
|
||||
pub fn from_env(
|
||||
repo_override: Option<String>,
|
||||
|
|
@ -398,18 +398,17 @@ pub fn index(n: u64) -> Result<i64> {
|
|||
/// Resolve the `(base_url, token)` pair the client authenticates with.
|
||||
/// `None` (the default) resolves the internal forge exactly as before:
|
||||
/// `HIVE_FORGE_URL` (default [`DEFAULT_URL`]) + `read_token()`.
|
||||
/// `Some(label)` (from `-f/--forge <label>`) instead resolves a
|
||||
/// dashboard-provisioned external forge account: the token comes from
|
||||
/// `Some(label)` (from `-f/--forge <label>`) instead resolves an
|
||||
/// external forge account linked in the swarm UI: the token comes from
|
||||
/// `${HYPERHIVE_STATE_DIR}/forge-<label>-token` and the base URL from
|
||||
/// the `base_url` key of the sibling `forge-<label>.json` sidecar —
|
||||
/// the exact same two files `dashboard/extra_forges.rs` writes, so the
|
||||
/// read side can't drift from the write side. An unknown label (either
|
||||
/// file missing) is a clear error listing the labels actually found in
|
||||
/// the state dir, not a raw file-not-found. A label outside the plain
|
||||
/// identifier charset the dashboard accepts (e.g. a typo containing
|
||||
/// `/` or `..`) is rejected up front with the same charset spelled out,
|
||||
/// rather than silently building a nonsense/traversing path and
|
||||
/// surfacing a confusing file error later.
|
||||
/// the two files `nix/agent-modules/forge-accounts.nix` writes. An
|
||||
/// unknown label (either file missing) is a clear error listing the
|
||||
/// labels actually found in the state dir, not a raw file-not-found. A
|
||||
/// label outside the plain identifier charset the swarm controller
|
||||
/// accepts (e.g. a typo containing `/` or `..`) is rejected up front
|
||||
/// with the same charset spelled out, rather than silently building a
|
||||
/// nonsense/traversing path and surfacing a confusing file error later.
|
||||
pub(crate) fn resolve_credentials(forge_label: Option<&str>) -> Result<(String, String)> {
|
||||
let Some(label) = forge_label else {
|
||||
let base = std::env::var("HIVE_FORGE_URL").unwrap_or_else(|_| DEFAULT_URL.to_owned());
|
||||
|
|
@ -419,8 +418,8 @@ pub(crate) fn resolve_credentials(forge_label: Option<&str>) -> Result<(String,
|
|||
if !is_plain_ident(label) {
|
||||
bail!(
|
||||
"hive-forge: invalid --forge label {label:?} — must be lowercase \
|
||||
letters, digits, and hyphens only (same rule the dashboard's \
|
||||
FORGES tab enforces)"
|
||||
letters, digits, and hyphens only (same rule the swarm UI's \
|
||||
link form enforces)"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -445,30 +444,29 @@ pub(crate) fn resolve_credentials(forge_label: Option<&str>) -> Result<(String,
|
|||
bail!("hive-forge: no such forge {label:?} — provisioned forges: {known}");
|
||||
}
|
||||
|
||||
/// Plain-identifier check matching `dashboard/extra_forges.rs`'s
|
||||
/// `is_plain_ident` (itself matching hive-priv's `validate_name_chars`)
|
||||
/// — lowercase ascii + digits + hyphens only. Rejecting anything else
|
||||
/// up front (rather than just letting a weird label fail to resolve a
|
||||
/// file) turns a confusing "no such forge" surprise into a precise
|
||||
/// "that's not a valid label" one, and incidentally means a label like
|
||||
/// `../../etc` can't be used to build a path outside the state dir.
|
||||
/// Plain-identifier check matching the label rule of swarm-controller's
|
||||
/// `put_forge_account` (`hive_types::Ident`) — lowercase ascii, digits and
|
||||
/// hyphens only. Rejecting anything else up front (rather than just
|
||||
/// letting a weird label fail to resolve a file) turns a confusing "no
|
||||
/// such forge" surprise into a precise "that's not a valid label" one,
|
||||
/// and incidentally means a label like `../../etc` can't be used to
|
||||
/// build a path outside the state dir.
|
||||
fn is_plain_ident(s: &str) -> bool {
|
||||
!s.is_empty()
|
||||
&& s.chars()
|
||||
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
|
||||
}
|
||||
|
||||
/// Sidecar shape `dashboard/extra_forges.rs` writes alongside each
|
||||
/// `forge-<label>-token` file: just the base URL, pinned to the same
|
||||
/// `base_url` JSON key the write side uses.
|
||||
/// Sidecar shape `nix/agent-modules/forge-accounts.nix` writes alongside
|
||||
/// each `forge-<label>-token` file: just the base URL, under the
|
||||
/// `base_url` key that unit's `jq` filter spells.
|
||||
#[derive(Deserialize)]
|
||||
struct ForgeSidecar {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
/// Scan the state dir for every `forge-<label>-token` file (mirroring
|
||||
/// `dashboard/extra_forges.rs`'s own listing logic) and return the
|
||||
/// labels found, sorted. Used to build a helpful "did you mean one of
|
||||
/// Scan the state dir for every `forge-<label>-token` file and return
|
||||
/// the labels found, sorted. Used to build a helpful "did you mean one of
|
||||
/// these" error when `--forge <label>` doesn't resolve. Best-effort:
|
||||
/// an unreadable state dir yields an empty list rather than erroring
|
||||
/// (the caller already has its own error to report).
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
//! cwd's git checkout > `HIVE_FORGE_REPO` (last-resort override, unset
|
||||
//! by default) > a hard error — see `client::Client::from_env`.
|
||||
//!
|
||||
//! The global `-f/--forge <label>` flag targets a dashboard-provisioned
|
||||
//! The global `-f/--forge <label>` flag targets a swarm-UI-linked
|
||||
//! external forge account instead: it reads `forge-<label>-token` +
|
||||
//! `forge-<label>.json` (base URL) from the state dir rather than
|
||||
//! `HIVE_FORGE_URL`/`forge-token`. See `client::Client::from_env`.
|
||||
|
|
@ -45,9 +45,8 @@ struct Cli {
|
|||
/// any verb.
|
||||
#[arg(short = 'r', long, global = true)]
|
||||
repo: Option<String>,
|
||||
/// Act as a dashboard-provisioned external forge account (by its
|
||||
/// FORGES-tab label) instead of the internal forge. Independent of
|
||||
/// `-r/--repo`.
|
||||
/// Act as an external forge account linked in the swarm UI (by its
|
||||
/// label) instead of the internal forge. Independent of `-r/--repo`.
|
||||
#[arg(short = 'f', long, global = true)]
|
||||
forge: Option<String>,
|
||||
/// Emit JSON instead of the default human-readable output (for verbs
|
||||
|
|
|
|||
Loading…
Reference in a new issue