OPS-011
A web installer whose defaults are a working site
Seed the installer from the settings of a site that already runs. A new install should be usable before the operator has made a single decision.
Most installers ask a long list of questions nobody can answer before the site exists — what rate, what cooldown, what minimum. The operator guesses, the economy is wrong, and they blame the software.
Seed the defaults from an install that works. Export the settings of your live site, strip the credentials and anything site-specific, and ship that as the installer's starting point. The operator supplies the database connection and an admin password and gets a site that runs sensibly on day one.
The installer must also remove itself from the equation once used, or it becomes the most dangerous file on the server.
<?php
// install.php — refuses to run twice.
require __DIR__ . '/includes/defaults.php'; // exported from a working site
if (is_file(__DIR__ . '/config.php')) {
exit('Already installed. Delete config.php to run the installer again.');
}
if (is_post()) {
$db = [
'host' => trim($_POST['host'] ?? 'localhost'),
'name' => trim($_POST['name'] ?? ''),
'user' => trim($_POST['user'] ?? ''),
'pass' => (string) ($_POST['pass'] ?? ''),
];
// 1. Prove the connection before writing anything.
try {
$pdo = new PDO("mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4",
$db['user'], $db['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (PDOException $e) {
$error = 'Could not connect: ' . $e->getMessage();
}
if (empty($error)) {
// 2. Schema, then the working defaults, then the operator's own values.
run_migrations($pdo, __DIR__ . '/migrations');
seed_settings($pdo, INSTALL_DEFAULTS);
seed_settings($pdo, [
'site_name' => trim($_POST['site_name'] ?? 'My site'),
'base_url' => rtrim(trim($_POST['base_url'] ?? ''), '/'),
]);
set_admin_password($pdo, (string) $_POST['admin_pass']);
// 3. Write config.php LAST — its existence is the installed flag.
write_config(__DIR__ . '/config.php', $db, bin2hex(random_bytes(24)));
// 4. Make the installer harmless.
@rename(__DIR__ . '/install.php', __DIR__ . '/install.php.done');
redirect('admin/login.php');
}
}
Using it
Write config.php last. Its presence is what says "installed", so a failure halfway leaves the installer able to run again.
Rename rather than delete the installer, so an operator who needs it can rename it back deliberately.
Check requirements before the form: PHP version, PDO MySQL, cURL, and whether the directory is writable. Failing those politely beats failing in the middle.
What bites people
An installer that still works after installation is a way for anyone to reset your site. Rename it, and have bootstrap refuse to run it when config exists.
Never ship real API keys in the defaults. Strip every credential from the export, and test the stripped file on a clean database.
Generate the app key at install time. Shipping one means every install shares it.
This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.