0ae44bd797
node-exporter runs as nobody:65534 inside its container and was hitting node_textfile_scrape_error 1 on homelab.prom, because the file was 0600 root:root (mktemp default). Set it to 0644 right before the atomic mv. Bundle inhaltsidentisch zum Git-Repo, ohne Secrets (.gitignore-abgedeckt) und nicht sensibler als die uebrigen /mnt/user/backups/borg/dumps/latest/*.dump-Files, die ebenfalls 0644 sind. So funktioniert auch der Nearline-Pull-Workflow ueber SMB (docs/H_DRIVE_NEARLINE_PULL.md). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
154 lines
4.5 KiB
Bash
154 lines
4.5 KiB
Bash
#!/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 "$@"
|