Zustandsriegel: die drei Einrichtungsskripte laufen nur bei espo_zustand = NEU

Statt lokaler Merkmale steuert das Custom Field espo_zustand zentral, was laufen
darf. Jedes der drei Skripte nimmt den Wert als Argument und bricht ohne
Aenderung ab, wenn er nicht NEU ist.

In ESPO-03 bleibt die Pruefung auf eine vorhandene Instanz als Rueckfall
erhalten: Der Zustand im RMM ist von Hand aenderbar, eine laufende Instanz ist
es nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
gordon.zimm 2026-09-23 15:10:15 +02:00
parent 25b2603856
commit dcd99c3ba9
3 changed files with 35 additions and 13 deletions

View File

@ -1,12 +1,21 @@
#!/bin/bash #!/bin/bash
# ESPO-01-Grundeinrichtung – schliesst die Basis-VM ab. # ESPO-01-Grundeinrichtung – schliesst die Basis-VM ab.
# Laeuft als root, ist idempotent und kann jederzeit wiederholt werden. # Laeuft als root, ist idempotent und kann jederzeit wiederholt werden.
# Aufruf: bash ESPO-01-Grundeinrichtung.sh "<Kundenname>" # Aufruf: bash ESPO-01-Grundeinrichtung.sh "<Kundenname>" "<Zustand>"
# Der RMM-Task ist nur ein duenner Ausloeser, der dieses Skript holt und mit %companyname% aufruft. # Der RMM-Task ist nur ein duenner Ausloeser, der dieses Skript holt und mit %companyname%
# und dem Wert des Custom Field espo_zustand aufruft.
set -u set -u
KUNDE_ROH="${1:-}" KUNDE_ROH="${1:-}"
ZUSTAND="${2:-}"
FEHLER=0 FEHLER=0
# 0. Zustandsriegel: Die Einrichtung laeuft nur an einer Instanz, die im RMM als NEU gefuehrt wird.
# Das Custom Field espo_zustand steuert damit zentral, was laufen darf.
if [ "$ZUSTAND" != "NEU" ]; then
echo "FEHLER: espo_zustand ist '${ZUSTAND:-leer}', erwartet NEU - Abbruch ohne Aenderung"
echo "ESPO01_ERGEBNIS: FEHLER"; exit 1
fi
# 1. Hostname aus dem Kundennamen ableiten (klein, nur a-z 0-9 und Bindestrich), damit die # 1. Hostname aus dem Kundennamen ableiten (klein, nur a-z 0-9 und Bindestrich), damit die
# Flotte im RMM und in Logs eindeutig benannt ist. # Flotte im RMM und in Logs eindeutig benannt ist.
KUNDE=$(printf '%s' "$KUNDE_ROH" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/-\+/-/g; s/^-//; s/-$//') KUNDE=$(printf '%s' "$KUNDE_ROH" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/-\+/-/g; s/^-//; s/-$//')

View File

@ -1,10 +1,19 @@
#!/bin/bash #!/bin/bash
# ESPO-02-Docker - installiert Docker in gepinnter Version, setzt die Daemon-Grundlagen und # ESPO-02-Docker - installiert Docker in gepinnter Version, setzt die Daemon-Grundlagen und
# beschraenkt den Zugang zu veroeffentlichten Container-Ports auf das eigene Subnetz. # beschraenkt den Zugang zu veroeffentlichten Container-Ports auf das eigene Subnetz.
# Laeuft als root ueber einen RMM-Bash-Task, idempotent. # Aufruf: bash ESPO-02-Docker.sh "<Zustand>" (Zustand = Wert des Custom Field espo_zustand)
# Laeuft als root ueber einen RMM-Task, idempotent.
set -u set -u
ZUSTAND="${1:-}"
FEHLER=0 FEHLER=0
# 0. Zustandsriegel: Die Einrichtung laeuft nur an einer Instanz, die im RMM als NEU gefuehrt wird.
# Das Custom Field espo_zustand steuert damit zentral, was laufen darf.
if [ "$ZUSTAND" != "NEU" ]; then
echo "FEHLER: espo_zustand ist '${ZUSTAND:-leer}', erwartet NEU - Abbruch ohne Aenderung"
echo "ESPO02_ERGEBNIS: FEHLER"; exit 1
fi
# Gepinnte Staende - hier wird der Flottenstand gesetzt, nicht auf der VM entschieden. # Gepinnte Staende - hier wird der Flottenstand gesetzt, nicht auf der VM entschieden.
DOCKER_VERSION="5:29.8.1-1~ubuntu.24.04~noble" DOCKER_VERSION="5:29.8.1-1~ubuntu.24.04~noble"
CONTAINERD_VERSION="2.3.5-1~ubuntu.24.04~noble" CONTAINERD_VERSION="2.3.5-1~ubuntu.24.04~noble"

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ESPO-03-First-Deploy - richtet EspoCRM auf einer vorbereiteten VM zum ersten Mal ein. # ESPO-03-First-Deploy - richtet EspoCRM auf einer vorbereiteten VM zum ersten Mal ein.
# Aufruf: ESPO-03-First-Deploy.sh <fqdn> <ring> # Aufruf: ESPO-03-First-Deploy.sh <fqdn> <ring> <zustand>
# Laeuft nur auf einer VM OHNE vorhandene Instanz. Fuer Aktualisierungen ist der Update-Weg zustaendig; # Laeuft nur auf einer VM OHNE vorhandene Instanz. Fuer Aktualisierungen ist der Update-Weg zustaendig;
# dieses Skript fasst eine bestehende Instanz nicht an, damit keine Migration ohne geprueftes Backup startet. # dieses Skript fasst eine bestehende Instanz nicht an, damit keine Migration ohne geprueftes Backup startet.
set -u set -u
@ -9,28 +9,32 @@ BASIS_URL=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main
WARTE_MAX=900 WARTE_MAX=900
FQDN="${1:-}" FQDN="${1:-}"
RING="${2:-}" RING="${2:-}"
ZUSTAND="${3:-}"
fehler() { echo "FEHLER: $*"; echo "ESPO03_ERGEBNIS: FEHLER"; exit 1; } fehler() { echo "FEHLER: $*"; echo "ESPO03_ERGEBNIS: FEHLER"; exit 1; }
# 1. Vorbedingungen - lieber hier abbrechen als auf halbem Weg. # 1. Zustandsriegel: Der First Deploy laeuft nur an einer Instanz, die im RMM als NEU gefuehrt wird.
[ "$ZUSTAND" = "NEU" ] || fehler "espo_zustand ist '${ZUSTAND:-leer}', erwartet NEU - Abbruch ohne Aenderung"
# 2. Vorbedingungen - lieber hier abbrechen als auf halbem Weg.
[ "$(id -u)" -eq 0 ] || fehler "nicht als root gestartet" [ "$(id -u)" -eq 0 ] || fehler "nicht als root gestartet"
command -v docker >/dev/null || fehler "Docker fehlt - erst ESPO-02 ausfuehren" command -v docker >/dev/null || fehler "Docker fehlt - erst ESPO-02 ausfuehren"
docker info >/dev/null 2>&1 || fehler "Docker laeuft nicht - erst ESPO-02 pruefen" docker info >/dev/null 2>&1 || fehler "Docker laeuft nicht - erst ESPO-02 pruefen"
[ -n "$FQDN" ] || fehler "kein FQDN uebergeben" [ -n "$FQDN" ] || fehler "kein FQDN uebergeben"
[ -n "$RING" ] || fehler "kein Ring uebergeben" [ -n "$RING" ] || fehler "kein Ring uebergeben"
# 2. Eine vorhandene Instanz ist der Abbruchgrund dieses Skripts - genau dafuer heisst es First Deploy. # 3. Rueckfallpruefung: Der Zustand im RMM ist von Hand aenderbar, eine vorhandene Instanz ist es nicht.
for C in espocrm espocrm-db espocrm-daemon espocrm-websocket; do for C in espocrm espocrm-db espocrm-daemon espocrm-websocket; do
docker ps -a --format '{{.Names}}' | grep -qx "$C" && fehler "Container '$C' existiert bereits - hier ist der Update-Weg zustaendig" docker ps -a --format '{{.Names}}' | grep -qx "$C" && fehler "Container '$C' existiert bereits - hier ist der Update-Weg zustaendig"
done done
[ -e /srv/espo/docker-compose.yml ] && fehler "/srv/espo/docker-compose.yml existiert bereits - hier ist der Update-Weg zustaendig" [ -e /srv/espo/docker-compose.yml ] && fehler "/srv/espo/docker-compose.yml existiert bereits - hier ist der Update-Weg zustaendig"
[ -e /srv/espo/data/config.php ] && fehler "EspoCRM ist bereits installiert (data/config.php) - hier ist der Update-Weg zustaendig" [ -e /srv/espo/data/config.php ] && fehler "EspoCRM ist bereits installiert (data/config.php) - hier ist der Update-Weg zustaendig"
# 3. Verzeichnisse anlegen. Die Eigentuemer setzen die Images beim ersten Start selbst. # 4. Verzeichnisse anlegen. Die Eigentuemer setzen die Images beim ersten Start selbst.
install -d -m 0755 /srv/espo /srv/espo/bin /srv/espo/mariadb /srv/espo/data /srv/espo/custom /srv/espo/client-custom install -d -m 0755 /srv/espo /srv/espo/bin /srv/espo/mariadb /srv/espo/data /srv/espo/custom /srv/espo/client-custom
install -d -m 0700 /srv/espo/secrets install -d -m 0700 /srv/espo/secrets
# 4. Geheimnisse auf der VM erzeugen - sie stehen in keinem Skript, keiner Ausgabe und keinem Repository. # 5. Geheimnisse auf der VM erzeugen - sie stehen in keinem Skript, keiner Ausgabe und keinem Repository.
# Ohne abschliessenden Zeilenumbruch, damit die _FILE-Auswertung der Images genau diesen Wert sieht. # Ohne abschliessenden Zeilenumbruch, damit die _FILE-Auswertung der Images genau diesen Wert sieht.
umask 077 umask 077
for G in db_root_password db_password; do for G in db_root_password db_password; do
@ -40,18 +44,18 @@ for G in db_root_password db_password; do
done done
umask 022 umask 022
# 5. Grundeinstellungen bereitlegen, bevor EspoCRM zum ersten Mal startet. # 6. Grundeinstellungen bereitlegen, bevor EspoCRM zum ersten Mal startet.
curl -fsSL "$BASIS_URL/docker/config-override.php" -o /srv/espo/data/config-override.php || fehler "config-override.php nicht abrufbar" curl -fsSL "$BASIS_URL/docker/config-override.php" -o /srv/espo/data/config-override.php || fehler "config-override.php nicht abrufbar"
# 6. Compose aus dem Stand des Rings schreiben. # 7. Compose aus dem Stand des Rings schreiben.
curl -fsSL "$BASIS_URL/bin/espo-compose-rendern" -o /srv/espo/bin/espo-compose-rendern || fehler "Render-Skript 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 chmod 0755 /srv/espo/bin/espo-compose-rendern
/srv/espo/bin/espo-compose-rendern "$FQDN" "$RING" || fehler "Compose konnte nicht geschrieben werden" /srv/espo/bin/espo-compose-rendern "$FQDN" "$RING" || fehler "Compose konnte nicht geschrieben werden"
# 7. Starten. Die Images werden dabei zum ersten Mal geholt. # 8. Starten. Die Images werden dabei zum ersten Mal geholt.
docker compose -f /srv/espo/docker-compose.yml up -d || fehler "Start fehlgeschlagen" docker compose -f /srv/espo/docker-compose.yml up -d || fehler "Start fehlgeschlagen"
# 8. Auf die Anwendung warten. Der erste Lauf installiert und baut das Schema auf, das dauert. # 9. Auf die Anwendung warten. Der erste Lauf installiert und baut das Schema auf, das dauert.
ENDE=$(( SECONDS + WARTE_MAX )) ENDE=$(( SECONDS + WARTE_MAX ))
while [ "$SECONDS" -lt "$ENDE" ]; do while [ "$SECONDS" -lt "$ENDE" ]; do
[ "$(docker inspect -f '{{.State.Health.Status}}' espocrm 2>/dev/null)" = "healthy" ] && break [ "$(docker inspect -f '{{.State.Health.Status}}' espocrm 2>/dev/null)" = "healthy" ] && break
@ -60,7 +64,7 @@ done
[ "$(docker inspect -f '{{.State.Health.Status}}' espocrm 2>/dev/null)" = "healthy" ] \ [ "$(docker inspect -f '{{.State.Health.Status}}' espocrm 2>/dev/null)" = "healthy" ] \
|| fehler "espocrm nach ${WARTE_MAX}s nicht gesund - 'docker logs espocrm' ansehen" || fehler "espocrm nach ${WARTE_MAX}s nicht gesund - 'docker logs espocrm' ansehen"
# 9. Ergebnis zeigen. Die Datenbankabfragen laufen im Container und lesen das Passwort dort aus der Secret-Datei, # 10. Ergebnis zeigen. Die Datenbankabfragen laufen im Container und lesen das Passwort dort aus der Secret-Datei,
# damit es weder im Skript noch in der Prozessliste der VM auftaucht. # damit es weder im Skript noch in der Prozessliste der VM auftaucht.
echo "--- Container" echo "--- Container"
docker compose -f /srv/espo/docker-compose.yml ps --format 'table {{.Name}}\t{{.Status}}' docker compose -f /srv/espo/docker-compose.yml ps --format 'table {{.Name}}\t{{.Status}}'