Add Gitea bundle recovery script

This commit is contained in:
2026-05-26 19:50:50 +02:00
parent f77a69a0b2
commit 5936a4d9c1
6 changed files with 159 additions and 7 deletions
+9
View File
@@ -5,6 +5,7 @@ These scripts are intended to run on the Unraid host before a Borg backup starts
## Current script
- `pre-backup-dumps.sh`
- `gitea-bundle-mirror.sh`
## Output
@@ -12,7 +13,13 @@ Fresh dump artifacts are written to:
- `/mnt/user/backups/borg/dumps/latest`
Fresh Gitea repository bundles are written to:
- `/mnt/user/backups/git-bundles/gitea`
Borg UI should include `/local/borg-dumps` as a backup source.
The Gitea bundle target should also be part of the Borg scope, either through
the backups share or an explicit Borg source.
The dump set also includes `unraid-flash-config.tar.gz`, a host-generated
archive of `/boot/config` plus checksum and manifest. Treat this archive as
@@ -21,6 +28,8 @@ secret backup material.
## Notes
- The script is written for host execution where `docker` is available.
- `gitea-bundle-mirror.sh` additionally expects host access to the Gitea bare
repositories under `/mnt/user/services/gitea/git/repositories`.
- It does not assume Backrest.
- It keeps only the latest dump set because Borg itself provides history.
+137
View File
@@ -0,0 +1,137 @@
#!/bin/sh
set -eu
# Run this on the Unraid host. It creates verified git bundles for every bare
# Gitea repository so a Gitea outage does not make repo bootstrap depend on the
# Gitea application database.
SOURCE_ROOT="${SOURCE_ROOT:-/mnt/user/services/gitea/git/repositories}"
BUNDLE_ROOT="${BUNDLE_ROOT:-/mnt/user/backups/git-bundles/gitea}"
TMP_ROOT="${TMP_ROOT:-$BUNDLE_ROOT/.tmp}"
REPORT_PATH="${REPORT_PATH:-$BUNDLE_ROOT/latest-report.md}"
MANIFEST_PATH="${MANIFEST_PATH:-$BUNDLE_ROOT/manifest.tsv}"
RUN_ID="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
log() {
printf '%s %s\n' "[gitea-bundles]" "$*"
}
warn() {
printf '%s %s\n' "[gitea-bundles][warn]" "$*" >&2
}
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
warn "Required command missing: $1"
exit 1
fi
}
bundle_target_for_repo() {
repo="$1"
rel="${repo#$SOURCE_ROOT/}"
rel="${rel%.git}.bundle"
printf '%s/%s\n' "$BUNDLE_ROOT" "$rel"
}
cleanup() {
rm -rf "$TMP_ROOT/run.$$"
}
trap cleanup EXIT
main() {
need_cmd git
need_cmd find
need_cmd sha256sum
if [ ! -d "$SOURCE_ROOT" ]; then
warn "Source root missing: $SOURCE_ROOT"
exit 1
fi
run_tmp="$TMP_ROOT/run.$$"
mkdir -p "$run_tmp" "$(dirname "$REPORT_PATH")" "$(dirname "$MANIFEST_PATH")"
manifest_tmp="$run_tmp/manifest.tsv"
report_tmp="$run_tmp/latest-report.md"
: > "$manifest_tmp"
total=0
bundled=0
skipped=0
failed=0
details="$run_tmp/details.txt"
: > "$details"
find "$SOURCE_ROOT" -type d -name '*.git' | sort | while IFS= read -r repo; do
total=$((total + 1))
if [ "$(git -C "$repo" rev-parse --is-bare-repository 2>/dev/null || true)" != "true" ]; then
skipped=$((skipped + 1))
printf 'SKIP\t%s\tnot a bare repository\n' "$repo" >> "$details"
continue
fi
target="$(bundle_target_for_repo "$repo")"
target_dir="$(dirname "$target")"
tmp="$run_tmp/$(basename "$target").tmp"
mkdir -p "$target_dir"
rel="${repo#$SOURCE_ROOT/}"
log "Bundling $rel"
if git -C "$repo" bundle create "$tmp" --all >/dev/null 2>&1 &&
git -C "$repo" bundle verify "$tmp" >/dev/null 2>&1; then
chmod 600 "$tmp"
mv "$tmp" "$target"
(
cd "$target_dir"
sha256sum "$(basename "$target")" > "$(basename "$target").sha256.tmp"
)
chmod 600 "$target.sha256.tmp"
mv "$target.sha256.tmp" "$target.sha256"
size_bytes="$(wc -c < "$target" | tr -d ' ')"
printf '%s\t%s\t%s\t%s\n' "$RUN_ID" "$rel" "${target#$BUNDLE_ROOT/}" "$size_bytes" >> "$manifest_tmp"
printf 'OK\t%s\t%s bytes\n' "$rel" "$size_bytes" >> "$details"
bundled=$((bundled + 1))
else
failed=$((failed + 1))
rm -f "$tmp"
printf 'FAIL\t%s\tbundle create or verify failed\n' "$rel" >> "$details"
fi
printf '%s\t%s\t%s\t%s\n' "$total" "$bundled" "$skipped" "$failed" > "$run_tmp/counts"
done
if [ -f "$run_tmp/counts" ]; then
IFS="$(printf '\t')" read -r total bundled skipped failed < "$run_tmp/counts"
fi
{
printf '# Gitea Bundle Mirror Report\n\n'
printf 'Timestamp: %s\n' "$RUN_ID"
printf 'Source: `%s`\n' "$SOURCE_ROOT"
printf 'Target: `%s`\n' "$BUNDLE_ROOT"
printf 'Total repositories: %s\n' "$total"
printf 'Bundled: %s\n' "$bundled"
printf 'Skipped: %s\n' "$skipped"
printf 'Failed: %s\n\n' "$failed"
printf '## Details\n\n'
if [ -s "$details" ]; then
while IFS="$(printf '\t')" read -r status name message; do
printf -- '- `%s` %s: %s\n' "$status" "$name" "$message"
done < "$details"
else
printf -- '- No repositories found.\n'
fi
} > "$report_tmp"
chmod 600 "$report_tmp" "$manifest_tmp"
mv "$report_tmp" "$REPORT_PATH"
mv "$manifest_tmp" "$MANIFEST_PATH"
log "Report written to $REPORT_PATH"
[ "$failed" -eq 0 ]
}
main "$@"