diff --git a/.gitignore b/.gitignore index e56bd73..b13a64d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ html/config.php info.php config.php *.old +html/.Trash-1000 diff --git a/migrations/README.md b/migrations/README.md new file mode 100644 index 0000000..6d74471 --- /dev/null +++ b/migrations/README.md @@ -0,0 +1,31 @@ +# Datenbank-Migrationen + +Migrationen liegen hier als `.sql`-Dateien, benannt nach dem Schema: + +``` +0001_kurze_beschreibung.sql +0002_naechste_aenderung.sql +``` + +`update.sh` ruft `migrations/run_migrations.php ` auf. Das Skript: + +- legt bei Bedarf die Tabelle `schema_migrations` an (Spalte `filename` als Primary Key), die protokolliert, welche Dateinamen bereits angewendet wurden +- führt alle noch nicht angewendeten `.sql`-Dateien in Dateinamen-Reihenfolge aus +- bricht beim ersten Fehler sofort ab (die fehlgeschlagene Datei wird NICHT als angewendet markiert, alle vorherigen bleiben es) + +## Regeln für neue Migrationen + +- Immer die nächste freie, vierstellige Nummer verwenden (`0003_...`). Eine bereits vergebene Nummer nie wiederverwenden, eine bereits angewendete Datei nachträglich nicht mehr ändern (neue Migration statt Änderung der alten). +- So schreiben, dass sie **keine Breaking Changes** verursachen, z. B.: + - `CREATE TABLE IF NOT EXISTS ...` statt `CREATE TABLE ...` + - Neue Spalten nullable oder mit `DEFAULT` anlegen, statt bestehende `NOT NULL`-Constraints nachträglich zu erzwingen + - Kein `DROP TABLE` / `DROP COLUMN` ohne vorherige Rücksprache +- Migrationen möglichst idempotent halten (`IF NOT EXISTS`), damit ein erneuter Lauf nach einem Teilfehler nicht sofort wieder bricht. + +## Historie + +`init.sql` (Ausgangsschema für Neuinstallationen) und `update.sql` (bereits manuell eingespieltes Aktien-Modul) liegen bewusst außerhalb dieses Verzeichnisses, da beide bereits auf allen bekannten Servern produktiv sind. Ab jetzt werden neue Schema-Änderungen ausschließlich als nummerierte Migration hier abgelegt. + +## Hinweis + +`run_migrations.php` bindet die produktive `config.php` per `require` ein, um die bestehende `$pdo`-Verbindung wiederzuverwenden. Da `config.php` `session_start()` aufruft, legt der CLI-Lauf dabei eine kurzlebige, ungenutzte Session-Datei an — das ist unschädlich, aber erwähnenswert. diff --git a/migrations/run_migrations.php b/migrations/run_migrations.php new file mode 100644 index 0000000..79cd1c5 --- /dev/null +++ b/migrations/run_migrations.php @@ -0,0 +1,63 @@ +exec( + "CREATE TABLE IF NOT EXISTS schema_migrations ( + filename VARCHAR(255) NOT NULL PRIMARY KEY, + applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )" +); + +$applied = $pdo->query("SELECT filename FROM schema_migrations")->fetchAll(PDO::FETCH_COLUMN); + +$files = glob($migrationsDir . '/*.sql') ?: []; +sort($files, SORT_STRING); + +$pending = array_values(array_filter( + $files, + fn(string $f): bool => !in_array(basename($f), $applied, true) +)); + +if (empty($pending)) { + echo "Keine neuen Migrationen.\n"; + exit(0); +} + +foreach ($pending as $file) { + $name = basename($file); + echo "-> Migration $name ... "; + + $sql = file_get_contents($file); + if ($sql === false || trim($sql) === '') { + fwrite(STDERR, "\nMigration $name ist leer oder konnte nicht gelesen werden.\n"); + exit(1); + } + + try { + $pdo->exec($sql); + $stmt = $pdo->prepare("INSERT INTO schema_migrations (filename) VALUES (?)"); + $stmt->execute([$name]); + echo "OK\n"; + } catch (Throwable $e) { + fwrite(STDERR, "\nFEHLER bei $name: " . $e->getMessage() . "\n"); + fwrite(STDERR, "Abbruch. Bereits erfolgreich angewendete Migrationen bleiben markiert, $name muss manuell geprueft werden.\n"); + exit(1); + } +} + +echo "Alle Migrationen angewendet.\n"; diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..591b420 --- /dev/null +++ b/update.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# +# Deployt den aktuellen git-Stand dieses Projektverzeichnisses in den Webroot +# und wendet danach ausstehende Datenbank-Migrationen an. +# +# Aufruf (im Projektverzeichnis): +# ./update.sh +# +# Optional per Umgebungsvariable überschreibbar: +# WEBROOT=/anderer/pfad ./update.sh +# CONFIG_FILE=/anderer/pfad/config.php ./update.sh + +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WEBROOT="${WEBROOT:-/var/www/html}" +CONFIG_FILE="${CONFIG_FILE:-$WEBROOT/config.php}" + +log() { echo "==> $*"; } +fail() { echo "FEHLER: $*" >&2; exit 1; } + +cd "$PROJECT_DIR" + +command -v php >/dev/null 2>&1 || fail "php ist nicht installiert bzw. nicht im PATH." +[ -d "$WEBROOT" ] || fail "Webroot $WEBROOT existiert nicht." +[ -w "$WEBROOT" ] || fail "Keine Schreibrechte auf $WEBROOT." + +# --- 1. Sicherstellen, dass das Projektverzeichnis sauber ist --- +if ! git diff --quiet || ! git diff --cached --quiet; then + fail "Lokale Änderungen im Projektverzeichnis vorhanden - bitte erst committen oder stashen." +fi + +# --- 2. Neuesten Stand holen --- +log "git pull..." +git pull --ff-only + +# --- 3. PHP-Syntaxcheck als Sicherheitsnetz vor dem Kopieren --- +log "Prüfe PHP-Syntax..." +while IFS= read -r -d '' file; do + php -l "$file" >/dev/null || fail "Syntaxfehler in $file - es wird nichts kopiert." +done < <(find html -name '*.php' -print0) + +# --- 4. Dateien kopieren, config.php bleibt unangetastet --- +log "Kopiere Dateien nach $WEBROOT..." +config_backup="" +if [ -f "$CONFIG_FILE" ]; then + config_backup="$(mktemp)" + cp "$CONFIG_FILE" "$config_backup" +fi + +cp -r html/. "$WEBROOT/" + +if [ -n "$config_backup" ]; then + cp "$config_backup" "$CONFIG_FILE" + rm -f "$config_backup" +fi + +# --- 5. Datenbank-Migrationen --- +if [ -d migrations ] && [ -f migrations/run_migrations.php ]; then + log "Wende Datenbank-Migrationen an..." + php migrations/run_migrations.php "$CONFIG_FILE" +fi + +# --- 6. Tests --- +if [ -f tests/run.sh ]; then + log "Führe Tests aus..." + bash tests/run.sh +else + log "Noch keine Tests vorhanden - werden später ergänzt." +fi + +log "Update abgeschlossen."