#!/usr/bin/env bash # Create emergency Git bundle backups from Gitea over a non-interactive Git remote. # This is a repository-history fallback only. It does not replace `gitea dump`, # does not back up Gitea DB rows, settings, issues, packages, secrets, or LFS. set -euo pipefail GITEA_BASE_URL="${GITEA_BASE_URL:-http://192.168.0.110:3001}" GIT_REMOTE_BASE="${GIT_REMOTE_BASE:-}" OUTPUT_ROOT="${OUTPUT_ROOT:-/home/ollama/backup/110/gitea/git-bundles}" TIMEOUT_LS_REMOTE_SECONDS="${TIMEOUT_LS_REMOTE_SECONDS:-60}" TIMEOUT_CLONE_SECONDS="${TIMEOUT_CLONE_SECONDS:-180}" TIMEOUT_BUNDLE_SECONDS="${TIMEOUT_BUNDLE_SECONDS:-180}" TIMEOUT_VERIFY_SECONDS="${TIMEOUT_VERIFY_SECONDS:-60}" STAMP="${STAMP:-$(date +%Y%m%d-%H%M%S)}" DRY_RUN=0 REPOS=() usage() { cat <<'USAGE' Usage: scripts/backup/gitea-repo-bundle-backup.sh [options] Options: --repo OWNER/NAME Repository to bundle. May be passed more than once. --output-root PATH Bundle root. Default: /home/ollama/backup/110/gitea/git-bundles --gitea-base URL Gitea base URL. Default: http://192.168.0.110:3001 --git-remote-base URL Git remote base, such as ssh://git@192.168.0.110:2222. When set, it is used instead of --gitea-base. --stamp STAMP Output directory stamp. Default: current local timestamp. --dry-run Print target URLs without cloning or writing bundles. -h, --help Show this help. This script uses GIT_TERMINAL_PROMPT=0 and never asks for, reads, or prints tokens or passwords. Private repositories without an existing non-interactive credential or SSH key fail closed in the manifest. USAGE } while [ "$#" -gt 0 ]; do case "$1" in --repo) shift REPOS+=("${1:?--repo requires OWNER/NAME}") ;; --output-root) shift OUTPUT_ROOT="${1:?--output-root requires PATH}" ;; --gitea-base) shift GITEA_BASE_URL="${1:?--gitea-base requires URL}" ;; --git-remote-base) shift GIT_REMOTE_BASE="${1:?--git-remote-base requires URL}" ;; --stamp) shift STAMP="${1:?--stamp requires value}" ;; --dry-run) DRY_RUN=1 ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 64 ;; esac shift done if [ "${#REPOS[@]}" -eq 0 ]; then REPOS=( "wooo/awoooi" "wooo/ewoooc" "wooo/2026FIFAWorldCup" "wooo/agent-bounty-protocol" "wooo/AwoooGo" "wooo/stockplatform-v2" "wooo/vibework" "wooo/momo-pro-system" "wooo/tsenyang-website" "wooo/clawbot-v5" "wooo/bitan-pharmacy" "wooo/vtuber" ) fi repo_remote_url() { repo="$1" if [ -n "$GIT_REMOTE_BASE" ]; then printf '%s/%s.git\n' "${GIT_REMOTE_BASE%/}" "$repo" return fi printf '%s/%s.git\n' "${GITEA_BASE_URL%/}" "$repo" } if [ "$DRY_RUN" -eq 1 ]; then for repo in "${REPOS[@]}"; do printf 'DRY_RUN repo=%s url=%s\n' "$repo" "$(repo_remote_url "$repo")" done exit 0 fi backup_root="${OUTPUT_ROOT%/}/${STAMP}" tmp_root="$(mktemp -d "${TMPDIR:-/tmp}/gitea-bundle.XXXXXX")" manifest="${backup_root}/manifest.tsv" mkdir -p "$backup_root" trap 'rm -rf "$tmp_root"' EXIT printf 'repo\tstatus\thead_count\tbundle\tchecksum\n' > "$manifest" for repo in "${REPOS[@]}"; do slug="${repo//\//__}" url="$(repo_remote_url "$repo")" refs_file="${backup_root}/${slug}.refs" err_file="${backup_root}/${slug}.err" bundle_file="${backup_root}/${slug}.bundle" checksum_file="${bundle_file}.sha256" repo_tmp="${tmp_root}/${slug}.git" if ! GIT_TERMINAL_PROMPT=0 timeout "$TIMEOUT_LS_REMOTE_SECONDS" \ git ls-remote --heads "$url" >"$refs_file" 2>"$err_file"; then printf '%s\tls_remote_failed\t0\t\t\n' "$repo" >> "$manifest" continue fi head_count="$(wc -l < "$refs_file" | tr -d ' ')" rm -rf "$repo_tmp" if ! GIT_TERMINAL_PROMPT=0 timeout "$TIMEOUT_CLONE_SECONDS" \ git clone --mirror --quiet "$url" "$repo_tmp" 2>>"$err_file"; then printf '%s\tclone_failed\t%s\t\t\n' "$repo" "$head_count" >> "$manifest" continue fi if ! timeout "$TIMEOUT_BUNDLE_SECONDS" \ git -C "$repo_tmp" bundle create "$bundle_file" --all >/dev/null 2>>"$err_file"; then printf '%s\tbundle_failed\t%s\t\t\n' "$repo" "$head_count" >> "$manifest" continue fi if ! timeout "$TIMEOUT_VERIFY_SECONDS" \ git -C "$repo_tmp" bundle verify "$bundle_file" >/dev/null 2>>"$err_file"; then printf '%s\tverify_failed\t%s\t%s\t\n' "$repo" "$head_count" "$bundle_file" >> "$manifest" continue fi (sha256sum "$bundle_file" 2>/dev/null || shasum -a 256 "$bundle_file") > "$checksum_file" rm -rf "$repo_tmp" printf '%s\tok\t%s\t%s\t%s\n' "$repo" "$head_count" "$bundle_file" "$checksum_file" >> "$manifest" done latest_link="${OUTPUT_ROOT%/}/latest" if [ ! -e "$latest_link" ] || [ -L "$latest_link" ]; then ln -sfn "$backup_root" "$latest_link" else echo "WARN latest path exists and is not a symlink: $latest_link" >&2 fi cat "$manifest"