Restore OpenWebUI with its matching image
This commit is contained in:
@@ -50,6 +50,7 @@ sudo ./platform/migration/restore-reference-backup.sh \
|
|||||||
Das Skript prüft zuerst die Archiv-Hashes und übernimmt ausschließlich:
|
Das Skript prüft zuerst die Archiv-Hashes und übernimmt ausschließlich:
|
||||||
|
|
||||||
- die persistente OpenWebUI-Datenbank samt Einstellungen und Uploads,
|
- die persistente OpenWebUI-Datenbank samt Einstellungen und Uploads,
|
||||||
|
- das zur Datenbank passende, gesicherte OpenWebUI-Container-Image,
|
||||||
- den dazugehörigen WebUI- und Router-Schlüssel,
|
- den dazugehörigen WebUI- und Router-Schlüssel,
|
||||||
- die freigegebenen HA-, ARR- und Unraid-Secrets,
|
- die freigegebenen HA-, ARR- und Unraid-Secrets,
|
||||||
- `runraid`, dessen privaten Schlüssel, Zertifikat und Known-Hosts-Datei.
|
- `runraid`, dessen privaten Schlüssel, Zertifikat und Known-Hosts-Datei.
|
||||||
@@ -59,5 +60,18 @@ Experimentdienste oder `.before-*`-Altstände. Vor dem Ersetzen des frischen
|
|||||||
OpenWebUI-Volumes wird unter `/data/open-webui-before-restore-*.tar.gz` eine
|
OpenWebUI-Volumes wird unter `/data/open-webui-before-restore-*.tar.gz` eine
|
||||||
Rückfallsicherung erstellt. Secret-Werte werden nicht ausgegeben.
|
Rückfallsicherung erstellt. Secret-Werte werden nicht ausgegeben.
|
||||||
|
|
||||||
|
OpenWebUI-Datenbanken sind nicht beliebig vor- oder rückwärtskompatibel. Das
|
||||||
|
Restore-Skript lädt deshalb bewusst das im Backup inventarisierte Original-
|
||||||
|
Image, versieht es lokal mit dem Tag `mike-ai/openwebui:reference` und trägt
|
||||||
|
diesen Tag sowohl in `stack.env` als auch – falls vorhanden – in
|
||||||
|
`/root/mike-ai-install.env` ein. Dadurch bleibt auch ein späterer, idempotenter
|
||||||
|
Installerlauf versionsgleich. Ein Upgrade auf eine neuere OpenWebUI-Version
|
||||||
|
erfolgt erst danach kontrolliert und mit einer eigenen Datenbanksicherung.
|
||||||
|
Falls ein älteres Image-Archiv trotz seines Namens das OpenWebUI-Image nicht
|
||||||
|
enthält, verwendet das Skript ausschließlich den im Docker-Inventar gesicherten
|
||||||
|
unveränderlichen Registry-Digest. Es fällt niemals auf `latest` zurück.
|
||||||
|
Da lokale Docker-Image-IDs beim Wiederherstellen von einem Registry-Digest
|
||||||
|
abweichen können, verifiziert es zusätzlich die gesicherte OCI-Build-Revision.
|
||||||
|
|
||||||
Der alte Host bleibt bis zum bestandenen Abnahmetest unverändert und dient nur
|
Der alte Host bleibt bis zum bestandenen Abnahmetest unverändert und dient nur
|
||||||
als Referenz. Es werden keine Caches oder unbekannten Altverzeichnisse kopiert.
|
als Referenz. Es werden keine Caches oder unbekannten Altverzeichnisse kopiert.
|
||||||
|
|||||||
@@ -185,6 +185,13 @@ experimentelle Altcontainer zurückzubringen. Der Leerhost-Probelauf wird auf
|
|||||||
Athena praktisch protokolliert und seine Korrekturen fließen direkt in den
|
Athena praktisch protokolliert und seine Korrekturen fließen direkt in den
|
||||||
Installer zurück.
|
Installer zurück.
|
||||||
|
|
||||||
|
Ein Container-Backup gilt nur dann als vollständig, wenn nach `docker save`
|
||||||
|
nicht bloß das Archiv und seine Prüfsumme existieren: Ein isolierter
|
||||||
|
Probeimport muss außerdem jede erwartete Image-ID beziehungsweise den
|
||||||
|
unveränderlichen Registry-Digest und die OCI-Build-Revision bestätigen. Die
|
||||||
|
OpenWebUI-Datenbank wird immer zusammen mit genau diesem geprüften Image
|
||||||
|
gesichert und wiederhergestellt.
|
||||||
|
|
||||||
Jeder Schritt muss wiederholbar, einzeln prüfbar und bei Fehlern abbrechbar
|
Jeder Schritt muss wiederholbar, einzeln prüfbar und bei Fehlern abbrechbar
|
||||||
sein. Ein fehlgeschlagener Schritt darf keinen halb aktivierten Dienst
|
sein. Ein fehlgeschlagener Schritt darf keinen halb aktivierten Dienst
|
||||||
hinterlassen.
|
hinterlassen.
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ BACKUP=${1:-}
|
|||||||
STACK_DIR=${STACK_DIR:-/opt/mike-ai/stack}
|
STACK_DIR=${STACK_DIR:-/opt/mike-ai/stack}
|
||||||
SECRETS_DIR=${SECRETS_DIR:-/etc/mike-ai}
|
SECRETS_DIR=${SECRETS_DIR:-/etc/mike-ai}
|
||||||
OPENWEBUI_VOLUME=${OPENWEBUI_VOLUME:-mike-ai_open-webui-data}
|
OPENWEBUI_VOLUME=${OPENWEBUI_VOLUME:-mike-ai_open-webui-data}
|
||||||
|
INSTALL_CONFIG=${INSTALL_CONFIG:-/root/mike-ai-install.env}
|
||||||
|
REFERENCE_OPENWEBUI_TAG=${REFERENCE_OPENWEBUI_TAG:-mike-ai/openwebui:reference}
|
||||||
|
|
||||||
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
|
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
|
||||||
log() { printf '\n==> %s\n' "$*"; }
|
log() { printf '\n==> %s\n' "$*"; }
|
||||||
@@ -20,7 +22,8 @@ log() { printf '\n==> %s\n' "$*"; }
|
|||||||
|
|
||||||
ui_archive=$BACKUP/archives/open-webui-migrated.tar.gz
|
ui_archive=$BACKUP/archives/open-webui-migrated.tar.gz
|
||||||
mcp_archive=$BACKUP/archives/mcp-and-search-config.tar.gz
|
mcp_archive=$BACKUP/archives/mcp-and-search-config.tar.gz
|
||||||
for archive in "$ui_archive" "$mcp_archive"; do
|
image_archive=$BACKUP/images/openwebui-mcp-images.tar.gz
|
||||||
|
for archive in "$ui_archive" "$mcp_archive" "$image_archive"; do
|
||||||
[[ -f $archive ]] || die "Archiv fehlt: $archive"
|
[[ -f $archive ]] || die "Archiv fehlt: $archive"
|
||||||
expected=$(awk -v suffix="/${archive#"$BACKUP"/}" \
|
expected=$(awk -v suffix="/${archive#"$BACKUP"/}" \
|
||||||
'$2 ~ suffix "$" {print $1; exit}' "$BACKUP/SHA256SUMS")
|
'$2 ~ suffix "$" {print $1; exit}' "$BACKUP/SHA256SUMS")
|
||||||
@@ -29,6 +32,62 @@ for archive in "$ui_archive" "$mcp_archive"; do
|
|||||||
[[ $actual == "$expected" ]] || die "Prüfsumme falsch: $archive"
|
[[ $actual == "$expected" ]] || die "Prüfsumme falsch: $archive"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
[[ -f $BACKUP/inventory/docker-images.txt ]] || \
|
||||||
|
die "Docker-Image-Inventar fehlt."
|
||||||
|
reference_openwebui_image=$(awk \
|
||||||
|
'$1 == "ghcr.io/open-webui/open-webui" { print $3; exit }' \
|
||||||
|
"$BACKUP/inventory/docker-images.txt")
|
||||||
|
[[ $reference_openwebui_image == sha256:* ]] || \
|
||||||
|
die "Gesicherte OpenWebUI-Image-ID ist nicht eindeutig ermittelbar."
|
||||||
|
[[ -f $BACKUP/inventory/docker-inspect.json ]] || \
|
||||||
|
die "Docker-Inspect-Inventar fehlt."
|
||||||
|
reference_openwebui_ref=$(python3 - "$BACKUP/inventory/docker-inspect.json" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
for container in json.load(open(sys.argv[1], encoding="utf-8")):
|
||||||
|
name = (container.get("Name") or "").lstrip("/").lower()
|
||||||
|
if name in {"open-webui", "mike-ai-open-webui"}:
|
||||||
|
image = (container.get("Config") or {}).get("Image", "")
|
||||||
|
if "@sha256:" in image:
|
||||||
|
print(image)
|
||||||
|
break
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
reference_openwebui_revision=$(python3 - "$BACKUP/inventory/docker-inspect.json" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
for container in json.load(open(sys.argv[1], encoding="utf-8")):
|
||||||
|
name = (container.get("Name") or "").lstrip("/").lower()
|
||||||
|
if name in {"open-webui", "mike-ai-open-webui"}:
|
||||||
|
labels = (container.get("Config") or {}).get("Labels") or {}
|
||||||
|
print(labels.get("org.opencontainers.image.revision", ""))
|
||||||
|
break
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
log "Versionsgleiches OpenWebUI-Image aus der Sicherung laden"
|
||||||
|
docker load -i "$image_archive" >/dev/null
|
||||||
|
selected_openwebui_image=$reference_openwebui_image
|
||||||
|
if ! docker image inspect "$selected_openwebui_image" >/dev/null 2>&1; then
|
||||||
|
[[ $reference_openwebui_ref == *@sha256:* ]] || \
|
||||||
|
die "Image-Archiv enthält OpenWebUI nicht und Registry-Digest fehlt."
|
||||||
|
log "Image-Archiv unvollständig; exakten OpenWebUI-Digest nachladen"
|
||||||
|
docker pull "$reference_openwebui_ref" >/dev/null
|
||||||
|
selected_openwebui_image=$reference_openwebui_ref
|
||||||
|
fi
|
||||||
|
docker image inspect "$selected_openwebui_image" >/dev/null 2>&1 || \
|
||||||
|
die "Gesichertes OpenWebUI-Image wurde nicht geladen."
|
||||||
|
actual_openwebui_revision=$(docker image inspect --format \
|
||||||
|
'{{ index .Config.Labels "org.opencontainers.image.revision" }}' \
|
||||||
|
"$selected_openwebui_image")
|
||||||
|
if [[ -n $reference_openwebui_revision && \
|
||||||
|
$actual_openwebui_revision != "$reference_openwebui_revision" ]]; then
|
||||||
|
die "OpenWebUI-Build-Revision stimmt nicht mit der Sicherung überein."
|
||||||
|
fi
|
||||||
|
docker tag "$selected_openwebui_image" "$REFERENCE_OPENWEBUI_TAG"
|
||||||
|
|
||||||
stage=$(mktemp -d /data/mike-ai-restore.XXXXXX)
|
stage=$(mktemp -d /data/mike-ai-restore.XXXXXX)
|
||||||
trap 'rm -rf "$stage"' EXIT
|
trap 'rm -rf "$stage"' EXIT
|
||||||
|
|
||||||
@@ -82,6 +141,10 @@ rsync -a --delete "$ui_source/data/" "$volume_path/"
|
|||||||
log "Schlüssel und aktuelle Tool-Secrets wiederherstellen"
|
log "Schlüssel und aktuelle Tool-Secrets wiederherstellen"
|
||||||
set_env_value "$SECRETS_DIR/stack.env" WEBUI_SECRET_KEY "$old_webui_key"
|
set_env_value "$SECRETS_DIR/stack.env" WEBUI_SECRET_KEY "$old_webui_key"
|
||||||
set_env_value "$SECRETS_DIR/stack.env" ROUTER_API_KEY "$old_router_key"
|
set_env_value "$SECRETS_DIR/stack.env" ROUTER_API_KEY "$old_router_key"
|
||||||
|
set_env_value "$SECRETS_DIR/stack.env" OPENWEBUI_IMAGE "$REFERENCE_OPENWEBUI_TAG"
|
||||||
|
if [[ -f $INSTALL_CONFIG ]]; then
|
||||||
|
set_env_value "$INSTALL_CONFIG" OPENWEBUI_IMAGE "$REFERENCE_OPENWEBUI_TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
install -d -m 0700 "$SECRETS_DIR/runraid" "$SECRETS_DIR/keys" "$SECRETS_DIR/ssh"
|
install -d -m 0700 "$SECRETS_DIR/runraid" "$SECRETS_DIR/keys" "$SECRETS_DIR/ssh"
|
||||||
install -m 0600 "$stage/approved/etc/mike-ai/homeassistant-admin-mcp.env" \
|
install -m 0600 "$stage/approved/etc/mike-ai/homeassistant-admin-mcp.env" \
|
||||||
|
|||||||
Reference in New Issue
Block a user