Add self-contained data-disk reinstall kit

This commit is contained in:
Mikei386 committed 2026-08-23 16:20:37 +02:00
1 parent ddae3e9bad
commit c226af7bc7
3 files changed
+196

No files matched your search

+24
View File
@@ -142,3 +142,27 @@ Noch organisatorisch zwingend: Die private age-Identität muss eine zweite,
vom Mac unabhängige Kopie in einem Passwortmanager oder auf einem Offline- vom Mac unabhängige Kopie in einem Passwortmanager oder auf einem Offline-
Datenträger erhalten. Ohne diese Identität ist das verschlüsselte Bundle nicht Datenträger erhalten. Ohne diese Identität ist das verschlüsselte Bundle nicht
wiederherstellbar. wiederherstellbar.
## Selbsttragender Recovery-Koffer auf der Data-SSD
Für den speziellen Ausfall **nur der System-SSD** kann zusätzlich ein
vollständiger Recovery-Koffer unter `/data/mike-ai-recovery-kit` liegen. Er
enthält das verschlüsselte Bundle, den dafür benötigten Schlüssel, die gesamte
Git-Historie und ein eigenständiges Startskript. Auf einem frischen Debian:
```bash
mount <DATA-PARTITION> /data
sudo /data/mike-ai-recovery-kit/reinstall-athena.sh
```
Nach dem einmaligen Start läuft der Wiederaufbau selbständig. Falls NVIDIA-
Treiber oder der stabile Interface-Name einen Neustart erfordern, hinterlegt
das Skript einen systemd-Fortsetzer, bindet `/data` über die vorhandene UUID
dauerhaft ein und setzt den Ablauf nach dem Reboot fort. Nach drei erfolglosen
Versuchen bricht es gegen eine Bootschleife ab.
Diese Bequemlichkeit besitzt bewusst eine andere Sicherheitsgrenze: Weil der
Entschlüsselungsschlüssel auf derselben Data-SSD liegt, kann eine Person mit
Lesezugriff auf diese SSD auch die enthaltenen Secrets entschlüsseln. Das
separate Off-Host-Bundle mit getrennt verwahrtem Schlüssel bleibt daher die
maßgebliche Sicherung gegen Diebstahl oder Verlust des gesamten Hosts.
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
RECOVERY_BUNDLE=${1:-}
AGE_IDENTITY=${2:-}
SOURCE_BUNDLE=${3:-}
RELEASE_DIR=${4:-}
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
[[ -s $RECOVERY_BUNDLE ]] || die "Recovery-Bundle fehlt."
[[ -s $AGE_IDENTITY ]] || die "age-Identität fehlt."
[[ -s $SOURCE_BUNDLE ]] || die "Git-Quellbundle fehlt."
[[ -n $RELEASE_DIR && $RELEASE_DIR == /data/* ]] || \
die "Release-Ziel muss ein eindeutiger Pfad unter /data sein."
[[ ! -e $RELEASE_DIR ]] || die "Release-Ziel existiert bereits."
command -v age >/dev/null || die "age fehlt."
command -v git >/dev/null || die "git fehlt."
stage=$(mktemp -d /tmp/mike-ai-data-kit.XXXXXX)
trap 'rm -rf "$stage"' EXIT
age -d -i "$AGE_IDENTITY" -o "$stage/bundle.tar.gz" "$RECOVERY_BUNDLE"
tar -C "$stage" -xzf "$stage/bundle.tar.gz" METADATA SHA256SUMS \
host-config.tar.gz openwebui-data.tar.gz
(cd "$stage" && sha256sum -c SHA256SUMS)
commit=$(sed -n 's/^source_commit=//p' "$stage/METADATA" | head -n 1)
[[ $commit =~ ^[0-9a-f]{40}$ ]] || die "Recovery-Commit in METADATA ist ungültig."
git clone -q "$SOURCE_BUNDLE" "$stage/source-check"
git -C "$stage/source-check" cat-file -e "$commit^{commit}"
install -d -m 0700 "$RELEASE_DIR"
install -m 0600 "$RECOVERY_BUNDLE" "$RELEASE_DIR/recovery.tar.age"
install -m 0600 "$AGE_IDENTITY" "$RELEASE_DIR/recovery.agekey"
install -m 0600 "$SOURCE_BUNDLE" "$RELEASE_DIR/source.git.bundle"
install -m 0700 "$ROOT_DIR/platform/recovery/reinstall-from-data.sh" \
"$RELEASE_DIR/reinstall-athena.sh"
cat >"$RELEASE_DIR/kit.env" <<EOF
RECOVERY_COMMIT=$commit
RECOVERY_BUNDLE=recovery.tar.age
SOURCE_BUNDLE=source.git.bundle
AGE_IDENTITY=recovery.agekey
EOF
cat >"$RELEASE_DIR/README.txt" <<'EOF'
ATHENA SELF-CONTAINED DATA-DISK RECOVERY
Auf einem frischen Debian die bestehende Data-SSD unter /data einhängen und
als root ausführen:
/data/mike-ai-recovery-kit/reinstall-athena.sh
Das Skript prüft alle Dateien, stellt einen persistenten /data-Mount her,
installiert minimale Werkzeuge und rekonstruiert den vollständigen MikeAI-
Stack. Erforderliche Treiber-Neustarts werden höchstens dreimal automatisch
fortgesetzt.
SICHERHEIT: Dieses Kit enthält den Entschlüsselungsschlüssel. Wer die Data-SSD
lesen kann, kann daher auch die enthaltenen Infrastruktur-Secrets entschlüsseln.
EOF
chmod 0600 "$RELEASE_DIR/kit.env" "$RELEASE_DIR/README.txt"
(
cd "$RELEASE_DIR"
sha256sum recovery.tar.age recovery.agekey source.git.bundle \
reinstall-athena.sh kit.env README.txt >SHA256SUMS
)
chmod 0600 "$RELEASE_DIR/SHA256SUMS"
link=/data/mike-ai-recovery-kit
[[ ! -e $link || -L $link ]] || die "$link existiert und ist kein verwalteter Symlink."
ln -sfn "$(basename "$RELEASE_DIR")" "$link"
printf 'SELF_CONTAINED_DATA_KIT_OK %s -> %s\n' "$link" "$RELEASE_DIR"
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# Self-contained Athena reinstall entry point. This file is copied into a
# root-only recovery kit on /data; it is not run during normal installation.
set -Eeuo pipefail
umask 077
KIT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
CONFIG="$KIT_DIR/kit.env"
WORK_DIR=/var/lib/mike-ai-data-reinstall
SERVICE=/etc/systemd/system/mike-ai-data-reinstall.service
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
log() { printf '\n==> %s\n' "$*"; }
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
[[ -s $CONFIG ]] || die "kit.env fehlt im Recovery-Kit."
# shellcheck disable=SC1090
source "$CONFIG"
required=(RECOVERY_COMMIT RECOVERY_BUNDLE SOURCE_BUNDLE AGE_IDENTITY)
for name in "${required[@]}"; do
[[ -n ${!name:-} ]] || die "Pflichtwert $name fehlt."
done
for file in "$RECOVERY_BUNDLE" "$SOURCE_BUNDLE" "$AGE_IDENTITY"; do
[[ -s $KIT_DIR/$file ]] || die "Recovery-Datei fehlt: $file"
done
log "Recovery-Kit kryptografisch prüfen"
(cd "$KIT_DIR" && sha256sum -c SHA256SUMS)
log "Persistenten Mount für die Data-SSD sicherstellen"
[[ $(findmnt -n -o TARGET --target "$KIT_DIR") == /data ]] || \
die "Recovery-Kit liegt nicht auf dem eingehängten /data-Dateisystem."
if ! findmnt -s -n -o TARGET | grep -qx /data; then
source_device=$(findmnt -n -o SOURCE --target "$KIT_DIR")
source_uuid=$(blkid -s UUID -o value "$source_device")
source_type=$(findmnt -n -o FSTYPE --target "$KIT_DIR")
[[ -n $source_uuid && -n $source_type ]] || \
die "UUID oder Dateisystemtyp der Data-SSD konnte nicht bestimmt werden."
printf 'UUID=%s /data %s defaults,nofail,x-systemd.device-timeout=30 0 2\n' \
"$source_uuid" "$source_type" >>/etc/fstab
systemctl daemon-reload
fi
log "Minimale Wiederherstellungswerkzeuge installieren"
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
age ca-certificates git rsync
install -d -m 0700 "$WORK_DIR"
if [[ ! -d $WORK_DIR/repository/.git ]]; then
git clone "$KIT_DIR/$SOURCE_BUNDLE" "$WORK_DIR/repository"
fi
git -C "$WORK_DIR/repository" checkout --detach "$RECOVERY_COMMIT"
[[ $(git -C "$WORK_DIR/repository" rev-parse HEAD) == "$RECOVERY_COMMIT" ]] || \
die "Der geforderte Recovery-Commit ist nicht ausgecheckt."
attempt=0
[[ ! -s $WORK_DIR/attempt ]] || attempt=$(<"$WORK_DIR/attempt")
(( attempt += 1 ))
printf '%s\n' "$attempt" >"$WORK_DIR/attempt"
(( attempt <= 3 )) || die "Mehr als drei automatische Recovery-Versuche; Abbruch gegen Bootschleife."
log "Bare-Metal-Wiederherstellung ausführen (Versuch $attempt/3)"
set +e
"$WORK_DIR/repository/platform/recovery/restore-recovery-bundle.sh" \
"$KIT_DIR/$RECOVERY_BUNDLE" "$KIT_DIR/$AGE_IDENTITY"
status=$?
set -e
if [[ $status == 20 || $status == 21 ]]; then
log "Ein kontrollierter Treiber-/Netzwerk-Neustart ist erforderlich"
cat >"$SERVICE" <<EOF
[Unit]
Description=Resume MikeAI data-disk disaster recovery
After=network-online.target local-fs.target
Wants=network-online.target
RequiresMountsFor=/data
[Service]
Type=oneshot
ExecStart=$KIT_DIR/reinstall-athena.sh --resume
StandardOutput=append:/var/log/mike-ai-data-reinstall.log
StandardError=append:/var/log/mike-ai-data-reinstall.log
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable mike-ai-data-reinstall.service
sync
systemctl reboot
exit 0
fi
[[ $status == 0 ]] || die "Wiederherstellung ist mit Status $status fehlgeschlagen."
systemctl disable mike-ai-data-reinstall.service >/dev/null 2>&1 || true
rm -f "$SERVICE" "$WORK_DIR/attempt"
systemctl daemon-reload
printf '\nDATA_DISK_REINSTALL_OK\n'
printf 'OpenWebUI, Router, Modelle und MCPs wurden wiederhergestellt.\n'