ESPO-22 kopiert bei gestopptem Stack den Anwendungsstand (mariadb, data, custom, client-custom) nach /srv/espo-baseline - ohne secrets, Compose, bin, data/config-override.php und data/logs, die von Tasks gepflegt werden bzw. als Verlauf bleiben. Dazu ein Manifest mit EspoCRM-Version, MariaDB-Image und den Extension-Kennungen; der neue Grundstand ersetzt den alten erst, wenn er vollstaendig ist. ESPO-23 spielt den Grundstand per rsync zurueck (taeglich 02:00) und bricht ab, wenn seit dem Sichern ESPO-10 oder ESPO-30 gelaufen ist oder ein Snapshot offen ist. Beide Skripte laufen nur auf Hostnamen espo-demo*; nach einem Fehler startet die Demo in jedem Fall wieder. In einer Sandbox mit allen Sperren und dem Fehlerfall nach dem Stoppen getestet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
6.5 KiB
Bash
136 lines
6.5 KiB
Bash
#!/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" <<EOF
|
|
created=$(date -Iseconds)
|
|
espo_version=$VERSION
|
|
db_image=$DBIMAGE
|
|
extension_ids=$IDS
|
|
extensions=$EXTENSIONS
|
|
EOF
|
|
|
|
# 6. Den alten Grundstand erst ersetzen, wenn der neue vollstaendig ist.
|
|
rm -rf "$GRUNDSTAND.old"
|
|
if [ -d "$GRUNDSTAND" ]; then mv "$GRUNDSTAND" "$GRUNDSTAND.old" || fehler "alter Grundstand nicht verschiebbar"; fi
|
|
if ! mv "$NEU" "$GRUNDSTAND"; then
|
|
[ -d "$GRUNDSTAND.old" ] && mv "$GRUNDSTAND.old" "$GRUNDSTAND"
|
|
fehler "neuer Grundstand nicht ablegbar - der alte bleibt gueltig"
|
|
fi
|
|
rm -rf "$GRUNDSTAND.old"
|
|
|
|
# 7. Wieder starten und warten.
|
|
START="$(docker compose -f "$COMPOSE" up -d 2>&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"
|