From 9496fb060b2f1da120aee4be4b9eecac12eeea5c Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 3 Jul 2026 19:32:05 +0200 Subject: [PATCH] fix(forge): set mirror_interval so pull-mirrors sync periodically, not on-access Forgejo's default mirror behaviour syncs on every git access, which re-introduces external DNS latency at clone time. The hive-ci runner shares the host netns, so a host-resolver blip turns an otherwise local clone into a hard failure (data.forgejo.org DNS lookup that has nothing to do with the repo being cloned). Fix: - New MIRROR_INTERVAL const (8h0m0s) used in ensure_mirror_repo. - On creation: pass interval=MIRROR_INTERVAL in the migrate API body. - On existing mirror: PATCH mirror_interval on every startup so repos seeded before this change converge without manual intervention. A stale mirror (up to 8 hours old) is fine for CI; a broken clone from a transient DNS blip is not. The actions/checkout mirror is refreshed periodically rather than triggered by runner pulls. --- hive-c0re/src/forge.rs | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/hive-c0re/src/forge.rs b/hive-c0re/src/forge.rs index c16e32b9..6dfe32f9 100644 --- a/hive-c0re/src/forge.rs +++ b/hive-c0re/src/forge.rs @@ -977,20 +977,41 @@ async fn ensure_mirrors(admin_token: &str) { } } +/// Periodic sync interval for pull-mirrors. Forgejo syncs mirrors +/// on-access by default, which re-introduces external DNS latency on +/// every `git clone` (the hive-ci runner shares the host netns and is +/// therefore affected by host resolver blips). A fixed periodic interval +/// isolates CI from transient DNS failures — a stale mirror is +/// acceptable; a broken clone because of a momentary DNS blip is not. +const MIRROR_INTERVAL: &str = "8h0m0s"; + /// Create `owner/repo` as a pull-mirror of `upstream` via the migrate API. -/// Idempotent: a cheap existence check skips an already-seeded mirror (it -/// persists in the non-ephemeral forge state across reboots); a 409 (a race -/// between that check and the POST) is also treated as success. +/// Idempotent: if the repo already exists this function patches its +/// `mirror_interval` to ensure it matches (covers mirrors that were +/// created before the interval was introduced). A 409 on the migrate +/// POST (a race between the GET check and the POST) is also success. async fn ensure_mirror_repo( upstream: &str, owner: &str, repo: &str, admin_token: &str, ) -> Result<()> { - let get_url = format!("{FORGE_HTTP}/api/v1/repos/{owner}/{repo}"); - let (status, _) = forge_http(reqwest::Method::GET, &get_url, admin_token, "").await?; + let repo_url = format!("{FORGE_HTTP}/api/v1/repos/{owner}/{repo}"); + let (status, _) = forge_http(reqwest::Method::GET, &repo_url, admin_token, "").await?; if status.is_success() { - tracing::debug!(%owner, %repo, "forge: pull-mirror already present"); + // Mirror already present. Patch interval so mirrors seeded before + // this field was introduced (or with a different value) converge. + let patch_body = serde_json::json!({ "mirror_interval": MIRROR_INTERVAL }).to_string(); + let (patch_status, patch_text) = + forge_http(reqwest::Method::PATCH, &repo_url, admin_token, &patch_body).await?; + if patch_status.is_success() { + tracing::debug!(%owner, %repo, interval = MIRROR_INTERVAL, "forge: pull-mirror interval updated"); + } else { + tracing::warn!( + %owner, %repo, status = %patch_status, body = %patch_text, + "forge: failed to set mirror_interval on existing pull-mirror" + ); + } return Ok(()); } // serde_json::json! → the upstream URL is escaped safely (no string @@ -1000,6 +1021,9 @@ async fn ensure_mirror_repo( "repo_owner": owner, "repo_name": repo, "mirror": true, + // Periodic refresh instead of on-access sync — keeps CI isolated + // from external DNS failures at clone time. + "interval": MIRROR_INTERVAL, "service": "git", "private": false, }) @@ -1008,7 +1032,7 @@ async fn ensure_mirror_repo( let (status, text) = forge_http(reqwest::Method::POST, &url, admin_token, &body).await?; match status.as_u16() { 201 => { - tracing::info!(%owner, %repo, %upstream, "forge: created pull-mirror"); + tracing::info!(%owner, %repo, %upstream, interval = MIRROR_INTERVAL, "forge: created pull-mirror"); Ok(()) } // 409 = a race created it between our GET check and here (the GET