228 lines
7.4 KiB
PHP
228 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
$rootPath = __DIR__;
|
|
$envPath = $rootPath . '/.env';
|
|
$templatePath = $rootPath . '/env.example';
|
|
|
|
/**
|
|
* Serialize a value for a dotenv-style line; quote when needed.
|
|
*/
|
|
function installFormatEnvValue(string $value): string {
|
|
if ($value === '' || preg_match('/[\s#=\\\\"]/', $value) === 1) {
|
|
$escaped = str_replace(['\\', '"'], ['\\\\', '\\"'], $value);
|
|
return '"' . $escaped . '"';
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Strip matching outer quotes from env.example defaults for display.
|
|
*/
|
|
function installUnquoteDefault(string $value): string {
|
|
$value = trim($value);
|
|
$len = strlen($value);
|
|
if ($len >= 2) {
|
|
if ($value[0] === '"' && $value[$len - 1] === '"') {
|
|
return substr($value, 1, -1);
|
|
}
|
|
if ($value[0] === "'" && $value[$len - 1] === "'") {
|
|
return substr($value, 1, -1);
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* @return list<array{section: string|null, key: string, default: string}>
|
|
*/
|
|
function installParseTemplateForForm(string $templatePath): array {
|
|
$raw = file($templatePath, FILE_IGNORE_NEW_LINES);
|
|
if ($raw === false) {
|
|
return [];
|
|
}
|
|
|
|
$entries = [];
|
|
$section = null;
|
|
foreach ($raw as $line) {
|
|
$trim = trim($line);
|
|
if ($trim === '') {
|
|
continue;
|
|
}
|
|
if (strpos($trim, '#') === 0) {
|
|
$section = trim(substr($trim, 1));
|
|
continue;
|
|
}
|
|
if (strpos($line, '=') === false) {
|
|
continue;
|
|
}
|
|
[$name, $val] = explode('=', $line, 2);
|
|
$name = trim($name);
|
|
$entries[] = [
|
|
'section' => $section,
|
|
'key' => $name,
|
|
'default' => installUnquoteDefault($val),
|
|
];
|
|
}
|
|
|
|
return $entries;
|
|
}
|
|
|
|
function installWriteEnvFromTemplate(string $templatePath, string $envPath, array $postEnv): bool {
|
|
$raw = file($templatePath, FILE_IGNORE_NEW_LINES);
|
|
if ($raw === false) {
|
|
return false;
|
|
}
|
|
|
|
$out = [];
|
|
foreach ($raw as $line) {
|
|
$trim = trim($line);
|
|
if ($trim === '' || strpos($trim, '#') === 0) {
|
|
$out[] = $line;
|
|
continue;
|
|
}
|
|
if (strpos($line, '=') === false) {
|
|
$out[] = $line;
|
|
continue;
|
|
}
|
|
[$name, $templateVal] = explode('=', $line, 2);
|
|
$name = trim($name);
|
|
if (array_key_exists($name, $postEnv)) {
|
|
$submitted = (string) $postEnv[$name];
|
|
} else {
|
|
$submitted = installUnquoteDefault($templateVal);
|
|
}
|
|
$out[] = $name . '=' . installFormatEnvValue($submitted);
|
|
}
|
|
|
|
$bytes = file_put_contents($envPath, implode("\n", $out) . "\n", LOCK_EX);
|
|
if ($bytes === false) {
|
|
return false;
|
|
}
|
|
@chmod($envPath, 0600);
|
|
return true;
|
|
}
|
|
|
|
// Already configured
|
|
if (file_exists($envPath)) {
|
|
header('Content-Type: text/html; charset=UTF-8');
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Family Hub — Setup</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
</head>
|
|
<body class="bg-light">
|
|
<div class="container py-5">
|
|
<div class="col-lg-8 mx-auto">
|
|
<h1 class="h3 mb-3">Family Hub</h1>
|
|
<p class="text-muted">This application is already configured (<code>.env</code> exists).</p>
|
|
<a href="index.php" class="btn btn-primary">Go to Family Hub</a>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
exit;
|
|
}
|
|
|
|
if (!is_readable($templatePath)) {
|
|
header('Content-Type: text/html; charset=UTF-8');
|
|
http_response_code(500);
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Family Hub — Setup error</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
</head>
|
|
<body class="bg-light">
|
|
<div class="container py-5">
|
|
<div class="col-lg-8 mx-auto">
|
|
<h1 class="h3 mb-3 text-danger">Setup cannot continue</h1>
|
|
<p>The template file <code>env.example</code> is missing or not readable. Restore it from the repository.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
exit;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$postEnv = $_POST['env'] ?? [];
|
|
if (!is_array($postEnv)) {
|
|
$postEnv = [];
|
|
}
|
|
if (installWriteEnvFromTemplate($templatePath, $envPath, $postEnv)) {
|
|
header('Location: index.php', true, 302);
|
|
exit;
|
|
}
|
|
$writeError = true;
|
|
} else {
|
|
$writeError = false;
|
|
}
|
|
|
|
$entries = installParseTemplateForForm($templatePath);
|
|
|
|
header('Content-Type: text/html; charset=UTF-8');
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Family Hub — Environment setup</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
</head>
|
|
<body class="bg-light">
|
|
<div class="container py-4">
|
|
<div class="col-lg-8 mx-auto">
|
|
<header class="mb-4">
|
|
<h1 class="h3"><i class="fa fa-sliders text-primary me-2"></i>Environment setup</h1>
|
|
<p class="text-muted mb-0">Values are generated from <code>env.example</code>. Fill in your credentials and save.</p>
|
|
</header>
|
|
|
|
<?php if (!empty($writeError)): ?>
|
|
<div class="alert alert-danger">Could not write <code>.env</code>. Check that the app directory is writable by the web server.</div>
|
|
<?php endif; ?>
|
|
|
|
<form method="post" class="card shadow-sm">
|
|
<div class="card-body">
|
|
<?php
|
|
$lastSection = null;
|
|
foreach ($entries as $row):
|
|
if ($row['section'] !== $lastSection && $row['section'] !== null && $row['section'] !== ''):
|
|
$lastSection = $row['section'];
|
|
?>
|
|
<h2 class="h6 text-uppercase text-secondary border-bottom pb-2 mt-4 first-section"><?= htmlspecialchars($row['section'], ENT_QUOTES, 'UTF-8') ?></h2>
|
|
<?php
|
|
elseif ($row['section'] !== $lastSection):
|
|
$lastSection = $row['section'];
|
|
endif;
|
|
$key = $row['key'];
|
|
$val = $row['default'];
|
|
?>
|
|
<div class="mb-3">
|
|
<label class="form-label font-monospace small mb-1" for="env_<?= htmlspecialchars($key, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($key, ENT_QUOTES, 'UTF-8') ?></label>
|
|
<input type="text" class="form-control font-monospace" name="env[<?= htmlspecialchars($key, ENT_QUOTES, 'UTF-8') ?>]" id="env_<?= htmlspecialchars($key, ENT_QUOTES, 'UTF-8') ?>" value="<?= htmlspecialchars($val, ENT_QUOTES, 'UTF-8') ?>" autocomplete="off">
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<div class="card-footer bg-white border-top-0 d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">Save and continue</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<style>.first-section:first-child { margin-top: 0 !important; }</style>
|
|
</body>
|
|
</html>
|