#!/usr/bin/env bash # Encrypted off-host backup for data-disk and total-loss recovery. set -Eeuo pipefail umask 077 CONFIG=${DISASTER_BACKUP_CONFIG:-/etc/mike-ai/disaster-backup.env} STATE=/var/lib/mike-ai-disaster-backup log() { printf '\n==> %s\n' "$*"; } die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; } [[ $EUID -eq 0 ]] || die "Bitte als root ausführen." [[ -r $CONFIG ]] || die "Konfiguration fehlt: $CONFIG" # shellcheck disable=SC1090 source "$CONFIG" [[ ${DISASTER_BACKUP_ENABLED:-false} == true ]] || die \ "Externes Backup ist noch nicht freigeschaltet (DISASTER_BACKUP_ENABLED=true)." [[ -n ${RESTIC_REPOSITORY:-} ]] || die "RESTIC_REPOSITORY fehlt." if [[ -n ${RESTIC_REQUIRE_MOUNT:-} ]]; then mountpoint -q "$RESTIC_REQUIRE_MOUNT" || die \ "Externes Backupziel ist nicht eingehängt: $RESTIC_REQUIRE_MOUNT" fi [[ -n ${RESTIC_PASSWORD_FILE:-} && -r $RESTIC_PASSWORD_FILE ]] || die \ "RESTIC_PASSWORD_FILE fehlt oder ist nicht lesbar." command -v restic >/dev/null || die "restic ist nicht installiert." command -v docker >/dev/null || die "Docker ist nicht installiert." exec 9>/run/lock/athena-disaster-backup.lock flock -n 9 || die "Ein Disaster-Backup läuft bereits." install -d -m 0700 "$STATE/latest" log "Konsistentes Docker-Schnellbackup erzeugen" docker inspect mike-ai-backup >/dev/null 2>&1 || die "mike-ai-backup fehlt." docker exec mike-ai-backup backup latest=$(readlink -f /data/docker-backups/athena-latest.tar.gz) [[ -s $latest ]] || die "Lokales Docker-Backup wurde nicht erzeugt." gzip -t "$latest" || die "Lokales Docker-Backup ist beschädigt." install -m 0600 "$latest" "$STATE/latest/docker-state.tar.gz" sha256sum "$STATE/latest/docker-state.tar.gz" >"$STATE/latest/docker-state.tar.gz.sha256" log "Wiederaufbau-Metadaten erfassen" { printf 'created_utc=%s\n' "$(date -u +%FT%TZ)" printf 'hostname=%s\n' "$(hostname)" printf 'source_commit=%s\n' "$(git -C /opt/mike-ai/stack rev-parse HEAD 2>/dev/null || printf unknown)" findmnt -rn -o SOURCE,UUID,FSTYPE,TARGET / /data 2>/dev/null || true } >"$STATE/latest/manifest.txt" find /data/models -type f -printf '%P\t%s\n' 2>/dev/null | sort \ >"$STATE/latest/model-manifest.tsv" docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Status}}' \ >"$STATE/latest/container-manifest.tsv" paths=(/etc/mike-ai /opt/mike-ai "$STATE/latest") for path in \ /data/voice /data/music /data/audio /data/llama-dashboard \ /data/mike-ai-operator /data/benchmarks /data/model-benchmarks \ /data/image-comparison /data/backups /data/deploy-backups; do [[ ! -e $path ]] || paths+=("$path") done tag=${RESTIC_TAG:-athena-disaster} log "Verschlüsseltes externes Backup schreiben" if ! restic snapshots >/dev/null 2>&1; then log "Neues Restic-Repository initialisieren" restic init fi restic backup --tag "$tag" "${paths[@]}" log "Aufbewahrung anwenden" restic forget --tag "$tag" \ --keep-daily "${RESTIC_KEEP_DAILY:-14}" \ --keep-weekly "${RESTIC_KEEP_WEEKLY:-8}" \ --keep-monthly "${RESTIC_KEEP_MONTHLY:-12}" --prune log "Letzten Snapshot verifizieren" restic snapshots --tag "$tag" --latest 1 restic check printf 'ATHENA_DISASTER_BACKUP_OK\n'