Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-c0re/src/forge/internal_meta.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

108 lines
3.6 KiB
Rust

//! This hive's reads of `internal/meta` and of an agent's config head, as
//! [`crate::meta_rev::MetaSource`], under the hive's forge read token.
use forgejo_api::structs::RepoGetRawFileQuery;
use forgejo_api::{ApiErrorKind, Forgejo, ForgejoError};
use reqwest::StatusCode;
use super::reader::ReaderToken;
use super::{CONFIG_ORG, bounded};
use crate::meta_rev::MetaSource;
/// The org and repo the swarm commits its meta flake to.
const META_OWNER: &str = "internal";
const META_REPO: &str = "meta";
/// The branch an agent's config is deployed from, which `fetch_forge_main`
/// and the swarm controller's pins both read.
const CONFIG_BRANCH: &str = "main";
/// [`MetaSource`] over the forge API.
pub(crate) struct ForgeSource {
api: Forgejo,
}
/// A source reading as this hive, or `None` when it holds no forge token.
pub(crate) async fn internal_meta_source() -> Option<ForgeSource> {
let token: ReaderToken = super::reader_token().await?;
match token.api() {
Ok(api) => Some(ForgeSource { api }),
Err(e) => {
tracing::warn!(error = ?e, "internal/meta: cannot build a forge client");
None
}
}
}
fn is_not_found(e: &ForgejoError) -> bool {
match e {
ForgejoError::ApiError(api) => match api.error_kind() {
ApiErrorKind::NotFound { .. } => true,
ApiErrorKind::Other(s) => *s == StatusCode::NOT_FOUND,
_ => false,
},
ForgejoError::UnexpectedStatusCode(s) => *s == StatusCode::NOT_FOUND,
_ => false,
}
}
impl ForgeSource {
/// The head commit of `branch` in `owner/repo`; `None` when the repo or
/// the branch is not there.
async fn branch_head(
&self,
owner: &str,
repo: &str,
branch: &str,
) -> anyhow::Result<Option<String>> {
let op = format!("read branch {branch} of {owner}/{repo}");
match bounded(&op, self.api.repo_get_branch(owner, repo, branch)).await? {
Ok(branch) => Ok(branch.commit.and_then(|c| c.id)),
Err(e) if is_not_found(&e) => Ok(None),
Err(e) => Err(anyhow::Error::new(e).context(op)),
}
}
}
impl MetaSource for ForgeSource {
fn nix_can_fetch(&self) -> bool {
super::core_token().is_some()
}
async fn meta_head(&self) -> anyhow::Result<Option<String>> {
use anyhow::Context as _;
let op = format!("read repo {META_OWNER}/{META_REPO}");
let repo = match bounded(&op, self.api.repo_get(META_OWNER, META_REPO)).await? {
Ok(repo) => repo,
Err(e) if is_not_found(&e) => return Ok(None),
Err(e) => return Err(anyhow::Error::new(e).context(op)),
};
if repo.empty.unwrap_or(false) {
return Ok(None);
}
let branch = repo
.default_branch
.with_context(|| format!("{META_OWNER}/{META_REPO} names no default branch"))?;
self.branch_head(META_OWNER, META_REPO, &branch).await
}
async fn flake_at(&self, rev: &str) -> anyhow::Result<String> {
let op = format!("read flake.nix of {META_OWNER}/{META_REPO} at {rev}");
let query = RepoGetRawFileQuery {
r#ref: Some(rev.to_owned()),
};
let raw = bounded(
&op,
self.api
.repo_get_raw_file(META_OWNER, META_REPO, "flake.nix", query),
)
.await?
.map_err(|e| anyhow::Error::new(e).context(op))?;
Ok(String::from_utf8_lossy(&raw).into_owned())
}
async fn config_head(&self, agent: &str) -> anyhow::Result<Option<String>> {
self.branch_head(CONFIG_ORG, agent, CONFIG_BRANCH).await
}
}