#!/bin/bash # ESPO-10-Update - hebt EspoCRM auf den Stand des Rings, mit LVM-Snapshot als Rueckweg. # Aufruf: ESPO-10-Update.sh # Ob der Lauf erlaubt ist, entscheidet der Task per If/Then auf espo_zustand (erwartet DEPLOYED). # Der Snapshot bleibt nach dem Lauf absichtlich bestehen - verworfen wird er erst durch ESPO-11, # nachdem jemand geprueft hat, dass die Instanz nach dem Update noch richtig arbeitet. set -u # Standzeile: zeigt im Protokoll, welche Fassung tatsaechlich gelaufen ist. echo "ESPO-10-Update, Skriptstand 2026-09-29" BASIS_URL=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main VG=ubuntu-vg URSPRUNG=/dev/ubuntu-vg/srv-lv SNAPSHOT=srv-vor-update SNAPGROESSE=10G WARTE_MAX=900 FQDN="${1:-}" RING="${2:-}" # Wartet, bis alle vier Container "healthy" melden - "starting" ist keine Aussage. alle_gesund() { local c for c in espocrm-db espocrm espocrm-daemon espocrm-websocket; do [ "$(docker inspect -f '{{.State.Health.Status}}' "$c" 2>/dev/null)" = "healthy" ] || return 1 done return 0 } # Nennt die Container, die noch nicht gesund sind - fuer die Fehlermeldung. nicht_gesund() { local c z="" for c in espocrm-db espocrm espocrm-daemon espocrm-websocket; do [ "$(docker inspect -f '{{.State.Health.Status}}' "$c" 2>/dev/null)" = "healthy" ] \ || z="$z $c=$(docker inspect -f '{{.State.Health.Status}}' "$c" 2>/dev/null || echo fehlt)" done printf '%s' "${z# }" } fehler() { echo "FEHLER: $*"; echo "ESPO10_ERGEBNIS: FEHLER"; exit 1; } # 1. Vorbedingungen - lieber hier abbrechen als auf halbem Weg. [ "$(id -u)" -eq 0 ] || fehler "nicht als root gestartet" command -v docker >/dev/null && docker info >/dev/null 2>&1 || fehler "Docker laeuft nicht" command -v lvcreate >/dev/null || fehler "LVM-Werkzeuge fehlen" [ -n "$FQDN" ] || fehler "kein FQDN uebergeben" [ -n "$RING" ] || fehler "kein Ring uebergeben" [ -b "$URSPRUNG" ] || fehler "$URSPRUNG nicht gefunden - ist /srv wirklich ein eigenes LV?" # 2. Ein Snapshot aus einem frueheren Lauf heisst: Das vorige Update ist nicht bestaetigt. # Zwei Snapshots uebereinander waeren kein Rueckweg mehr, sondern ein Ratespiel. if lvs --noheadings -o lv_name "$VG" 2>/dev/null | grep -qw "$SNAPSHOT"; then fehler "Snapshot '$SNAPSHOT' besteht noch - das vorige Update wurde nicht mit ESPO-11 abgeschlossen" fi # 3. Ausgangsstand festhalten, damit der Bericht ein Vorher und ein Nachher hat. VORHER_IMAGE="$(docker inspect -f '{{.Config.Image}}' espocrm 2>/dev/null)" VORHER_VERSION="$(docker exec -u www-data espocrm php /var/www/html/command.php version 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1)" VORHER_ESPO_ID="$(docker inspect -f '{{.Image}}' espocrm 2>/dev/null)" VORHER_DB_ID="$(docker inspect -f '{{.Image}}' espocrm-db 2>/dev/null)" VORHER_DB_IMAGE="$(docker inspect -f '{{.Config.Image}}' espocrm-db 2>/dev/null)" echo "Vorher: Image ${VORHER_IMAGE:-unbekannt}, Version ${VORHER_VERSION:-unbekannt}" # 4. Erst der Rueckweg, dann das Update. Ohne Snapshot wird nichts angefasst. lvcreate -s -n "$SNAPSHOT" -L "$SNAPGROESSE" "$URSPRUNG" >/dev/null 2>&1 \ || fehler "Snapshot nicht anlegbar (freie Extents in der VG?) - kein Update ohne Rueckweg" lvs --noheadings -o lv_name "$VG" 2>/dev/null | grep -qw "$SNAPSHOT" \ || fehler "Snapshot nach dem Anlegen nicht auffindbar" echo "Snapshot: $SNAPSHOT angelegt ($SNAPGROESSE)" # 5. Stand des Rings holen: Compose und Grundeinstellungen gehoeren zusammen. # Dieselbe Zuordnung steht in bin/espo-compose-rendern - beide zusammen halten. case "$RING" in 0) RINGORDNER="0%20-%20intern" ;; 1) RINGORDNER="1%20-%20Pilotkunde" ;; 2) RINGORDNER="2%20-%20Produktivkunde" ;; *) fehler "unbekannter Ring '$RING' (erwartet 0, 1 oder 2)" ;; esac curl -fsSL "$BASIS_URL/docker/$RINGORDNER/config-override.php" -o /srv/espo/data/config-override.php \ || fehler "config-override.php fuer Ring '$RING' nicht abrufbar" curl -fsSL "$BASIS_URL/bin/espo-compose-rendern" -o /srv/espo/bin/espo-compose-rendern \ || fehler "Render-Skript nicht abrufbar" chmod 0755 /srv/espo/bin/espo-compose-rendern /srv/espo/bin/espo-compose-rendern "$FQDN" "$RING" || fehler "Compose konnte nicht geschrieben werden" # 6. Anwenden. Der Entrypoint startet die Migration von sich aus, sobald das Image wechselt. START_AUSGABE="$(docker compose -f /srv/espo/docker-compose.yml up -d --quiet-pull 2>&1)" || { echo "$START_AUSGABE" | tail -n 20 fehler "Start nach dem Update fehlgeschlagen - Snapshot '$SNAPSHOT' steht fuer den Rueckweg bereit" } # 7. Auf die Anwendung warten. Waehrend der Migration meldet app-check absichtlich nicht gesund. ENDE=$(( SECONDS + WARTE_MAX )) while [ "$SECONDS" -lt "$ENDE" ]; do alle_gesund && break sleep 10 done alle_gesund || fehler "nach ${WARTE_MAX}s nicht alle Container gesund: $(nicht_gesund) - 'docker logs ' ansehen" # 8. Ergebnis zeigen. echo "--- Container" docker compose -f /srv/espo/docker-compose.yml ps --format 'table {{.Name}}\t{{.Status}}' echo "--- Instanz" NACHHER_IMAGE="$(docker inspect -f '{{.Config.Image}}' espocrm 2>/dev/null)" NACHHER_ESPO_ID="$(docker inspect -f '{{.Image}}' espocrm 2>/dev/null)" NACHHER_VERSION="$(docker exec -u www-data espocrm php /var/www/html/command.php version 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1)" echo "Image: ${VORHER_IMAGE:-unbekannt} -> ${NACHHER_IMAGE:-unbekannt}" echo "Version: ${VORHER_VERSION:-unbekannt} -> ${NACHHER_VERSION:-unbekannt}" echo "Digest: $(docker image inspect --format '{{if .RepoDigests}}{{index .RepoDigests 0}}{{else}}{{.Id}}{{end}}' "$NACHHER_IMAGE" 2>/dev/null)" echo "--- Datenbank" NACHHER_DB_IMAGE="$(docker inspect -f '{{.Config.Image}}' espocrm-db 2>/dev/null)" NACHHER_DB_ID="$(docker inspect -f '{{.Image}}' espocrm-db 2>/dev/null)" echo "Image: ${VORHER_DB_IMAGE:-unbekannt} -> ${NACHHER_DB_IMAGE:-unbekannt}" echo "Version: $(docker exec espocrm-db sh -c 'mariadb -u root -p"$(cat /run/secrets/db_root_password)" -N -B -e "select version()"' 2>/dev/null | tr -d '\r')" echo "Digest: $(docker image inspect --format '{{if .RepoDigests}}{{index .RepoDigests 0}}{{else}}{{.Id}}{{end}}' "$NACHHER_DB_IMAGE" 2>/dev/null)" # Alte Images wegraeumen: Es bleiben genau zwei je Anwendung - das laufende und das vorherige. # Das vorherige muss bleiben, sonst zeigt die zurueckgerollte Compose nach ESPO-12 auf einen Tag, # der lokal nicht mehr liegt. Alles aeltere ist nur Platzverbrauch auf dem docker-lv. aufraeumen() { # aufraeumen local repo="$1" a="$2" b="$3" id docker image ls --no-trunc --format '{{.ID}} {{.Repository}}' 2>/dev/null \ | awk -v r="$repo" '$2 == r {print $1}' | sort -u | while read -r id; do [ "$id" = "$a" ] || [ "$id" = "$b" ] || docker rmi "$id" >/dev/null 2>&1 || true done } aufraeumen espocrm "$NACHHER_ESPO_ID" "$VORHER_ESPO_ID" aufraeumen mariadb "$NACHHER_DB_ID" "$VORHER_DB_ID" echo "--- Images auf der VM (behalten: laufendes und vorheriges)" docker image ls --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' 2>/dev/null \ | grep -E 'REPOSITORY|^espocrm|^mariadb' | sed 's/^/ /' echo "--- Rueckweg" lvs --noheadings -o lv_name,lv_attr,origin,lv_size,data_percent "$VG/$SNAPSHOT" 2>/dev/null | sed 's/^ */ /' echo "--- Naechster Schritt von Hand" echo "Instanz pruefen (Anmeldung, ein Datensatz, E-Mail, WebSocket). Erst danach ESPO-11 ausfuehren," echo "das den Snapshot verwirft. Solange er besteht, kostet jeder Schreibvorgang zusaetzliche Zeit," echo "und laeuft er voll, verwirft LVM ihn stillschweigend - dann ist der Rueckweg weg." echo "ESPO10_ERGEBNIS: OK"