Version 1.0.0 — Freigabe für den Echtbetrieb (#17)
This commit was merged in pull request #17.
This commit is contained in:
@@ -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