#!/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/ 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 ]]