#!/usr/bin/env bash # Aktualisiert Open Notebook auf das neueste veroeffentlichte Image — mit Rueckfallpunkt. # # Ablauf: # 1. Neuen Digest aus der Registry holen (nichts zu tun, wenn schon aktuell). # 2. Stack stoppen und Daten sichern (surreal_data + notebook_data + docker-compose.yml). # 3. Digest in docker-compose.yml eintragen, Stack starten. # 4. Rauchtest (scripts/smoke_test.sh). # 5. Rot -> alles zurueckrollen (Daten UND Compose-Datei). Gruen -> fertig. # # Der Rollback der Daten ist der Grund fuer das Backup: Open Notebook migriert die # SurrealDB beim Start, und ein Downgrade der Anwendung kann eine migrierte Datenbank # nicht wieder lesen. Ohne Backup waere ein missgluecktes Update eine Einbahnstrasse. # # ./scripts/update_stack.sh # auf neuestes Image # ./scripts/update_stack.sh --dry-run # nur zeigen, was passieren wuerde set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO" TAG="lfnovo/open_notebook:v1-latest" BACKUP_DIR="$REPO/backups" KEEP_BACKUPS=3 DRY_RUN=0 [ "${1:-}" = "--dry-run" ] && DRY_RUN=1 say() { printf '\n\033[1m%s\033[0m\n' "$1"; } # Gezielt aus der open_notebook-Zeile. Ein blindes grep auf den ersten Digest der Datei # erwischt surrealdb (steht weiter oben) — das sed weiter unten wuerde dann den falschen # Digest ersetzen und die Datenbank austauschen statt der Anwendung. pinned="$(grep -oP 'lfnovo/open_notebook\S*@\Ksha256:[0-9a-f]+' docker-compose.yml | head -1)" [ -n "$pinned" ] || { echo "FEHLER: kein open_notebook-Digest in docker-compose.yml gepinnt." >&2; exit 1; } say "1/5 Registry abfragen" remote="$(docker buildx imagetools inspect "$TAG" --format '{{.Manifest.Digest}}')" echo " gepinnt: $pinned" echo " neu : $remote" if [ "$remote" = "$pinned" ]; then echo " Bereits aktuell — nichts zu tun." exit 0 fi if [ "$DRY_RUN" = 1 ]; then echo " (--dry-run: hier wuerde das Update starten)" exit 0 fi ts="$(date +%Y%m%d-%H%M%S)" backup="$BACKUP_DIR/$ts" mkdir -p "$backup" say "2/5 Stack stoppen und sichern -> backups/$ts" docker compose down cp docker-compose.yml "$backup/docker-compose.yml" # Direkt auf dem Host, weil die Container als Host-User laufen (user: "1000:1000") # und die Daten uns gehoeren. Liefen sie als root, scheiterte das an den Rechten. tar -czf "$backup/data.tgz" surreal_data notebook_data echo " $(du -sh "$backup/data.tgz" | cut -f1) gesichert" restore() { say "ROLLBACK" docker compose down || true cp "$backup/docker-compose.yml" docker-compose.yml rm -rf surreal_data notebook_data tar -xzf "$backup/data.tgz" docker compose up -d echo " Alter Stand ($pinned) ist wiederhergestellt, Daten aus backups/$ts." echo " Das neue Image bleibt lokal liegen — Ursache pruefen, dann erneut versuchen." } say "3/5 Auf neues Image umstellen" sed -i "s|@$pinned|@$remote|" docker-compose.yml docker compose up -d echo " gestartet, warte auf die API..." for _ in $(seq 1 30); do curl -sf -m 3 http://127.0.0.1:5055/api/models/providers >/dev/null 2>&1 && break sleep 5 done say "4/5 Rauchtest" if ./scripts/smoke_test.sh; then say "5/5 Fertig" newver="$(docker compose exec -T open_notebook grep -m1 '^version' /app/pyproject.toml | grep -oP '(?<=")[^"]+' || echo '?')" echo " Update auf $newver erfolgreich, Rauchtest gruen." echo " Backup: backups/$ts (bleibt liegen)" echo echo " Bitte den neuen Digest committen:" echo " git add docker-compose.yml && git commit -m 'Update Open Notebook auf $newver'" else echo echo " Rauchtest ROT — das Update wird zurueckgenommen." restore exit 1 fi # Alte Backups aufraeumen (Platte ist knapp). ls -1dt "$BACKUP_DIR"/*/ 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | while read -r old; do echo " raeume altes Backup weg: $(basename "$old")" rm -rf "$old" done