forge: ensure core/meta repo + mirror meta commits to forge
startup sweep adds ensure_repo('meta', core_token) after the orgs
so the first push isn't a 404. meta::git_commit now calls
forge::push_meta after every successful commit — token-in-URL
`git push http://core:$token@localhost:3000/core/meta.git` —
gated on the core token file existing (no-op when forge isn't
seeded). push failures log warn, don't bubble up.
no tea needed on the host; git is already on the hive-c0re service
PATH via /run/current-system/sw.
This commit is contained in:
parent
68020a15c9
commit
600ed509f4
2 changed files with 95 additions and 1 deletions
|
|
@ -231,6 +231,86 @@ async fn ensure_core_user_and_token() -> Result<String> {
|
||||||
Ok(raw.trim().to_owned())
|
Ok(raw.trim().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST `/api/v1/user/repos` to create a repo in the authenticated
|
||||||
|
/// user's own namespace. `token` belongs to the user we want the
|
||||||
|
/// repo owned by (we use `core`'s token for `core/meta`). Idempotent:
|
||||||
|
/// HTTP 409 ("repository already exists") is treated as success.
|
||||||
|
pub async fn ensure_repo(name: &str, token: &str) -> Result<()> {
|
||||||
|
let body = format!(r#"{{"name":"{name}","auto_init":false,"private":true,"default_branch":"main"}}"#);
|
||||||
|
let url = format!("{FORGE_HTTP}/api/v1/user/repos");
|
||||||
|
let out = Command::new("curl")
|
||||||
|
.args([
|
||||||
|
"-sS",
|
||||||
|
"-o",
|
||||||
|
"/dev/null",
|
||||||
|
"-w",
|
||||||
|
"%{http_code}",
|
||||||
|
"-X",
|
||||||
|
"POST",
|
||||||
|
"-H",
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"-H",
|
||||||
|
&format!("Authorization: token {token}"),
|
||||||
|
"-d",
|
||||||
|
&body,
|
||||||
|
&url,
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.context("invoke curl POST /api/v1/user/repos")?;
|
||||||
|
let code = String::from_utf8_lossy(&out.stdout).trim().to_owned();
|
||||||
|
match code.as_str() {
|
||||||
|
"201" => {
|
||||||
|
tracing::info!(%name, "forge: created repo");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
"409" | "422" => {
|
||||||
|
tracing::debug!(%name, "forge: repo already exists");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
other => anyhow::bail!(
|
||||||
|
"POST /api/v1/user/repos name={name} returned HTTP {other}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the persisted core token, or None when the forge isn't
|
||||||
|
/// seeded yet. Cheap — just a file read.
|
||||||
|
pub fn core_token() -> Option<String> {
|
||||||
|
std::fs::read_to_string(CORE_TOKEN_PATH)
|
||||||
|
.ok()
|
||||||
|
.map(|s| s.trim().to_owned())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push `dir` (the meta repo) to `core/meta` on the local forge.
|
||||||
|
/// Best-effort: returns Err which callers log + ignore. No-op when
|
||||||
|
/// the core token isn't present (forge not enabled).
|
||||||
|
pub async fn push_meta(dir: &Path) -> Result<()> {
|
||||||
|
let Some(token) = core_token() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
// Token-in-URL push. Forgejo accepts `oauth2:<token>` or just
|
||||||
|
// any-username:<token>; using `core` matches the owner so the
|
||||||
|
// remote name is self-describing.
|
||||||
|
let url = format!("http://core:{token}@localhost:3000/core/meta.git");
|
||||||
|
let out = Command::new("git")
|
||||||
|
.current_dir(dir)
|
||||||
|
.args(["push", "--force", &url, "HEAD:main"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.context("invoke git push core/meta")?;
|
||||||
|
if !out.status.success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"git push core/meta failed ({}): {}",
|
||||||
|
out.status,
|
||||||
|
String::from_utf8_lossy(&out.stderr).trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tracing::info!("forge: pushed meta to core/meta");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// POST `/api/v1/orgs` to create an org named `name`. Idempotent:
|
/// POST `/api/v1/orgs` to create an org named `name`. Idempotent:
|
||||||
/// HTTP 422 ("user already exists") is treated as success.
|
/// HTTP 422 ("user already exists") is treated as success.
|
||||||
async fn ensure_org(name: &str, admin_token: &str) -> Result<()> {
|
async fn ensure_org(name: &str, admin_token: &str) -> Result<()> {
|
||||||
|
|
@ -297,6 +377,12 @@ pub async fn ensure_all() {
|
||||||
tracing::warn!(%org, error = ?e, "forge: ensure_org failed");
|
tracing::warn!(%org, error = ?e, "forge: ensure_org failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Meta repo lives at core/meta — pushed from git_commit in
|
||||||
|
// meta.rs on every deploy/lock-update. Make sure it exists
|
||||||
|
// before the first push hits a 404.
|
||||||
|
if let Err(e) = ensure_repo("meta", token).await {
|
||||||
|
tracing::warn!(error = ?e, "forge: ensure_repo core/meta failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let Ok(containers) = crate::lifecycle::list().await else {
|
let Ok(containers) = crate::lifecycle::list().await else {
|
||||||
tracing::warn!("forge: nixos-container list failed; skipping user sweep");
|
tracing::warn!("forge: nixos-container list failed; skipping user sweep");
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,15 @@ async fn git_commit(dir: &Path, message: &str) -> Result<()> {
|
||||||
message,
|
message,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
|
// Best-effort mirror to the bundled forge. No-op when the forge
|
||||||
|
// isn't seeded (no core token on disk); push failures log a warn
|
||||||
|
// but don't bubble up — a missing mirror shouldn't fail an
|
||||||
|
// otherwise successful deploy.
|
||||||
|
if let Err(e) = crate::forge::push_meta(dir).await {
|
||||||
|
tracing::warn!(error = ?e, "forge: meta push after commit failed (non-fatal)");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn nix(dir: &Path, args: &[&str]) -> Result<()> {
|
async fn nix(dir: &Path, args: &[&str]) -> Result<()> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue