factory-import: --save-as-builtin Flag + README-Überarbeitung
Schließt die Lücke „nach Import landet die ZIP nicht im Image-Default":
./factory-import.sh --save-as-builtin
→ entpackt die ZIP nach erfolgreichem DB-Import zusätzlich in
backend/factory-defaults/ (alter Inhalt vorher aufgeräumt, README.md
und .gitkeep bleiben). Beim nächsten Image-Build sind die Defaults
drin und seeden frische VMs automatisch.
README-Abschnitt „Factory-Defaults" komplett überarbeitet:
- Drei Transport-Pfade explizit erklärt (laufende DB / Drop-Box / Image)
- HTML-Standardtexte + AppSetting-Whitelist dokumentiert
- Auto-Seed-Verhalten + Berechtigungen aktualisiert
- Typische Workflows als End-zu-End-Sequenz inkl. scp-Sync
Live verifiziert: STALE_FILE.txt im backend/factory-defaults/ wurde beim
--save-as-builtin sauber entfernt, README.md blieb erhalten, Subfolder neu
befüllt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1066,8 +1066,9 @@ Folgende Felder werden in Audit-Logs gefiltert:
|
||||
## Factory-Defaults: Stammdaten-Kataloge teilen
|
||||
|
||||
Das **Factory-Defaults**-System erlaubt den Export und Import von
|
||||
Stammdaten-Katalogen (Anbieter, Tarife, PDF-Vorlagen usw.) zwischen verschiedenen
|
||||
OpenCRM-Installationen. Es ist bewusst **streng abgegrenzt** zu Datenbank-Backups:
|
||||
Stammdaten-Katalogen (Anbieter, Tarife, PDF-Auftragsvorlagen, HTML-Standardtexte)
|
||||
zwischen verschiedenen OpenCRM-Installationen. Es ist bewusst **streng abgegrenzt**
|
||||
zu Datenbank-Backups:
|
||||
|
||||
### Abgrenzung
|
||||
|
||||
@@ -1075,64 +1076,117 @@ OpenCRM-Installationen. Es ist bewusst **streng abgegrenzt** zu Datenbank-Backup
|
||||
|---|---|---|
|
||||
| Anbieter, Tarife, Kündigungsfristen, Laufzeiten, Kategorien | ✅ | ✅ |
|
||||
| PDF-Auftragsvorlagen (inkl. Dateien + Feldzuordnungen) | ✅ | ✅ |
|
||||
| HTML-Standardtexte: Datenschutzerklärung, Impressum, Vollmacht-Vorlage, Website-Datenschutz | ✅ | ✅ |
|
||||
| **Kundendaten, Verträge, Dokumente** | ❌ | ✅ |
|
||||
| **Emails, SMTP-/IMAP-Zugangsdaten** | ❌ | ✅ |
|
||||
| **System-Einstellungen, Datenschutzerklärungen, Impressum** | ❌ | ✅ |
|
||||
| **Secrets, JWT, Encryption-Keys, User-Accounts** | ❌ | ✅ |
|
||||
| Zwischen verschiedenen Installationen teilbar | ✅ | ❌ (zu firmen-spezifisch) |
|
||||
|
||||
> **Kurz:** Factory-Defaults = reine Kataloge, Backup = alles.
|
||||
> **Kurz:** Factory-Defaults = generische Stammdaten + rechtliche Standardtexte,
|
||||
> Backup = die komplette Instanz.
|
||||
|
||||
### Export (Installation A → ZIP)
|
||||
### Drei Wege, eine ZIP zu transportieren
|
||||
|
||||
Es gibt drei Pfade, je nachdem wo die ZIP gerade liegen soll:
|
||||
|
||||
| Wo | Pfad | Wann |
|
||||
|---|---|---|
|
||||
| **Laufende DB einer Instanz** | UI-Upload oder `./factory-import.sh` | Bestehende Live-Instanz updaten |
|
||||
| **Drop-Box im Repo** (`factory-exports/`) | `./factory-export.sh` legt ab, `./factory-import.sh` liest | Transfer zwischen dev und prod via `scp` |
|
||||
| **Werkseinstellung im Image** (`backend/factory-defaults/`) | `./factory-import.sh --save-as-builtin` oder manuell entpacken | Neue VMs sollen die Defaults beim allerersten Start mitbringen |
|
||||
|
||||
Alle drei sind unabhängig, **alle drei zusammen** decken den typischen Workflow ab.
|
||||
|
||||
### Export
|
||||
|
||||
**Variante A – UI:**
|
||||
1. **Einstellungen** → **Factory-Defaults** öffnen
|
||||
2. Übersicht prüfen (Anzahl pro Kategorie)
|
||||
3. Button **„Factory-Defaults exportieren"** klicken
|
||||
4. ZIP wird als `factory-defaults-YYYY-MM-DD.zip` heruntergeladen
|
||||
2. Button **„Factory-Defaults exportieren"** klicken
|
||||
3. ZIP wird als `factory-defaults-YYYY-MM-DD.zip` heruntergeladen
|
||||
|
||||
**Variante B – CLI (für scp-Transfers):**
|
||||
```bash
|
||||
./factory-export.sh # → factory-exports/factory-defaults-…zip
|
||||
OPENCRM_URL=https://crm.prod.example.de \
|
||||
OPENCRM_EMAIL=admin@example.de ./factory-export.sh # gegen Prod-Instanz
|
||||
```
|
||||
Ohne `OPENCRM_PASSWORD` wird das Passwort interaktiv abgefragt. Der Zielordner
|
||||
`factory-exports/` ist gitignored – die ZIPs landen also nicht ins Repo.
|
||||
|
||||
**ZIP-Struktur:**
|
||||
```
|
||||
factory-defaults-2026-04-23.zip
|
||||
factory-defaults-2026-05-07-1949.zip
|
||||
├── manifest.json # Version + Datum + Counts
|
||||
├── providers/
|
||||
│ └── providers.json # Anbieter inkl. zugehöriger Tarife
|
||||
├── providers/providers.json
|
||||
├── contract-meta/
|
||||
│ ├── cancellation-periods.json # Kündigungsfristen (Code + Beschreibung)
|
||||
│ ├── contract-durations.json # Laufzeiten (Code + Beschreibung)
|
||||
│ └── contract-categories.json # Kategorien (Strom, Gas, DSL, ...)
|
||||
└── pdf-templates/
|
||||
├── pdf-templates.json # Vorlagen-Metadaten + Feldzuordnungen
|
||||
└── *.pdf # Die eigentlichen PDF-Dateien
|
||||
│ ├── cancellation-periods.json
|
||||
│ ├── contract-durations.json
|
||||
│ └── contract-categories.json
|
||||
├── pdf-templates/
|
||||
│ ├── pdf-templates.json
|
||||
│ └── *.pdf # Die eigentlichen PDF-Dateien
|
||||
└── app-settings/
|
||||
└── app-settings.json # HTML-Templates (Whitelist-only)
|
||||
```
|
||||
|
||||
Die ZIP kann an andere Installationen weitergegeben werden
|
||||
(Partner, Test-System, neue Installation).
|
||||
### Import
|
||||
|
||||
### Import (ZIP → Installation B)
|
||||
**Variante A – UI:**
|
||||
1. **Einstellungen** → **Factory-Defaults**
|
||||
2. Bereich **Import** → **„ZIP hochladen"** → Datei wählen
|
||||
3. Erfolgs-Box zeigt Counts pro Kategorie
|
||||
|
||||
1. ZIP herunterladen bzw. erhalten
|
||||
2. Inhalt nach `backend/factory-defaults/` entpacken (Unterordnerstruktur beibehalten)
|
||||
3. Im Backend-Verzeichnis ausführen:
|
||||
```bash
|
||||
npm run seed:defaults
|
||||
```
|
||||
**Variante B – CLI:**
|
||||
```bash
|
||||
./factory-import.sh # nimmt jüngste ZIP aus factory-exports/
|
||||
./factory-import.sh ./factory-exports/foo.zip # explizite ZIP
|
||||
./factory-import.sh --save-as-builtin # zusätzlich ins Image-Default
|
||||
./factory-import.sh --save-as-builtin ./foo.zip # entpacken (siehe unten)
|
||||
```
|
||||
|
||||
Konfigurierbar per ENV: `OPENCRM_URL`, `OPENCRM_EMAIL`, `OPENCRM_PASSWORD`.
|
||||
|
||||
**Variante C – Container-Bare-Metal (für Migration / mehrere ZIPs zusammenführen):**
|
||||
```bash
|
||||
# Inhalt der ZIP nach backend/factory-defaults/ entpacken (Unterordner beibehalten)
|
||||
cd backend && npm run seed:defaults
|
||||
```
|
||||
|
||||
**Beispiel-Output:**
|
||||
```
|
||||
📦 Factory-Defaults werden eingespielt...
|
||||
|
||||
✓ Anbieter: 7, Tarife: 12
|
||||
✓ Kündigungsfristen: 5
|
||||
✓ Laufzeiten: 4
|
||||
✓ Vertragskategorien: 8
|
||||
✓ PDF-Vorlagen: 3
|
||||
|
||||
✅ Factory-Defaults erfolgreich eingespielt.
|
||||
✓ Anbieter: 10
|
||||
✓ Tarife: 4
|
||||
✓ Kündigungsfristen: 18
|
||||
✓ Laufzeiten: 18
|
||||
✓ Vertragskategorien: 8
|
||||
✓ PDF-Vorlagen: 2
|
||||
✓ HTML-Templates: 2
|
||||
```
|
||||
|
||||
### Mehrere ZIPs kombinieren
|
||||
### `--save-as-builtin`: ZIP zur Werkseinstellung machen
|
||||
|
||||
Du kannst mehrere Exporte in `backend/factory-defaults/` übereinanderlegen –
|
||||
JSON-Dateien werden automatisch gemerged:
|
||||
Mit `--save-as-builtin` entpackt `factory-import.sh` die ZIP nach **erfolgreichem
|
||||
DB-Import** zusätzlich in `backend/factory-defaults/`. Beim nächsten
|
||||
`docker-compose up --build` landen die Defaults im Image. Frisch hochgezogene
|
||||
VMs bringen sie dann beim ersten Start automatisch mit (Auto-Seed-Pfad im
|
||||
Container-Entrypoint).
|
||||
|
||||
```bash
|
||||
# typischer Sync prod → dev → Image-Default
|
||||
ssh prod './factory-export.sh'
|
||||
scp prod:opencrm/factory-exports/factory-defaults-*.zip factory-exports/
|
||||
./factory-import.sh --save-as-builtin
|
||||
docker-compose up -d --build # neuer Build, neue VMs starten direkt mit Defaults
|
||||
```
|
||||
|
||||
Der Inhalt von `backend/factory-defaults/` wird beim `--save-as-builtin` vorher
|
||||
geleert (außer `README.md` und `.gitkeep`), damit nichts Veraltetes liegen
|
||||
bleibt.
|
||||
|
||||
### Mehrere ZIPs kombinieren (CLI-only, Variante C)
|
||||
|
||||
`backend/factory-defaults/` darf mehrere `*.json` pro Unterordner haben –
|
||||
`npm run seed:defaults` merged sie automatisch:
|
||||
|
||||
```
|
||||
backend/factory-defaults/
|
||||
@@ -1142,40 +1196,73 @@ backend/factory-defaults/
|
||||
eigene.json # 5 eigene Anbieter
|
||||
```
|
||||
|
||||
Das Import-Script liest **alle** `*.json` im jeweiligen Unterordner und merged per
|
||||
unique Key (letzter Eintrag gewinnt). Duplikate sind also unproblematisch.
|
||||
Bei gleichem Unique-Key gewinnt der zuletzt gelesene Eintrag. Der UI-/HTTP-Import
|
||||
nimmt nur eine ZIP entgegen – für Merges nutze `npm run seed:defaults`.
|
||||
|
||||
### Idempotenz
|
||||
|
||||
Das Script nutzt ausschließlich Prisma `upsert`:
|
||||
Alle Pfade nutzen Prisma `upsert`:
|
||||
- **Neue Einträge** werden angelegt
|
||||
- **Bestehende Einträge** (per unique Key: `name`, `code`) werden aktualisiert
|
||||
- **Bestehende Einträge** (per unique Key: `name` / `code` / `key`) werden aktualisiert
|
||||
- Nichts wird gelöscht
|
||||
|
||||
Du kannst `npm run seed:defaults` also beliebig oft ausführen, ohne Datenverlust
|
||||
Du kannst Imports also beliebig oft hintereinander ausführen, ohne Datenverlust
|
||||
oder Duplikate.
|
||||
|
||||
### PDF-Dateien beim Import
|
||||
### PDF-Dateien
|
||||
|
||||
Beim Import werden PDF-Vorlagen aus `factory-defaults/pdf-templates/*.pdf` nach
|
||||
`uploads/pdf-templates/` kopiert und die Pfade in der DB entsprechend gesetzt.
|
||||
Beim Re-Import wird die alte Datei in `uploads/` entsorgt und durch die neue
|
||||
ersetzt.
|
||||
Beim Import werden PDF-Vorlagen aus dem ZIP nach `uploads/pdf-templates/`
|
||||
kopiert (mit eindeutigem Suffix) und die `templatePath`-Spalte entsprechend
|
||||
gesetzt. Beim Re-Import wird die alte Datei in `uploads/` entsorgt und durch
|
||||
die neue ersetzt.
|
||||
|
||||
### AppSettings-Whitelist
|
||||
|
||||
Beim Import werden nur die Keys mit AppSetting-Schreibzugriff gewährt, die auch
|
||||
exportiert werden – aktuell:
|
||||
|
||||
- `privacyPolicyHtml`
|
||||
- `imprintHtml`
|
||||
- `authorizationTemplateHtml`
|
||||
- `websitePrivacyPolicyHtml`
|
||||
|
||||
Andere Keys (SMTP, JWT, etc.) werden mit einer Warnung ignoriert. Whitelist ist
|
||||
in [`backend/src/services/factoryDefaults.service.ts`](backend/src/services/factoryDefaults.service.ts)
|
||||
zentral gepflegt.
|
||||
|
||||
### Auto-Seed beim Erst-Deploy
|
||||
|
||||
Bei einer **frischen** Installation (leere DB) spielt der Container-Entrypoint
|
||||
nach dem Prisma-Seed automatisch das Built-in-Verzeichnis ein:
|
||||
|
||||
```
|
||||
[entrypoint] DB ist leer (User-Count=0) – Auto-Seed wird ausgeführt
|
||||
[entrypoint] Spiele eingebaute Factory-Defaults ein…
|
||||
✓ Anbieter: 10, Tarife: 4
|
||||
…
|
||||
```
|
||||
|
||||
Bei bestehenden Installs passiert das **nicht** – nur frische DBs.
|
||||
|
||||
### Berechtigungen
|
||||
|
||||
| Aktion | Berechtigung |
|
||||
|--------|--------------|
|
||||
| Factory-Defaults Vorschau | `settings:read` |
|
||||
| Factory-Defaults Export | `settings:update` |
|
||||
| Factory-Defaults Import (CLI) | Server-Zugang (SSH/Shell) |
|
||||
| Factory-Defaults Export (UI/CLI) | `settings:update` |
|
||||
| Factory-Defaults Import (UI/CLI) | `settings:update` |
|
||||
| Werkseinstellungen ändern (`--save-as-builtin` / `npm run seed:defaults`) | Server-Zugang (SSH/Shell) |
|
||||
|
||||
### Typischer Einsatzzweck
|
||||
### Typische Einsatzzwecke
|
||||
|
||||
- **Neue Installation aufsetzen**: Eine Kollegen-ZIP importieren und sofort mit
|
||||
gepflegtem Anbieter- und Vorlagenkatalog loslegen
|
||||
- **Neue VM aufsetzen**: ZIP einmalig nach `backend/factory-defaults/` entpacken
|
||||
(oder per `--save-as-builtin`), dann `docker-compose up --build` – die
|
||||
Werkseinstellungen sind beim ersten Start automatisch drin.
|
||||
- **Prod-Stand zurück nach dev synchronisieren**: `./factory-export.sh` auf prod,
|
||||
`scp` ins dev, `./factory-import.sh --save-as-builtin` lokal – damit ist
|
||||
sowohl die dev-DB aktuell als auch der nächste Image-Build.
|
||||
- **Vorlagen-Paket teilen**: Eine ZIP mit nur PDF-Vorlagen weitergeben
|
||||
(die anderen Ordner einfach aus der ZIP entfernen vor dem Entpacken)
|
||||
(andere Ordner aus der ZIP entfernen vor dem Entpacken).
|
||||
- **Anbieter-Paket teilen**: ZIP mit nur `providers/` weitergeben
|
||||
- **Versionskontrolle**: Die entpackten JSON-Dateien unter Versionskontrolle
|
||||
stellen (außerhalb von `backend/factory-defaults/`, da der Ordner gitignored ist)
|
||||
|
||||
+7
-4
@@ -97,16 +97,19 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🔁 Factory-Defaults Sync-Scripts (dev ↔ prod)**
|
||||
- [x] **🔁 Factory-Defaults Sync-Scripts (dev ↔ prod ↔ Image)**
|
||||
- `./factory-export.sh` zieht eine ZIP per API in `factory-exports/`
|
||||
(gitignored Drop-Box).
|
||||
- `./factory-import.sh [zip]` lädt die ZIP per API in eine andere Instanz
|
||||
– ohne Argument wählt es die jüngste ZIP automatisch.
|
||||
- `./factory-import.sh --save-as-builtin` entpackt die ZIP zusätzlich nach
|
||||
`backend/factory-defaults/` (vorher aufgeräumt). Damit landet sie beim
|
||||
nächsten `docker-compose up --build` als Werkseinstellung im Image und
|
||||
seedet frische DBs automatisch.
|
||||
- Konfigurierbar per Env: `OPENCRM_URL`, `OPENCRM_EMAIL`,
|
||||
`OPENCRM_PASSWORD` (sonst interaktive Abfrage).
|
||||
- Use-Case: Anbieter/Tarife auf prod erweitert? `./factory-export.sh` auf
|
||||
prod, `scp` ins dev, `./factory-import.sh` lokal – fertig. Geht in
|
||||
beide Richtungen.
|
||||
- README-Abschnitt „Factory-Defaults: Stammdaten-Kataloge teilen"
|
||||
komplett überarbeitet (drei Transport-Pfade, Auto-Seed, Whitelist).
|
||||
|
||||
- [x] **🚀 Auto-Seed: Werkseinstellungen beim Erst-Deploy**
|
||||
- Inhalt von `backend/factory-defaults/` wird via Dockerfile als
|
||||
|
||||
+50
-7
@@ -3,9 +3,13 @@
|
||||
# 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/
|
||||
# ./factory-import.sh # jüngste ZIP aus factory-exports/
|
||||
# ./factory-import.sh ./factory-exports/foo.zip # explizite ZIP
|
||||
# ./factory-import.sh --save-as-builtin # nach Import auch ins
|
||||
# ./factory-import.sh --save-as-builtin ./foo.zip # backend/factory-defaults/
|
||||
# # entpacken → nächster
|
||||
# # Image-Build hat sie
|
||||
# # als Werkseinstellung
|
||||
#
|
||||
# ENV (wie factory-export.sh):
|
||||
# OPENCRM_URL (default http://localhost:3010)
|
||||
@@ -20,8 +24,29 @@ PASSWORD="${OPENCRM_PASSWORD:-}"
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EXPORT_DIR="$REPO_ROOT/factory-exports"
|
||||
BUILTIN_DIR="$REPO_ROOT/backend/factory-defaults"
|
||||
|
||||
# Argumente parsen: erlaubt sind --save-as-builtin und 0/1 ZIP-Pfade in
|
||||
# beliebiger Reihenfolge.
|
||||
SAVE_AS_BUILTIN=false
|
||||
ZIP_PATH=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--save-as-builtin) SAVE_AS_BUILTIN=true ;;
|
||||
-h|--help)
|
||||
sed -n '2,16p' "$0" | sed 's/^# \?//'
|
||||
exit 0
|
||||
;;
|
||||
--*) echo "✗ Unbekanntes Flag: $arg"; exit 2 ;;
|
||||
*)
|
||||
if [ -n "$ZIP_PATH" ]; then
|
||||
echo "✗ Mehrere ZIP-Pfade angegeben (nur einer erlaubt)"; exit 2
|
||||
fi
|
||||
ZIP_PATH="$arg"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
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)"
|
||||
@@ -64,7 +89,7 @@ RESPONSE="$(curl -sS -X POST "$URL/api/factory-defaults/import" \
|
||||
-F "zip=@$ZIP_PATH")"
|
||||
|
||||
# Hübsch ausgeben + auf success prüfen
|
||||
if printf '%s' "$RESPONSE" | python3 -c '
|
||||
if ! printf '%s' "$RESPONSE" | python3 -c '
|
||||
import json, sys
|
||||
r = json.load(sys.stdin)
|
||||
if not r.get("success"):
|
||||
@@ -91,7 +116,25 @@ if warnings:
|
||||
for w in warnings:
|
||||
print(f" - {w}")
|
||||
'; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --save-as-builtin: ZIP zusätzlich in backend/factory-defaults/ entpacken,
|
||||
# damit der nächste Image-Build sie als Werkseinstellung mitnimmt.
|
||||
# Vorher räumen wir auf (außer README.md + .gitkeep), damit nichts Veraltetes
|
||||
# liegen bleibt.
|
||||
if [ "$SAVE_AS_BUILTIN" = "true" ]; then
|
||||
echo
|
||||
echo "→ --save-as-builtin: aktualisiere $BUILTIN_DIR/"
|
||||
if [ ! -d "$BUILTIN_DIR" ]; then
|
||||
mkdir -p "$BUILTIN_DIR"
|
||||
fi
|
||||
# Aufräumen: alles außer README.md und .gitkeep löschen
|
||||
find "$BUILTIN_DIR" -mindepth 1 \
|
||||
\! -name 'README.md' \! -name '.gitkeep' \
|
||||
-delete
|
||||
# ZIP entpacken (manifest.json kommt mit, ist aber harmlos)
|
||||
unzip -q -o "$ZIP_PATH" -d "$BUILTIN_DIR"
|
||||
echo "✓ Werkseinstellungen aktualisiert. Beim nächsten 'docker-compose up"
|
||||
echo " --build' landen sie im Image und seeden frische DBs automatisch."
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user