Pin images by digest; add update, smoke-test and status scripts
Goal: at every session start, run exactly the application the repo says — and be able to move to a new upstream release deliberately, with a way back. The old setup (v1-latest + pull_policy: always) was unreliable in both directions. `docker compose up -d` silently pulled a new application, including an irreversible SurrealDB schema migration, while a reboot (restart: always) or `docker compose start` kept running the old image without pulling anything. The running version was effectively unpredictable. Note v1-latest tracks releases, not main: the current image (v1.10.0, 2026-06-18) IS the latest release — the repo being "ahead" is unreleased code, which is explicitly not wanted here. So this is about guaranteeing and detecting, not catching up. - docker-compose.yml: both images pinned by digest, pull_policy: missing. - scripts/check_updates.sh: reports running version/digest vs the latest release and registry digest. Changes nothing; meant for the session-start ritual. - scripts/update_stack.sh: resolve new digest -> stop -> back up surreal_data, notebook_data and docker-compose.yml -> repin -> start -> smoke test -> roll back data AND compose file if the smoke test fails. The data backup is the point: the app migrates the DB on startup and an older app cannot read a migrated DB, so a bad update would otherwise be a one-way door. tar runs inside a container because the data dirs are root-owned and the host user cannot restore over them. - scripts/smoke_test.sh: checks what actually breaks here, not just "does it start". Every local adaptation leans on upstream internals and can break silently: the prompts/podcast directory mount masks the image's directory (a new template upstream would be invisible), config/content_core.yaml freezes content-core's defaults (because CCORE_CONFIG_PATH replaces rather than merges), and the env-var workarounds depend on current content-core/esperanto/podcast_creator behaviour. So it verifies those assumptions explicitly, plus API, TTS audio, STT and a real YouTube transcript. Both scripts read the digest from the lfnovo/open_notebook line specifically. A naive "first sha256 in the file" grep matches surrealdb (listed first) — caught while testing: check_updates.sh falsely reported an update, and update_stack.sh would have rewritten the database image instead of the application. Verified: smoke test green against the current stack, and red (exit 1) when failures are injected (dead TTS port, removed template variable). Backup/restore mechanics exercised separately: 44 MB in 3.3s, restore yields 13 DB files and 91 notebook files. The update path itself cannot be exercised end to end until a newer image exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f350e49bd5
commit
4805fe75ed
7 changed files with 434 additions and 7 deletions
104
scripts/update_stack.sh
Executable file
104
scripts/update_stack.sh
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
#!/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"
|
||||
# Die Datenverzeichnisse gehoeren root (der Container schreibt als root), deshalb wird
|
||||
# im Container gepackt statt auf dem Host — spart sudo.
|
||||
docker run --rm -v "$REPO:/work" -w /work --entrypoint tar \
|
||||
"lfnovo/open_notebook@$pinned" -czf "backups/$ts/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
|
||||
docker run --rm -v "$REPO:/work" -w /work --entrypoint sh \
|
||||
"lfnovo/open_notebook@$pinned" -c \
|
||||
"rm -rf surreal_data notebook_data && tar -xzf backups/$ts/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
|
||||
Loading…
Add table
Add a link
Reference in a new issue