Files
awoooi/scripts/backup/gitea-repo-bundle-backup.sh
ogt 48ed9c2381
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m51s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
fix(backup): support ssh gitea bundle remotes
2026-07-09 18:38:53 +08:00

167 lines
4.9 KiB
Bash
Executable File

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