Nothing reads core/meta: builds come from internal/meta. Remove push_meta, ensure_meta_remote, meta_read_access, ensure_repo, the per-agent forge sync that only served them, and the git commit path in meta.rs; remove_permission_files now just deletes the seed files.
139 lines
5.8 KiB
Rust
139 lines
5.8 KiB
Rust
//! Repo plumbing on the local Forgejo: reseeding an agent's `applied` and
|
|
//! `proposed` repos from `agent-configs/<name>`. The typed API-client
|
|
//! constructor + org-name constants live in the module root (`super`).
|
|
|
|
use std::path::Path;
|
|
|
|
use super::{CONFIG_ORG, forge_git_url, reader_token};
|
|
|
|
/// Reseed a revived agent's `applied` repo — missing its `.git` entirely,
|
|
/// e.g. after `destroy --purge` — by fetching `agent-configs/<name>`'s
|
|
/// `main` from the forge. Unlike `proposed` (seeded once at first spawn and
|
|
/// never touched again — see `lifecycle::setup_proposed`'s own doc
|
|
/// comment), forge `main` advances with every operator-merged config PR, so
|
|
/// this reflects the last merged config rather than a stale creation-time
|
|
/// snapshot. A config deployed through hive-c0re without a forge merge never
|
|
/// reaches it.
|
|
///
|
|
/// Best-effort: returns `false` (not an error) rather than bailing when there
|
|
/// is no [`reader_token`], `applied` already has a `.git` (nothing to do), or
|
|
/// any git step fails — callers fall back to a less-current source or the
|
|
/// original hard error. Only `true` once `applied` is
|
|
/// actually seeded and tagged `deployed/0`, mirroring first-spawn's own
|
|
/// `setup_applied` seed shape exactly.
|
|
pub async fn fetch_config_main_into_applied(name: &str) -> bool {
|
|
let Some(reader) = reader_token().await else {
|
|
return false;
|
|
};
|
|
let applied = crate::paths::applied_dir(name);
|
|
if applied.join(".git").exists() {
|
|
return false;
|
|
}
|
|
if let Err(e) = std::fs::create_dir_all(&applied) {
|
|
tracing::warn!(%name, error = ?e, "forge: applied reseed mkdir failed");
|
|
return false;
|
|
}
|
|
if let Err(e) = crate::lifecycle::git(&applied, &["init", "--initial-branch=main"]).await {
|
|
tracing::warn!(%name, error = ?e, "forge: applied reseed init failed");
|
|
return false;
|
|
}
|
|
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
|
|
let auth = reader.auth_header();
|
|
let out = crate::lifecycle::git_command_authed(&auth)
|
|
.current_dir(&applied)
|
|
.args([
|
|
"fetch",
|
|
"--no-tags",
|
|
"--update-head-ok",
|
|
&url,
|
|
"refs/heads/main:refs/heads/main",
|
|
])
|
|
.output()
|
|
.await;
|
|
match out {
|
|
Ok(o) if o.status.success() => {}
|
|
Ok(o) => {
|
|
tracing::warn!(
|
|
%name,
|
|
stderr = %String::from_utf8_lossy(&o.stderr).trim(),
|
|
"forge: applied reseed fetch failed"
|
|
);
|
|
return false;
|
|
}
|
|
Err(e) => {
|
|
tracing::warn!(%name, error = ?e, "forge: applied reseed fetch failed");
|
|
return false;
|
|
}
|
|
}
|
|
if let Err(e) = crate::lifecycle::git_read_tree_reset(&applied, "refs/heads/main").await {
|
|
tracing::warn!(%name, error = ?e, "forge: applied reseed read-tree failed");
|
|
return false;
|
|
}
|
|
if let Err(e) = crate::lifecycle::git_tag(&applied, "deployed/0", "refs/heads/main").await {
|
|
tracing::warn!(%name, error = ?e, "forge: applied reseed tag failed");
|
|
return false;
|
|
}
|
|
tracing::info!(%name, "forge: reseeded applied repo from agent-configs main");
|
|
true
|
|
}
|
|
|
|
/// Seed an agent's `proposed` repo from `agent-configs/<name>` on the forge.
|
|
///
|
|
/// When the swarm creates an agent it writes that agent's config to the forge
|
|
/// before any hive is told to deploy it, so the hive's job here is to **take**
|
|
/// that config rather than author a second one beside it. The local template
|
|
/// is the fallback, not the default.
|
|
///
|
|
/// Best-effort like [`fetch_config_main_into_applied`], and `false` is an
|
|
/// ordinary answer rather than an error: there is no [`reader_token`], the
|
|
/// forge is unreachable, or `agent-configs/<name>` has no `main` — the last of
|
|
/// which is the *normal* case right after `swarm-controller` creates the
|
|
/// agent, before it has pushed anything into the repo it just made.
|
|
///
|
|
/// `clone` rather than `init` + `fetch`: git removes a directory it created
|
|
/// when the clone fails, and cloning into an existing empty dir leaves no
|
|
/// `.git` behind either — so the caller's "does this repo already exist"
|
|
/// check reads exactly the same before and after a failed attempt.
|
|
pub async fn clone_config_into_proposed(dir: &Path, name: &str) -> bool {
|
|
let Some(reader) = reader_token().await else {
|
|
return false;
|
|
};
|
|
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
|
|
// `--branch main` fails outright against a repo with no commits, so an
|
|
// empty repo reads as "nothing to take" instead of cloning to an unborn
|
|
// HEAD that would then look like a seeded checkout.
|
|
let out = crate::lifecycle::git_command_authed(&reader.auth_header())
|
|
.args([
|
|
"clone",
|
|
"--branch",
|
|
"main",
|
|
&url,
|
|
&dir.display().to_string(),
|
|
])
|
|
.output()
|
|
.await;
|
|
match out {
|
|
Ok(o) if o.status.success() => {
|
|
tracing::info!(%name, "forge: seeded proposed repo from agent-configs main");
|
|
true
|
|
}
|
|
Ok(o) => {
|
|
// `info`, not `warn`: the normal case right after
|
|
// `swarm-controller` creates the agent, before it has pushed
|
|
// anything into the repo it just made, and nothing is wrong.
|
|
// Logged at all because "where did this agent's config come
|
|
// from" is worth answering from the journal, and proposed is
|
|
// seeded exactly once.
|
|
tracing::info!(
|
|
%name,
|
|
stderr = %String::from_utf8_lossy(&o.stderr).trim(),
|
|
"forge: no config to clone for this agent; seeding the template"
|
|
);
|
|
false
|
|
}
|
|
Err(e) => {
|
|
tracing::warn!(%name, error = ?e, "forge: proposed clone failed to run");
|
|
false
|
|
}
|
|
}
|
|
}
|