Version 1.0.0 — Freigabe fuer den Echtbetrieb
Kein neues Modul. Zwoelf Phasen sind fertig, und die Wehr kann ab jetzt echte Daten hineingeben, ohne befuerchten zu muessen, sie beim naechsten Update zu verlieren. Neu dazu, weil beides zum Betrieb gehoert: update.sh aktualisiert auf einem Linux-Server in einem Befehl. Der Ablauf darf an jeder Stelle abbrechen, ohne eine halb aktualisierte Anlage zu hinterlassen: erst nachsehen ob es etwas Neues gibt, dann sichern, dann holen und bauen (schlaegt das fehl, laeuft die alte Fassung unveraendert weiter), dann umschalten, dann pruefen ob die Anwendung antwortet. Tut sie es nicht, stehen die Protokollzeilen da und die Befehle fuer den Rueckweg. docs/KURZANLEITUNG.md ist der Einstieg fuer die Wehr - nach Rollen sortiert, aufgabenorientiert statt nach Modulen, und bei jedem heiklen Punkt steht das Warum dabei. Die vollstaendige Anleitung hat 63 Kapitel; die liest niemand, bevor er das erste Geraet pruefen will. Die Roadmap haelt den Stand JEDER Freigabevoraussetzung fest, auch der offenen: Datenuebernahme entfaellt (die Wehr pflegt von Hand ein), Sicherungskonzept erledigt, Loeschkonzept technisch vollstaendig aber nicht abgenommen, Schulung steht aus, Atemschutzhinweis bewusst stehen gelassen. Eine Freigabe, die abhakt was nicht geprueft wurde, ist keine. Nebenbei die Ursache der Testflackerei gefunden, die sich durch mehrere Phasen zog: zwei Datenbanktests schalteten beim Aufraeumen ihre Trigger mit ALTER TABLE ... DISABLE TRIGGER ab - eine Schemaaenderung, die fuer die ganze Datenbank gilt. Vitest faehrt die Dateien parallel, also pruefte eine Datei gerade dass ein Trigger greift, waehrend eine andere ihn abgeraeumt hatte. Jetzt sitzungslokal ueber session_replication_role. Erstmals in diesem Projekt: fuenf Unit-Testlaeufe hintereinander 601 gruen und ein E2E-Lauf mit 268 gruen ohne Fehlschlag und ohne Flackern. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9156dcc144
commit
30faa6cb02
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# FLORIAN aktualisieren.
|
||||
#
|
||||
# ./update.sh neue Fassung holen und einspielen
|
||||
# ./update.sh --pruefen nur nachsehen, ob es etwas Neues gibt
|
||||
# ./update.sh --ohne-sicherung für Testinstallationen
|
||||
#
|
||||
# Der Ablauf ist so gebaut, dass er an jeder Stelle abbrechen darf, ohne die
|
||||
# Wehr mit einer halb aktualisierten Anlage zurückzulassen:
|
||||
#
|
||||
# 1. Nachsehen, ob überhaupt etwas Neues da ist.
|
||||
# 2. SICHERN — Datenbank und Dateien. Vor allem anderen.
|
||||
# 3. Neue Fassung holen und Abbilder bauen.
|
||||
# 4. Umschalten. Der App-Container spielt dabei die Migrationen ein.
|
||||
# 5. Nachsehen, ob die Anwendung wieder antwortet.
|
||||
#
|
||||
# Geht Schritt 3 schief, läuft die alte Fassung weiter — es wurde nichts
|
||||
# angefasst. Geht Schritt 4 oder 5 schief, steht in der Ausgabe, mit welchem
|
||||
# Befehl sich der vorherige Stand zurückholen lässt.
|
||||
#
|
||||
# Voraussetzungen: Linux, Docker mit Compose-Erweiterung, git, ein .env im
|
||||
# selben Verzeichnis.
|
||||
# =============================================================================
|
||||
set -Eeuo pipefail
|
||||
|
||||
VERZEICHNIS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$VERZEICHNIS"
|
||||
|
||||
COMPOSE=(docker compose -f docker-compose.yml)
|
||||
NUR_PRUEFEN=false
|
||||
OHNE_SICHERUNG=false
|
||||
|
||||
for argument in "$@"; do
|
||||
case "$argument" in
|
||||
--pruefen) NUR_PRUEFEN=true ;;
|
||||
--ohne-sicherung) OHNE_SICHERUNG=true ;;
|
||||
-h|--help)
|
||||
sed -n '3,20p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unbekannte Angabe: $argument" >&2
|
||||
echo "Bekannt sind: --pruefen, --ohne-sicherung, --help" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Ausgabe ----------------------------------------------------------------
|
||||
# Farben nur, wenn wirklich ein Terminal dranhängt. In einem Cron-Protokoll
|
||||
# sind Steuerzeichen nur Müll.
|
||||
if [ -t 1 ]; then
|
||||
ROT=$'\033[31m'; GRUEN=$'\033[32m'; GELB=$'\033[33m'; FETT=$'\033[1m'; AUS=$'\033[0m'
|
||||
else
|
||||
ROT=''; GRUEN=''; GELB=''; FETT=''; AUS=''
|
||||
fi
|
||||
|
||||
schritt() { echo; echo "${FETT}==> $*${AUS}"; }
|
||||
hinweis() { echo " $*"; }
|
||||
warnung() { echo "${GELB} $*${AUS}"; }
|
||||
fehler() { echo "${ROT}FEHLER: $*${AUS}" >&2; }
|
||||
|
||||
# Wo wir standen, bevor etwas verändert wurde — für die Rückkehr.
|
||||
STAND_VORHER=""
|
||||
SICHERUNG=""
|
||||
|
||||
abbruch() {
|
||||
local zeile=$1
|
||||
echo
|
||||
fehler "Abbruch in Zeile $zeile."
|
||||
echo
|
||||
if [ -n "$STAND_VORHER" ]; then
|
||||
echo "Der vorherige Stand war: $STAND_VORHER"
|
||||
echo "Zurueck damit:"
|
||||
echo " git checkout $STAND_VORHER"
|
||||
echo " ${COMPOSE[*]} up -d --build"
|
||||
fi
|
||||
if [ -n "$SICHERUNG" ]; then
|
||||
echo
|
||||
echo "Die Sicherung von vorhin liegt in: $SICHERUNG"
|
||||
echo "Zurueckspielen (ueberschreibt den Bestand):"
|
||||
echo " npm run wiederherstellen -- --quelle $SICHERUNG --wirklich"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
trap 'abbruch $LINENO' ERR
|
||||
|
||||
# --- Voraussetzungen --------------------------------------------------------
|
||||
schritt "Voraussetzungen"
|
||||
|
||||
for werkzeug in git docker; do
|
||||
if ! command -v "$werkzeug" >/dev/null 2>&1; then
|
||||
fehler "$werkzeug ist nicht installiert."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
fehler "Docker Compose fehlt (die Erweiterung 'docker compose', nicht docker-compose)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
fehler "Es gibt keine .env in $VERZEICHNIS."
|
||||
hinweis "Fuer eine Erstinstallation: cp .env.example .env und ausfuellen."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ein Update mit unverbuchten Aenderungen wuerde sie beim Holen ueberschreiben
|
||||
# oder in einem Konflikt enden. Beides mitten in einem Update ist unschoen.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
fehler "Im Verzeichnis liegen Aenderungen, die nicht eingecheckt sind."
|
||||
git status --short | head -20
|
||||
echo
|
||||
hinweis "Bitte erst klaeren. Die .env gehoert nicht ins Git und stoert hier nicht."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hinweis "Docker, git und .env sind da."
|
||||
|
||||
# --- Gibt es etwas Neues? ---------------------------------------------------
|
||||
schritt "Nach einer neuen Fassung sehen"
|
||||
|
||||
ZWEIG="$(git rev-parse --abbrev-ref HEAD)"
|
||||
STAND_VORHER="$(git rev-parse HEAD)"
|
||||
|
||||
git fetch --quiet origin "$ZWEIG"
|
||||
|
||||
STAND_NEU="$(git rev-parse "origin/$ZWEIG")"
|
||||
|
||||
if [ "$STAND_VORHER" = "$STAND_NEU" ]; then
|
||||
hinweis "Alles aktuell — nichts zu tun."
|
||||
hinweis "Stand: $(git log -1 --format='%h %s')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ANZAHL="$(git rev-list --count "$STAND_VORHER..$STAND_NEU")"
|
||||
hinweis "$ANZAHL neue Aenderung(en):"
|
||||
git log --oneline --no-decorate "$STAND_VORHER..$STAND_NEU" | head -20 | sed 's/^/ /'
|
||||
|
||||
if [ "$NUR_PRUEFEN" = true ]; then
|
||||
echo
|
||||
hinweis "Nur nachgesehen — es wurde nichts geaendert."
|
||||
hinweis "Zum Einspielen: ./update.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Sichern ----------------------------------------------------------------
|
||||
# Vor allem anderen, und ohne Umgehung im Regelfall. Ein Update ist der
|
||||
# haeufigste Anlass, eine Sicherung zu brauchen.
|
||||
if [ "$OHNE_SICHERUNG" = true ]; then
|
||||
schritt "Sicherung uebersprungen"
|
||||
warnung "--ohne-sicherung ist gesetzt. Bei einem Fehler gibt es kein Zurueck."
|
||||
else
|
||||
schritt "Sichern"
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
fehler "npm fehlt — ohne das laeuft das Sicherungsskript nicht."
|
||||
hinweis "Entweder Node installieren oder bewusst mit --ohne-sicherung fahren."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npm run --silent sicherung
|
||||
SICHERUNG="$(find sicherungen -maxdepth 1 -mindepth 1 -type d | sort | tail -1)"
|
||||
hinweis "Liegt in: $SICHERUNG"
|
||||
fi
|
||||
|
||||
# --- Holen und bauen --------------------------------------------------------
|
||||
# Bis hierher ist nichts umgeschaltet: schlaegt der Bau fehl, laeuft die alte
|
||||
# Fassung unveraendert weiter.
|
||||
schritt "Neue Fassung holen"
|
||||
git merge --ff-only "origin/$ZWEIG"
|
||||
hinweis "Jetzt auf: $(git log -1 --format='%h %s')"
|
||||
|
||||
schritt "Abbilder bauen"
|
||||
hinweis "Das dauert ein paar Minuten."
|
||||
"${COMPOSE[@]}" build
|
||||
|
||||
# --- Umschalten -------------------------------------------------------------
|
||||
# Der App-Container spielt beim Start die ausstehenden Migrationen ein; der
|
||||
# Worker tut das bewusst nicht, damit nicht zwei Prozesse gleichzeitig
|
||||
# migrieren.
|
||||
schritt "Umschalten"
|
||||
"${COMPOSE[@]}" up -d --remove-orphans
|
||||
|
||||
# --- Nachsehen --------------------------------------------------------------
|
||||
schritt "Nachsehen, ob die Anwendung antwortet"
|
||||
|
||||
ADRESSE="http://localhost:3000/api/health"
|
||||
if grep -q '^APP_URL=' .env; then
|
||||
ADRESSE="$(grep '^APP_URL=' .env | head -1 | cut -d= -f2- | tr -d '"'"'"' ')/api/health"
|
||||
fi
|
||||
|
||||
hinweis "Frage $ADRESSE ..."
|
||||
|
||||
GESUND=false
|
||||
for versuch in $(seq 1 30); do
|
||||
if curl -fsS --max-time 5 "$ADRESSE" >/dev/null 2>&1; then
|
||||
GESUND=true
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
[ $((versuch % 6)) -eq 0 ] && hinweis " noch nicht da, warte weiter ($((versuch * 5)) s) ..."
|
||||
done
|
||||
|
||||
if [ "$GESUND" != true ]; then
|
||||
fehler "Die Anwendung antwortet nach zweieinhalb Minuten nicht."
|
||||
echo
|
||||
echo "Die letzten Zeilen aus dem Protokoll:"
|
||||
"${COMPOSE[@]}" logs --tail 40 app || true
|
||||
echo
|
||||
echo "Zurueck auf den vorherigen Stand:"
|
||||
echo " git checkout $STAND_VORHER"
|
||||
echo " ${COMPOSE[*]} up -d --build"
|
||||
[ -n "$SICHERUNG" ] && echo " npm run wiederherstellen -- --quelle $SICHERUNG --wirklich"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ab hier ist nichts mehr rueckgaengig zu machen, was das Skript angerichtet
|
||||
# haette — deshalb keine Fehlerfalle mehr, die zum Rueckbau raet.
|
||||
trap - ERR
|
||||
|
||||
schritt "Fertig"
|
||||
echo "${GRUEN} FLORIAN laeuft auf dem Stand $(git log -1 --format='%h %s')${AUS}"
|
||||
echo
|
||||
hinweis "Zustand der Dienste:"
|
||||
"${COMPOSE[@]}" ps --format ' {{.Service}}\t{{.Status}}' 2>/dev/null || "${COMPOSE[@]}" ps
|
||||
|
||||
if [ -n "$SICHERUNG" ]; then
|
||||
echo
|
||||
hinweis "Die Sicherung von vorhin liegt in $SICHERUNG."
|
||||
hinweis "Sie kann weg, sobald ein paar Tage nichts aufgefallen ist."
|
||||
fi
|
||||
Reference in New Issue
Block a user