#!/usr/bin/env bash # Gitea 저장소 mirror 백업. .env의 GIT_* 및 GITEA_* 사용. # Gitea 메타 DB(giteadb 등)는 scripts/mr-backup.sh(MariaDB 전체 백업)에 포함됩니다. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" # shellcheck source=scripts/lib/load-env.sh source "$REPO_ROOT/scripts/lib/load-env.sh" # shellcheck source=scripts/lib/common.sh source "$REPO_ROOT/scripts/lib/common.sh" load_project_env "$REPO_ROOT/.env" : "${GIT_USER:?GIT_USER is required in .env}" : "${GIT_TOKEN:?GIT_TOKEN is required in .env}" GIT_BASE_URL="${GIT_BASE_URL:-https://git.ncue.net}" GITEA_API_URL="${GITEA_API_URL:-${GIT_BASE_URL%/}/api/v1}" BACKUP_DIR="${BACKUP_DIR:-$REPO_ROOT/backup}" BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-30}" GITEA_REPO_LIMIT="${GITEA_REPO_LIMIT:-100}" if ! command -v git >/dev/null 2>&1; then echo "gitea-backup: git not found." >&2 exit 1 fi if ! command -v python3 >/dev/null 2>&1; then echo "gitea-backup: python3 not found." >&2 exit 1 fi resolve_run_dir "gitea" mkdir -p "$BACKUP_DIR/latest" PERSISTENT_MIRROR_ROOT="${GITEA_MIRROR_DIR:-$BACKUP_DIR/_gitea_mirrors}" MIRROR_ROOT="$PERSISTENT_MIRROR_ROOT" mkdir -p "$MIRROR_ROOT" echo "[$(log_ts)] gitea-backup start → $RUN_DIR (mirrors=$MIRROR_ROOT, api=${GITEA_API_URL})" REPO_LIST_FILE="$RUN_DIR/00_repos.json" MANIFEST="$RUN_DIR/00_manifest.txt" mirror_result="$(python3 - "$GITEA_API_URL" "$GIT_TOKEN" "$GITEA_REPO_LIMIT" "$REPO_LIST_FILE" "$MIRROR_ROOT" "$GIT_USER" "$GIT_TOKEN" <<'PY' import json import os import subprocess import sys import urllib.error import urllib.request api_base, token, limit_str, repo_list_file, mirror_root, git_user, git_token = sys.argv[1:8] limit = int(limit_str) headers = {"Authorization": f"token {token}"} def fetch_json(url: str): req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=120) as resp: return json.load(resp) repos = [] page = 1 while True: url = f"{api_base.rstrip('/')}/user/repos?limit={limit}&page={page}" try: batch = fetch_json(url) except urllib.error.HTTPError as exc: print(f"API error {exc.code} for {url}", file=sys.stderr) raise SystemExit(1) from exc if not batch: break repos.extend(batch) if len(batch) < limit: break page += 1 with open(repo_list_file, "w", encoding="utf-8") as fh: json.dump(repos, fh, ensure_ascii=False, indent=2) ok = 0 fail = 0 manifest_lines = [] for repo in repos: full_name = repo["full_name"] clone_url = repo["clone_url"] safe_name = full_name.replace("/", "__") mirror_path = os.path.join(mirror_root, f"{safe_name}.git") auth_url = clone_url.replace("https://", f"https://{git_user}:{git_token}@", 1) env = os.environ.copy() env["GIT_TERMINAL_PROMPT"] = "0" if os.path.isdir(mirror_path): subprocess.run(["git", "-C", mirror_path, "remote", "set-url", "origin", auth_url], check=True) proc = subprocess.run(["git", "-C", mirror_path, "remote", "update", "--prune"], env=env) action = "updated" else: proc = subprocess.run(["git", "clone", "--mirror", auth_url, mirror_path], env=env) action = "cloned" if proc.returncode == 0: size_kb = max(1, sum( os.path.getsize(os.path.join(root, name)) for root, _, files in os.walk(mirror_path) for name in files ) // 1024) manifest_lines.append(f"{full_name} {action} {size_kb}KB") ok += 1 else: manifest_lines.append(f"{full_name} failed") fail += 1 print(json.dumps({"ok": ok, "fail": fail, "count": len(repos), "manifest": manifest_lines})) PY )" repo_count="$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['count'])" "$mirror_result")" mirror_ok="$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['ok'])" "$mirror_result")" mirror_fail="$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['fail'])" "$mirror_result")" { echo "# gitea-backup manifest $(log_ts)" echo "api=${GITEA_API_URL}" echo "user=${GIT_USER}" echo "repo_count=${repo_count}" python3 -c "import json,sys; [print(line) for line in json.loads(sys.argv[1])['manifest']]" "$mirror_result" } > "$MANIFEST" if [[ "$mirror_ok" -eq 0 ]]; then echo "gitea-backup: all repository mirrors failed." >&2 exit 1 fi if [[ "$mirror_fail" -gt 0 ]]; then echo "[$(log_ts)] warning: ${mirror_fail} mirror(s) failed, ${mirror_ok} succeeded." >&2 fi ln -sfn "$MIRROR_ROOT" "$RUN_DIR/mirrors" update_latest_link "gitea" if [[ -z "${BACKUP_RUN_DIR:-}" ]]; then prune_old_backups "$BACKUP_DIR" "$BACKUP_RETENTION_DAYS" fi echo "[$(log_ts)] gitea-backup done: ${mirror_ok} repository mirror(s), latest → $BACKUP_DIR/latest/gitea"