127 lines
10 KiB
PHP
127 lines
10 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
$root = __DIR__;
|
|
|
|
session_name('PNP_INSTALL');
|
|
session_start([
|
|
'use_strict_mode' => true,
|
|
'cookie_httponly' => true,
|
|
'cookie_samesite' => 'Strict',
|
|
'cookie_secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
|
]);
|
|
header('X-Content-Type-Options: nosniff');
|
|
header('X-Frame-Options: DENY');
|
|
header('Referrer-Policy: no-referrer');
|
|
header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'");
|
|
|
|
$completeFile = __DIR__ . '/.install-complete';
|
|
if (is_file($completeFile)) {
|
|
http_response_code(404);
|
|
exit('Not found');
|
|
}
|
|
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(32));
|
|
|
|
$error = null;
|
|
$success = false;
|
|
$values = [
|
|
'db_host' => 'localhost', 'db_name' => 'pnpaas', 'db_user' => 'pnpaas',
|
|
'admin_user' => 'admin', 'admin_email' => '', 'app_url' => '',
|
|
'portainer_url' => '', 'terms_url' => '', 'terms_version' => '',
|
|
'privacy_url' => '', 'privacy_version' => '', 'withdrawal_url' => '',
|
|
'withdrawal_version' => '', 'portainer_endpoint_id' => '3',
|
|
];
|
|
|
|
function setup_h(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
|
|
function setup_https_url(string $value, string $label, bool $required = false): string {
|
|
$value = trim($value);
|
|
if (!$required && $value === '') return '';
|
|
if (filter_var($value, FILTER_VALIDATE_URL) === false || !str_starts_with(strtolower($value), 'https://')) {
|
|
throw new RuntimeException("$label muss eine gültige HTTPS-URL sein.");
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
|
|
foreach ($values as $key => $_) $values[$key] = trim((string)($_POST[$key] ?? ''));
|
|
try {
|
|
if (!hash_equals((string)$_SESSION['csrf'], (string)($_POST['csrf'] ?? ''))) throw new RuntimeException('Die Sitzung ist abgelaufen.');
|
|
if (!preg_match('/^[A-Za-z0-9_.:-]{1,253}$/', $values['db_host'])) throw new RuntimeException('Der Datenbank-Host ist ungültig.');
|
|
if (!preg_match('/^[A-Za-z0-9_]{1,64}$/', $values['db_name']) || !preg_match('/^[A-Za-z0-9_]{1,32}$/', $values['db_user'])) throw new RuntimeException('Datenbankname oder Benutzername ist ungültig.');
|
|
if ((string)($_POST['db_password'] ?? '') === '') throw new RuntimeException('Das Datenbankpasswort ist erforderlich.');
|
|
if (!preg_match('/^[A-Za-z0-9_.-]{3,80}$/', $values['admin_user'])) throw new RuntimeException('Der Admin-Benutzername ist ungültig.');
|
|
if (!filter_var($values['admin_email'], FILTER_VALIDATE_EMAIL)) throw new RuntimeException('Die Admin-E-Mail-Adresse ist ungültig.');
|
|
if (strlen((string)($_POST['admin_password'] ?? '')) < 12) throw new RuntimeException('Das Adminpasswort muss mindestens 12 Zeichen enthalten.');
|
|
$appUrl = setup_https_url($values['app_url'], 'Die App-URL', true);
|
|
$portainerUrl = setup_https_url($values['portainer_url'], 'Die Portainer-URL');
|
|
$termsUrl = setup_https_url($values['terms_url'], 'Die AGB-URL', true);
|
|
$privacyUrl = setup_https_url($values['privacy_url'], 'Die Datenschutz-URL', true);
|
|
$withdrawalUrl = setup_https_url($values['withdrawal_url'], 'Die Widerrufs-URL', true);
|
|
if (!ctype_digit($values['portainer_endpoint_id']) || (int)$values['portainer_endpoint_id'] < 1) throw new RuntimeException('Die Portainer-Umgebungs-ID ist ungültig.');
|
|
|
|
$dsn = 'mysql:host=' . $values['db_host'] . ';dbname=' . $values['db_name'] . ';charset=utf8mb4';
|
|
$db = new PDO($dsn, $values['db_user'], (string)($_POST['db_password'] ?? ''), [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
]);
|
|
$schema = file_get_contents($root . '/database/schema.sql');
|
|
if ($schema === false || $schema === '') throw new RuntimeException('Das Datenbankschema fehlt.');
|
|
$db->exec($schema);
|
|
foreach (glob($root . '/database/migrations/*.sql') ?: [] as $migration) {
|
|
$sql = file_get_contents($migration);
|
|
if ($sql === false) throw new RuntimeException('Eine Datenbankmigration konnte nicht gelesen werden.');
|
|
$db->exec($sql);
|
|
}
|
|
$adminHash = password_hash((string)$_POST['admin_password'], PASSWORD_DEFAULT);
|
|
$admin = $db->prepare("INSERT INTO users (username, email, first_name, last_name, street_address, postal_code, city, country_code, password_hash, role, status) VALUES (:username, :email, '', '', '', '', '', 'AT', :password_hash, 'admin', 'active') ON DUPLICATE KEY UPDATE email = VALUES(email), password_hash = VALUES(password_hash), role = 'admin', status = 'active'");
|
|
$admin->execute(['username' => $values['admin_user'], 'email' => $values['admin_email'], 'password_hash' => $adminHash]);
|
|
|
|
$runtimeConfig = [
|
|
'PNPAAS_ADMIN_USER' => $values['admin_user'],
|
|
'PNPAAS_ADMIN_PASSWORD_HASH' => $adminHash,
|
|
'PNPAAS_DB_DSN' => $dsn,
|
|
'PNPAAS_DB_USER' => $values['db_user'],
|
|
'PNPAAS_DB_PASSWORD' => (string)$_POST['db_password'],
|
|
'PNPAAS_APP_URL' => $appUrl,
|
|
'PNPAAS_FORCE_SECURE_COOKIES' => '1',
|
|
'PORTAINER_URL' => $portainerUrl,
|
|
'PORTAINER_API_KEY' => (string)($_POST['portainer_api_key'] ?? ''),
|
|
'PNPAAS_PORTAINER_ENDPOINT_ID' => $values['portainer_endpoint_id'],
|
|
'PNPAAS_TERMS_URL' => $termsUrl,
|
|
'PNPAAS_TERMS_VERSION' => $values['terms_version'],
|
|
'PNPAAS_PRIVACY_URL' => $privacyUrl,
|
|
'PNPAAS_PRIVACY_VERSION' => $values['privacy_version'],
|
|
'PNPAAS_WITHDRAWAL_URL' => $withdrawalUrl,
|
|
'PNPAAS_WITHDRAWAL_VERSION' => $values['withdrawal_version'],
|
|
];
|
|
$configPath = $root . '/includes/runtime-config.php';
|
|
if (!is_writable(dirname($configPath))) throw new RuntimeException('Das Include-Verzeichnis ist für den Webserver nicht beschreibbar.');
|
|
$configCode = "<?php\ndeclare(strict_types=1);\nreturn " . var_export($runtimeConfig, true) . ";\n";
|
|
if (file_put_contents($configPath, $configCode, LOCK_EX) === false) throw new RuntimeException('Die Anwendungskonfiguration konnte nicht geschrieben werden.');
|
|
chmod($configPath, 0640);
|
|
file_put_contents($completeFile, date(DATE_ATOM), LOCK_EX);
|
|
chmod($completeFile, 0640);
|
|
@unlink(__FILE__);
|
|
$success = true;
|
|
} catch (PDOException $exception) {
|
|
error_log('PnPaaS web installer database error: ' . $exception->getMessage());
|
|
$error = 'Die Datenbankverbindung oder der Datenbankimport ist fehlgeschlagen.';
|
|
} catch (Throwable $exception) {
|
|
error_log('PnPaaS web installer error: ' . $exception->getMessage());
|
|
$error = $exception->getMessage();
|
|
}
|
|
}
|
|
?>
|
|
<!doctype html>
|
|
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>PnPaaS installieren</title>
|
|
<style>body{font:16px system-ui,sans-serif;background:#eef2f5;color:#17202a;margin:0}.card{max-width:760px;margin:32px auto;background:#fff;padding:28px;border-radius:12px;box-shadow:0 8px 30px #0002}h1{margin-top:0}fieldset{border:1px solid #ccd5dc;margin:18px 0;padding:16px}legend{font-weight:700}label{display:block;margin:10px 0 4px;font-weight:600}input{box-sizing:border-box;width:100%;padding:10px;border:1px solid #9aa8b3;border-radius:5px}button{padding:11px 18px;background:#1261a0;color:white;border:0;border-radius:5px;font-weight:700}.alert{padding:12px;margin:12px 0;border-radius:5px}.error{background:#ffe1e1;color:#8b1111}.success{background:#dcf7e5;color:#135d2d}.hint{color:#52616b;font-size:.92em}</style></head>
|
|
<body><main class="card"><h1>PnPaaS installieren</h1>
|
|
<?php if ($success): ?><div class="alert success">Installation abgeschlossen. Der Webinstaller ist deaktiviert. Bitte härten Sie jetzt die Dateirechte und konfigurieren Sie HTTPS.</div>
|
|
<?php else: ?><?php if ($error): ?><div class="alert error"><?= setup_h($error) ?></div><?php endif; ?><p class="hint">Die Datenbank muss bereits existieren. Der Assistent benötigt nur den MariaDB-Anwendungsbenutzer, nicht den MariaDB-Root-Zugang.</p>
|
|
<form method="post"><input type="hidden" name="csrf" value="<?= setup_h((string)$_SESSION['csrf']) ?>">
|
|
<fieldset><legend>MariaDB</legend><label>Host<input name="db_host" value="<?= setup_h($values['db_host']) ?>" required></label><label>Datenbankname<input name="db_name" value="<?= setup_h($values['db_name']) ?>" required></label><label>Datenbankbenutzer<input name="db_user" value="<?= setup_h($values['db_user']) ?>" required></label><label>Datenbankpasswort<input type="password" name="db_password" autocomplete="new-password" required></label></fieldset>
|
|
<fieldset><legend>Administrator</legend><label>Benutzername<input name="admin_user" value="<?= setup_h($values['admin_user']) ?>" required></label><label>E-Mail<input type="email" name="admin_email" value="<?= setup_h($values['admin_email']) ?>" required></label><label>Passwort<input type="password" name="admin_password" minlength="12" autocomplete="new-password" required></label></fieldset>
|
|
<fieldset><legend>Anwendung und Portainer</legend><label>Öffentliche HTTPS-App-URL<input name="app_url" placeholder="https://example.org/pnpaas" value="<?= setup_h($values['app_url']) ?>" required></label><label>Portainer-URL<input name="portainer_url" value="<?= setup_h($values['portainer_url']) ?>"></label><label>Portainer-API-Key<input type="password" name="portainer_api_key" autocomplete="off"></label><label>Portainer-Umgebungs-ID<input name="portainer_endpoint_id" value="<?= setup_h($values['portainer_endpoint_id']) ?>"></label></fieldset>
|
|
<fieldset><legend>Rechtstexte</legend><label>AGB-URL<input name="terms_url" value="<?= setup_h($values['terms_url']) ?>" required></label><label>AGB-Version<input name="terms_version" value="<?= setup_h($values['terms_version']) ?>" required></label><label>Datenschutz-URL<input name="privacy_url" value="<?= setup_h($values['privacy_url']) ?>" required></label><label>Datenschutz-Version<input name="privacy_version" value="<?= setup_h($values['privacy_version']) ?>" required></label><label>Widerrufs-URL<input name="withdrawal_url" value="<?= setup_h($values['withdrawal_url']) ?>" required></label><label>Widerrufs-Version<input name="withdrawal_version" value="<?= setup_h($values['withdrawal_version']) ?>" required></label></fieldset>
|
|
<button type="submit">Installation durchführen</button></form><?php endif; ?></main></body></html>
|