Refactor installation scripts and update documentation
- Replaced PowerShell scripts with PHP scripts for checking prerequisites and preparing the environment. - Added new PHP scripts: `check-prerequisites.php`, `prepare-saas-env.php`, `install-saas.php`, `build-migration-bundle.php`, and `run-sql-migrations.php`. - Updated README and installation documentation to reflect the new PHP scripts and installation steps. - Created a generated SQL migration bundle file structure and added SQL migration scripts. - Enhanced the public index page with navigation and installation steps. - Removed obsolete PowerShell scripts.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$projectRoot = dirname(__DIR__);
|
||||
$migrationDir = $projectRoot . DIRECTORY_SEPARATOR . 'saas-app' . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations';
|
||||
$outputPath = $argv[1] ?? (
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'saas-app' . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR . 'generated' . DIRECTORY_SEPARATOR . 'all-migrations.sql'
|
||||
);
|
||||
|
||||
if (!is_dir($migrationDir)) {
|
||||
fwrite(STDERR, "Migrationsverzeichnis nicht gefunden: {$migrationDir}" . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$files = glob($migrationDir . DIRECTORY_SEPARATOR . '*.php');
|
||||
sort($files, SORT_STRING);
|
||||
|
||||
if ($files === []) {
|
||||
fwrite(STDERR, "Keine Migrationsdateien gefunden." . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$outputDir = dirname($outputPath);
|
||||
if (!is_dir($outputDir) && !mkdir($outputDir, 0777, true) && !is_dir($outputDir)) {
|
||||
fwrite(STDERR, "Ausgabeverzeichnis konnte nicht erstellt werden: {$outputDir}" . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$bundle = [];
|
||||
$bundle[] = '-- Generated migration bundle for Kaffeeliste SaaS';
|
||||
$bundle[] = '-- Generated at ' . date('c');
|
||||
$bundle[] = 'SET XACT_ABORT ON;';
|
||||
$bundle[] = 'BEGIN TRANSACTION;';
|
||||
|
||||
foreach ($files as $file) {
|
||||
$sql = require $file;
|
||||
|
||||
if (!is_string($sql) || trim($sql) === '') {
|
||||
fwrite(STDERR, "Ungueltige Migration: {$file}" . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$bundle[] = '';
|
||||
$bundle[] = '-- Migration: ' . basename($file);
|
||||
$bundle[] = trim($sql);
|
||||
}
|
||||
|
||||
$bundle[] = '';
|
||||
$bundle[] = 'COMMIT TRANSACTION;';
|
||||
$bundle[] = '';
|
||||
|
||||
file_put_contents($outputPath, implode(PHP_EOL, $bundle));
|
||||
|
||||
fwrite(STDOUT, $outputPath . PHP_EOL);
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/support.php';
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$composerExists = scripts_check_command('composer');
|
||||
$gitExists = scripts_check_command('git');
|
||||
|
||||
scripts_stdout('Pruefe lokale Voraussetzungen fuer Kaffeeliste SaaS...');
|
||||
scripts_stdout('Projektwurzel: ' . scripts_project_root());
|
||||
scripts_stdout('');
|
||||
|
||||
$checks = [
|
||||
['PHP', true, $phpVersion],
|
||||
['Composer', $composerExists, $composerExists ? 'verfuegbar' : 'nicht gefunden'],
|
||||
['Git', $gitExists, $gitExists ? 'verfuegbar' : 'nicht gefunden'],
|
||||
['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'],
|
||||
];
|
||||
|
||||
foreach ($checks as [$label, $passed, $detail]) {
|
||||
$prefix = $passed ? '[OK]' : '[FEHLT]';
|
||||
scripts_stdout(sprintf('%s %s - %s', $prefix, $label, $detail));
|
||||
}
|
||||
|
||||
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.');
|
||||
@@ -1,48 +0,0 @@
|
||||
param(
|
||||
[string]$ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$saasAppPath = Join-Path $ProjectRoot 'saas-app'
|
||||
$envExamplePath = Join-Path $saasAppPath '.env.example'
|
||||
|
||||
function Test-Command {
|
||||
param([string]$Name)
|
||||
|
||||
return $null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Write-Check {
|
||||
param(
|
||||
[string]$Label,
|
||||
[bool]$Passed,
|
||||
[string]$Detail
|
||||
)
|
||||
|
||||
$status = if ($Passed) { '[OK]' } else { '[FEHLT]' }
|
||||
Write-Host "$status $Label - $Detail"
|
||||
}
|
||||
|
||||
Write-Host 'Pruefe lokale Voraussetzungen fuer Kaffeeliste SaaS...'
|
||||
Write-Host "Projektwurzel: $ProjectRoot"
|
||||
Write-Host ''
|
||||
|
||||
$phpExists = Test-Command 'php'
|
||||
$composerExists = Test-Command 'composer'
|
||||
$gitExists = Test-Command 'git'
|
||||
|
||||
Write-Check -Label 'PHP' -Passed $phpExists -Detail ($(if ($phpExists) { (php -r "echo PHP_VERSION;") } else { 'nicht gefunden' }))
|
||||
Write-Check -Label 'Composer' -Passed $composerExists -Detail ($(if ($composerExists) { (composer --version | Select-Object -First 1) } else { 'nicht gefunden' }))
|
||||
Write-Check -Label 'Git' -Passed $gitExists -Detail ($(if ($gitExists) { 'verfuegbar' } else { 'nicht gefunden' }))
|
||||
Write-Check -Label 'SaaS-App' -Passed (Test-Path $saasAppPath) -Detail $saasAppPath
|
||||
Write-Check -Label '.env.example' -Passed (Test-Path $envExamplePath) -Detail $envExamplePath
|
||||
|
||||
$envPath = Join-Path $saasAppPath '.env'
|
||||
Write-Check -Label '.env' -Passed (Test-Path $envPath) -Detail ($(if (Test-Path $envPath) { 'vorhanden' } else { 'noch nicht angelegt' }))
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Naechste Schritte:'
|
||||
Write-Host '1. Falls Composer fehlt, zuerst Composer installieren.'
|
||||
Write-Host '2. Mit scripts/prepare-saas-env.ps1 eine lokale .env anlegen.'
|
||||
Write-Host '3. Danach saas-app konfigurieren und die Installationsanleitung in docs/installationshandbuch.md befolgen.'
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/support.php';
|
||||
|
||||
$options = scripts_parse_options($argv);
|
||||
$forceEnv = isset($options['force-env']);
|
||||
$prepareEnv = isset($options['prepare-env']) || !is_file(scripts_env_path());
|
||||
|
||||
scripts_stdout('Starte SaaS-Installationsvorbereitung...');
|
||||
scripts_stdout('');
|
||||
|
||||
require __DIR__ . '/check-prerequisites.php';
|
||||
|
||||
if ($prepareEnv) {
|
||||
$command = 'php ' . escapeshellarg(__DIR__ . '/prepare-saas-env.php');
|
||||
|
||||
if ($forceEnv) {
|
||||
$command .= ' --force';
|
||||
}
|
||||
|
||||
passthru($command, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
exit($exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
$bundlePath = scripts_project_root() . DIRECTORY_SEPARATOR . 'saas-app' . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR . 'generated' . DIRECTORY_SEPARATOR . 'all-migrations.sql';
|
||||
$command = 'php ' . escapeshellarg(__DIR__ . '/build-migration-bundle.php') . ' ' . escapeshellarg($bundlePath);
|
||||
passthru($command, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
scripts_stdout('');
|
||||
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('3. Danach ersten Tenant, ersten Benutzer und erste Member-Zuordnung anlegen.');
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/support.php';
|
||||
|
||||
$options = scripts_parse_options($argv);
|
||||
$force = isset($options['force']);
|
||||
|
||||
$sourcePath = scripts_env_example_path();
|
||||
$targetPath = scripts_env_path();
|
||||
|
||||
if (!is_file($sourcePath)) {
|
||||
scripts_stderr('Quelle nicht gefunden: ' . $sourcePath);
|
||||
}
|
||||
|
||||
if (is_file($targetPath) && !$force) {
|
||||
scripts_stderr('.env existiert bereits. Nutze `php scripts/prepare-saas-env.php --force` fuer ein Ueberschreiben.');
|
||||
}
|
||||
|
||||
if (!copy($sourcePath, $targetPath)) {
|
||||
scripts_stderr('Die .env konnte nicht angelegt werden.');
|
||||
}
|
||||
|
||||
scripts_stdout('Lokale .env wurde angelegt: ' . $targetPath);
|
||||
scripts_stdout('Passe jetzt DB-, Mail-, Tenancy- und OIDC-Werte an.');
|
||||
@@ -1,23 +0,0 @@
|
||||
param(
|
||||
[string]$ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$saasAppPath = Join-Path $ProjectRoot 'saas-app'
|
||||
$sourcePath = Join-Path $saasAppPath '.env.example'
|
||||
$targetPath = Join-Path $saasAppPath '.env'
|
||||
|
||||
if (-not (Test-Path $sourcePath)) {
|
||||
throw "Quelle nicht gefunden: $sourcePath"
|
||||
}
|
||||
|
||||
if ((Test-Path $targetPath) -and -not $Force) {
|
||||
throw ".env existiert bereits. Nutze -Force, wenn sie neu erzeugt werden soll."
|
||||
}
|
||||
|
||||
Copy-Item -Path $sourcePath -Destination $targetPath -Force
|
||||
|
||||
Write-Host "Lokale .env wurde angelegt: $targetPath"
|
||||
Write-Host 'Passe jetzt DB-, Mail-, Tenancy- und OIDC-Werte an.'
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/support.php';
|
||||
|
||||
$options = scripts_parse_options($argv);
|
||||
$env = scripts_read_env_file(scripts_env_path());
|
||||
|
||||
$server = $options['server'] ?? $env['DB_HOST'] ?? null;
|
||||
$database = $options['database'] ?? $env['DB_DATABASE'] ?? null;
|
||||
$port = $options['port'] ?? $env['DB_PORT'] ?? '1433';
|
||||
$username = $options['username'] ?? $env['DB_USERNAME'] ?? null;
|
||||
$password = $options['password'] ?? $env['DB_PASSWORD'] ?? null;
|
||||
|
||||
if ($server === null || $database === null) {
|
||||
scripts_stderr('Bitte --server und --database angeben oder eine passende saas-app/.env pflegen.');
|
||||
}
|
||||
|
||||
if (!extension_loaded('pdo_sqlsrv')) {
|
||||
scripts_stderr('Die PHP-Erweiterung pdo_sqlsrv ist nicht geladen. Fuehre das Bundle manuell aus oder aktiviere den Treiber.');
|
||||
}
|
||||
|
||||
$migrationDir = scripts_project_root() . DIRECTORY_SEPARATOR . 'saas-app' . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations';
|
||||
$files = glob($migrationDir . DIRECTORY_SEPARATOR . '*.php');
|
||||
sort($files, SORT_STRING);
|
||||
|
||||
if ($files === []) {
|
||||
scripts_stderr('Keine Migrationsdateien gefunden.');
|
||||
}
|
||||
|
||||
$dsn = sprintf('sqlsrv:Server=%s,%s;Database=%s;TrustServerCertificate=1', $server, $port, $database);
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $username, $password, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
foreach ($files as $file) {
|
||||
$sql = require $file;
|
||||
|
||||
if (!is_string($sql) || trim($sql) === '') {
|
||||
throw new RuntimeException('Ungueltige Migration: ' . basename($file));
|
||||
}
|
||||
|
||||
scripts_stdout('Fuehre aus: ' . basename($file));
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
scripts_stdout('Migrationen wurden erfolgreich ausgefuehrt.');
|
||||
} catch (Throwable $exception) {
|
||||
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
scripts_stderr('Migration fehlgeschlagen: ' . $exception->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function scripts_project_root(): string
|
||||
{
|
||||
return dirname(__DIR__);
|
||||
}
|
||||
|
||||
function scripts_saas_app_path(): string
|
||||
{
|
||||
return scripts_project_root() . DIRECTORY_SEPARATOR . 'saas-app';
|
||||
}
|
||||
|
||||
function scripts_env_example_path(): string
|
||||
{
|
||||
return scripts_saas_app_path() . DIRECTORY_SEPARATOR . '.env.example';
|
||||
}
|
||||
|
||||
function scripts_env_path(): string
|
||||
{
|
||||
return scripts_saas_app_path() . DIRECTORY_SEPARATOR . '.env';
|
||||
}
|
||||
|
||||
function scripts_check_command(string $command): bool
|
||||
{
|
||||
$where = stripos(PHP_OS_FAMILY, 'Windows') === 0 ? 'where' : 'command -v';
|
||||
$output = [];
|
||||
$exitCode = 0;
|
||||
$redirect = stripos(PHP_OS_FAMILY, 'Windows') === 0 ? ' 2>NUL' : ' 2>/dev/null';
|
||||
@exec($where . ' ' . escapeshellarg($command) . $redirect, $output, $exitCode);
|
||||
|
||||
return $exitCode === 0;
|
||||
}
|
||||
|
||||
function scripts_parse_options(array $argv): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
foreach (array_slice($argv, 1) as $arg) {
|
||||
if (!str_starts_with($arg, '--')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arg = substr($arg, 2);
|
||||
|
||||
if (str_contains($arg, '=')) {
|
||||
[$key, $value] = explode('=', $arg, 2);
|
||||
$options[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[$arg] = true;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function scripts_read_env_file(string $path): array
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$values = [];
|
||||
|
||||
foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
|
||||
$trimmed = trim($line);
|
||||
|
||||
if ($trimmed === '' || str_starts_with($trimmed, '#') || !str_contains($trimmed, '=')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$key, $value] = explode('=', $trimmed, 2);
|
||||
$values[trim($key)] = trim($value, " \t\n\r\0\x0B\"");
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
function scripts_stdout(string $message): void
|
||||
{
|
||||
fwrite(STDOUT, $message . PHP_EOL);
|
||||
}
|
||||
|
||||
function scripts_stderr(string $message, int $exitCode = 1): never
|
||||
{
|
||||
fwrite(STDERR, $message . PHP_EOL);
|
||||
exit($exitCode);
|
||||
}
|
||||
Reference in New Issue
Block a user