body { background-color: #2b2a3d; color: #d7e6e1; font-family: monospace, monospace; padding: 50px; font-size: 13px; } input[type="text"],input[type="email"]{ width: 550px; background-color: #151220; color: #d7e6e1; font-size: 100%; } label { text-align: right; display: block; } a { color: #6e9fff; } '); $installerHead = 'Installer' . $style . ''; $okTile = '[ OK ]'; $failTile = '[ FAIL ]'; $nullTile = '[ -- ]'; $backButton = ''; $formBody = ( '

Database connection details


 
'); function resultHtmlStart() { global $installerHead, $logo; return $installerHead . '
' . $logo;
}

function resultHtmlEnd() {
    return '
'; } function formHtml() { global $installerHead, $formBody; return $installerHead . $formBody . ''; } function finishOk() { $out = "\n\n======================== Setup completed! ========================"; $out .= "\n* Please delete the ./install directory and all its included files."; $out .= "\n* Visit /signup to create your account."; return $out; } function finishError() { global $backButton; $out = "\n====================== Something went wrong ======================"; $out .= "\n$backButton"; return $out; } $steps = [ [ 'description' => 'Compatibility checks', 'tasks' => [ ['description' => 'PHP version', 'status' => null], ['description' => 'PDO PostgreSQL driver', 'status' => null], ['description' => 'Configuration folder (/config) read/write permission', 'status' => null], ['description' => '.htaccess available', 'status' => null], ['description' => 'cURL', 'status' => null], ['description' => 'Memory limit (Min. 128MB)', 'status' => null], ], ], [ 'description' => 'Database params', 'tasks' => [ ['description' => 'Schema accessible', 'status' => null], ['description' => 'Database name', 'status' => null], ['description' => 'Database user', 'status' => null], ['description' => 'Database password', 'status' => null], ['description' => 'Database host', 'status' => null], ['description' => 'Database port', 'status' => null], ], ], [ 'description' => 'Database setup', 'tasks' => [ ['description' => 'Database connection', 'status' => null], /*['description' => 'Database version', 'status' => null],*/ ['description' => 'Apply database schema', 'status' => null], ], ], [ 'description' => 'Config build', 'tasks' => [ ['description' => 'Write config file', 'status' => null], ], ], ]; function proceed() { $out = ''; if (configAlreadyExists()) { $out .= resultHtmlStart(); $out .= "\nThe app is already configured."; $out .= resultHtmlEnd(); echo $out; return; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $out .= resultHtmlStart(); [$status, $result, $config] = execute($_POST); $out .= $result; $out .= $status ? finishOk() : finishError(); $out .= resultHtmlEnd(); } else { substituteFormWithEnv(); $out .= formHtml(); } echo $out; } function execute(array $values) { global $steps; $out = ''; compatibilityCheck(0, $steps); $out .= printTasks($steps[0]); if (!tasksCompleted($steps[0])) { return [false, $out, null]; } dbConfig(1, $values, $steps); $out .= printTasks($steps[1]); if (!tasksCompleted($steps[1])) { return [false, $out, null]; } dbSaveConfig(2, $values, $steps); $out .= printTasks($steps[2]); if (!tasksCompleted($steps[2])) { return [false, $out, null]; } if (strval($values['mode'] ?? '') !== 'schema') { $config = saveConfig(3, $values, $steps); $out .= printTasks($steps[3]); if (!tasksCompleted($steps[3])) { return [false, $out, null]; } } return [true, $out, $config]; } function configAlreadyExists(): bool { return (getenv('SITE') && getenv('DATABASE_URL')) || file_exists('../config/local/config.local.ini'); } function substituteFormWithEnv(): void { global $formBody; if (strval($_GET['mode'] ?? '') === 'schema') { $formBody = preg_replace( '/(]*>)/i', '$1', $formBody ); } $dbUrl = getenv('DATABASE_URL'); if ($dbUrl) { $parts = parse_url($dbUrl); if ($parts) { $values = [ 'db_user' => $parts['user'] ?? '', 'db_pass' => $parts['pass'] ?? '', 'db_host' => $parts['host'] ?? '', 'db_port' => $parts['port'] ?? '', 'db_name' => trim(($parts['path'] ?? ''), '/'), ]; foreach ($values as $key => $value) { $safe = htmlspecialchars($value, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); $formBody = preg_replace( '/(]*\bname="' . preg_quote($key, '/') . '"(?![^>]*\bvalue=)[^>]*)(>)/i', '$1 value="' . $safe . '"$2', $formBody ); } } } } function compatibilityCheck(int $step, array &$steps) { $steps[$step]['tasks'][0]['status'] = version_compare(PHP_VERSION, MIN_PHP_VERSION) >= 0; $steps[$step]['tasks'][1]['status'] = extension_loaded('pdo_pgsql') && extension_loaded('pgsql'); try { if (is_writable('../config')) { $f = fopen('../config/local/config.local.ini', 'w'); fclose($f); unlink('../config/local/config.local.ini'); $steps[$step]['tasks'][2]['status'] = true; } else { $steps[$step]['tasks'][2]['status'] = false; } } catch (\Exception $e) { $steps[$step]['tasks'][2]['status'] = false; } $steps[$step]['tasks'][3]['status'] = is_file('../.htaccess') && is_readable('../.htaccess'); $steps[$step]['tasks'][4]['status'] = extension_loaded('curl'); $memoryLimit = @ini_get('memory_limit'); $memLim = $memoryLimit; preg_match('#^(\d+)(\w+)$#', strtolower($memLim), $match); $memLim = match ($match[2]) { 'g' => intval($memLim) * 1024 * 1024 * 1024, 'm' => intval($memLim) * 1024 * 1024, 'k' => intval($memLim) * 1024, default => intval($memLim), }; $steps[$step]['tasks'][5]['status'] = $memLim >= MIN_MEMORY_LIM; } function dbConfig(int $step, array &$values, array &$steps) { $steps[$step]['tasks'][0]['status'] = is_file('./install.sql'); $steps[$step]['tasks'][1]['status'] = $values['db_name'] !== ''; $steps[$step]['tasks'][2]['status'] = $values['db_user'] !== ''; $steps[$step]['tasks'][3]['status'] = $values['db_pass'] !== ''; $steps[$step]['tasks'][4]['status'] = $values['db_host'] !== ''; $steps[$step]['tasks'][5]['status'] = $values['db_port'] !== ''; } function saveConfig(int $step, array $values, array &$steps): ?array { $configData = null; try { $httpHost = strtolower(filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_URL)); if (strpos($httpHost, 'www.') === 0) { $httpHost = substr($httpHost, 4); } elseif (substr_count($httpHost, '.') == 1) { $httpHost = 'www.' . $httpHost; } $httpHost = explode(':', $httpHost)[0]; $configData = [ 'SITE' => $httpHost, 'DATABASE_URL' => "postgres://$values[db_user]:$values[db_pass]@$values[db_host]:$values[db_port]/$values[db_name]", 'PEPPER' => strval(bin2hex(random_bytes(32))), ]; if ($values['admin_email'] !== '') { $configData['ADMIN_EMAIL'] = $values['admin_email']; } $config = "\n[globals]"; foreach ($configData as $key => $value) { $config .= "\n$key=$value"; } $configPath = '../config/local/config.local.ini'; $configFile = fopen($configPath, 'w'); fwrite($configFile, $config); fclose($configFile); $steps[$step]['tasks'][0]['status'] = true; } catch (\Exception $e) { $steps[$step]['tasks'][0]['status'] = false; $steps[$step]['tasks'][0]['error'] = $e->getMessage(); } return $configData; } function dbSaveConfig(int $step, array $values, array &$steps) { $database = null; $dsn = "pgsql:dbname=$values[db_name];host=$values[db_host];port=$values[db_port]"; $driverOptions = array( \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION ); try { $database = new \PDO($dsn, $values['db_user'], $values['db_pass'], $driverOptions); } catch (\Exception $e) { $steps[$step]['tasks'][0]['status'] = false; $steps[$step]['tasks'][0]['error'] = $e->getMessage(); return; } $steps[$step]['tasks'][0]['status'] = true; /*$query = $database->query('SELECT VERSION()'); [$dbVersion] = $query->fetch(\PDO::FETCH_NUM); if (!preg_match('/PostgreSQL (\d+\.\d+)/', $dbVersion, $matches) || version_compare($matches[1], '12.0', '<')) { $steps[$step]['tasks'][1]['status'] = false; return; } $steps[$step]['tasks'][1]['status'] = true;*/ try { $sql = file_get_contents('./install.sql'); $database->exec($sql); } catch (\Exception $e) { $steps[$step]['tasks'][1]['status'] = false; $steps[$step]['tasks'][1]['error'] = $e->getMessage(); return; } $steps[$step]['tasks'][1]['status'] = true; } function printTasks(array $tasks) { global $okTile, $failTile, $nullTile; $out = ''; $side = intdiv(64 - strlen($tasks['description']), 2); $header = str_repeat('=', $side) . ' ' . $tasks['description'] . ' ' . str_repeat('=', $side); $out .= "\n\n" . $header; foreach ($tasks['tasks'] as $task) { $st = ($task['status'] === true) ? $okTile : (($task['status'] === false) ? $failTile : $nullTile); $err = array_key_exists('error', $task) ? ' (' . $task['error'] . ')' : ''; $out .= "\n" . $st . ' ' . $task['description'] . $err; } return $out; } function tasksCompleted(array $tasks) { $results = array_column($tasks['tasks'], 'status'); return count(array_filter($results)) === count($results); } proceed();