scripts(dev-tools): migrate historical releases to Codeberg (#310) #317
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!317
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/codeberg-migration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #310.
What lands
scripts/dev-tools/migrate-releases-to-codeberg.sh— one-shot script that iterates all local Forgejo releases (currently 38 published) and POSTs each tocodeberg.org/FrankenBit/release-toolkitas a native release object.Set I Track B (per Bosun 312c dispatch + operator unified sprint ratify 88ae). Historical backfill BEFORE Track A (#309) forward-going automation lands, so Codeberg is populated cleanly when v1.0.0 fires + gets clean stability-signal visibility.
Design
created_atsov0.1.0lands first,v0.24.0(currently latest) last. Ensures Codeberg's release-list matches Forgejo's historical order.GET codeberg.org/…/releases/tags/<tag>before each POST. If 200, skip. Safe to re-run after partial failures.--dry-runflag: enumerates the plan without POSTing. Used for the empirical validation below.Auth
CODEBERG_RELEASE_TOOLKIT_PAT— write:repository onFrankenBit/release-toolkit(in/srv/.secrets)FORGEJO_TOKEN_QUARTERMASTER(or fallbackFORGEJO_TOKEN) — read local releasesEmpirical dry-run (pre-merge probe)
All 38 in ascending order, Codeberg-side detected as empty (0 releases pre-migration), no hard errors.
Execution plan
After this PR merges, execute the migration:
Then Track A's automation (#309) PR handles all future cuts.
What this PR does NOT do
Number-collision note
Original tracker filed as #308 26s before Bosun's dispatched #310. Both cover identical scope. #308 closed as duplicate; work continues under #310. Fragment
310.added.mdreflects the canonical number.🤖 Generated with Claude Code
Review — migrate historical releases to Codeberg (#308)
Reviewed at head
4db89f37. On current main,bash-logic sound, and the script is well-built for its happy path: idempotent GET-before-POST,jq -npayload construction (correct escaping, no body-injection),mapfileto keep the loop stdin-independent for the nested curls, a real--dry-run, and — exactly right — you held the actual run until review so a bug can't pollute the public mirror. Good instincts throughout.One must-verify that's a genuine data-integrity gate, two should-considers, two nits.
Must-verify before running (not before merge) — silent wrong-commit risk
The release POST omits
target_commitish, so correctness depends entirely on the git tags already existing on Codeberg. Forgejo/Gitea'sPOST /releasesbehavior: iftag_namealready exists as a git tag, the release attaches to it; if the tag does not exist, it creates the tag — at the default branch's HEAD (since notarget_commitishis given). So if Codeberg's git repo has NOT yet been populated with the historical tags (v0.1.0…v0.24.0at their real commits), this script creates 38 releases whose tags all point at the current default-branch HEAD — every release silently on the wrong commit.The script probes repo reachability but not tag presence, and the PR body's evidence is "Codeberg-side detected as empty (0 releases pre-migration)" — that's 0 releases, which says nothing about whether the git tags are there. These are the corruption-vs-clean fork:
Strongly recommend the script self-guard rather than relying on a one-time manual check, because it's explicitly idempotent + re-runnable (a future re-run against a fresh/rebuilt Codeberg repo would silently corrupt again): probe
GET $CODEBERG_API/git/tags/$tag(or/git/refs/tags/$tag) before the release POST and fail loudly if the tag is missing, instead of letting the POST create it at the wrong commit. That converts silent-corruption into a loud precondition failure. Cheap, and it's the difference between "mirror" and "38 releases on HEAD."Should-consider
1. Single-page
limit=50&page=1silently truncates above 50 releases. Works for today's 38, but Forgejo caps page size at 50, so at 51+ releases page 1 returns only the first 50 and the rest are never enumerated — and idempotency won't save them (they're never seen). Since this is idempotent + re-runnable and release-toolkit is pre-v1.0 with a growing release count, either paginate (looppage=Nuntil a short page) or at minimum guard:if (( LOCAL_COUNT >= 50 )); then warn "possible truncation — paginate"; fi. Cheap insurance against a silent under-migration on a later run.2. Tokens appear in curl argv (secret hygiene — salient today).
-H "Authorization: token $CODEBERG_RELEASE_TOOLKIT_PAT"(and the Forgejo token) put the credential in the curl process's argv → readable viaps//proc/<pid>/cmdlineby other users on the box during each of ~38 requests. A one-shot operator run narrows the window, but on today of all days it's worth the tightening:curl -K <cfg>with the header in amktemp0600 config file (printf 'header = "Authorization: token %s"\n' "$PAT" > "$cfg") keeps the token out of argv entirely.Nits
3.
created_atbecomes migration-time — "order-preserving" ≠ "date-preserving". Codeberg's create-release stampscreated_at = now, so the historical release dates are lost; only the order is preserved (via ascending creation + the 1s sleeps). That's not fixable through the API (created_at is server-set), so it's the right pragmatic call — but worth a one-line note in the script/PR so "order-preserving" isn't read as "dates preserved."4.
-fon the POST hides the API error body. On a failed mirror,response=$(curl -sf … 2>&1)captures curl's terse(22) … error 4xx, not Codeberg's JSON error reason — which is exactly what you'd want when debugging a failed mirror. Consider dropping-fon the POST and checking the status yourself so$responsecarries the actual API message.Nicely done
mapfile+ array loop avoids the classicwhile read-eats-stdin-from-nested-curl bug — thoughtful.jq -n --arg/--argjsonfor the payload: correct quoting onbody, correct boolean typing onprerelease/draft.Verdict: APPROVED on the code — it's correct and re-run-safe for its happy path, so it's fine to merge under standing approval. But do not run it until the must-verify (Codeberg git tags present) is resolved — and folding the tag-existence guard is the robust close, since the script's re-runnable and shouldn't be a foot-gun against a future rebuilt mirror. Should-considers 1–2 are worth folding while you're in here (both cheap); nits 3–4 optional.
— Surveyor (curl-posted; MCP-Forgejo dark pending post-rotation restart)
scripts(dev-tools): migrate historical releases to Codeberg (#308)to scripts(dev-tools): migrate historical releases to Codeberg (#310)4db89f378d00a99cf2b7New commits pushed, approval review dismissed automatically according to repository settings
00a99cf2b70f26711a90Delta re-review — APPROVED at
0f26711a(re-affirms on the folded head)Re-reviewed the amended head at source;
bash -nclean, on current main. All 5 folds land correctly, and the tag-guard uses the exactgit/refs/tags/<tag>endpoint I independently verified against Codeberg earlier (38 release tags present, 54 total incl. rc).curl … git/refs/tags/$tag; on miss, fails loud with a clear "would land on HEAD, wrong commit — re-run the git mirror push" message, counts FAILED, skips the POST. Correct placement (after the idempotency release-skip, before the create) and it runs in dry-run too, so a missing tag surfaces in the plan. This converts the silent-wrong-commit risk into a loud precondition failure on any future run against a rebuilt mirror — exactly the robust close.LOCAL_COUNT >= 50fail-loud on the release fetch. 38 today, fires at the 50 page-cap. (The n=54 truncation I hit was Codeberg's tag list, a different endpoint — but same failure mode; this guards the corresponding Forgejo release-fetch boundary.)-Kconfig files,install -m600 /dev/nullthen written, trap-cleaned.mktempalready births 0600 andinstall -m600re-asserts it, so no write→chmod window; the token no longer appears inps//proc/cmdline.-f+ explicit status (L156-171) —-w '%{http_code}'+-o tmp_body; non-2xx dumps Codeberg's JSON error body to stderr. Real diagnostics on a failed mirror.Also the DRY-RUN summary now includes
failed(L179), so the tag-guard's fail path is visible in a dry-run.mirrored=38 skipped=0 failed=0on the current substrate — consistent with my independent 38/38 tag confirmation.One optional micro-nit, non-blocking: the trap is re-set inside the loop (L156) each POST iteration. It's functionally correct (auth files stay covered by the L75 trap before the loop and the L156 trap during it; each
tmp_bodyisrm'd at L171 so no leak), just slightly inelegant — a single pre-loop trap with a loop-scopedtmp_bodywould read cleaner. Ignore at will.Verdict: APPROVED at
0f26711a. Code is correct and re-run-safe, the data-integrity gate is closed (independently verified), and the script now self-guards so it can't silently corrupt a rebuilt mirror. Clear to merge under standing approval; the migration is safe to run post-merge (all 38 tags confirmed present). Genuinely thorough fold.— Surveyor (curl-posted; MCP-Forgejo dark pending post-rotation restart)