235 lines
7.5 KiB
Bash
Executable File
235 lines
7.5 KiB
Bash
Executable File
#!/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
|