Produktiv- und Testtabellen per Prefix trennen

This commit is contained in:
2026-08-22 15:29:52 +02:00
parent a31a235422
commit 213dd10dbd
11 changed files with 490 additions and 24 deletions
+8 -1
View File
@@ -36,6 +36,11 @@ Wichtige Punkte:
- `--single-transaction` sorgt für einen konsistenten Snapshot ohne
Tabellen zu sperren (InnoDB, wie hier durchgängig verwendet).
- Da `test_` und `prod_` in derselben physischen Datenbank liegen, enthält
der vollständige Dump beide logisch getrennten Schemas. Beim Restore darf
nicht nur ein einzelner Prefix zurückgespielt werden, ohne Abhängigkeiten
und den jeweiligen Stand von `test_schema_migrations` beziehungsweise
`prod_schema_migrations` zu prüfen.
- Der Backup-Ordner muss **außerhalb** des über HTTP erreichbaren Webroots
liegen, sonst wären Dumps öffentlich abrufbar.
- Zusätzlich zu lokalen Backups auf dem Webspace mindestens eine Kopie an
@@ -55,7 +60,9 @@ gunzip -c kaffeeliste_20260715_030000.sql.gz | mysql -h "$DB_HOST" -u "$DB_USER"
Danach `php scripts/migrate.php` laufen lassen, falls das Backup älter als
die zuletzt eingespielten Migrationen ist (das Skript ist idempotent und
wendet nur fehlende Migrationen an).
wendet nur fehlende Migrationen im konfigurierten `DB_TABLE_PREFIX` an).
Den Lauf daher mit der Konfiguration jeder wiederhergestellten Umgebung
separat ausführen.
**Restore-Test:** Mindestens einmal pro Quartal einen echten Restore gegen
eine separate Test-Datenbank durchführen und die App dagegen starten
+41 -14
View File
@@ -9,14 +9,22 @@ Dieses Dokument beschreibt, wie die Anwendung auf den Netcup-Webspace
## Umgebungen
| Umgebung | Host | Webroot | Zweck |
| --- | --- | --- | --- |
| Staging | `testumgebung.kaffeeliste.de` | `/testumgebung.kaffeeliste.de/httpdocs/` | Vollständiger Durchlauf vor jedem Produktiv-Deploy |
| Produktion | `app.kaffeeliste.de` (App) + `kaffeeliste.de` (Marketing) | noch einzurichten | Echtbetrieb |
| Umgebung | Host | Webroot | Tabellen-Prefix | Zweck |
| --- | --- | --- | --- | --- |
| Staging | `testumgebung.kaffeeliste.de` | `/testumgebung.kaffeeliste.de/httpdocs/` | `test_` | Vollständiger Durchlauf vor jedem Produktiv-Deploy |
| Produktion | `app.kaffeeliste.de` (App) + `kaffeeliste.de` (Marketing) | noch einzurichten | `prod_` | Echtbetrieb |
Beide Umgebungen brauchen **eigene Datenbanken** und **eigene**
`env.local.php`. Die Staging-Umgebung läuft mit Stripe-Testkeys, die
Produktion mit Live-Keys.
Beide Umgebungen verwenden dieselbe physische Datenbank, aber zwingend
unterschiedliche Tabellen-Prefixe und jeweils eine eigene `env.local.php`.
Damit sind Daten, Migrationstabellen und Fremdschlüssel von Staging und
Produktion logisch getrennt. Die Staging-Umgebung läuft mit Stripe-Testkeys,
die Produktion mit Live-Keys.
Der Prefix schützt vor versehentlichem Zugriff der einen Umgebung auf die
Tabellen der anderen, ersetzt aber keine getrennte Datenbank als harte
Sicherheitsgrenze: Ein kompromittierter gemeinsamer Datenbankzugang kann
weiterhin alle Tabellen sehen. Deshalb Zugangsdaten besonders restriktiv
behandeln und immer die gesamte Datenbank sichern.
### Host-Split beachten
@@ -80,12 +88,13 @@ Die temporäre `phpinfo()`-Datei danach wieder löschen.
### 2. Datenbank anlegen
In Plesk eine leere MySQL-Datenbank samt eigenem Benutzer anlegen. Die
Zugangsdaten werden gleich in `env.local.php` eingetragen.
In Plesk eine MySQL-Datenbank samt Benutzer anlegen. Beide Umgebungen dürfen
dieselben Zugangsdaten verwenden; die Trennung erfolgt über
`DB_TABLE_PREFIX=test_` beziehungsweise `DB_TABLE_PREFIX=prod_`.
Die vorhandene Entwicklungsdatenbank **nicht** übernehmen: sie enthält
ausschließlich Testdaten inklusive des migrierten Default-Mandanten aus
der Legacy-App.
Unpräfixierte vorhandene Testdaten dürfen ausschließlich mit dem dafür
vorgesehenen Übernahmeskript nach `test_` kopiert werden. Sie dürfen niemals
nach `prod_` übernommen werden.
### 3. Dateien hochladen
@@ -121,12 +130,16 @@ putenv('APP_TIMEZONE=Europe/Berlin');
putenv('APP_DB_DRIVER=mysql');
putenv('DB_HOST=localhost');
putenv('DB_NAME=…'); putenv('DB_USER=…'); putenv('DB_PASS=…');
putenv('DB_TABLE_PREFIX=test_');
putenv('APP_SESSION_PATH=' . __DIR__ . '/var/sessions');
putenv('APP_MAIL_TRANSPORT=mail');
putenv('APP_MAIL_FROM=noreply@kaffeeliste.de');
```
Stripe-, Dolibarr- und IMAP-Werte je Umgebung ergänzen (siehe unten).
In der produktiven `env.local.php` muss stattdessen
`DB_TABLE_PREFIX=prod_` stehen. Ein leerer Prefix ist im laufenden Betrieb
nicht zulässig.
### 5. Schreibrechte für `var/`
@@ -139,7 +152,21 @@ den CSV-Import. Beide sind per `.htaccess` von außen gesperrt.
Es gibt 30 versionierte Migrationen in `database/migrations/`, angewendet
über `scripts/migrate.php`. Das Skript ist idempotent und wendet nur
fehlende Migrationen an.
fehlende Migrationen an. Es arbeitet ausschließlich im Prefix aus der
jeweiligen `env.local.php`; daher müssen Migrationen einmal in Staging und
einmal in Produktion ausgeführt werden.
Für die einmalige Übernahme vorhandener, bisher unpräfixierter Testdaten:
```bash
php scripts/migrate-table-prefix.php --target-prefix=test_ --replace
```
Das Skript migriert zuerst das `test_`-Schema, ersetzt dessen Inhalt
transaktional durch eine exakte Kopie der unpräfixierten Daten und vergleicht
pro Tabelle die Zeilenzahl. Die unpräfixierten Quelltabellen bleiben als
Rückfallmöglichkeit unverändert. Vor dem Lauf trotzdem ein vollständiges
Datenbank-Backup erstellen.
Ohne SSH-Zugang läuft das über **Plesk → Geplante Aufgaben (Scheduled
Tasks)** als einmalig ausgeführter Task vom Typ *PHP-Skript ausführen*:
@@ -348,7 +375,7 @@ Rechten der Secret-Dateien oder einer nicht angewendeten Legal-Migration.
ein Vorlauf mit `--dry-run` wurde kontrolliert
- [ ] Stündlicher Task `php scripts/purge-expired-operational-data.php` ist
eingerichtet; ein Vorlauf mit `--dry-run` wurde kontrolliert
- [ ] Frische Produktivdatenbank ohne Testdaten
- [ ] Frisches `prod_`-Schema ohne Testdaten; Staging verwendet ausschließlich `test_`
## Bewusst offen
+4 -1
View File
@@ -44,6 +44,7 @@ export DB_PORT="3306"
export DB_NAME="example_database"
export DB_USER="example_user"
export DB_PASS="example_password"
export DB_TABLE_PREFIX="test_"
export DEV_AUTH_EMAIL="admin@test.local"
export DEV_AUTH_NAME="Test Admin"
# Optional: Standard ist var/sessions im Repository.
@@ -95,7 +96,9 @@ der Port über die Ports-Ansicht weitergeleitet werden.
Nachrichten unter `var/mail` ab. Für Produktion kann zunächst
`APP_MAIL_TRANSPORT=mail` mit passendem `APP_BASE_URL` und Absender gesetzt
werden; eine SMTP-/Provider-Anbindung bleibt ein späterer Betriebsausbau.
- Die Tabellen werden über `database/migrations/` angelegt.
- Die Tabellen werden über `database/migrations/` mit dem Prefix aus
`DB_TABLE_PREFIX` angelegt. Lokal und auf Staging ist das `test_`, in
Produktion ausschließlich `prod_`.
- `database/mysql-dev-schema.sql` bleibt als historische Dev-Schema-Baseline
erhalten; der aktive Weg ist `scripts/migrate.php`.
- Session-Dateien liegen standardmäßig unter `var/sessions`; `var/` wird von
+2 -2
View File
@@ -111,8 +111,8 @@ LD_LIBRARY_PATH="$PWD/.local/php/usr/lib/x86_64-linux-gnu:$PWD/.local/php/usr/li
```
Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`,
`DB_USER` und `DB_PASS`. `scripts/init-mysql-dev.php` braucht zusätzlich
`DEV_AUTH_EMAIL`.
`DB_USER`, `DB_PASS` und `DB_TABLE_PREFIX`. `scripts/init-mysql-dev.php`
braucht zusätzlich `DEV_AUTH_EMAIL`.
## Aktueller Prüfstatus