mysql Integration

This commit is contained in:
2026-03-21 21:53:46 +01:00
parent 9ae81e8347
commit 491605b328
12 changed files with 307 additions and 95 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ unter `saas-app/public/install/`.
- `scripts/prepare-saas-env.php` legt aus `.env.example` eine lokale `.env` an.
- `scripts/install-saas.php` fuehrt den lokalen Setup-Grundlauf aus.
- `scripts/build-migration-bundle.php` baut die SQL-Migrationen zu einer Datei.
- `scripts/run-sql-migrations.php` fuehrt die SQL-Migrationen direkt per PDO SQL Server aus.
- `scripts/run-sql-migrations.php` fuehrt die SQL-Migrationen direkt per PDO fuer den konfigurierten DB-Treiber aus.
## Hinweise Zum Umbau
+23 -9
View File
@@ -15,7 +15,7 @@ Der Installer kann:
- die `.env` speichern
- das SQL-Bundle erzeugen
- Migrationen direkt per PHP ausfuehren, wenn `pdo_sqlsrv` verfuegbar ist
- Migrationen direkt per PHP ausfuehren, wenn die zum Treiber passende PDO-Erweiterung verfuegbar ist
- sich nach erfolgreicher Einrichtung sperren
Die CLI-Skripte unter `scripts/*.php` bleiben als Alternative fuer lokale
@@ -25,7 +25,7 @@ Vorbereitung oder Server mit Shell-Zugriff erhalten.
- PHP 8.2 oder neuer
- Composer fuer den spaeteren Laravel-Bootstrap
- SQL Server oder eine kompatible Datenbank fuer das Zielsystem
- MySQL oder MariaDB fuer das Zielsystem
- Webserver mit Document-Root auf `public/` des Zielprojekts
- Cron-Zugang
- optional: SMTP-Zugang fuer Mails
@@ -38,7 +38,7 @@ Vorbereitung oder Server mit Shell-Zugriff erhalten.
3. Mit `php scripts/prepare-saas-env.php` eine `.env` aus `.env.example` anlegen.
4. Datenbankzugang, URL, Mail und Tenancy-Werte in `saas-app/.env` eintragen.
5. Mit `php scripts/install-saas.php` das SQL-Bundle erzeugen.
6. Das SQL-Bundle manuell in SQL Server ausfuehren oder mit `php scripts/run-sql-migrations.php --server=<server> --database=<db> --username=<user> --password=<pass>` direkt einspielen.
6. Das SQL-Bundle manuell in MySQL/MariaDB importieren oder mit `php scripts/run-sql-migrations.php --connection=mysql --server=<server> --database=<db> --username=<user> --password=<pass>` direkt einspielen.
7. Einen ersten Mandanten anlegen.
8. Einen ersten Benutzer und eine Mitgliedszuordnung anlegen.
9. Spaeter Composer und das eigentliche Laravel-Bootstrap nachziehen.
@@ -49,12 +49,12 @@ Die aktuellen Dateien unter `saas-app/database/migrations/` sind SQL-Skizzen in
PHP-Dateien, keine Laravel-Migrationsklassen. Deshalb gibt es aktuell bewusst
kein `php artisan migrate`.
Stattdessen wird die SQL-Datei ueber Skripte erzeugt:
Stattdessen wird die SQL-Datei passend zum konfigurierten DB-Treiber ueber Skripte erzeugt:
- Installationsvorbereitung inklusive SQL-Bundle:
`php scripts/install-saas.php`
- SQL direkt gegen SQL Server ausfuehren:
`php scripts/run-sql-migrations.php --server=localhost --database=kaffeeliste_saas --username=sa --password=<passwort>`
- SQL direkt gegen MySQL/MariaDB ausfuehren:
`php scripts/run-sql-migrations.php --connection=mysql --server=localhost --database=kaffeeliste_saas --username=root --password=<passwort>`
Die erzeugte SQL-Datei liegt anschliessend unter:
@@ -76,6 +76,7 @@ Die wichtigsten Werte liegen in `saas-app/.env.example` und muessen fuer die
eigene Umgebung angepasst werden:
- `APP_URL`
- `DB_CONNECTION`
- `DB_HOST`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`
- `TENANCY_MODE`
- `TENANCY_CENTRAL_DOMAINS`
@@ -88,9 +89,22 @@ eigene Umgebung angepasst werden:
1. Den Inhalt von `saas-app/` auf den Zielserver hochladen.
2. Das Document-Root auf `public/` zeigen lassen.
3. Schreibrechte fuer `saas-app/`, `.env`, `.installer.lock` und `database/migrations/generated/` sicherstellen.
4. Den Installer unter `/install/` aufrufen und die Einrichtung durchfuehren.
5. Nach erfolgreicher Einrichtung den Installer sperren.
6. Die Anwendung einmal per Browser aufrufen und die Grundseiten pruefen.
4. Sicherstellen, dass `open_basedir` nicht nur auf `public/` eingeschraenkt ist. PHP muss mindestens auf den kompletten Ordner `saas-app/` zugreifen duerfen, obwohl der Document-Root auf `public/` zeigt.
5. Den Installer unter `/install/` aufrufen und die Einrichtung durchfuehren.
6. Nach erfolgreicher Einrichtung den Installer sperren.
7. Die Anwendung einmal per Browser aufrufen und die Grundseiten pruefen.
## MySQL Und MariaDB
Der aktuelle Installer ist jetzt auf MySQL/MariaDB ausgelegt:
- Standardwert `DB_CONNECTION=mysql`
- Standardport `3306`
- direkte PHP-Migration ueber `pdo_mysql`
- SQL-Bundle im MySQL/MariaDB-Dialekt
Wenn `pdo_mysql` auf dem Hosting nicht geladen ist, kann das SQL-Bundle weiter
manuell im Datenbank-Tool des Hosters importiert werden.
## Cron- und Batch-Betrieb
+2 -2
View File
@@ -7,9 +7,9 @@ APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=sqlsrv
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=1433
DB_PORT=3306
DB_DATABASE=kaffeeliste_saas
DB_USERNAME=saas_user
DB_PASSWORD=
+3 -3
View File
@@ -31,7 +31,7 @@ Alternative mit PHP-CLI:
1. `php ../scripts/check-prerequisites.php`
2. `php ../scripts/install-saas.php`
3. `php ../scripts/run-sql-migrations.php --server=... --database=...`
3. `php ../scripts/run-sql-migrations.php --connection=mysql --server=... --database=...`
## Migrationen
@@ -42,8 +42,8 @@ Skripte verarbeitet:
- `..\scripts\build-migration-bundle.php`
- `..\scripts\run-sql-migrations.php`
Wenn `pdo_sqlsrv` lokal nicht verfuegbar ist, muss das erzeugte SQL-Bundle
manuell gegen SQL Server ausgefuehrt werden.
Wenn `pdo_mysql` lokal oder auf dem Hosting nicht verfuegbar ist, muss das
erzeugte SQL-Bundle manuell gegen MySQL/MariaDB ausgefuehrt werden.
## Migration Aus Dem Legacy-System
@@ -1,7 +1,8 @@
-- Generated migration bundle for Kaffeeliste SaaS
-- Generated at 2026-03-21T19:24:13+00:00
SET XACT_ABORT ON;
BEGIN TRANSACTION;
-- Generated at 2026-03-21T20:39:08+00:00
-- Target: MySQL
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS=0;
-- Migration: 2026_03_20_000001_create_tenants_table.php
CREATE TABLE tenants (
@@ -12,7 +13,7 @@ CREATE TABLE tenants (
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE (tenant_key)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000002_create_users_table.php
CREATE TABLE users (
@@ -20,11 +21,11 @@ CREATE TABLE users (
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NULL,
display_name VARCHAR(255) NOT NULL,
is_platform_admin BIT NOT NULL DEFAULT 0,
is_platform_admin TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE (email)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000003_create_tenant_users_table.php
CREATE TABLE tenant_users (
@@ -37,7 +38,7 @@ CREATE TABLE tenant_users (
UNIQUE (tenant_id, user_id),
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000004_create_roles_table.php
CREATE TABLE roles (
@@ -48,7 +49,7 @@ CREATE TABLE roles (
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE (role_key, scope)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000005_create_tenant_user_roles_table.php
CREATE TABLE tenant_user_roles (
@@ -59,7 +60,7 @@ CREATE TABLE tenant_user_roles (
UNIQUE (tenant_user_id, role_id),
FOREIGN KEY (tenant_user_id) REFERENCES tenant_users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000006_create_members_table.php
CREATE TABLE members (
@@ -75,7 +76,7 @@ CREATE TABLE members (
UNIQUE (tenant_id, tenant_user_id),
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
FOREIGN KEY (tenant_user_id) REFERENCES tenant_users(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000006_create_password_reset_tokens_table.php
CREATE TABLE password_reset_tokens (
@@ -83,7 +84,7 @@ CREATE TABLE password_reset_tokens (
token VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (email)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000007_create_coffee_entries_table.php
CREATE TABLE coffee_entries (
@@ -99,7 +100,7 @@ CREATE TABLE coffee_entries (
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
FOREIGN KEY (member_id) REFERENCES members(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000007_create_tenant_identity_providers_table.php
CREATE TABLE tenant_identity_providers (
@@ -116,12 +117,12 @@ CREATE TABLE tenant_identity_providers (
userinfo_url VARCHAR(1024) NULL,
redirect_uri VARCHAR(1024) NOT NULL,
scopes TEXT NULL,
is_enabled BIT NOT NULL DEFAULT 1,
is_enabled TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE (tenant_id, provider_key),
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000008_create_payment_entries_table.php
CREATE TABLE payment_entries (
@@ -135,7 +136,7 @@ CREATE TABLE payment_entries (
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
FOREIGN KEY (member_id) REFERENCES members(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000008_create_user_identities_table.php
CREATE TABLE user_identities (
@@ -150,7 +151,7 @@ CREATE TABLE user_identities (
UNIQUE (provider_key, external_subject),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000009_create_ledger_entries_table.php
CREATE TABLE ledger_entries (
@@ -166,7 +167,7 @@ CREATE TABLE ledger_entries (
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
FOREIGN KEY (member_id) REFERENCES members(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000010_create_announcements_table.php
CREATE TABLE announcements (
@@ -175,11 +176,11 @@ CREATE TABLE announcements (
title VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
visible_until DATETIME NULL,
is_active BIT NOT NULL DEFAULT 1,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000011_create_faq_items_table.php
CREATE TABLE faq_items (
@@ -188,11 +189,11 @@ CREATE TABLE faq_items (
question VARCHAR(255) NOT NULL,
answer TEXT NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
is_active BIT NOT NULL DEFAULT 1,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000012_create_surveys_table.php
CREATE TABLE surveys (
@@ -205,7 +206,7 @@ CREATE TABLE surveys (
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000013_create_survey_questions_table.php
CREATE TABLE survey_questions (
@@ -213,12 +214,12 @@ CREATE TABLE survey_questions (
survey_id CHAR(36) NOT NULL,
question TEXT NOT NULL,
question_type VARCHAR(50) NOT NULL,
is_required BIT NOT NULL DEFAULT 0,
is_required TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (survey_id) REFERENCES surveys(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000014_create_survey_answers_table.php
CREATE TABLE survey_answers (
@@ -232,7 +233,7 @@ CREATE TABLE survey_answers (
FOREIGN KEY (survey_id) REFERENCES surveys(id),
FOREIGN KEY (question_id) REFERENCES survey_questions(id),
FOREIGN KEY (tenant_user_id) REFERENCES tenant_users(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000015_create_import_jobs_table.php
CREATE TABLE import_jobs (
@@ -246,7 +247,7 @@ CREATE TABLE import_jobs (
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000016_create_export_jobs_table.php
CREATE TABLE export_jobs (
@@ -258,7 +259,7 @@ CREATE TABLE export_jobs (
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Migration: 2026_03_20_000017_create_notification_logs_table.php
CREATE TABLE notification_logs (
@@ -272,6 +273,6 @@ CREATE TABLE notification_logs (
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
COMMIT TRANSACTION;
SET FOREIGN_KEY_CHECKS=1;
+3 -3
View File
@@ -50,8 +50,8 @@ $migrationSteps = [
'Die Dateien unter `saas-app/database/migrations/*.php` sind keine Laravel-Migrationsklassen, sondern SQL-Skizzen.',
'Das Bundle wird ueber `php scripts/build-migration-bundle.php` erzeugt.',
'Empfohlener Weg: `php scripts/install-saas.php`.',
'Direkte SQL-Server-Ausfuehrung: `php scripts/run-sql-migrations.php --server=<server> --database=<db> --username=<user> --password=<pass>`.',
'Ohne `pdo_sqlsrv` die erzeugte SQL-Datei manuell in SQL Server Management Studio oder einem kompatiblen Tool ausfuehren.',
'Direkte MySQL/MariaDB-Ausfuehrung: `php scripts/run-sql-migrations.php --connection=mysql --server=<server> --database=<db> --username=<user> --password=<pass>`.',
'Ohne `pdo_mysql` die erzeugte SQL-Datei manuell im Datenbank-Tool des Hosters importieren.',
];
function renderList(array $items): void
@@ -380,7 +380,7 @@ function renderList(array $items): void
<ul class="list">
<li>Composer ist fuer den finalen Bootstrap weiterhin erforderlich.</li>
<li>Die Migrationen sind aktuell SQL-basiert, nicht <code>artisan</code>-basiert.</li>
<li>Fuer automatische Ausfuehrung wird die PHP-Erweiterung <code>pdo_sqlsrv</code> benoetigt.</li>
<li>Fuer automatische Ausfuehrung auf MySQL/MariaDB wird die PHP-Erweiterung <code>pdo_mysql</code> benoetigt.</li>
<li>Der bevorzugte Hosting-Pfad ist jetzt <code>/install/</code> als gefuehrter Einmal-Installer.</li>
</ul>
</article>
+171 -30
View File
@@ -36,6 +36,56 @@ function scripts_installer_lock_path(): string
return scripts_saas_app_path() . DIRECTORY_SEPARATOR . '.installer.lock';
}
function scripts_supported_connections(): array
{
return [
'mysql' => 'MySQL',
'mariadb' => 'MariaDB',
'sqlsrv' => 'SQL Server',
];
}
function scripts_normalize_db_connection(?string $connection): string
{
$normalized = strtolower(trim((string) $connection));
if ($normalized === 'maria' || $normalized === 'maria-db') {
$normalized = 'mariadb';
}
if ($normalized === 'sqlserver' || $normalized === 'mssql') {
$normalized = 'sqlsrv';
}
return array_key_exists($normalized, scripts_supported_connections()) ? $normalized : 'mysql';
}
function scripts_connection_label(string $connection): string
{
$connection = scripts_normalize_db_connection($connection);
$supported = scripts_supported_connections();
return $supported[$connection];
}
function scripts_default_db_port(string $connection): string
{
$connection = scripts_normalize_db_connection($connection);
if ($connection === 'sqlsrv') {
return '1433';
}
return '3306';
}
function scripts_required_pdo_extension(string $connection): string
{
$connection = scripts_normalize_db_connection($connection);
return $connection === 'sqlsrv' ? 'pdo_sqlsrv' : 'pdo_mysql';
}
function scripts_parent_directory(string $path): string
{
return dirname($path);
@@ -215,8 +265,85 @@ function scripts_format_env_value(string $value): string
return $value;
}
function scripts_build_migration_bundle(?string $outputPath = null): string
function scripts_transform_migration_sql(string $sql, string $connection): string
{
$connection = scripts_normalize_db_connection($connection);
$sql = trim(str_replace(["\r\n", "\r"], "\n", $sql));
if ($connection === 'sqlsrv') {
return $sql;
}
$sql = preg_replace('/\bBIT\s+NOT NULL\s+DEFAULT\s+([01])\b/i', 'TINYINT(1) NOT NULL DEFAULT $1', $sql) ?? $sql;
$sql = preg_replace('/\bBIT\b/i', 'TINYINT(1)', $sql) ?? $sql;
if (preg_match('/^CREATE TABLE\s+/i', $sql) === 1) {
$sql = preg_replace(
'/\)\s*;\s*$/',
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;',
$sql
) ?? $sql;
}
return $sql;
}
function scripts_load_migration_sql(string $file, string $connection): string
{
$sql = require $file;
if (!is_string($sql) || trim($sql) === '') {
throw new RuntimeException('Ungueltige Migration: ' . $file);
}
return scripts_transform_migration_sql($sql, $connection);
}
function scripts_bundle_prologue(string $connection): array
{
$connection = scripts_normalize_db_connection($connection);
if ($connection === 'sqlsrv') {
return [
'-- Generated migration bundle for Kaffeeliste SaaS',
'-- Generated at ' . date('c'),
'-- Target: ' . scripts_connection_label($connection),
'SET XACT_ABORT ON;',
'BEGIN TRANSACTION;',
];
}
return [
'-- Generated migration bundle for Kaffeeliste SaaS',
'-- Generated at ' . date('c'),
'-- Target: ' . scripts_connection_label($connection),
'SET NAMES utf8mb4;',
'SET FOREIGN_KEY_CHECKS=0;',
];
}
function scripts_bundle_epilogue(string $connection): array
{
$connection = scripts_normalize_db_connection($connection);
if ($connection === 'sqlsrv') {
return [
'',
'COMMIT TRANSACTION;',
'',
];
}
return [
'',
'SET FOREIGN_KEY_CHECKS=1;',
'',
];
}
function scripts_build_migration_bundle(?string $outputPath = null, string $connection = 'mysql'): string
{
$connection = scripts_normalize_db_connection($connection);
$migrationDir = scripts_saas_app_path()
. DIRECTORY_SEPARATOR . 'database'
. DIRECTORY_SEPARATOR . 'migrations';
@@ -241,27 +368,19 @@ function scripts_build_migration_bundle(?string $outputPath = null): string
throw new RuntimeException('Ausgabeverzeichnis konnte nicht erstellt werden: ' . $outputDir);
}
$bundle = [];
$bundle[] = '-- Generated migration bundle for Kaffeeliste SaaS';
$bundle[] = '-- Generated at ' . date('c');
$bundle[] = 'SET XACT_ABORT ON;';
$bundle[] = 'BEGIN TRANSACTION;';
$bundle = scripts_bundle_prologue($connection);
foreach ($files as $file) {
$sql = require $file;
if (!is_string($sql) || trim($sql) === '') {
throw new RuntimeException('Ungueltige Migration: ' . $file);
}
$sql = scripts_load_migration_sql($file, $connection);
$bundle[] = '';
$bundle[] = '-- Migration: ' . basename($file);
$bundle[] = trim($sql);
$bundle[] = $sql;
}
$bundle[] = '';
$bundle[] = 'COMMIT TRANSACTION;';
$bundle[] = '';
foreach (scripts_bundle_epilogue($connection) as $line) {
$bundle[] = $line;
}
if (file_put_contents($outputPath, implode(PHP_EOL, $bundle)) === false) {
throw new RuntimeException('Das SQL-Bundle konnte nicht geschrieben werden: ' . $outputPath);
@@ -272,9 +391,10 @@ function scripts_build_migration_bundle(?string $outputPath = null): string
function scripts_run_sql_migrations(array $config): array
{
$connection = scripts_normalize_db_connection(isset($config['connection']) ? (string) $config['connection'] : 'mysql');
$server = isset($config['server']) ? $config['server'] : null;
$database = isset($config['database']) ? $config['database'] : null;
$port = isset($config['port']) ? (string) $config['port'] : '1433';
$port = isset($config['port']) ? (string) $config['port'] : scripts_default_db_port($connection);
$username = isset($config['username']) ? $config['username'] : null;
$password = isset($config['password']) ? $config['password'] : null;
@@ -282,8 +402,9 @@ function scripts_run_sql_migrations(array $config): array
throw new RuntimeException('Bitte Server und Datenbank angeben.');
}
if (!extension_loaded('pdo_sqlsrv')) {
throw new RuntimeException('Die PHP-Erweiterung pdo_sqlsrv ist nicht geladen.');
$requiredExtension = scripts_required_pdo_extension($connection);
if (!extension_loaded($requiredExtension)) {
throw new RuntimeException('Die PHP-Erweiterung ' . $requiredExtension . ' ist nicht geladen.');
}
$migrationDir = scripts_saas_app_path()
@@ -297,33 +418,53 @@ function scripts_run_sql_migrations(array $config): array
throw new RuntimeException('Keine Migrationsdateien gefunden.');
}
$dsn = sprintf('sqlsrv:Server=%s,%s;Database=%s;TrustServerCertificate=1', $server, $port, $database);
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$pdo->beginTransaction();
if ($connection === 'sqlsrv') {
$dsn = sprintf('sqlsrv:Server=%s,%s;Database=%s;TrustServerCertificate=1', $server, $port, $database);
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
} else {
$dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $server, $port, $database);
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
$executed = [];
try {
foreach ($files as $file) {
$sql = require $file;
if ($connection === 'sqlsrv') {
$pdo->beginTransaction();
} else {
$pdo->exec('SET NAMES utf8mb4');
$pdo->exec('SET FOREIGN_KEY_CHECKS=0');
}
if (!is_string($sql) || trim($sql) === '') {
throw new RuntimeException('Ungueltige Migration: ' . basename($file));
}
foreach ($files as $file) {
$sql = scripts_load_migration_sql($file, $connection);
$pdo->exec($sql);
$executed[] = basename($file);
}
$pdo->commit();
if ($connection === 'sqlsrv') {
$pdo->commit();
} else {
$pdo->exec('SET FOREIGN_KEY_CHECKS=1');
}
} catch (Throwable $exception) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
if ($connection !== 'sqlsrv') {
try {
$pdo->exec('SET FOREIGN_KEY_CHECKS=1');
} catch (Throwable $ignored) {
}
}
throw $exception;
}
+42 -9
View File
@@ -33,10 +33,12 @@ if (!isset($_SESSION['installer_csrf'])) {
$defaults = scripts_read_env_file(scripts_env_example_path());
$currentEnv = scripts_read_env_file(scripts_env_path());
$defaultConnection = scripts_normalize_db_connection($currentEnv['DB_CONNECTION'] ?? ($defaults['DB_CONNECTION'] ?? 'mysql'));
$values = [
'APP_URL' => $currentEnv['APP_URL'] ?? ($defaults['APP_URL'] ?? ''),
'DB_CONNECTION' => $defaultConnection,
'DB_HOST' => $currentEnv['DB_HOST'] ?? ($defaults['DB_HOST'] ?? '127.0.0.1'),
'DB_PORT' => $currentEnv['DB_PORT'] ?? ($defaults['DB_PORT'] ?? '1433'),
'DB_PORT' => $currentEnv['DB_PORT'] ?? ($defaults['DB_PORT'] ?? scripts_default_db_port($defaultConnection)),
'DB_DATABASE' => $currentEnv['DB_DATABASE'] ?? ($defaults['DB_DATABASE'] ?? ''),
'DB_USERNAME' => $currentEnv['DB_USERNAME'] ?? ($defaults['DB_USERNAME'] ?? ''),
'DB_PASSWORD' => '',
@@ -64,6 +66,15 @@ $openBaseDir = (string) ini_get('open_basedir');
$envPathState = scripts_path_writable_state(scripts_env_path());
$lockPathState = scripts_path_writable_state(scripts_installer_lock_path());
$bundlePathState = scripts_path_writable_state(scripts_bundle_output_path());
$activeConnection = scripts_normalize_db_connection($values['DB_CONNECTION']);
$activeConnectionLabel = scripts_connection_label($activeConnection);
$requiredExtension = scripts_required_pdo_extension($activeConnection);
$requiredExtensionLoaded = extension_loaded($requiredExtension);
if (!$saasAppVisible) {
$errors[] = 'PHP darf den SaaS-App-Ordner aktuell nicht lesen. Die `open_basedir`-Konfiguration erlaubt nur `public/`.';
$errors[] = 'Stelle den Document-Root weiter auf `saas-app/public`, erweitere aber `open_basedir` mindestens auf `saas-app/`.';
}
if ($requestMethod === 'POST' && !$locked) {
$csrf = (string) ($_POST['csrf'] ?? '');
@@ -77,6 +88,17 @@ if ($requestMethod === 'POST' && !$locked) {
}
}
$values['DB_CONNECTION'] = scripts_normalize_db_connection($values['DB_CONNECTION']);
if ($values['DB_PORT'] === '') {
$values['DB_PORT'] = scripts_default_db_port($values['DB_CONNECTION']);
}
$activeConnection = scripts_normalize_db_connection($values['DB_CONNECTION']);
$activeConnectionLabel = scripts_connection_label($activeConnection);
$requiredExtension = scripts_required_pdo_extension($activeConnection);
$requiredExtensionLoaded = extension_loaded($requiredExtension);
$values['OIDC_ENABLED'] = isset($_POST['OIDC_ENABLED']) ? 'true' : 'false';
if ($values['DB_PASSWORD'] === '' && isset($currentEnv['DB_PASSWORD'])) {
@@ -98,18 +120,19 @@ if ($requestMethod === 'POST' && !$locked) {
scripts_write_env_file($values);
$messages[] = '.env wurde geschrieben.';
$bundlePath = scripts_build_migration_bundle();
$messages[] = 'SQL-Bundle wurde erzeugt.';
$bundlePath = scripts_build_migration_bundle(null, $values['DB_CONNECTION']);
$messages[] = 'SQL-Bundle fuer ' . scripts_connection_label($values['DB_CONNECTION']) . ' wurde erzeugt.';
if (isset($_POST['run_migrations'])) {
$executedMigrations = scripts_run_sql_migrations([
'connection' => $values['DB_CONNECTION'],
'server' => $values['DB_HOST'],
'database' => $values['DB_DATABASE'],
'port' => $values['DB_PORT'],
'username' => $values['DB_USERNAME'],
'password' => $values['DB_PASSWORD'],
]);
$messages[] = 'Migrationen wurden direkt ueber PHP ausgefuehrt.';
$messages[] = 'Migrationen wurden direkt ueber PHP fuer ' . $activeConnectionLabel . ' ausgefuehrt.';
}
if (isset($_POST['lock_installer'])) {
@@ -123,8 +146,8 @@ if ($requestMethod === 'POST' && !$locked) {
} catch (Throwable $exception) {
$message = $exception->getMessage();
if (scripts_string_contains($message, 'pdo_sqlsrv')) {
$errors[] = 'Migrationen konnten nicht direkt ueber PHP ausgefuehrt werden. Bitte `pdo_sqlsrv` pruefen oder das SQL-Bundle manuell importieren.';
if (scripts_string_contains($message, 'pdo_')) {
$errors[] = 'Migrationen konnten nicht direkt ueber PHP ausgefuehrt werden. Bitte `' . $requiredExtension . '` pruefen oder das SQL-Bundle manuell importieren.';
} elseif (scripts_string_contains($message, '.env')) {
$errors[] = 'Die Konfiguration konnte nicht gespeichert werden. Zielpfad: ' . scripts_env_path();
$errors[] = 'Status fuer .env: ' . scripts_path_state_label($envPathState) . '. Elternordner: ' . $envPathState['parent'];
@@ -352,6 +375,14 @@ function h(string $value): string
<label for="APP_URL">APP_URL</label>
<input id="APP_URL" name="APP_URL" value="<?= h($values['APP_URL']) ?>" required>
</div>
<div class="field">
<label for="DB_CONNECTION">DB_CONNECTION</label>
<select id="DB_CONNECTION" name="DB_CONNECTION">
<?php foreach (scripts_supported_connections() as $connectionKey => $connectionLabel): ?>
<option value="<?= h($connectionKey) ?>"<?= $values['DB_CONNECTION'] === $connectionKey ? ' selected' : '' ?>><?= h($connectionLabel) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field">
<label for="DB_HOST">DB_HOST</label>
<input id="DB_HOST" name="DB_HOST" value="<?= h($values['DB_HOST']) ?>" required>
@@ -426,8 +457,8 @@ function h(string $value): string
<div class="stack">
<label class="checkbox">
<input name="run_migrations" type="checkbox"<?= extension_loaded('pdo_sqlsrv') ? '' : ' disabled' ?>>
<span>Migrationen direkt ueber PHP ausfuehren<?= extension_loaded('pdo_sqlsrv') ? '' : ' (pdo_sqlsrv fehlt aktuell)' ?></span>
<input name="run_migrations" type="checkbox"<?= $requiredExtensionLoaded ? '' : ' disabled' ?>>
<span>Migrationen direkt ueber PHP fuer <?= h($activeConnectionLabel) ?> ausfuehren<?= $requiredExtensionLoaded ? '' : ' (' . h($requiredExtension) . ' fehlt aktuell)' ?></span>
</label>
<label class="checkbox">
<input name="lock_installer" type="checkbox" checked>
@@ -452,13 +483,15 @@ function h(string $value): string
<li>SaaS-App Pfad: <code><?= h($saasAppPath) ?></code></li>
<li>SaaS-App sichtbar: <code><?= $saasAppVisible ? 'ja' : 'nein' ?></code></li>
<li>open_basedir: <code><?= h($openBaseDir !== '' ? $openBaseDir : 'nicht gesetzt') ?></code></li>
<li>DB_CONNECTION: <code><?= h($activeConnection) ?></code></li>
<li>Ziel-Datenbank: <code><?= h($activeConnectionLabel) ?></code></li>
<li>.env.example: <code><?= is_file(scripts_env_example_path()) ? 'vorhanden' : 'fehlt' ?></code></li>
<li>.env: <code><?= is_file(scripts_env_path()) ? 'vorhanden' : 'fehlt' ?></code></li>
<li>.env Status: <code><?= h(scripts_path_state_label($envPathState)) ?></code></li>
<li>.env Zielpfad: <code><?= h($envPathState['path']) ?></code></li>
<li>.env Elternordner: <code><?= h($envPathState['parent']) ?></code></li>
<li>.env Elternordner beschreibbar: <code><?= $envPathState['parent_writable'] ? 'ja' : 'nein' ?></code></li>
<li>pdo_sqlsrv: <code><?= extension_loaded('pdo_sqlsrv') ? 'aktiv' : 'nicht geladen' ?></code></li>
<li><?= h($requiredExtension) ?>: <code><?= $requiredExtensionLoaded ? 'aktiv' : 'nicht geladen' ?></code></li>
<li>Installer-Lock: <code><?= $locked ? 'aktiv' : 'offen' ?></code></li>
<li>Lock-Datei Status: <code><?= h(scripts_path_state_label($lockPathState)) ?></code></li>
<li>Bundle-Pfad: <code><?= h(scripts_bundle_output_path()) ?></code></li>
+13 -2
View File
@@ -4,10 +4,21 @@ declare(strict_types=1);
require_once __DIR__ . '/support.php';
$outputPath = $argv[1] ?? scripts_bundle_output_path();
$options = scripts_parse_options($argv);
$outputPath = scripts_bundle_output_path();
$connection = scripts_normalize_db_connection($options['connection'] ?? scripts_read_env_file(scripts_env_path())['DB_CONNECTION'] ?? 'mysql');
foreach (array_slice($argv, 1) as $arg) {
if (scripts_string_starts_with($arg, '--')) {
continue;
}
$outputPath = $arg;
break;
}
try {
scripts_stdout(scripts_build_migration_bundle($outputPath));
scripts_stdout(scripts_build_migration_bundle($outputPath, $connection));
} catch (Throwable $exception) {
scripts_stderr($exception->getMessage());
}
+10 -2
View File
@@ -7,7 +7,11 @@ require_once __DIR__ . '/support.php';
$phpVersion = PHP_VERSION;
$composerExists = scripts_check_command('composer');
$gitExists = scripts_check_command('git');
$env = scripts_read_env_file(scripts_env_path());
$dbConnection = scripts_normalize_db_connection($env['DB_CONNECTION'] ?? 'mysql');
$pdoMysqlLoaded = extension_loaded('pdo_mysql');
$pdoSqlsrvLoaded = extension_loaded('pdo_sqlsrv');
$requiredExtension = scripts_required_pdo_extension($dbConnection);
scripts_stdout('Pruefe lokale Voraussetzungen fuer Kaffeeliste SaaS...');
scripts_stdout('Projektwurzel: ' . scripts_project_root());
@@ -17,7 +21,10 @@ $checks = [
['PHP', true, $phpVersion],
['Composer', $composerExists, $composerExists ? 'verfuegbar' : 'nicht gefunden'],
['Git', $gitExists, $gitExists ? 'verfuegbar' : 'nicht gefunden'],
['DB_CONNECTION', true, $dbConnection],
['pdo_mysql', $pdoMysqlLoaded, $pdoMysqlLoaded ? 'geladen' : 'nicht geladen'],
['pdo_sqlsrv', $pdoSqlsrvLoaded, $pdoSqlsrvLoaded ? 'geladen' : 'nicht geladen'],
['Erforderliche PDO-Erweiterung', extension_loaded($requiredExtension), $requiredExtension],
['SaaS-App', is_dir(scripts_saas_app_path()), scripts_saas_app_path()],
['.env.example', is_file(scripts_env_example_path()), scripts_env_example_path()],
['.env', is_file(scripts_env_path()), is_file(scripts_env_path()) ? 'vorhanden' : 'noch nicht angelegt'],
@@ -32,5 +39,6 @@ scripts_stdout('');
scripts_stdout('Naechste Schritte:');
scripts_stdout('1. Falls Composer fehlt, zuerst Composer installieren.');
scripts_stdout('2. Mit `php scripts/prepare-saas-env.php` eine lokale .env anlegen.');
scripts_stdout('3. Mit `php scripts/install-saas.php` das SQL-Bundle erzeugen.');
scripts_stdout('4. Optional `php scripts/run-sql-migrations.php --server=... --database=...` fuer die direkte SQL-Ausfuehrung verwenden.');
scripts_stdout('3. DB_CONNECTION in `saas-app/.env` auf mysql, mariadb oder sqlsrv pruefen.');
scripts_stdout('4. Mit `php scripts/install-saas.php` das SQL-Bundle fuer den gewaehlten Treiber erzeugen.');
scripts_stdout('5. Optional `php scripts/run-sql-migrations.php --connection=' . $dbConnection . ' --server=... --database=...` fuer die direkte SQL-Ausfuehrung verwenden.');
+5 -3
View File
@@ -28,14 +28,16 @@ if ($prepareEnv) {
}
try {
$bundlePath = scripts_build_migration_bundle();
$envValues = scripts_read_env_file(scripts_env_path());
$dbConnection = scripts_normalize_db_connection($envValues['DB_CONNECTION'] ?? 'mysql');
$bundlePath = scripts_build_migration_bundle(null, $dbConnection);
} catch (Throwable $exception) {
scripts_stderr($exception->getMessage());
}
scripts_stdout('');
scripts_stdout('SQL-Bundle erzeugt: ' . $bundlePath);
scripts_stdout('SQL-Bundle fuer ' . scripts_connection_label($dbConnection) . ' erzeugt: ' . $bundlePath);
scripts_stdout('Naechste Schritte:');
scripts_stdout('1. saas-app/.env mit echten DB-, Mail- und Tenancy-Werten fuellen.');
scripts_stdout('2. Das SQL-Bundle manuell importieren oder `php scripts/run-sql-migrations.php --server=... --database=...` verwenden.');
scripts_stdout('2. Das SQL-Bundle manuell importieren oder `php scripts/run-sql-migrations.php --connection=' . $dbConnection . ' --server=... --database=...` verwenden.');
scripts_stdout('3. Danach ersten Tenant, ersten Benutzer und erste Member-Zuordnung anlegen.');
+4 -2
View File
@@ -6,18 +6,20 @@ require_once __DIR__ . '/support.php';
$options = scripts_parse_options($argv);
$env = scripts_read_env_file(scripts_env_path());
$connection = scripts_normalize_db_connection($options['connection'] ?? $env['DB_CONNECTION'] ?? 'mysql');
$config = [
'connection' => $connection,
'server' => $options['server'] ?? $env['DB_HOST'] ?? null,
'database' => $options['database'] ?? $env['DB_DATABASE'] ?? null,
'port' => $options['port'] ?? $env['DB_PORT'] ?? '1433',
'port' => $options['port'] ?? $env['DB_PORT'] ?? scripts_default_db_port($connection),
'username' => $options['username'] ?? $env['DB_USERNAME'] ?? null,
'password' => $options['password'] ?? $env['DB_PASSWORD'] ?? null,
];
try {
scripts_run_sql_migrations($config);
scripts_stdout('Migrationen wurden erfolgreich ausgefuehrt.');
scripts_stdout('Migrationen fuer ' . scripts_connection_label($connection) . ' wurden erfolgreich ausgefuehrt.');
} catch (Throwable $exception) {
scripts_stderr('Migration fehlgeschlagen: ' . $exception->getMessage());
}