Add Gitea bundle recovery script
This commit is contained in:
@@ -16,7 +16,7 @@ Status: Arbeitsliste fuer die Umsetzung. Authelia-2FA/OIDC bleibt bewusst spaet,
|
||||
Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
|
||||
|
||||
1. USV-Entscheidung treffen: aktuell ist keine funktionierende USV-Abschaltung nachgewiesen.
|
||||
2. Gitea-Bundle-/Mirror-Mechanik und Borg-Passphrase-Offsite-Sicherung entscheiden.
|
||||
2. Borg-Passphrase-Offsite-Sicherung und USV-Entscheidung mit Operator treffen.
|
||||
3. Authelia 2FA/OIDC weiterhin nicht anfassen; das bleibt bewusst der letzte Block.
|
||||
|
||||
## Sprint 0 - Inventar und Baseline
|
||||
@@ -47,7 +47,7 @@ Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
|
||||
|---|---|---|
|
||||
| offen | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei wird als `docs/STORAGE_LAYOUT.md` Active gefuehrt |
|
||||
| offen | Disk- und Share-TBDs eintragen | Modelle, Groessen, Seriennummern, Filesysteme und Cache-Settings sind dokumentiert |
|
||||
| offen | Gitea-Repo-Mirror-Mechanik definieren | Mirror fuer `/mnt/user/services/gitea/git/repositories/` mit Frequenz <= 6 h ist spezifiziert |
|
||||
| erledigt (Skript) | Gitea-Repo-Mirror-Mechanik definieren | `ops/borg-ui/scripts/gitea-bundle-mirror.sh` erzeugt verifizierte Bundles unter `/mnt/user/backups/git-bundles/gitea`; Host-Schedule/Trockenlauf bleibt offen |
|
||||
| offen | Komodo-Bootstrap-Pfad beschreiben | Kaltstart ohne laufendes Komodo ist dokumentiert |
|
||||
| offen | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium stehen fest |
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
|
||||
## Historische Meilensteine
|
||||
|
||||
### 2026-05-26 - Gitea-Bundle-Mechanik definiert
|
||||
|
||||
- `ops/borg-ui/scripts/gitea-bundle-mirror.sh` ergaenzt: erstellt verifizierte `git bundle`-Artefakte fuer alle bare Gitea-Repositories, schreibt Checksums und einen Markdown-Report.
|
||||
- Zielpfad ist `/mnt/user/backups/git-bundles/gitea`; dieser Pfad muss in den Borg/off-site Scope aufgenommen und hostseitig geplant werden.
|
||||
- `docs/SERVICES_RECOVERY.md` und `docs/RESTORE_MATRIX.md` dokumentieren Bundles jetzt als zweite Repo-Bootstrap-Schicht neben dem GitHub-Mirror.
|
||||
|
||||
### 2026-05-26 - Audit-Baseline-Tag gesetzt
|
||||
|
||||
- Der Stand nach Hardware-/Capacity-Baseline, Policy-Triage und Recovery-Doku wurde als `audit-2026-05-25-baseline` markiert und nach Gitea gepusht.
|
||||
|
||||
@@ -33,7 +33,7 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
| PostgreSQL 17 | Share + Dumps | `/mnt/user/appdata/postgresql17` | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt` | `backend_net` | DB startet, Ziel-Datenbanken vorhanden |
|
||||
| Redis | Share / Host | `/mnt/user/appdata/redis` | keine | `redis_password.txt` | `backend_net` | Redis startet, Apps verbinden sich |
|
||||
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 17, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut |
|
||||
| Gitea | GitHub-Mirror fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data` | `gitea.sqlite.dump` | `borg_repo_passphrase.txt` fuer Restore-Tests; GitHub-Push-Mirror-PAT liegt nur in Gitea-Mirror-Settings | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert; GitHub-Push-Mirror synchronisiert ohne `last_error`; Mini-Restore nach `/mnt/user/backups/restore-lab/gitea` am 2026-05-07 erfolgreich validiert |
|
||||
| Gitea | GitHub-Mirror + Gitea-Bundles fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data`, `/mnt/user/backups/git-bundles/gitea` | `gitea.sqlite.dump`, Bundle-Report `latest-report.md` | `borg_repo_passphrase.txt` fuer Restore-Tests; GitHub-Push-Mirror-PAT liegt nur in Gitea-Mirror-Settings | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert; Bundle laesst sich klonen und `git fsck` ist sauber; GitHub-Push-Mirror synchronisiert ohne `last_error`; Mini-Restore nach `/mnt/user/backups/restore-lab/gitea` am 2026-05-07 erfolgreich validiert |
|
||||
| Komodo | Borg / Share | `/mnt/user/appdata/komodo/core`, `/mnt/user/appdata/komodo/periphery`, `/mnt/user/services/stacks` | `komodo-mongo.archive.gz` falls verifiziert | `komodo_mongo_password.txt`, `KOMODO_*` Stack ENV | Traefik, Mongo, Gitea | UI erreichbar, Periphery verbunden |
|
||||
| GitOps Host Automation | Borg / Git | `/mnt/user/services/homelab-infra`, `/mnt/user/services/posture-check` | keine eigene DB | keine | Gitea, Komodo, Unraid User Scripts | `posture-check` laeuft vom Host-Pfad und liefert `warning_count: 0` im bekannten Uebergangszustand |
|
||||
| Vaultwarden | Borg + Dump | `/mnt/user/appdata/vaultwarden` | `vaultwarden.sqlite.dump` | `vaultwarden_admin_token.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | Traefik | Login-Seite erreichbar, Tresor-Daten sichtbar; Mini-Restore nach `/mnt/user/backups/restore-lab/vaultwarden` am 2026-05-07 erfolgreich validiert |
|
||||
|
||||
@@ -31,9 +31,9 @@ Optionen:
|
||||
|
||||
Empfohlener Start:
|
||||
|
||||
1. `git bundle`-Job fuer alle Gitea-Repositories definieren.
|
||||
2. Ziel auf zweitem physischen Medium oder separatem Off-site-Ziel ablegen.
|
||||
3. Job alle 6 Stunden ausfuehren.
|
||||
1. `ops/borg-ui/scripts/gitea-bundle-mirror.sh` auf dem Host ausfuehren.
|
||||
2. Ziel `/mnt/user/backups/git-bundles/gitea` in Borg/off-site Scope aufnehmen.
|
||||
3. Job alle 6 Stunden oder mindestens vor Borg ausfuehren.
|
||||
4. Stichprobe: ein Bundle in Wegwerfpfad klonen.
|
||||
|
||||
Erfolgskriterium:
|
||||
@@ -94,7 +94,7 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge
|
||||
|
||||
| Status | Aufgabe |
|
||||
|---|---|
|
||||
| offen | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
|
||||
| erledigt (Skript) | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
|
||||
| erledigt | Komodo-Bootstrap-Quelle finalisieren |
|
||||
| offen | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
|
||||
| erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user