rework(#2072): seed mirrors in c0re startup, not a host-side oneshot

Per mara: the mirror seeding belongs in hive-c0re's forge provisioning
sweep, where the core admin token + org-ensure already live — not a parallel
host-side nix oneshot.

- forge.rs: ensure_mirrors() reads HYPERHIVE_FORGE_MIRRORS (JSON list of
  {upstream,dest}), ensures each dest org (reuse ensure_org) + creates the
  pull-mirror via the migrate API (reuse forge_http, serde_json::json! body,
  409/existing = success). Called in ensure_all() right after the SEEDED_ORGS
  loop (token in scope, warn-and-continue like the other ensure_* steps).
- hive-forge.nix: forward effectiveMirrors to c0re via
  systemd.services.hive-c0re.environment.HYPERHIVE_FORGE_MIRRORS; drop the
  forgejo-seed-mirrors.service + its script + the host-side core-token read.
  Keep the forge.mirrors option, DEFAULT_ACTIONS_URL=self (CI-gated), and the
  dest-shape / no-c0re-namespace-collision assertions.

Verified locally: nix parse + treefmt (incl rustfmt) clean; serde/serde_json
patterns mirror dashboard.rs. cargo build runs in CI (no cc in my container).
This commit is contained in:
atlas 2026-06-29 00:10:15 +02:00 committed by mara
commit 53df2c9598
2 changed files with 100 additions and 101 deletions

View file

@ -53,86 +53,6 @@ let
++ lib.optional (
ciEnabled && !(lib.any (m: m.dest == actionCheckoutMirror.dest) cfg.mirrors)
) actionCheckoutMirror;
# Host-side core admin token hive-c0re mints after provisioning the forge
# admin (same file hive-ci-prefetch reads). Root-only; never enters a
# container — so the mirror seed runs host-side, exactly like
# hive-ci-prefetch, rather than minting a second token in-container.
coreTokenPath = "/var/lib/hyperhive/forge-core-token";
# Idempotently create each `effectiveMirrors` entry as a real Forgejo
# pull-mirror via the migrate API. Host-side: only talks to the forge on
# loopback (forgejo itself does the upstream clone, so the upstream-DNS
# dependency lives in the container + is off the CI critical path).
# Modelled on hive-ci-prefetch's wait-for-core-token loop.
seedMirrorsScript = pkgs.writeShellScript "forgejo-seed-mirrors" ''
set -uo pipefail
FORGE_URL="http://127.0.0.1:${toString cfg.httpPort}"
CORE_TOKEN=""
for i in $(seq 1 60); do
if [ -f "${coreTokenPath}" ]; then CORE_TOKEN=$(cat "${coreTokenPath}"); break; fi
echo "forgejo-seed-mirrors: waiting for core token ($i/60)..." >&2
sleep 1
done
if [ -z "''${CORE_TOKEN:-}" ]; then
echo "forgejo-seed-mirrors: core token absent after 60s cannot seed mirrors" >&2
exit 1
fi
AUTH="Authorization: token $CORE_TOKEN"
rc=0
seed_one() {
upstream="$1"; owner="$2"; repo="$3"
# Ensure the dest org (idempotent: 201 created / 422 already exists).
org_body=$(${pkgs.jq}/bin/jq -nc --arg u "$owner" '{ username: $u }')
ohttp=$(${pkgs.curl}/bin/curl -s -o /dev/null -w '%{http_code}' -X POST \
-H "$AUTH" -H 'Content-Type: application/json' \
"$FORGE_URL/api/v1/orgs" -d "$org_body" || echo 000)
case "$ohttp" in
201 | 422) ;;
*) echo "forgejo-seed-mirrors: ensure org '$owner' returned HTTP $ohttp" >&2 ;;
esac
# Skip if the repo already exists (the mirror persists across reboots
# in the non-ephemeral forge state, so this no-ops on every reboot
# after the first).
rhttp=$(${pkgs.curl}/bin/curl -s -o /dev/null -w '%{http_code}' \
-H "$AUTH" "$FORGE_URL/api/v1/repos/$owner/$repo" || echo 000)
if [ "$rhttp" = 200 ]; then
echo "forgejo-seed-mirrors: $owner/$repo already present skipping" >&2
return 0
fi
# Create the pull-mirror. service=git → generic git clone of
# clone_addr (no upstream API token needed); mirror=true → forgejo
# keeps it re-syncing on its mirror interval.
mig_body=$(${pkgs.jq}/bin/jq -nc \
--arg c "$upstream" --arg o "$owner" --arg r "$repo" \
'{ clone_addr: $c, repo_owner: $o, repo_name: $r, mirror: true, service: "git", private: false }')
resp=$(${pkgs.curl}/bin/curl -s -w $'\n%{http_code}' -X POST \
-H "$AUTH" -H 'Content-Type: application/json' \
"$FORGE_URL/api/v1/repos/migrate" \
-d "$mig_body" \
|| printf '\n000')
mhttp=$(printf '%s' "$resp" | tail -n1)
case "$mhttp" in
2*) echo "forgejo-seed-mirrors: created pull-mirror $owner/$repo from $upstream" >&2 ;;
*)
echo "forgejo-seed-mirrors: migrate $owner/$repo failed HTTP $mhttp: $(printf '%s' "$resp" | sed '$d')" >&2
rc=1
;;
esac
}
${lib.concatMapStringsSep "\n" (
m:
let
parts = lib.splitString "/" m.dest;
in
"seed_one ${lib.escapeShellArg m.upstream} ${lib.escapeShellArg (builtins.elemAt parts 0)} ${lib.escapeShellArg (builtins.elemAt parts 1)}"
) effectiveMirrors}
exit $rc
'';
in
{
# Private Forgejo in a `hive-forge` nixos-container, shared host
@ -591,26 +511,12 @@ in
];
};
# Seed the configured pull-mirrors once the forge + core token are up.
# Host-side (the core admin token never enters a container) and modelled
# on hive-ci-prefetch. partOf the forge container so it re-runs (and
# re-ensures, idempotently) on every forge (re)start. Only present when
# there's something to seed.
systemd.services.forgejo-seed-mirrors = lib.mkIf (effectiveMirrors != [ ]) {
description = "Seed Forgejo pull-mirrors (host-side)";
after = [
"hive-c0re.service"
"container@hive-forge.service"
];
wants = [ "container@hive-forge.service" ];
wantedBy = [ "multi-user.target" ];
partOf = [ "container@hive-forge.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = seedMirrorsScript;
SyslogIdentifier = "forgejo-seed-mirrors";
};
};
# Forward the declared pull-mirrors to hive-c0re, which seeds them in
# its forge provisioning sweep (`forge.rs::ensure_mirrors`, alongside
# the SEEDED_ORGS ensure). c0re already holds the core admin token and
# ensures the orgs there, so the seeding lives in one place rather than
# a parallel host-side unit. JSON-encoded list of { upstream, dest };
# `[]` when nothing to seed (c0re no-ops).
systemd.services.hive-c0re.environment.HYPERHIVE_FORGE_MIRRORS = builtins.toJSON effectiveMirrors;
};
}