#!/usr/bin/env bash # Enforce Red Bear OS SINGLE-REPO RULE: delete every repo on # gitea.redbearos.org/vasilito/ except RedBear-OS and hiperiso. # # Token is read at runtime from $REDBEAR_GITEA_TOKEN (or ~/.netrc or # git credential helper). It is NEVER written to disk, logged, or echoed. # See local/AGENTS.md § Token Policy. # # Usage: # ./local/scripts/delete-per-component-repos.sh # interactive # ./local/scripts/delete-per-component-repos.sh --dry-run # list only # ./local/scripts/delete-per-component-repos.sh --yes # no prompts # ./local/scripts/delete-per-component-repos.sh --only redbear-os-base,redox-drm # ./local/scripts/delete-per-component-repos.sh -h # this help set -euo pipefail GITEA_HOST="${GITEA_HOST:-https://gitea.redbearos.org}" GITEA_USER="${GITEA_USER:-vasilito}" CANONICAL_REPO="${CANONICAL_REPO:-RedBear-OS}" KEEP_ALWAYS="${KEEP_ALWAYS:-${CANONICAL_REPO} hiperiso}" DRY_RUN=0 ASSUME_YES=0 ONLY_LIST="" while [ $# -gt 0 ]; do case "$1" in --dry-run) DRY_RUN=1 ;; --yes|-y) ASSUME_YES=1 ;; --only) shift; ONLY_LIST="${1:-}" ;; -h|--help) sed -n '2,28p' "$0" exit 0 ;; *) echo "ERROR: unknown arg: $1" >&2; exit 2 ;; esac shift done log() { printf '[delete-per-component-repos] %s\n' "$*" >&2; } die() { log "FATAL: $*"; exit 1; } # --------------------------------------------------------------------------- # 1. Locate the token — env var, .netrc, or git credential helper (in order). # Never echo it, never log it, never write it to disk. # --------------------------------------------------------------------------- resolve_token() { if [ -n "${REDBEAR_GITEA_TOKEN:-}" ]; then printf '%s' "$REDBEAR_GITEA_TOKEN" return 0 fi if command -v git >/dev/null 2>&1; then local helper_token helper_token="$(git credential fill </dev/null | sed -n 's/^password=//p' | head -n1 protocol=https host=gitea.redbearos.org username=${GITEA_USER} EOF )" if [ -n "$helper_token" ]; then printf '%s' "$helper_token" return 0 fi fi if [ -r "$HOME/.netrc" ]; then local netrc_token netrc_token="$(awk -v host="gitea.redbearos.org" -v user="$GITEA_USER" ' $1=="machine" && $2==host { in_block=1; next } in_block && $1=="login" && $2==user { want=1; next } in_block && want && $1=="password" { print $2; exit } in_block && $1=="machine" { in_block=0 } ' "$HOME/.netrc")" if [ -n "$netrc_token" ]; then printf '%s' "$netrc_token" return 0 fi fi return 1 } log "Resolving Gitea token (env → git helper → ~/.netrc) ..." TOKEN="$(resolve_token || true)" [ -n "$TOKEN" ] || die "no token found. Set REDBEAR_GITEA_TOKEN or configure ~/.netrc / git credential helper." # --------------------------------------------------------------------------- # 2. List every repo visible to the user via the Gitea API. # --------------------------------------------------------------------------- api_get() { curl -fsS -H "Authorization: token $TOKEN" \ -H "Accept: application/json" \ "$GITEA_HOST/api/v1$1" } log "Fetching repo list from ${GITEA_HOST}/api/v1/users/${GITEA_USER}/repos ..." REPOS_JSON="$(api_get "/users/${GITEA_USER}/repos?limit=200")" || die "Gitea API call failed." REPOS_ALL="$(printf '%s' "$REPOS_JSON" | jq -r '.[].name')" [ -n "$REPOS_ALL" ] || die "no repos returned — check token permissions." # --------------------------------------------------------------------------- # 3. Filter the deletion candidates. # --------------------------------------------------------------------------- CANDIDATES="" while IFS= read -r repo; do [ -n "$repo" ] || continue case " $KEEP_ALWAYS " in *" $repo "*) continue ;; # canonical + user-named keepers esac if [ -n "$ONLY_LIST" ]; then # comma-separated whitelist case ",$ONLY_LIST," in *",$repo,"*) ;; # match *) continue ;; esac fi CANDIDATES="${CANDIDATES}${repo}"$'\n' done <<< "$REPOS_ALL" CANDIDATES="$(printf '%s' "$CANDIDATES" | sed '/^$/d')" if [ -z "$CANDIDATES" ]; then log "No repos match deletion criteria. Nothing to do." exit 0 fi log "Repos flagged for deletion:" while IFS= read -r r; do [ -n "$r" ] || continue log " - $r" done <<< "$CANDIDATES" if [ "$DRY_RUN" -eq 1 ]; then log "Dry run — no deletions performed." exit 0 fi # --------------------------------------------------------------------------- # 4. Confirm with the operator (unless --yes). # --------------------------------------------------------------------------- if [ "$ASSUME_YES" -ne 1 ]; then log "" log "WARNING: This DELETES repositories from ${GITEA_HOST}." log "Make sure each repo above has been migrated as a submodule branch" log "inside ${CANONICAL_REPO} first (see redirect-to-submodules.sh)." log "The deletion is irreversible." log "" read -r -p "Type 'delete' to confirm: " confirm if [ "$confirm" != "delete" ]; then log "Aborted." exit 1 fi fi # --------------------------------------------------------------------------- # 5. Delete each candidate via DELETE /repos/{owner}/{repo}. # --------------------------------------------------------------------------- fail_count=0 ok_count=0 while IFS= read -r repo; do [ -n "$repo" ] || continue log "Deleting ${GITEA_USER}/${repo} ..." http_code="$(curl -sS -o /dev/null -w '%{http_code}' \ -X DELETE \ -H "Authorization: token $TOKEN" \ -H "Accept: application/json" \ "${GITEA_HOST}/api/v1/repos/${GITEA_USER}/${repo}")" case "$http_code" in 2*) log " ✓ deleted ($http_code)"; ok_count=$((ok_count+1)) ;; 403) log " ✗ FORBIDDEN — token lacks delete permission on ${repo}"; fail_count=$((fail_count+1)) ;; 404) log " – already gone (404)"; ok_count=$((ok_count+1)) ;; *) log " ✗ FAILED ($http_code)"; fail_count=$((fail_count+1)) ;; esac done <<< "$CANDIDATES" log "" log "Summary: ${ok_count} deleted, ${fail_count} failed." [ "$fail_count" -eq 0 ] || exit 3 exit 0