#!/bin/bash # ESPO-22-Demo-Stand-sichern - sichert den Anwendungsstand einer Demo-Instanz als Grundstand fuer ESPO-23. # Aufruf: ohne Argumente, von Hand - nach dem Einrichten der Demo-Daten und nach jedem ESPO-10 oder ESPO-30. # Laeuft nur auf Demo-Instanzen (Hostname espo-demo*): Der Grundstand ist die Vorlage fuer das naechtliche # Zuruecksetzen, und das darf es auf einer Kundeninstanz nie geben (ADR 0014). set -u # Standzeile: zeigt im Protokoll, welche Fassung tatsaechlich gelaufen ist. echo "ESPO-22-Demo-Stand-sichern, Skriptstand 2026-10-01" BASIS=/srv/espo GRUNDSTAND=/srv/espo-baseline NEU=/srv/espo-baseline.new COMPOSE=$BASIS/docker-compose.yml VG=ubuntu-vg SNAPSHOT=srv-vor-update WARTE_MAX=600 # Was Demo-Benutzer ueber die Anwendung aendern koennen. Nicht dabei: secrets/ (pflegt ESPO-13), Compose, # bin/ und data/config-override.php (pflegen ESPO-03 und ESPO-10) sowie data/logs/ (Verlauf der Demo). VERZEICHNISSE="mariadb data custom client-custom" fehler() { echo "FEHLER: $*"; echo "ESPO22_ERGEBNIS: FEHLER"; exit 1; } # 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# }" } # Die drei Merkmale, an denen ESPO-23 erkennt, ob der Grundstand noch passt - alle aus Dateien lesbar, # also auch bei gestoppten Containern: # Version, wie EspoCRM sie in data/state.php fuehrt. espo_version() { sed -n "s/^[[:space:]]*'version' => '\([^']*\)'.*/\1/p" "$1/data/state.php" 2>/dev/null | head -n 1; } # MariaDB-Image laut Compose - das, was beim naechsten Start laeuft. db_image() { sed -n 's/^[[:space:]]*image:[[:space:]]*\(mariadb:[^[:space:]]*\).*/\1/p' "$COMPOSE" 2>/dev/null | head -n 1; } # Je installierter Extension legt EspoCRM ein Verzeichnis unter data/.backup/extensions an; jede Installation # ueber ESPO-30 aendert diese Liste. Demo-Benutzer ohne Adminrechte koennen das nicht. extension_ids() { ls -1 "$1/data/.backup/extensions" 2>/dev/null | sort | paste -sd ' '; } # 1. Vorbedingungen. [ "$(id -u)" -eq 0 ] || fehler "nicht als root gestartet" [[ "$(hostname -s)" == espo-demo* ]] || fehler "nur auf Demo-Instanzen (Hostname espo-demo*), hier: $(hostname -s)" command -v docker >/dev/null && docker info >/dev/null 2>&1 || fehler "Docker laeuft nicht" [ -f "$COMPOSE" ] || fehler "$COMPOSE fehlt - ist die Instanz deployt?" if ! command -v rsync >/dev/null; then DEBIAN_FRONTEND=noninteractive apt-get install -y -q rsync >/dev/null 2>&1 || fehler "rsync fehlt und ist nicht installierbar" echo "rsync installiert" fi if lvs --noheadings -o lv_name "$VG" 2>/dev/null | grep -qw "$SNAPSHOT"; then fehler "Snapshot '$SNAPSHOT' besteht - die letzte Aenderung erst mit ESPO-11 bestaetigen oder mit ESPO-12 zuruecknehmen" fi alle_gesund || fehler "Instanz nicht gesund: $(nicht_gesund) - gesichert wird nur ein funktionierender Stand" # 2. Merkmale und lesbare Angaben festhalten, solange die Anwendung laeuft. VERSION="$(espo_version "$BASIS")" [ -n "$VERSION" ] || fehler "EspoCRM-Version in data/state.php nicht lesbar" DBIMAGE="$(db_image)" [ -n "$DBIMAGE" ] || fehler "MariaDB-Image in der Compose nicht gefunden" IDS="$(extension_ids "$BASIS")" EXTENSIONS="$(docker exec -u www-data espocrm php /var/www/html/command.php extension --list 2>/dev/null \ | awk '/^ Name: /{n=substr($0,8)} /^ Version: /{v=substr($0,11)} /^ Installed: yes/{print n " " v}' \ | sort | paste -sd ';' | sed 's/;/; /g')" [ -n "$EXTENSIONS" ] || EXTENSIONS="none" # 3. Platz pruefen: Der neue Grundstand entsteht neben dem alten und ersetzt ihn erst, wenn er vollstaendig ist. BEDARF_KB="$(cd "$BASIS" && du -sk -c --exclude=data/logs $VERZEICHNISSE 2>/dev/null | tail -n 1 | cut -f1)" FREI_KB="$(df -Pk /srv | awk 'NR == 2 {print $4}')" [ "$FREI_KB" -gt $(( BEDARF_KB * 12 / 10 )) ] \ || fehler "zu wenig Platz auf /srv: gebraucht etwa $(( BEDARF_KB / 1024 )) MB, frei $(( FREI_KB / 1024 )) MB" # 4. Stack stoppen - die MariaDB-Dateien sind nur im Stillstand konsistent. Was auch passiert: Am Ende laeuft # die Demo wieder, und ein halber neuer Grundstand bleibt nicht liegen. GESTOPPT=0 trap '[ "$GESTOPPT" -eq 1 ] && docker compose -f "$COMPOSE" up -d >/dev/null 2>&1; rm -rf "$NEU"' EXIT docker compose -f "$COMPOSE" stop >/dev/null 2>&1 || fehler "Stack nicht stoppbar" GESTOPPT=1 # 5. Kopieren. Besitzer und Rechte bleiben erhalten (MariaDB und www-data laufen mit eigenen Kennungen). rm -rf "$NEU" install -d -m 0700 "$NEU" || fehler "$NEU nicht anlegbar" for V in $VERZEICHNISSE; do AUSNAHMEN=() [ "$V" = "data" ] && AUSNAHMEN=(--exclude=/logs/ --exclude=/config-override.php) rsync -a --numeric-ids --delete "${AUSNAHMEN[@]}" "$BASIS/$V/" "$NEU/$V/" || fehler "Kopie von $V fehlgeschlagen" done cat > "$NEU/manifest" <&1)" || { printf '%s\n' "$START" | tail -n 10; fehler "Start fehlgeschlagen"; } GESTOPPT=0 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)" # 8. Bericht. echo "--- Grundstand" echo "Ablage: $GRUNDSTAND ($(du -sh "$GRUNDSTAND" 2>/dev/null | cut -f1))" echo "EspoCRM: $VERSION" echo "MariaDB: $DBIMAGE" echo "Extensions: $EXTENSIONS" echo "--- Naechster Schritt" echo "ESPO-23 setzt die Instanz ab jetzt auf genau diesen Stand zurueck. Nach jedem ESPO-10 oder ESPO-30" echo "auf dieser Instanz ESPO-22 erneut ausfuehren - sonst bricht ESPO-23 mit einem Hinweis ab." echo "ESPO22_ERGEBNIS: OK"