Script uses Gitea API to set archived=true on all org repos after Forgejo cutover (RAIL-HO-WP-0005). Ran 2026-07-08: 79/79 archived on coulombcore.
112 lines
No EOL
3.1 KiB
Bash
Executable file
112 lines
No EOL
3.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Archive all coulomb org repos on coulombcore Gitea (read-only mirror policy).
|
|
# Per RAIL-HO-WP-0005 / forgejo-tier3 playbook org-wide operator action.
|
|
set -euo pipefail
|
|
|
|
CONF="${GITEA_CONF:-$HOME/.railiance_gitea.conf}"
|
|
API="${GITEA_API:-https://gitea.coulomb.social}"
|
|
ORG="${GITEA_ORG:-coulomb}"
|
|
DRY_RUN="${DRY_RUN:-0}"
|
|
LIMIT="${PAGE_LIMIT:-50}"
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: gitea-set-readonly.sh [repo ...]
|
|
|
|
With no args: archive every repo in org coulomb (idempotent).
|
|
With slugs: archive only those coulomb/<slug> repos.
|
|
|
|
Environment:
|
|
GITEA_CONF Token file (default: ~/.railiance_gitea.conf)
|
|
GITEA_API HTTPS API base (default: https://gitea.coulomb.social)
|
|
DRY_RUN=1 Print actions only
|
|
USAGE
|
|
}
|
|
|
|
if [[ -f "$CONF" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$CONF"
|
|
fi
|
|
: "${GITEA_TOKEN:?Set GITEA_TOKEN or provide $CONF}"
|
|
|
|
auth=(-H "Authorization: token ${GITEA_TOKEN}")
|
|
|
|
fetch_all_repos() {
|
|
local page=1
|
|
local out=/tmp/gitea-readonly-repos.jsonl
|
|
: >"$out"
|
|
while true; do
|
|
local http body
|
|
body=$(mktemp)
|
|
http=$(curl -sS --max-time 30 -o "$body" -w '%{http_code}' \
|
|
"${auth[@]}" "${API}/api/v1/orgs/${ORG}/repos?limit=${LIMIT}&page=${page}")
|
|
if [[ "$http" != "200" ]]; then
|
|
echo "FAIL list page=${page} http=${http}" >&2
|
|
cat "$body" >&2
|
|
exit 1
|
|
fi
|
|
local count
|
|
count=$(python3 -c "import json; print(len(json.load(open('$body'))))")
|
|
python3 -c "import json; [print(r['name']) for r in json.load(open('$body'))]" >>"$out"
|
|
rm -f "$body"
|
|
[[ "$count" -lt "$LIMIT" ]] && break
|
|
page=$((page + 1))
|
|
done
|
|
sort -u "$out"
|
|
}
|
|
|
|
archive_repo() {
|
|
local name="$1"
|
|
local http archived
|
|
http=$(curl -sS --max-time 30 -o /tmp/gitea-get.json -w '%{http_code}' \
|
|
"${auth[@]}" "${API}/api/v1/repos/${ORG}/${name}")
|
|
if [[ "$http" == "404" ]]; then
|
|
echo "SKIP ${name} (not found)"
|
|
return 0
|
|
fi
|
|
if [[ "$http" != "200" ]]; then
|
|
echo "FAIL ${name} GET http=${http}" >&2
|
|
return 1
|
|
fi
|
|
archived=$(python3 -c "import json; print(json.load(open('/tmp/gitea-get.json')).get('archived', False))")
|
|
if [[ "$archived" == "True" ]]; then
|
|
echo "OK ${name} (already archived)"
|
|
return 0
|
|
fi
|
|
if [[ "$DRY_RUN" == "1" ]]; then
|
|
echo "DRY ${name} -> archived"
|
|
return 0
|
|
fi
|
|
http=$(curl -sS --max-time 30 -o /tmp/gitea-patch.json -w '%{http_code}' \
|
|
-X PATCH "${auth[@]}" -H "Content-Type: application/json" \
|
|
-d '{"archived":true}' "${API}/api/v1/repos/${ORG}/${name}")
|
|
if [[ "$http" == "200" ]]; then
|
|
echo "ARCH ${name}"
|
|
return 0
|
|
fi
|
|
echo "FAIL ${name} PATCH http=${http}" >&2
|
|
cat /tmp/gitea-patch.json >&2
|
|
return 1
|
|
}
|
|
|
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
mapfile -t TARGETS < <(printf '%s\n' "$@")
|
|
else
|
|
mapfile -t TARGETS < <(fetch_all_repos)
|
|
fi
|
|
|
|
echo "=== Gitea read-only: archive ${#TARGETS[@]} repo(s) in ${ORG} ==="
|
|
echo "API: ${API}"
|
|
|
|
failed=0
|
|
for name in "${TARGETS[@]}"; do
|
|
archive_repo "$name" || failed=$((failed + 1))
|
|
done
|
|
|
|
echo "=== Done (failed=${failed}) ==="
|
|
[[ "$failed" -eq 0 ]] |