Ringe als Ordner: docker/<ring>/docker-compose.yml

Statt einer Vorlage fuer alle liegt je Ring eine vollstaendige Compose im Repo.
Eine Freigabe ist damit das Kopieren der Datei in den naechsten Ordner und in
jedem git diff sichtbar. Fuer den Anfang ist nur "0 - intern" gefuellt; die
beiden anderen Ordner erklaeren, dass dort noch kein Stand freigegeben ist.

espo-compose-rendern nimmt jetzt <fqdn> und <ring> und bricht ab, wenn fuer den
Ring kein Stand hinterlegt ist. Die Ringwerte 0/1/2 entsprechen dem Custom Field
espo_ring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
gordon.zimm 2026-09-23 14:40:48 +02:00
parent 996490075a
commit 823cf43135
6 changed files with 49 additions and 22 deletions

View File

@ -8,7 +8,7 @@ es steht deshalb **kein Passwort, kein Token und keine Kundenangabe** in diesem
| `autoinstall/` | NoCloud-Seed für die Basis-VM (`user-data`, `meta-data`) |
| `rmm-tasks/` | Skripte, die als Task im ConnectWise RMM hinterlegt sind (Quelle für Änderungen) |
| `bin/` | Skripte, die auf der VM unter `/srv/espo/bin/` liegen und von Tasks aufgerufen werden |
| `compose/` | Compose-Vorlage der Kundeninstanz und die EspoCRM-Grundeinstellungen (`config-override.php`) |
| `docker/` | je Ring ein Ordner mit der `docker-compose.yml` dieses Standes, dazu die EspoCRM-Grundeinstellungen (`config-override.php`) |
Konzept, Entscheidungen und Runbooks liegen im internen Repository `espoCRM/espo-fleet-ops`.
@ -20,12 +20,21 @@ autoinstall ds=nocloud-net\;s=https://gitea.subcura.net/EspoCRM_Public/espo-depl
Das Semikolon muss maskiert werden – in GRUB trennt ein unmaskiertes `;` zwei Befehle.
## Compose
## Compose und Ringe
`compose/docker-compose.vorlage.yml` ist für alle Kunden identisch und enthält genau einen Platzhalter: `__FQDN__`.
`bin/espo-compose-rendern <fqdn>` setzt ihn ein und schreibt `/srv/espo/docker-compose.yml`. Diese Datei auf der VM ist
ein **Erzeugnis** – sie wird bei jedem Lauf neu geschrieben, Änderungen von Hand gehen verloren. Geändert wird die
Vorlage hier im Repository.
| Ordner | Ring | Inhalt |
|---|---|---|
| `docker/0 - intern/` | 0 | der aktuelle Stand, hier wird entwickelt und getestet |
| `docker/1 - Pilotkunde/` | 1 | freigegebener Stand für Pilotkunden |
| `docker/2 - Produktivkunde/` | 2 | freigegebener Stand für den Regelbetrieb |
Jeder Ordner enthält eine vollständige `docker-compose.yml` mit genau einem Platzhalter: `__FQDN__`.
**Eine Freigabe ist das Kopieren dieser Datei in den nächsten Ordner** – damit steht in jedem `git diff`, welche
Instanzen den neuen Stand bekommen.
`bin/espo-compose-rendern <fqdn> <ring>` holt den Stand des Rings, setzt den FQDN ein und schreibt
`/srv/espo/docker-compose.yml`. Diese Datei auf der VM ist ein **Erzeugnis** – sie wird bei jedem Lauf neu geschrieben,
Änderungen von Hand gehen verloren. Geändert wird im Repository.
## Kundenspezifisches

View File

@ -1,13 +1,13 @@
#!/bin/bash
# espo-compose-rendern - erzeugt /srv/espo/docker-compose.yml aus der versionierten Vorlage.
# Einziger kundenspezifischer Wert ist der FQDN; er kommt aus dem Custom Field espo_fqdn.
# Aufruf: espo-compose-rendern <fqdn> (von ESPO-10 und ESPO-30)
# espo-compose-rendern - schreibt /srv/espo/docker-compose.yml aus dem Stand des jeweiligen Rings.
# Aufruf: espo-compose-rendern <fqdn> <ring> (aus ESPO-10-Deploy; fqdn und ring kommen aus den Custom Fields)
# Idempotent: gleiche Eingabe, gleiche Datei; geschrieben wird nur bei Unterschied.
set -u
VORLAGE_URL=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main/compose/docker-compose.vorlage.yml
BASIS_URL=https://gitea.subcura.net/EspoCRM_Public/espo-deploy/raw/branch/main/docker
ZIEL=/srv/espo/docker-compose.yml
FQDN="${1:-}"
RING="${2:-}"
# 1. Ohne gueltigen FQDN waeren Site-URL und WebSocket-Adresse falsch - hier abbrechen, nicht spaeter raten.
if ! printf '%s' "$FQDN" | grep -Eq '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$'; then
@ -15,29 +15,38 @@ if ! printf '%s' "$FQDN" | grep -Eq '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([
echo "ESPO_COMPOSE_ERGEBNIS: FEHLER"; exit 1
fi
# 2. Vorlage holen - geheimnisfrei und ohne Anmeldung abrufbar.
# 2. Der Ring bestimmt, welcher Stand gilt. Die Ordnernamen im Repo tragen Leerzeichen, daher kodiert.
case "$RING" in
0) ORDNER="0%20-%20intern" ; KLAR="0 - intern" ;;
1) ORDNER="1%20-%20Pilotkunde" ; KLAR="1 - Pilotkunde" ;;
2) ORDNER="2%20-%20Produktivkunde" ; KLAR="2 - Produktivkunde" ;;
*) echo "FEHLER: unbekannter Ring '$RING' (erwartet 0, 1 oder 2)"
echo "ESPO_COMPOSE_ERGEBNIS: FEHLER"; exit 1 ;;
esac
# 3. Stand des Rings holen - geheimnisfrei und ohne Anmeldung abrufbar.
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
curl -fsSL "$VORLAGE_URL" -o "$TMP/vorlage.yml" || {
echo "FEHLER: Vorlage nicht abrufbar ($VORLAGE_URL)"
curl -fsSL "$BASIS_URL/$ORDNER/docker-compose.yml" -o "$TMP/stand.yml" || {
echo "FEHLER: fuer Ring '$KLAR' ist kein Stand hinterlegt oder das Repository ist nicht erreichbar"
echo "ESPO_COMPOSE_ERGEBNIS: FEHLER"; exit 1; }
# 3. Platzhalter ersetzen.
sed "s|__FQDN__|${FQDN}|g" "$TMP/vorlage.yml" > "$TMP/neu.yml"
# 4. Platzhalter ersetzen.
sed "s|__FQDN__|${FQDN}|g" "$TMP/stand.yml" > "$TMP/neu.yml"
# 4. Kein Platzhalter darf uebrig bleiben, sonst startete Docker mit einer unfertigen Datei.
# 5. Kein Platzhalter darf uebrig bleiben, sonst startete Docker mit einer unfertigen Datei.
OFFEN="$(grep -oE '__[A-Z][A-Z0-9_]*__' "$TMP/neu.yml" | sort -u | tr '\n' ' ')"
if [ -n "$OFFEN" ]; then
echo "FEHLER: unersetzte Platzhalter: $OFFEN"
echo "ESPO_COMPOSE_ERGEBNIS: FEHLER"; exit 1
fi
# 5. Die drei EspoCRM-Dienste teilen ueber volumes_from denselben Code - verschiedene Images waeren ein Fehler.
# 6. Die drei EspoCRM-Dienste teilen ueber volumes_from denselben Code - verschiedene Images waeren ein Fehler.
if [ "$(grep -E '^[[:space:]]+image: espocrm' "$TMP/neu.yml" | sort -u | wc -l)" -ne 1 ]; then
echo "FEHLER: die EspoCRM-Dienste verwenden verschiedene Images"
echo "ESPO_COMPOSE_ERGEBNIS: FEHLER"; exit 1
fi
# 6. Nur bei Unterschied schreiben, damit wiederholte Laeufe nichts anfassen.
# 7. Nur bei Unterschied schreiben, damit wiederholte Laeufe nichts anfassen.
install -d -m 0755 /srv/espo
if [ -f "$ZIEL" ] && cmp -s "$TMP/neu.yml" "$ZIEL"; then
STATUS=UNVERAENDERT
@ -46,12 +55,12 @@ else
STATUS=GEAENDERT
fi
# 7. Docker selbst pruefen lassen, sobald die Geheimnisdateien liegen - startet nichts.
# 8. Docker selbst pruefen lassen, sobald die Geheimnisdateien liegen - startet nichts.
if [ -r /srv/espo/secrets/db_password ]; then
docker compose -f "$ZIEL" config -q || {
echo "FEHLER: Compose-Datei nicht gueltig"
echo "ESPO_COMPOSE_ERGEBNIS: FEHLER"; exit 1; }
fi
echo "$ZIEL $STATUS (FQDN $FQDN)"
echo "$ZIEL $STATUS (Ring '$KLAR', FQDN $FQDN)"
echo "ESPO_COMPOSE_ERGEBNIS: OK"

View File

@ -1,6 +1,7 @@
# Vorlage fuer die Kunden-VM. Einziger kundenspezifischer Wert: __FQDN__.
# Gerendert von bin/espo-compose-rendern nach /srv/espo/docker-compose.yml.
# Stand fuer Ring 0 (intern). Einziger kundenspezifischer Wert: __FQDN__.
# espo-compose-rendern <fqdn> <ring> setzt ihn ein und schreibt /srv/espo/docker-compose.yml.
# Aenderungen ausschliesslich hier im Repository, nie auf der VM.
# Freigabe in den naechsten Ring = diese Datei in dessen Ordner kopieren.
name: espocrm
services:

View File

@ -0,0 +1,4 @@
# Ring 1 - Pilotkunde
Für diesen Ring ist noch **kein Stand freigegeben**. Sobald ein Stand aus Ring 0 freigegeben wird, kommt die
dazugehörige `docker-compose.yml` hierher. Bis dahin bricht `espo-compose-rendern` für diesen Ring ab.

View File

@ -0,0 +1,4 @@
# Ring 2 - Produktivkunde
Für diesen Ring ist noch **kein Stand freigegeben**. Sobald ein Stand aus Ring 0 freigegeben wird, kommt die
dazugehörige `docker-compose.yml` hierher. Bis dahin bricht `espo-compose-rendern` für diesen Ring ab.