factory-defaults: CLI-Sync zwischen dev und prod

Zwei kleine Bash-Wrapper im Repo-Root, die den vorhandenen Export- und
Import-Endpoint per curl ansteuern und damit den Hin- und Her-Transfer von
Stammdaten + HTML-Templates zwischen Instanzen ohne Browser ermöglichen.

  ./factory-export.sh                    # ZIP nach factory-exports/
  ./factory-import.sh                    # nimmt jüngste ZIP automatisch
  ./factory-import.sh path/zur.zip       # explizit

Konfigurierbar via OPENCRM_URL / OPENCRM_EMAIL / OPENCRM_PASSWORD;
ohne PASSWORD wird interaktiv abgefragt.

Workflow: prod erweitert Anbieter → ./factory-export.sh → scp → dev
./factory-import.sh – funktioniert in beide Richtungen.

`factory-exports/` ist gitignored (nur .gitkeep getrackt).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 19:51:19 +02:00
parent 365c7994d5
commit 4407bbfbb8
5 changed files with 176 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Factory-Defaults-Import pflegt eine ZIP in eine OpenCRM-Instanz ein.
# Idempotent (upserts pro Kategorie, nichts wird gelöscht).
#
# Aufruf:
# ./factory-import.sh ./factory-exports/factory-defaults-2026-05-07-1923.zip
# ./factory-import.sh # ohne Argument: nimmt automatisch
# # die jüngste ZIP aus factory-exports/
#
# ENV (wie factory-export.sh):
# OPENCRM_URL (default http://localhost:3010)
# OPENCRM_EMAIL (default admin@admin.com)
# OPENCRM_PASSWORD (sonst interaktiv)
set -euo pipefail
URL="${OPENCRM_URL:-http://localhost:3010}"
EMAIL="${OPENCRM_EMAIL:-admin@admin.com}"
PASSWORD="${OPENCRM_PASSWORD:-}"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXPORT_DIR="$REPO_ROOT/factory-exports"
ZIP_PATH="${1:-}"
if [ -z "$ZIP_PATH" ]; then
# Jüngste ZIP automatisch wählen
ZIP_PATH="$(ls -1t "$EXPORT_DIR"/*.zip 2>/dev/null | head -1 || true)"
if [ -z "$ZIP_PATH" ]; then
echo "✗ Keine ZIP angegeben und keine in $EXPORT_DIR/ gefunden."
echo " Aufruf: ./factory-import.sh <pfad/zur/factory-defaults.zip>"
exit 1
fi
echo "→ Keine ZIP angegeben nehme jüngste aus $EXPORT_DIR/:"
echo " $(basename "$ZIP_PATH")"
fi
if [ ! -f "$ZIP_PATH" ]; then
echo "✗ Datei nicht gefunden: $ZIP_PATH"
exit 1
fi
if [ -z "$PASSWORD" ]; then
read -r -s -p "Passwort für $EMAIL @ $URL: " PASSWORD
echo
fi
echo "→ Login als $EMAIL @ $URL"
LOGIN_RESPONSE="$(curl -sS -X POST "$URL/api/auth/login" \
-H 'Content-Type: application/json' \
--data-raw "$(E="$EMAIL" P="$PASSWORD" python3 -c 'import json,os;print(json.dumps({"email":os.environ["E"],"password":os.environ["P"]}))')")"
TOKEN="$(printf '%s' "$LOGIN_RESPONSE" | python3 -c 'import json,sys;d=json.load(sys.stdin);print((d.get("data") or {}).get("token","") or d.get("token",""))')"
if [ -z "$TOKEN" ]; then
echo "✗ Login fehlgeschlagen. Antwort:"
echo "$LOGIN_RESPONSE" | head -c 500
echo
exit 1
fi
echo "→ Upload + Import: $(basename "$ZIP_PATH")"
RESPONSE="$(curl -sS -X POST "$URL/api/factory-defaults/import" \
-H "Authorization: Bearer $TOKEN" \
-F "zip=@$ZIP_PATH")"
# Hübsch ausgeben + auf success prüfen
if printf '%s' "$RESPONSE" | python3 -c '
import json, sys
r = json.load(sys.stdin)
if not r.get("success"):
print("✗ Import fehlgeschlagen:", r.get("error", "(unbekannt)"))
sys.exit(1)
d = r.get("data", {})
print("✓ Import erfolgreich:")
for label, key in [
("Anbieter", "providers"),
("Tarife", "tariffs"),
("Kündigungsfristen", "cancellationPeriods"),
("Laufzeiten", "contractDurations"),
("Vertragskategorien","contractCategories"),
("PDF-Vorlagen", "pdfTemplates"),
("HTML-Templates", "appSettings"),
]:
print(f" {label}: {d.get(key, 0)}")
skipped = d.get("pdfTemplatesSkipped", 0)
if skipped:
print(f" (PDF-Vorlagen übersprungen: {skipped})")
warnings = d.get("warnings", []) or []
if warnings:
print("Hinweise:")
for w in warnings:
print(f" - {w}")
'; then
exit 0
else
exit 1
fi