Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-c0re/src/forge/reader.rs
atlas 7c0a622d41 build agents from internal/meta when it pins their config
A rebuild's MetaSync resolves the head of the swarm's internal/meta once and
records it; the prebuild and the swap build that flake instead of the hive's
own, and a first deploy does the same in Create. hive-priv takes the commit as
a validated meta_rev on UpdateContainer and CreateContainer and builds from its
own forge URL (HIVE_PRIV_FORGE_URL).

The head is used only when its flake lists the agent at the config commit the
forge's main is at; otherwise, and when the repo has no commit or this hive has
no core token file for nix's git, the hive renders locally as before.

hive-c0re's and hive-priv's gitconfig now share the forge credential helper and
rewrite the forge's https URLs to http, which hive-priv lacked.
2026-10-24 21:13:24 +02:00

159 lines
5.1 KiB
Rust

//! The credential this hive reads the forge with: its own read-only account,
//! whose token `swarm-controller` mints into the store at
//! `swarm/hives/<hive>/forge/token`, and the core token file where the store
//! holds none.
//!
//! The file is the fallback so a hive deployed before the controller mints its
//! token keeps the reads it had: the forge host holds the file, no other hive
//! does.
use super::{basic_auth_header, core_token};
/// The basic-auth user [`ReaderToken::auth_header`] names for the core token.
const CORE_LOGIN: &str = "core";
/// A forge token and the account it belongs to.
///
/// No `Debug` derive: `value` is a live forge credential.
pub struct ReaderToken {
login: String,
value: String,
}
impl std::fmt::Debug for ReaderToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReaderToken")
.field("login", &self.login)
.field("value", &"<redacted>")
.finish()
}
}
impl ReaderToken {
/// The `http.extraHeader` value for git; see [`basic_auth_header`].
pub fn auth_header(&self) -> String {
basic_auth_header(&self.login, &self.value)
}
/// A forge API client acting as this token's account.
pub(crate) fn api(&self) -> anyhow::Result<forgejo_api::Forgejo> {
super::api(&self.value)
}
}
/// The token to read the forge with, or `None` when this hive has neither.
///
/// 🩸 Logs the store path and which source won, never a value.
pub async fn reader_token() -> Option<ReaderToken> {
pick(stored_reader_token().await, core_token())
}
/// The store's token wins over the core file's.
fn pick(stored: Option<ReaderToken>, core: Option<String>) -> Option<ReaderToken> {
let picked = stored.or_else(|| {
core.map(|value| ReaderToken {
login: CORE_LOGIN.to_owned(),
value,
})
});
if let Some(t) = &picked {
tracing::debug!(login = %t.login, "forge: reading as this account");
} else {
tracing::debug!("forge: no read token in the store or the core token file");
}
picked
}
/// This hive's token from the store, under the hive's own identity.
///
/// `None`, never an error, for every way it can come up empty — no hive name,
/// no `BAO_*` identity, an unreachable store, nothing at the path — the same
/// as `matrix::stored_sender_token`, and for the same reason: each sends the
/// caller to the fallback.
async fn stored_reader_token() -> Option<ReaderToken> {
let hive = std::env::var("HYPERHIVE_HIVE_NAME")
.ok()
.filter(|h| !h.is_empty())?;
let path = match swarm_secret_client::forge::hive_token_path(&hive) {
Ok(path) => path,
Err(e) => {
tracing::warn!(error = %e, "forge: this hive's name does not form a store path");
return None;
}
};
let store = match swarm_secret_client::SecretStore::from_env(&hive).await {
Ok(store) => store,
Err(e) => {
tracing::debug!(error = %e, "forge: no swarm secret store to read the hive token from");
return None;
}
};
match store
.read::<swarm_secret_client::forge::Credential>(&path)
.await
{
Ok(credential) if !credential.value.trim().is_empty() => Some(ReaderToken {
login: swarm_secret_client::forge::hive_login(&hive),
value: credential.value.trim().to_owned(),
}),
Ok(_) => {
tracing::warn!(%path, "forge: the stored hive token is empty");
None
}
Err(e) => {
tracing::debug!(%path, error = %e, "forge: no hive token in the store");
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn stored() -> ReaderToken {
ReaderToken {
login: "hive-alpha".to_owned(),
value: "from-the-store".to_owned(),
}
}
#[test]
fn the_store_wins_over_the_core_file() {
let t = pick(Some(stored()), Some("from-the-file".to_owned())).expect("a token");
assert_eq!(t.value, "from-the-store");
assert_eq!(t.login, "hive-alpha");
}
#[test]
fn the_core_file_is_the_fallback() {
let t = pick(None, Some("from-the-file".to_owned())).expect("a token");
assert_eq!(t.value, "from-the-file");
assert_eq!(t.login, CORE_LOGIN);
}
#[test]
fn neither_is_none() {
assert!(pick(None, None).is_none());
}
#[test]
fn the_header_names_the_tokens_own_account() {
use base64::Engine as _;
let header = stored().auth_header();
let encoded = header
.strip_prefix("Authorization: Basic ")
.expect("a basic auth header");
let decoded = base64::engine::general_purpose::STANDARD
.decode(encoded)
.expect("base64");
assert_eq!(decoded, b"hive-alpha:from-the-store");
}
#[test]
fn debug_never_prints_the_value() {
let shown = format!("{:?}", stored());
assert!(!shown.contains("from-the-store"), "{shown}");
assert!(shown.contains("hive-alpha"), "{shown}");
}
}