#!/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. # # Bundles and their sha256 sidecars are written 0644 on purpose, so the # Nearline-Pull-Workflow (docs/H_DRIVE_NEARLINE_PULL.md) kann sie ueber den # SMB-Read-Share holen. Bundle-Inhalt = Git-Historie ohne Secrets (durch # .gitignore abgedeckt) und nicht sensibler als die uebrigen Dumps unter # /mnt/user/backups/borg/dumps/latest/, die ebenfalls 0644 sind. SOURCE_ROOT="${SOURCE_ROOT:-/mnt/user/services/gitea/data/git/repositories}" BUNDLE_ROOT="${BUNDLE_ROOT:-/mnt/user/backups/git-bundles/gitea}" TMP_ROOT="${TMP_ROOT:-/mnt/cache/tmp/gitea-bundle-mirror}" 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" repo_list="$run_tmp/repos.txt" find "$SOURCE_ROOT" -type d -name '*.git' | sort > "$repo_list" while IFS= read -r repo; do total=$((total + 1)) if [ "$(git --git-dir="$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" printf '%s\t%s\t%s\t%s\n' "$total" "$bundled" "$skipped" "$failed" > "$run_tmp/counts" continue fi target="$(bundle_target_for_repo "$repo")" target_dir="$(dirname "$target")" tmp="$run_tmp/$(basename "$target").tmp" target_tmp="$target_dir/.$(basename "$target").tmp" mkdir -p "$target_dir" rel="${repo#$SOURCE_ROOT/}" log "Bundling $rel" if git --git-dir="$repo" bundle create "$tmp" --all >/dev/null 2>&1 && git --git-dir="$repo" bundle verify "$tmp" >/dev/null 2>&1; then chmod 644 "$tmp" rm -f "$target_tmp" cp "$tmp" "$target_tmp" chmod 644 "$target_tmp" mv "$target_tmp" "$target" rm -f "$tmp" git --git-dir="$repo" bundle verify "$target" >/dev/null 2>&1 ( cd "$target_dir" sha256sum "$(basename "$target")" > "$(basename "$target").sha256.tmp" ) chmod 644 "$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 < "$repo_list" 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 644 "$report_tmp" "$manifest_tmp" mv "$report_tmp" "$REPORT_PATH" mv "$manifest_tmp" "$MANIFEST_PATH" log "Report written to $REPORT_PATH" [ "$failed" -eq 0 ] } main "$@"