#!/usr/bin/env bash set -euo pipefail # --- Config --- GITHUB_USER="andwati" GITHUB_TOKEN="${GITHUB_TOKEN:?Set GITHUB_TOKEN env var (PAT with repo read access, incl. private repos)}" GITEA_URL="${GITEA_URL:?Set GITEA_URL env var, e.g. https://gitea.andwati.com}" GITEA_TOKEN="${GITEA_TOKEN:?Set GITEA_TOKEN env var (Gitea access token, repo scope)}" GITEA_OWNER="${GITEA_OWNER:-andwati}" MIRROR_INTERVAL="${MIRROR_INTERVAL:-8h0m0s}" # --- Fetch all repos for the GitHub user (paginated), including private ones --- # Uses /user/repos (authenticated, sees private repos owned by the token's user) # rather than /users/{user}/repos (public-only). page=1 repos_json="[]" while :; do resp=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ "https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner") count=$(echo "$resp" | jq 'length') [ "$count" -eq 0 ] && break repos_json=$(jq -s '.[0] + .[1]' <(echo "$repos_json") <(echo "$resp")) page=$((page + 1)) done total=$(echo "$repos_json" | jq 'length') echo "Found ${total} repos on GitHub for ${GITHUB_USER}" while read -r entry; do name=$(echo "$entry" | jq -r '.name') private=$(echo "$entry" | jq -r '.private') echo "Processing: ${name} (private=${private})" # Check if it already exists in Gitea existing_resp=$(curl -s -w "\n%{http_code}" \ -H "Authorization: token ${GITEA_TOKEN}" \ "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${name}") existing_status=$(echo "$existing_resp" | tail -n1) existing_body=$(echo "$existing_resp" | sed '$d') if [ "$existing_status" == "200" ]; then gitea_private=$(echo "$existing_body" | jq -r '.private') if [ "$gitea_private" != "$private" ]; then echo " Already mirrored, but visibility differs (GitHub private=${private}, Gitea private=${gitea_private}). Syncing..." patch_code=$(curl -s -o /tmp/gitea_patch_resp.json -w "%{http_code}" \ -X PATCH "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${name}" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"private\": ${private}}") if [ "$patch_code" == "200" ]; then echo " Visibility updated to private=${private}." else echo " Failed to update visibility (HTTP ${patch_code}):" cat /tmp/gitea_patch_resp.json fi else echo " Already mirrored, visibility already matches (private=${gitea_private}). Skipping." fi continue fi payload=$(jq -n \ --arg clone_addr "https://github.com/${GITHUB_USER}/${name}.git" \ --arg repo_name "$name" \ --arg repo_owner "$GITEA_OWNER" \ --arg mirror_interval "$MIRROR_INTERVAL" \ --argjson private "$private" \ --arg auth_token "$GITHUB_TOKEN" \ '{ clone_addr: $clone_addr, repo_name: $repo_name, repo_owner: $repo_owner, mirror: true, mirror_interval: $mirror_interval, private: $private, service: "github", auth_token: $auth_token }') http_code=$(curl -s -o /tmp/gitea_migrate_resp.json -w "%{http_code}" \ -X POST "${GITEA_URL}/api/v1/repos/migrate" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ --data-raw "$payload") if [ "$http_code" == "201" ]; then echo " Created mirror (private=${private})." else echo " Failed (HTTP ${http_code}):" cat /tmp/gitea_migrate_resp.json fi sleep 1 # be gentle on both APIs done < <(echo "$repos_json" | jq -c '.[] | {name, private}') echo "Done."