feat: update.sh für den Betrieb unter Linux
Release / veroeffentlichen (push) Canceled after 0s
CI / pruefen (push) Canceled after 0s

Erst sichern, dann anfassen: schlägt die Sicherung fehl, bricht das Skript ab
statt weiterzumachen. Geht danach etwas schief, nennt es den konkreten Weg
zurück — mit dem Commit, zu dem man zurückspringt, und der Datei, die man
einspielt, nicht bloß dem Rat, ein Backup zu haben.

Lokale Änderungen im Arbeitsverzeichnis werden erkannt und nicht überfahren.
Die Erreichbarkeit wird über den veröffentlichten Port geprüft und, falls der
nicht nach außen geht, über den Healthcheck des Containers.

Das Ausführbar-Bit steckt im Repository — nach dem Klonen ist es startklar.
This commit is contained in:
Florian Wilde
2026-09-01 22:46:36 +02:00
parent 9ee8664233
commit 19f8003945
8 changed files with 321 additions and 8 deletions
Executable
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/env bash
#
# Flimmer aktualisieren.
#
# ./update.sh sichern, holen, bauen, neu starten
# ./update.sh --ohne-backup ohne Datenbanksicherung
# ./update.sh --ohne-pull nur neu bauen und starten (kein git pull)
# ./update.sh --hilfe
#
# Grundhaltung: Erst sichern, dann anfassen. Bricht etwas ab, steht am Ende,
# wie der Weg zurück aussieht — mit dem konkreten Befehl und der konkreten
# Sicherungsdatei, nicht bloß dem Rat, ein Backup zu haben.
set -euo pipefail
# --- Ausgabe -----------------------------------------------------------------
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
AKZENT=$'\033[38;5;214m'; GUT=$'\033[32m'; WARN=$'\033[33m'
FEHLER=$'\033[31m'; LEISE=$'\033[2m'; AUS=$'\033[0m'
else
AKZENT=""; GUT=""; WARN=""; FEHLER=""; LEISE=""; AUS=""
fi
schritt() { printf '\n%s▸ %s%s\n' "$AKZENT" "$1" "$AUS"; }
info() { printf ' %s\n' "$1"; }
leise() { printf ' %s%s%s\n' "$LEISE" "$1" "$AUS"; }
gut() { printf ' %s✓ %s%s\n' "$GUT" "$1" "$AUS"; }
warnung() { printf ' %s! %s%s\n' "$WARN" "$1" "$AUS"; }
abbruch() { printf '\n%s✗ %s%s\n' "$FEHLER" "$1" "$AUS" >&2; exit 1; }
hilfe() {
cat <<'ENDE'
Flimmer aktualisieren
./update.sh [OPTIONEN]
Ablauf: Datenbank sichern → Änderungen holen → Image bauen → neu starten →
warten, bis die Instanz wieder antwortet. Die Datenbankmigrationen spielt der
Container beim Start selbst ein.
Optionen:
--ohne-backup Keine Sicherung anlegen (nicht empfohlen)
--ohne-pull Nichts aus dem Repository holen, nur neu bauen und starten
--behalte N Wie viele Sicherungen aufgehoben werden (Standard: 10)
--hilfe, -h Diese Übersicht
Die Sicherungen liegen unter backups/ und sind gzip-komprimierte pg_dumps.
ENDE
}
# --- Optionen ----------------------------------------------------------------
MIT_BACKUP=1
MIT_PULL=1
BEHALTE=10
while [ $# -gt 0 ]; do
case "$1" in
--ohne-backup) MIT_BACKUP=0 ;;
--ohne-pull) MIT_PULL=0 ;;
--behalte) shift; BEHALTE="${1:-10}" ;;
--hilfe|-h) hilfe; exit 0 ;;
*) abbruch "Unbekannte Option: $1 (./update.sh --hilfe)" ;;
esac
shift
done
# --- Voraussetzungen ---------------------------------------------------------
cd "$(dirname "$0")"
[ -f docker-compose.yml ] || abbruch "Hier liegt keine docker-compose.yml. Das Skript gehört ins Flimmer-Verzeichnis."
[ -f .env ] || abbruch "Es fehlt die .env. Lege sie an: cp .env.example .env"
command -v docker >/dev/null 2>&1 || abbruch "docker ist nicht installiert oder nicht im Pfad."
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
else
abbruch "Weder 'docker compose' noch 'docker-compose' gefunden."
fi
docker info >/dev/null 2>&1 || abbruch "Der Docker-Dienst antwortet nicht. Läuft er?"
# Werte aus der .env lesen, ohne sie auszuführen.
env_wert() {
local schluessel="$1" standard="${2:-}"
local zeile
zeile=$(grep -E "^[[:space:]]*${schluessel}=" .env 2>/dev/null | tail -1 || true)
if [ -z "$zeile" ]; then printf '%s' "$standard"; return; fi
zeile="${zeile#*=}"
zeile="${zeile%\"}"; zeile="${zeile#\"}"
zeile="${zeile%\'}"; zeile="${zeile#\'}"
printf '%s' "$zeile"
}
DB_USER=$(env_wert POSTGRES_USER flimmer)
DB_NAME=$(env_wert POSTGRES_DB flimmer)
PORT=$(env_wert APP_PORT 3000)
printf '%s\n' "Flimmer aktualisieren"
leise "Verzeichnis: $(pwd)"
leise "Compose: $COMPOSE"
# --- Stand festhalten --------------------------------------------------------
IST_GIT=0
VORHER_REF=""
if [ -d .git ] && command -v git >/dev/null 2>&1; then
IST_GIT=1
VORHER_REF=$(git rev-parse --short HEAD 2>/dev/null || echo "unbekannt")
fi
VORHER_VERSION=$(grep -m1 '"version"' package.json 2>/dev/null | sed 's/.*"version"[^"]*"\([^"]*\)".*/\1/' || echo "?")
leise "Aktuell: Version $VORHER_VERSION${VORHER_REF:+ (${VORHER_REF})}"
# --- Sicherung ---------------------------------------------------------------
SICHERUNG=""
if [ "$MIT_BACKUP" -eq 1 ]; then
schritt "Datenbank sichern"
# -q gibt es in beiden Compose-Fassungen; --status erst ab v2.
if [ -z "$($COMPOSE ps -q db 2>/dev/null)" ]; then
warnung "Die Datenbank läuft nicht — es gibt also auch nichts zu sichern."
else
mkdir -p backups
SICHERUNG="backups/flimmer-$(date +%Y-%m-%d-%H%M%S).sql.gz"
if $COMPOSE exec -T db pg_dump -U "$DB_USER" -d "$DB_NAME" --clean --if-exists \
| gzip > "$SICHERUNG" 2>/dev/null && [ -s "$SICHERUNG" ]; then
gut "$SICHERUNG ($(du -h "$SICHERUNG" | cut -f1))"
else
rm -f "$SICHERUNG"
SICHERUNG=""
abbruch "Die Sicherung ist fehlgeschlagen. Abbruch — ohne Sicherung wird hier nichts aktualisiert.
Wenn du es trotzdem willst: ./update.sh --ohne-backup"
fi
# Ältere Sicherungen aufräumen, die jüngsten bleiben.
if [ "$BEHALTE" -gt 0 ]; then
ANZAHL=$(ls -1 backups/flimmer-*.sql.gz 2>/dev/null | wc -l)
if [ "$ANZAHL" -gt "$BEHALTE" ]; then
ls -1t backups/flimmer-*.sql.gz | tail -n +$((BEHALTE + 1)) | xargs -r rm --
leise "Ältere Sicherungen entfernt, die letzten $BEHALTE bleiben."
fi
fi
fi
else
warnung "Ohne Sicherung — auf eigenes Risiko."
fi
# --- Änderungen holen --------------------------------------------------------
if [ "$MIT_PULL" -eq 1 ]; then
schritt "Änderungen holen"
if [ "$IST_GIT" -eq 0 ]; then
warnung "Kein Git-Repository — übersprungen."
elif [ -n "$(git status --porcelain 2>/dev/null)" ]; then
warnung "Es gibt lokale Änderungen; git pull wird übersprungen."
leise "Ansehen mit: git status"
else
ZWEIG=$(git rev-parse --abbrev-ref HEAD)
if git pull --ff-only 2>&1 | sed 's/^/ /'; then
NACHHER_REF=$(git rev-parse --short HEAD)
if [ "$NACHHER_REF" = "$VORHER_REF" ]; then
gut "Schon auf dem neuesten Stand ($ZWEIG)."
else
gut "$VORHER_REF → $NACHHER_REF"
fi
else
abbruch "git pull ist fehlgeschlagen. Vermutlich sind Zweig und Gegenstelle auseinandergelaufen."
fi
fi
fi
NEUE_VERSION=$(grep -m1 '"version"' package.json 2>/dev/null | sed 's/.*"version"[^"]*"\([^"]*\)".*/\1/' || echo "?")
# --- Bauen und starten -------------------------------------------------------
schritt "Image bauen"
info "Das dauert beim ersten Mal einige Minuten."
$COMPOSE build 2>&1 | sed 's/^/ /' || abbruch "Der Build ist fehlgeschlagen. Die laufende Instanz ist unangetastet."
gut "Gebaut."
schritt "Neu starten"
$COMPOSE up -d 2>&1 | sed 's/^/ /' || abbruch "Der Start ist fehlgeschlagen. Log ansehen: $COMPOSE logs -f app"
gut "Container laufen."
# --- Warten, bis die Instanz antwortet ---------------------------------------
schritt "Auf die Instanz warten"
info "Die Datenbankmigrationen spielt der Container beim Start selbst ein."
GESUND=0
for _ in $(seq 1 60); do
if curl -fsS "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then
GESUND=1
break
fi
sleep 2
done
if [ "$GESUND" -eq 1 ]; then
ANTWORT=$(curl -fsS "http://127.0.0.1:${PORT}/api/health" 2>/dev/null || true)
gut "Flimmer antwortet auf Port ${PORT}."
leise "$ANTWORT"
printf '\n%s✓ Fertig — Version %s%s\n' "$GUT" "$NEUE_VERSION" "$AUS"
if [ "$VORHER_VERSION" != "$NEUE_VERSION" ]; then
info "Was sich geändert hat, steht in CHANGELOG.md."
fi
[ -n "$SICHERUNG" ] && leise "Sicherung: $SICHERUNG"
exit 0
fi
# --- Fehlschlag: konkreter Weg zurück ----------------------------------------
printf '\n%s✗ Die Instanz antwortet nach zwei Minuten nicht.%s\n\n' "$FEHLER" "$AUS" >&2
info "Log ansehen:"
printf ' %s logs --tail 50 app\n\n' "$COMPOSE"
if [ -n "$SICHERUNG" ] || [ -n "$VORHER_REF" ]; then
info "Weg zurück:"
if [ "$IST_GIT" -eq 1 ] && [ -n "$VORHER_REF" ]; then
printf ' git checkout %s && %s up -d --build\n' "$VORHER_REF" "$COMPOSE"
fi
if [ -n "$SICHERUNG" ]; then
printf ' gunzip -c %s | %s exec -T db psql -U %s -d %s\n' \
"$SICHERUNG" "$COMPOSE" "$DB_USER" "$DB_NAME"
fi
printf '\n'
fi
exit 1