<?php
/**
 * Pharos web installer: https://pharos.solutionmax.net/pharos-install.php
 *
 * Upload this one file into the folder your (sub)domain points at and open it
 * in a browser. It checks the server, downloads the signed release, verifies
 * the signature and checksum, unpacks the app *outside* the web folder, writes
 * .env, migrates the database, shows the one cron line, and deletes itself.
 *
 * A private setup key protects installation. Panel automation uses a temporary
 * Login Key or API token; application files are installed only after verification.
 *
 * Every screen is a plain form and works without JavaScript. With JavaScript the
 * long steps report live progress: the working request writes a small progress
 * file outside the web folder and the page polls it (?do=progress), so nothing
 * depends on output buffering or streaming, which shared hosting often breaks.
 */
declare(strict_types=1);

const PHAROS_RELEASES = 'https://pharos.solutionmax.net/releases';
const PHAROS_PUBKEY_HEX = '68d158ba363853e3b64efa7c2082015d198db31a4a039f05a24d3bbd93308ff2';
const PHAROS_MIN_PHP = '8.3.0';
// Leave empty to install the newest release; set "0.5.0" (or open ?version=0.5.0) to pin one.
const PHAROS_PIN_VERSION = '0.7.1';

if (defined('PHAROS_INSTALLER_LIBRARY')) { return; }

error_reporting(E_ALL);
ini_set('display_errors', '0');
@set_time_limit(600);
@ini_set('memory_limit', '256M');

$webRoot = __DIR__;
$appDir = pharos_app_dir($webRoot);
// Step 1 (unlock) has no address of its own: it is whatever page a locked visitor opens.
$step = max(2, (int) ($_GET['step'] ?? 2));
$action = $_POST['action'] ?? null;
$do = (string) ($_GET['do'] ?? '');
// JSON only for the installer's own script: a custom header cannot be sent cross site without a preflight.
$json = ($_SERVER['HTTP_X_PHAROS_INSTALLER'] ?? '') === '1';
$errors = [];
session_name('pharos_installer');
session_start(['cookie_httponly' => true, 'cookie_samesite' => 'Strict', 'cookie_secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'use_strict_mode' => true]);
$identity = hash('sha256', $webRoot);
$keyFile = dirname($appDir).'/.pharos-setup-'.substr($identity, 0, 16).'.key';
$progressFile = dirname($appDir).'/.pharos-progress-'.substr($identity, 0, 16).'.json';
try {
    $installKey = pharos_setup_key($keyFile);
} catch (Throwable $e) {
    if ($json) { pharos_json(['ok' => false, 'errors' => [$e->getMessage()]], 500); }
    pharos_page('Installation needs a private writable folder', '<div class="note bad">'.h($e->getMessage()).'</div>', 1, [], [], ['fail' => true]);
    exit;
}
if (isset($_POST['install_key']) && hash_equals($installKey, (string) $_POST['install_key'])) {
    session_regenerate_id(true);
    $_SESSION['pharos_owner'] = hash('sha256', $installKey);
}
if (!hash_equals(hash('sha256', $installKey), (string) ($_SESSION['pharos_owner'] ?? ''))) {
    if ($json) { pharos_json(['ok' => false, 'errors' => ['Unlock the installer again.'], 'locked' => true], 401); }
    $wrong = isset($_POST['install_key']) ? ['That key does not match. Copy it again from the file, all 64 characters.'] : [];
    pharos_page('Unlock your installer', pharos_view_unlock($keyFile), 1, $wrong, [], ['lede' => 'Anyone could open this page, so Pharos asks for a key that only the owner of this hosting account can read. It was just written next to your web folder.']);
    exit;
}
if (!isset($_SESSION['pharos_csrf'])) { $_SESSION['pharos_csrf'] = bin2hex(random_bytes(32)); }
if ($action !== null && !hash_equals($_SESSION['pharos_csrf'], (string) ($_POST['csrf'] ?? ''))) {
    if ($json) { pharos_json(['ok' => false, 'errors' => ['This page expired. Reload it and try again.']], 403); }
    http_response_code(403);
    pharos_page('Request expired', '<div class="note bad">Reload the installer and try again.</div>', 2);
    exit;
}

// Read only answers for the page's own polling. They come before the install lock on
// purpose: a download or migration holds that lock for as long as it runs.
if ($json && $do === 'progress') { pharos_json(pharos_progress_read($progressFile)); }
if ($json && $do === 'cron') { pharos_json(pharos_scheduler_status($appDir)); }

// Nothing below writes the session, and a held session lock would stall those polls.
session_write_close();

$installLock = fopen($keyFile.'.lock', 'c');
if (!$installLock || !flock($installLock, LOCK_EX | LOCK_NB)) {
    if ($json) { pharos_json(['ok' => false, 'errors' => ['Another installation request is running. Wait for it to finish.']], 409); }
    http_response_code(409);
    pharos_page('Installation is busy', '<div class="note warn">Another installation request is running. Wait for it to finish, then reload this page.</div>', 2);
    exit;
}
register_shutdown_function(function () use ($installLock) { flock($installLock, LOCK_UN); fclose($installLock); });

$log = [];

// ---------- guard: never run over a *finished* install (a half done one may resume) ----------
if (is_file("$appDir/.pharos-done")) {
    if ($json) { pharos_json(['ok' => false, 'errors' => ['Pharos is already installed here.']], 409); }
    pharos_page('Already installed', '<p class="lede">Pharos is already installed in <code>'.h($appDir).'</code>. This installer refuses to run over it.</p>
      <div class="note warn">If you really want a fresh install, remove that folder first. Then delete this file: it has no business staying on a live site.</div>
      <div class="actions"><a class="btn" href="'.h(pharos_base_url()).'/admin">Open the admin</a></div>', 7, [], [], ['kicker' => 'Nothing to do']);
    exit;
}

if ($json && $do === 'checks') {
    $checks = pharos_checks($appDir, $webRoot);
    pharos_json(['ok' => true, 'summary' => pharos_check_summary($checks), 'html' => pharos_view_check_live($checks)]);
}

// ---------- actions ----------
$download = null;   // what the download step did, for its screen
$testdb = null;
try {
    if ($action === 'download') {
        $step = 3;
        if (is_file("$appDir/artisan") || is_file("$appDir/.env")) { throw new RuntimeException('An application already exists here. Continue its configuration; the installer never downloads over existing data.'); }
        $download = pharos_install_release($appDir, $webRoot, $progressFile);
        $log = $download['log'];
        if ($download['error'] !== null) { throw new RuntimeException($download['error']); }
    } elseif ($action === 'testdb') {
        $step = 4;
        $testdb = pharos_test_db($_POST);
    } elseif ($action === 'configure') {
        $step = 4;
        if (!is_file("$appDir/.pharos-installer") || is_file("$appDir/.pharos-configured")) { throw new RuntimeException('This installation cannot be reconfigured.'); }
        $url = rtrim(trim((string) ($_POST['app_url'] ?? '')), '/');
        if (! preg_match('~^https?://[^/\s]+$~', $url)) {
            throw new RuntimeException('The site address must look like https://status.example.com');
        }
        $db = ($_POST['db'] ?? 'sqlite') === 'mysql' ? 'mysql' : 'sqlite';
        if (!extension_loaded($db === 'mysql' ? 'pdo_mysql' : 'pdo_sqlite')) { throw new RuntimeException('Enable the PDO extension for your selected database first.'); }
        pharos_configure($appDir, $webRoot, $url, $db, $_POST, $installKey, $progressFile, $log);
        $step = 5;
    } elseif ($action === 'directadmin_cron') {
        $step = 5;
        if (!is_file("$appDir/.pharos-configured")) { throw new RuntimeException('Configure the app first.'); }
        $cli = pharos_cron_php();
        $selectedPhp = trim((string)($_POST['cron_php'] ?? $cli['path']));
        if (!preg_match('~^/[A-Za-z0-9/._-]+$~', $selectedPhp) || !pharos_php_version_of($selectedPhp) || version_compare(pharos_php_version_of($selectedPhp), pharos_min_mm(), '<')) { throw new RuntimeException('Enter a versioned absolute path to PHP '.pharos_min_mm().' or later.'); }
        $panelType = ($_POST['panel_type'] ?? 'directadmin') === 'cpanel' ? 'cpanel' : 'directadmin';
        $panelRequest = function ($panel, $user, $key, $fields) use ($panelType) { return pharos_da_request($panel, $user, $key, $fields, $panelType); };
        $log[] = pharos_da_cron(trim((string)($_POST['panel_url'] ?? '')), (string)($_POST['panel_user'] ?? ''), (string)($_POST['panel_key'] ?? ''), $appDir, $selectedPhp, $panelRequest);
        file_put_contents("$appDir/.pharos-cron-php", $selectedPhp);
    } elseif ($action === 'finish') {
        if (!is_file("$appDir/.pharos-configured")) { throw new RuntimeException('Finish configuration before closing the installer.'); }
        @file_put_contents("$appDir/.pharos-done", date('c'));
        @unlink($progressFile);
        @unlink(__FILE__);
        header('Location: '.pharos_base_url().'/admin/install');
        exit;
    }
} catch (Throwable $e) {
    $errors[] = $e->getMessage();
    if ($action === 'configure') { pharos_progress_write($progressFile, ['op' => 'configure', 'stage' => 'failed', 'log' => $log, 'error' => $e->getMessage()]); }
}

if ($json && $action !== null) {
    $out = ['ok' => !$errors, 'errors' => $errors, 'log' => $log, 'next' => '?step='.$step];
    if ($download !== null) { $out['download'] = $download; }
    if ($testdb !== null) { $out['testdb'] = $testdb; }
    pharos_json($out, $errors ? 422 : 200);
}

// ---------- resume a half done install: release unpacked, settings not finished ----------
$hasApp = is_file("$appDir/artisan");
$configured = is_file("$appDir/.pharos-configured");
if ($action === null) {
    if ($step === 2 && $hasApp) {
        $step = $configured ? 5 : 4;
        $log[] = 'Found a release already unpacked in '.$appDir.'. Continuing where it stopped; your existing encryption key is kept.';
    } elseif ($step === 4 && $configured) {
        $step = 5;
    } elseif (($step === 4 && !$hasApp) || ($step === 5 && !$configured)) {
        $step = $hasApp ? 4 : 2;
    }
}

// ---------- screens ----------
if ($step === 2 && version_compare(PHP_VERSION, PHAROS_MIN_PHP, '<')) {
    pharos_page('This PHP is too old', pharos_view_php_old(), 2, [], [], ['fail' => true, 'lede' => 'This site runs PHP '.PHP_VERSION.'. Pharos needs '.PHAROS_MIN_PHP.' or later. Nothing was written.']);
    exit;
}
if ($step === 2) {
    $checks = pharos_checks($appDir, $webRoot);
    $sum = pharos_check_summary($checks);
    pharos_page('Can this server run Pharos?', pharos_view_checks($checks), 2, $errors, $log, ['fail' => $sum['bad'] > 0, 'lede' => 'A quick look at PHP, its extensions, the folders and the network. Nothing is written yet.']);
} elseif ($step === 3) {
    if ($download === null && $hasApp) { $download = pharos_download_from_disk($appDir); }
    $failed = $download !== null && $download['error'] !== null;
    pharos_page($download === null ? 'Download Pharos' : ($failed ? 'The download was stopped' : 'Pharos is downloaded'), pharos_view_download($download, $hasApp), 3, $failed ? [] : $errors, [], ['fail' => $failed || ($errors && $download === null), 'lede' => 'The release is signed. The installer checks the signature and the checksum before a single file is unpacked.']);
} elseif ($step === 4) {
    pharos_page('Where it lives, and where it keeps its data', pharos_view_configure($_POST, $testdb), 4, $errors, $log, ['fail' => (bool) $errors, 'lede' => 'The address is where people will open your status page. SQLite needs nothing from your host and suits almost everyone.']);
} elseif ($step === 5) {
    pharos_page('One cron line. That is the whole scheduler.', pharos_view_cron($appDir, $installKey), 5, $errors, $log, ['lede' => 'Checks, notifications and maintenance windows run every minute from this line. Add it in your control panel; this page notices the first run by itself.']);
} else {
    header('Location: ?step=2');
}

// ============================================================ install steps

/** Answer one of the page's own fetch() calls and stop. */
function pharos_json(array $data, int $status = 200): void
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-store');
    header('X-Content-Type-Options: nosniff');
    echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR);
    exit;
}

/** The progress file lives next to the key file, outside the web folder, and holds no secrets. */
function pharos_progress_write(string $file, array $data): void
{
    $tmp = $file.'.'.bin2hex(random_bytes(4));
    $mask = umask(0077);
    $ok = @file_put_contents($tmp, json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR));
    umask($mask);
    if ($ok === false) { return; }
    if (!@rename($tmp, $file)) { @unlink($tmp); }
}

function pharos_progress_read(string $file): array
{
    $data = is_file($file) ? json_decode((string) @file_get_contents($file), true) : null;
    if (!is_array($data)) { return ['op' => '', 'stage' => 'idle']; }
    $keep = ['op', 'stage', 'version', 'bytes', 'total', 'files', 'total_files', 'migrations', 'total_migrations', 'log', 'error'];
    return array_intersect_key($data, array_flip($keep));
}

/**
 * Manifest, download, checksum, unpack, publish. Each stage reports into the progress
 * file as it goes; the returned array is what the download screen shows either way.
 */
function pharos_install_release(string $appDir, string $webRoot, string $progressFile): array
{
    $state = ['op' => 'download', 'stage' => 'manifest', 'version' => '', 'bytes' => 0, 'total' => 0, 'files' => 0, 'total_files' => 0, 'log' => [], 'error' => null, 'failed' => null];
    $report = function (array $change) use (&$state, $progressFile) {
        $state = array_merge($state, $change);
        pharos_progress_write($progressFile, $state);
    };
    $report([]);
    $tmp = null;
    try {
        $manifest = pharos_manifest();
        $state['log'][] = 'Manifest fetched, Ed25519 signature valid, version '.$manifest['version'];
        $report(['stage' => 'download', 'version' => $manifest['version']]);
        $tmp = pharos_tmp_dir($appDir);
        $zip = "$tmp/pharos.zip";
        $last = 0.0;
        pharos_download($manifest['url'], $zip, function ($total, $now) use ($report, &$last) {
            $t = microtime(true);
            if ($t - $last < 0.25) { return; }
            $last = $t;
            $report(['bytes' => (int) $now, 'total' => (int) $total]);
        });
        $size = (int) filesize($zip);
        $state['log'][] = 'pharos-'.$manifest['version'].'.zip downloaded, '.pharos_mb($size).' MB';
        $report(['stage' => 'checksum', 'bytes' => $size, 'total' => $size]);
        $got = hash_file('sha256', $zip);
        if (! hash_equals(strtolower($manifest['sha256']), strtolower((string) $got))) {
            $state['failed'] = 'checksum';
            throw new RuntimeException('SHA 256 mismatch: the download does not match the signed manifest. Nothing was unpacked and the download was deleted.');
        }
        $state['log'][] = 'SHA 256 matches the signed manifest';
        $report(['stage' => 'unpack']);
        $files = pharos_unpack($zip, $tmp, $appDir, function ($done, $total) use ($report) { $report(['files' => $done, 'total_files' => $total]); });
        $state['log'][] = 'Unpacked '.number_format($files).' files into '.$appDir;
        $report(['stage' => 'publish', 'files' => $files, 'total_files' => $files]);
        pharos_publish_public($appDir, $webRoot);
        $state['log'][] = 'Public folder copied into '.$webRoot.', index.php points at the app';
        pharos_rmdir($tmp);
        file_put_contents("$appDir/.pharos-installer", json_encode(['version' => $manifest['version'], 'at' => date('c'), 'size' => $size, 'files' => $files]));
        $report(['stage' => 'done']);
    } catch (Throwable $e) {
        if ($tmp !== null) { pharos_rmdir($tmp); }
        $map = ['manifest' => 'signature', 'download' => 'download', 'checksum' => 'checksum'];
        $failed = $state['failed'] ?: ($map[$state['stage']] ?? 'unpack');
        $state['log'][] = 'Stopped: '.$e->getMessage();
        $report(['stage' => 'failed', 'failed' => $failed, 'error' => $e->getMessage()]);
    }
    unset($state['op']);
    return $state;
}

/** For a page load after the download already happened: the facts, from the marker it left. */
function pharos_download_from_disk(string $appDir): array
{
    $m = json_decode((string) @file_get_contents("$appDir/.pharos-installer"), true);
    $m = is_array($m) ? $m : [];
    return ['stage' => 'done', 'version' => (string) ($m['version'] ?? ''), 'bytes' => (int) ($m['size'] ?? 0), 'total' => (int) ($m['size'] ?? 0),
        'files' => (int) ($m['files'] ?? 0), 'total_files' => (int) ($m['files'] ?? 0), 'error' => null, 'failed' => null,
        'log' => ['Release '.($m['version'] ?? '').' is already verified and unpacked in '.$appDir]];
}

function pharos_mb(int $bytes): string { return number_format($bytes / 1048576, 1); }

/** .env, migrations (reported one by one through the framework's own events), storage link. */
function pharos_configure(string $appDir, string $webRoot, string $url, string $db, array $in, string $installKey, string $progressFile, array &$log): void
{
    $total = count(glob("$appDir/database/migrations/*.php") ?: []);
    $state = ['op' => 'configure', 'stage' => 'env', 'migrations' => 0, 'total_migrations' => $total, 'log' => []];
    $report = function (array $change) use (&$state, $progressFile) { $state = array_merge($state, $change); pharos_progress_write($progressFile, $state); };
    $report([]);
    pharos_write_env($appDir, $url, $db, $in, $installKey);
    $log[] = '.env written, app key generated';
    $report(['stage' => 'migrate', 'log' => $log]);
    try {
        $out = pharos_artisan($appDir, 'migrate', ['--force' => true], function ($app) use ($report, &$state) {
            if (!class_exists('Illuminate\Database\Events\MigrationEnded')) { return; }
            $app['events']->listen('Illuminate\Database\Events\MigrationEnded', function () use ($report, &$state) {
                $report(['migrations' => $state['migrations'] + 1]);
            });
        });
    } catch (Throwable $e) {
        throw new RuntimeException('Setting up the database failed: '.$e->getMessage().'. Fix the cause (usually the PHP version, a missing extension or the database login), then press the button again.');
    }
    $ran = $state['migrations'] > 0 ? $state['migrations'] : max(0, preg_match_all('/\bDONE\b/', $out) - (strpos($out, 'Creating migration table') !== false ? 1 : 0));
    $log[] = $ran > 0 ? 'Database set up, '.$ran.' migrations' : 'Database already up to date';
    $report(['stage' => 'link', 'migrations' => $ran, 'log' => $log]);
    pharos_link_storage($appDir, $webRoot, $log);
    file_put_contents("$appDir/.pharos-configured", date('c'));
    $report(['stage' => 'done', 'log' => $log]);
}

/** "Test connection" for MySQL: connect, report the server and whether the database is empty. Nothing is written. */
function pharos_test_db(array $in): array
{
    if (!extension_loaded('pdo_mysql')) { return ['ok' => false, 'message' => 'The pdo_mysql extension is not loaded for this PHP.']; }
    $host = trim((string) ($in['db_host'] ?? ''));
    $name = trim((string) ($in['db_name'] ?? ''));
    if (!preg_match('/^[A-Za-z0-9._\-]+$/', $host) || !preg_match('/^[A-Za-z0-9_$\-]+$/', $name)) {
        return ['ok' => false, 'message' => 'Fill in a host and a database name.'];
    }
    try {
        $pdo = new PDO('mysql:host='.$host.';port=3306;dbname='.$name.';charset=utf8mb4', (string) ($in['db_user'] ?? ''), (string) ($in['db_pass'] ?? ''), [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5]);
        $version = (string) $pdo->query('SELECT VERSION()')->fetchColumn();
        $tables = count($pdo->query('SHOW TABLES')->fetchAll());
    } catch (Throwable $e) {
        return ['ok' => false, 'message' => 'Could not connect: '.preg_replace('/^SQLSTATE\[[^\]]*\]\s*(\[\d+\]\s*)?/', '', $e->getMessage())];
    }
    $kind = stripos($version, 'mariadb') !== false ? 'MariaDB' : 'MySQL';
    $short = preg_replace('/^(\d+\.\d+(\.\d+)?).*$/', '$1', $version);
    return ['ok' => true, 'message' => 'Connected, '.$kind.' '.$short.', '.($tables === 0 ? 'the database is empty' : $tables.' tables already in it')];
}

/**
 * Has the scheduler run since this install? Current releases stamp
 * storage/framework/pharos-scheduler-last-run on every run; older ones only
 * record checks.last_run_at in the settings table, read here without booting the app.
 */
function pharos_scheduler_status(string $appDir): array
{
    $meta = json_decode((string) @file_get_contents("$appDir/.pharos-installer"), true);
    $since = is_array($meta) && !empty($meta['at']) ? (int) strtotime((string) $meta['at']) : 0;
    $at = '';
    $marker = "$appDir/storage/framework/pharos-scheduler-last-run";
    if (is_file($marker)) { $at = trim((string) @file_get_contents($marker)); }
    if ($at === '') { $at = pharos_db_last_run($appDir); }
    $ts = $at !== '' ? strtotime($at) : false;
    // A stamp from before this install (a marker shipped by mistake, say) proves nothing.
    $ran = $ts !== false && $ts >= $since - 5;
    return ['ok' => true, 'ran' => $ran, 'at' => $ran ? gmdate('c', (int) $ts) : null, 'label' => $ran ? gmdate('H:i:s', (int) $ts).' UTC' : null];
}

function pharos_db_last_run(string $appDir): string
{
    $env = pharos_env_read($appDir);
    try {
        $opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 3];
        if (($env['DB_CONNECTION'] ?? 'sqlite') === 'sqlite') {
            $file = $env['DB_DATABASE'] ?? '';
            $file = $file !== '' && $file[0] === '/' ? $file : "$appDir/database/database.sqlite";
            if (!extension_loaded('pdo_sqlite') || !is_file($file) || filesize($file) === 0) { return ''; }
            $pdo = new PDO('sqlite:'.$file, null, null, $opts);
        } elseif (in_array($env['DB_CONNECTION'] ?? '', ['mysql', 'mariadb'], true) && extension_loaded('pdo_mysql')) {
            $pdo = new PDO('mysql:host='.($env['DB_HOST'] ?? '127.0.0.1').';port='.((int) ($env['DB_PORT'] ?? 3306)).';dbname='.($env['DB_DATABASE'] ?? ''), $env['DB_USERNAME'] ?? '', $env['DB_PASSWORD'] ?? '', $opts);
        } else {
            return '';
        }
        $q = $pdo->prepare('SELECT value FROM settings WHERE '.(($env['DB_CONNECTION'] ?? 'sqlite') === 'sqlite' ? '"key"' : '`key`').' = ?');
        $q->execute(['checks.last_run_at']);
        return trim((string) $q->fetchColumn());
    } catch (Throwable $e) {
        return '';
    }
}

/** The few .env values the installer wrote itself, read back with the same escaping. */
function pharos_env_read(string $appDir): array
{
    $out = [];
    $raw = (string) @file_get_contents("$appDir/.env");
    if (!preg_match_all('/^(DB_[A-Z]+)=(.*)$/m', $raw, $m, PREG_SET_ORDER)) { return $out; }
    foreach ($m as $row) {
        $v = trim($row[2]);
        if (strlen($v) >= 2 && $v[0] === '"' && substr($v, -1) === '"') {
            $v = strtr(substr($v, 1, -1), ['\\\\' => '\\', '\\"' => '"', '\\$' => '$', '\\n' => "\n", '\\r' => "\r"]);
        }
        $out[$row[1]] = $v;
    }
    return $out;
}

// ============================================================ screens

function pharos_view_unlock(string $keyFile): string
{
    return '<form method="post" class="card"><div class="bd">
        <span class="f">Where to find it (open it in your hosting File Manager)</span>
        <div class="path mt8"><code>'.h($keyFile).'</code><button class="copy" type="button" data-copy="'.h($keyFile).'">Copy path</button></div>
        <div class="grid2 mt18"><label class="f">Installation key<input class="inp" type="password" name="install_key" required autocomplete="off" autofocus placeholder="Paste the key from that file"></label></div>
        <div class="actions"><button class="btn" type="submit">Unlock</button><span class="sub">You paste the same key once more when you create your account.</span></div>
      </div></form>';
}

/** Totals and the one sentence that says what they mean. */
function pharos_check_summary(array $checks): array
{
    $bad = []; $warn = 0;
    foreach ($checks as $c) {
        if ($c['ok']) { continue; }
        if (empty($c['soft'])) { $bad[] = $c; } else { $warn++; }
    }
    $total = count($checks);
    if ($bad) {
        $head = count($bad) === 1 ? 'One thing to fix first' : count($bad).' things to fix first';
        $one = count($bad) === 1 ? $bad[0] : null;
        if ($one && $one['group'] === 'ext') {
            $text = 'The '.$one['label'].' extension is missing.'.($one['label'] === 'sodium' ? ' It verifies signed releases and licences.' : '').' The steps below show where to turn it on.';
        } else {
            $text = 'Fix what is red, then press Check again. Nothing was written.';
        }
        $tone = 'bad';
    } elseif ($warn) {
        $head = $warn === 1 ? 'Ready, with one warning' : 'Ready, with '.$warn.' warnings';
        $text = 'The install works. Without a PHP for cron the scheduler needs a path you set on step 5.';
        $tone = 'warn';
    } else {
        $head = 'All '.$total.' checks passed';
        $text = 'This server can run Pharos. Next, the signed release is downloaded.';
        $tone = 'ok';
    }
    return ['total' => $total, 'bad' => count($bad), 'warn' => $warn, 'head' => $head, 'text' => $text, 'tone' => $tone];
}

function pharos_check_state(array $c): string { return $c['ok'] ? 'ok' : (empty($c['soft']) ? 'bad' : 'warn'); }

/** The summary card with its ring, the four groups, and the fix card when something is red. */
function pharos_view_check_live(array $checks): string
{
    $sum = pharos_check_summary($checks);
    $groups = ['php' => 'PHP', 'fs' => 'Folders', 'ext' => 'Extensions', 'net' => 'Network and scheduler'];
    $html = '';
    foreach ($groups as $g => $label) {
        $inner = '';
        foreach ($checks as $c) {
            if ($c['group'] !== $g) { continue; }
            $st = pharos_check_state($c);
            if ($g === 'ext') {
                $inner .= '<span class="chip '.$st.'" data-state="'.$st.'" title="'.h($c['detail']).'">'.h($c['label']).'</span>';
            } else {
                $mark = $st === 'ok' ? '✓' : ($st === 'warn' ? '!' : '×');
                $inner .= '<div class="row" data-state="'.$st.'"><span class="st '.$st.'" aria-hidden="true">'.$mark.'</span><span class="k">'.h($c['name']).'<span class="sr"> '.($st === 'ok' ? 'passed' : ($st === 'warn' ? 'warning' : 'failed')).'</span></span><span class="d">'.h($c['detail']).'</span></div>';
            }
        }
        $html .= '<div class="grp"><h4>'.$label.'</h4>'.($g === 'ext' ? '<div class="chips">'.$inner.'</div>' : $inner).'</div>';
    }
    $c = 195;
    return '<div class="card"><div class="sum"><div class="ring" data-tone="'.$sum['tone'].'"><svg width="74" height="74" viewBox="0 0 74 74" aria-hidden="true"><circle class="bg" cx="37" cy="37" r="31"/><circle class="fg" cx="37" cy="37" r="31" stroke-dasharray="'.$c.'" stroke-dashoffset="0"/></svg><b data-ring-n>'.$sum['total'].'</b></div>
        <div><h3 data-sum-h data-final="'.h($sum['head']).'">'.h($sum['head']).'</h3><p data-sum-p data-final="'.h($sum['text']).'">'.h($sum['text']).'</p></div></div>
        <div class="groups">'.$html.'</div></div>'.pharos_view_fix($checks);
}

function pharos_view_checks(array $checks): string
{
    $sum = pharos_check_summary($checks);
    $version = isset($_GET['version']) && preg_match('/^\d+\.\d+\.\d+$/', (string) $_GET['version']) ? '&version='.$_GET['version'] : '';
    return '<div data-live>'.pharos_view_check_live($checks).'</div>
      <form method="post" action="?step=3'.h($version).'" data-to-download><input type="hidden" name="action" value="download">
        <div class="actions"><a class="btn ghost" href="?step=2'.h($version).'" data-recheck>Check again</a>
        <button class="btn" type="submit" data-go '.($sum['bad'] ? 'disabled' : '').'>Download the release</button></div>
      </form>';
}

/** Per panel instructions, only for what actually failed. */
function pharos_view_fix(array $checks): string
{
    $bad = array_filter($checks, function ($c) { return !$c['ok'] && empty($c['soft']); });
    if (!$bad) { return ''; }
    $ext = []; $phpOld = false; $fs = []; $net = ''; $existing = '';
    foreach ($bad as $c) {
        if ($c['group'] === 'ext') { $ext[] = $c['label']; }
        elseif ($c['name'] === 'Database driver') { $ext[] = 'pdo_sqlite'; }
        elseif ($c['name'] === 'PHP for the website') { $phpOld = true; }
        elseif ($c['name'] === 'Existing install') { $existing = $c['detail']; }
        elseif ($c['group'] === 'fs') { $fs[] = $c['path'] ?? $c['detail']; }
        elseif ($c['group'] === 'net') { $net = $c['detail']; }
    }
    $code = function (array $list) { return implode(', ', array_map(function ($x) { return '<code>'.h($x).'</code>'; }, $list)); };
    $steps = pharos_fix_php_steps($phpOld);
    if ($ext) {
        $steps['cp'][] = 'Open <b>Select PHP Version</b> (CloudLinux) and tick '.$code($ext).'. On cPanel without CloudLinux your host adds extensions in EasyApache.';
        $steps['da'][] = 'Extensions are built server wide in DirectAdmin. Ask your host to enable '.$code($ext).' in CustomBuild, or pick a PHP version under <b>Domain Setup</b> that has it.';
        $steps['pl'][] = 'Websites and Domains → <b>PHP Settings</b>: enable '.$code($ext).' and apply.';
    }
    foreach (['cp', 'da', 'pl'] as $p) {
        if ($fs) { $steps[$p][] = 'In <b>File Manager</b>, make '.$code($fs).' writable for your account (permissions 755), or ask your host why it is read only.'; }
        if ($net !== '') { $steps[$p][] = 'Ask your host to allow outbound HTTPS to <code>'.h((string) parse_url(PHAROS_RELEASES, PHP_URL_HOST)).'</code>. The server said: '.h($net); }
        if ($existing !== '') { $steps[$p][] = 'An earlier Pharos is '.h($existing).'. Open this installer again to continue it, or remove that folder in <b>File Manager</b> to start over.'; }
        $steps[$p][] = 'Come back and press <b>Check again</b>.';
    }
    return '<div class="card fix" data-fix><div class="hd"><h3>How to fix this</h3><span class="hint">Pick your control panel</span></div><div class="bd">'.pharos_view_tabs($steps).'</div></div>';
}

/** The web PHP is too old: where each panel switches it, with the newer binaries this server already has. */
function pharos_fix_php_steps(bool $phpOld): array
{
    $steps = ['cp' => [], 'da' => [], 'pl' => []];
    if (!$phpOld) { return $steps; }
    $mm = pharos_min_mm();
    $have = [];
    foreach (pharos_php_installs(true) as $i) { $have[$i['panel']][] = $i['path']; }
    $found = function (array $panels) use ($have) {
        foreach ($panels as $p) { if (!empty($have[$p])) { return ' Found on this server: <code>'.h($have[$p][0]).'</code>.'; } }
        return '';
    };
    $steps['cp'][] = 'Open <b>MultiPHP Manager</b> (or <b>Select PHP Version</b> on CloudLinux), tick this domain and choose <b>PHP '.$mm.'</b> or newer.'.$found(['cPanel', 'CloudLinux']);
    $steps['da'][] = 'Account Manager → <b>Domain Setup</b> → this domain → <b>PHP Version</b>. Pick the '.$mm.' or newer entry and save.'.$found(['DirectAdmin']);
    $steps['pl'][] = 'Websites and Domains → <b>PHP Settings</b> → PHP version <b>'.$mm.'</b> or newer.'.$found(['Plesk']);
    return $steps;
}

/** Three panes. Without JavaScript all three show one below the other; with it they become tabs. */
function pharos_view_tabs(array $steps): string
{
    $names = ['cp' => 'cPanel', 'da' => 'DirectAdmin', 'pl' => 'Plesk'];
    $first = ['cPanel' => 'cp', 'DirectAdmin' => 'da', 'Plesk' => 'pl'][pharos_panel()] ?? 'cp';
    $tabs = ''; $panes = '';
    foreach ($names as $p => $name) {
        $on = $p === $first;
        $tabs .= '<button type="button" role="tab" aria-selected="'.($on ? 'true' : 'false').'" data-p="'.$p.'">'.$name.'</button>';
        $panes .= '<div class="tabpane'.($on ? ' on' : '').'" role="tabpanel" data-pane="'.$p.'"><h5>'.$name.'</h5><ol><li>'.implode('</li><li>', $steps[$p]).'</li></ol></div>';
    }
    return '<div class="tabs" role="tablist" data-tabs>'.$tabs.'</div>'.$panes;
}

function pharos_view_php_old(): string
{
    $newer = pharos_php_installs(true);
    $rows = '';
    foreach (array_slice($newer, 0, 6) as $i) {
        $rows .= '<div class="row" data-state="ok"><span class="st ok" aria-hidden="true">✓</span><span class="k">PHP '.h($i['version']).' ('.h($i['panel']).')</span><span class="d">'.h($i['path']).'</span></div>';
    }
    $have = $rows ? '<div class="card"><div class="hd"><h3>Good news: this server already has a newer PHP</h3></div><div class="bd">'.$rows.'<p class="help">Point the domain at one of these and reload this page.</p></div></div>' : '';
    $steps = pharos_fix_php_steps(true);
    foreach (['cp', 'da', 'pl'] as $p) { $steps[$p][] = 'Come back and press <b>Check again</b>.'; }
    return $have.'<div class="card fix"><div class="hd"><h3>How to fix this</h3><span class="hint">Pick your control panel</span></div><div class="bd">'.pharos_view_tabs($steps).'
        <p class="help">Extensions to switch on while you are there: <code>sodium</code>, <code>zip</code>, <code>pdo_sqlite</code>, <code>mbstring</code>, <code>fileinfo</code>, <code>curl</code>, <code>xml</code>.</p></div></div>
      <div class="actions"><a class="btn" href="?step=2">Check again</a></div>';
}

/** Which of the three proof cards is done, running or failed, from the stage the download reached. */
function pharos_proof_states(?array $d): array
{
    $s = ['signature' => '', 'checksum' => '', 'unpack' => ''];
    if ($d === null) { return $s; }
    $order = ['manifest' => 0, 'download' => 1, 'checksum' => 2, 'unpack' => 3, 'publish' => 4, 'done' => 5];
    if (($d['stage'] ?? '') === 'failed') {
        $f = $d['failed'] ?? 'unpack';
        $s['signature'] = $f === 'signature' ? 'bad' : 'ok';
        $s['checksum'] = $f === 'checksum' ? 'bad' : ($f === 'unpack' ? 'ok' : '');
        $s['unpack'] = $f === 'unpack' ? 'bad' : '';
        return $s;
    }
    $at = $order[$d['stage'] ?? 'manifest'] ?? 0;
    $s['signature'] = $at > 0 ? 'ok' : 'run';
    $s['checksum'] = $at > 2 ? 'ok' : ($at === 2 ? 'run' : '');
    $s['unpack'] = $at > 4 ? 'ok' : ($at >= 3 ? 'run' : '');
    return $s;
}

function pharos_view_download(?array $d, bool $hasApp): string
{
    $failed = $d !== null && $d['error'] !== null;
    $done = $d !== null && !$failed;
    $proof = pharos_proof_states($d);
    $mark = function ($st) { return '<span class="st '.$st.'" aria-hidden="true">'.($st === 'ok' ? '✓' : ($st === 'bad' ? '×' : '')).'</span>'; };
    $card = function ($key, $title, $text) use ($proof, $mark) {
        return '<div class="'.($proof[$key] !== '' ? 'on' : '').'" data-proof="'.$key.'"><b>'.$mark($proof[$key]).$title.'</b><span>'.$text.'</span></div>';
    };
    $name = $d && $d['version'] !== '' ? 'pharos-'.$d['version'].'.zip' : 'Signed release';
    $bytes = $d ? (int) $d['bytes'] : 0; $total = $d ? (int) $d['total'] : 0;
    $pct = $total > 0 ? min(100, (int) round($bytes / $total * 100)) : ($done ? 100 : 0);
    $mb = $total > 0 ? pharos_mb($bytes).' of '.pharos_mb($total).' MB' : ($done ? 'verified' : 'waiting to start');
    $files = $d && $d['files'] ? number_format((int) $d['files']).' files in place' : 'files in place';
    $lines = '<div class="dim">$ fetch the signed manifest from '.h((string) parse_url(PHAROS_RELEASES, PHP_URL_HOST)).'</div>';
    foreach ($d['log'] ?? [] as $l) {
        $bad = strpos($l, 'Stopped: ') === 0;
        $lines .= '<div class="'.($bad ? 'bad' : 'ok').'">'.($bad ? '× ' : '✓ ').h($bad ? substr($l, 9) : $l).'</div>';
    }
    if ($failed) { $lines .= '<div class="dim">  nothing was unpacked, the download was deleted</div>'; }
    if ($d === null) { $lines .= '<div class="dim">  waiting for you to start</div>'; }
    $version = isset($_GET['version']) && preg_match('/^\d+\.\d+\.\d+$/', (string) $_GET['version']) ? '&version='.$_GET['version'] : '';
    $actions = $done
        ? '<a class="btn" href="?step=4" data-next>Continue</a><span class="sub" data-dl-sub>Verified and unpacked.</span>'
        : '<button class="btn" type="submit" data-dl-go>'.($failed ? 'Try again' : 'Download and verify').'</button><span class="sub" data-dl-sub>'.($failed ? h($d['error']).' Try again; if it repeats, contact support.' : 'About a minute on most hosting.').'</span>';
    $auto = !$d && !$hasApp && isset($_GET['start']) ? ' data-auto="1"' : '';
    return '<form method="post" action="?step=3'.h($version).'" data-download'.$auto.'><input type="hidden" name="action" value="download">
      <div class="card"><div class="bd">
        <div class="barl"><span data-dl-name>'.h($name).'</span><span data-dl-mb>'.h($mb).'</span></div>
        <div class="bar'.($failed && ($d['failed'] ?? '') === 'download' ? ' bad' : '').'" role="progressbar" aria-label="Download" aria-valuemin="0" aria-valuemax="100" aria-valuenow="'.$pct.'"><i data-dl-bar style="width:'.$pct.'%"></i></div>
        <div class="proof">'.$card('signature', 'Signature', 'Ed25519, release key').$card('checksum', 'Checksum', 'SHA 256 matches').$card('unpack', 'Unpacked', '<span data-dl-files>'.h($files).'</span>').'</div>
      </div></div>
      <div class="card"><div class="hd"><h3>What happened</h3><span class="hint">Log</span></div><div class="bd"><div class="term" data-term aria-live="polite">'.$lines.'</div></div></div>
      <div class="actions" data-dl-actions>'.$actions.'</div>
    </form>';
}

function pharos_view_configure(array $in, ?array $testdb): string
{
    $url = isset($in['app_url']) ? (string) $in['app_url'] : pharos_base_url();
    $hasSqlite = extension_loaded('pdo_sqlite'); $hasMysql = extension_loaded('pdo_mysql');
    $db = isset($in['db']) ? ((string) $in['db'] === 'mysql' ? 'mysql' : 'sqlite') : ($hasSqlite || !$hasMysql ? 'sqlite' : 'mysql');
    $v = function ($k, $d = '') use ($in) { return h(isset($in[$k]) ? (string) $in[$k] : $d); };
    $missing = function ($ok, $ext) { return $ok ? '' : ' <span class="pill warn">'.$ext.' missing</span>'; };
    $res = $testdb === null ? '' : '<span class="'.($testdb['ok'] ? 'good' : 'badtxt').'">'.($testdb['ok'] ? '✓ ' : '× ').h($testdb['message']).'</span>';
    return '<form method="post" action="?step=4" data-configure><input type="hidden" name="action" value="configure">
      <div class="card"><div class="bd">
        <label class="f">Site address<input class="inp" name="app_url" value="'.h($url).'" required inputmode="url" autocomplete="url"></label>
        <p class="help">Detected from this page. Use https if your hosting has a certificate.</p>
        <div class="choice" role="radiogroup" aria-label="Database">
          <label><input type="radio" name="db" value="sqlite"'.($db === 'sqlite' ? ' checked' : '').'><span><b>SQLite <span class="pill">Recommended</span>'.$missing($hasSqlite, 'pdo_sqlite').'</b><small>One file next to the app. No database to create, backups are a single copy.</small></span></label>
          <label><input type="radio" name="db" value="mysql"'.($db === 'mysql' ? ' checked' : '').'><span><b>MySQL or MariaDB'.$missing($hasMysql, 'pdo_mysql').'</b><small>For hosts where you already manage databases, or many thousands of subscribers.</small></span></label>
        </div>
        <div class="grid2 mysql">
          <label class="f">Host<input class="inp" name="db_host" value="'.$v('db_host', '127.0.0.1').'"></label>
          <label class="f">Database<input class="inp" name="db_name" value="'.$v('db_name').'" autocomplete="off"></label>
          <label class="f">User<input class="inp" name="db_user" value="'.$v('db_user').'" autocomplete="off"></label>
          <label class="f">Password<input class="inp" name="db_pass" type="password" autocomplete="new-password"></label>
          <div class="testrow"><button class="btn ghost jsonly" type="button" data-testdb>Test connection</button><span data-dbres aria-live="polite">'.$res.'</span></div>
        </div>
        <ul class="migr" data-migr aria-live="polite"></ul>
      </div></div>
      <div class="actions"><button class="btn" type="submit" data-cf-go>Save settings and set up the database</button><span class="sub">Takes a few seconds. If it stops halfway, press it again: nothing is lost.</span></div>
    </form>';
}

function pharos_view_cron(string $appDir, string $installKey): string
{
    $cli = pharos_cron_php();
    $php = is_file("$appDir/.pharos-cron-php") ? trim((string) file_get_contents("$appDir/.pharos-cron-php")) : $cli['path'];
    $cron = '* * * * * cd '.pharos_shell_arg($appDir).' && '.pharos_shell_arg($php).' artisan schedule:run >> /dev/null 2>&1';
    $note = $cli['note'] === '' ? '' : '<div class="note mt12'.($cli['warn'] ? ' warn' : '').'">'.h($cli['note']).'</div>';
    $s = pharos_scheduler_status($appDir);
    $wait = $s['ran']
        ? 'The scheduler ran at <time datetime="'.h((string) $s['at']).'">'.h((string) $s['label']).'</time>. Checks will start within a minute.'
        : 'Waiting for the first run. Add the line, it usually shows up within a minute.';
    $steps = [
        'cp' => ['<b>Advanced</b> → <b>Cron Jobs</b>.', 'Common settings: <b>Once per minute</b>.', 'Paste the line in <b>Command</b> and add it.'],
        'da' => ['<b>Advanced Features</b> → <b>Cron Jobs</b> → Create.', 'Put <code>*</code> in all five time fields.', 'Paste the line. Keep the full PHP path: cron uses the server default PHP otherwise, not the one you picked in Domain Setup.'],
        'pl' => ['<b>Tools and Settings</b> → <b>Scheduled Tasks</b> → Add task.', 'Choose <b>Run a command</b>, every minute, and paste the line.'],
    ];
    return '<div class="cron"><code>'.h($cron).'</code><button class="copy" type="button" data-copy="'.h($cron).'">Copy</button></div>'.$note.'
      <div class="waiting'.($s['ran'] ? ' ok' : '').'" data-waiting role="status"><i></i><span data-wait-text>'.$wait.'</span>'.($s['ran'] ? '' : '<a class="nojs" href="?step=5">Check again</a>').'</div>
      <div class="card mt16"><div class="hd"><h3>Where to add it</h3><span class="hint">Pick your control panel</span></div><div class="bd">'.pharos_view_tabs($steps).'</div></div>
      <details class="more"><summary>Let the installer add it through your panel</summary><div>
        <p class="help">cPanel uses an API token and port 2083; DirectAdmin a Login Key restricted to <code>CMD_API_CRON_JOBS</code> and this server, port 2222. The token is used for this one request and never stored; existing tasks are kept. Revoke it afterwards. For Plesk, or a panel without API access, add the line by hand or run <code>php artisan pharos:cron --install</code> with the PHP path above.</p>
        <form method="post" action="?step=5"><input type="hidden" name="action" value="directadmin_cron">
          <div class="grid2">
            <label class="f">Panel<select class="inp" name="panel_type"><option value="directadmin">DirectAdmin</option><option value="cpanel">cPanel or CloudLinux with cPanel</option></select></label>
            <label class="f">Panel address<input class="inp" type="url" name="panel_url" placeholder="https://panel.example.com:2222" required></label>
            <label class="f">Username<input class="inp" name="panel_user" required autocomplete="off"></label>
            <label class="f">Login key or API token<input class="inp" type="password" name="panel_key" required autocomplete="off"></label>
            <label class="f span2">PHP for cron (full path)<input class="inp" name="cron_php" value="'.h($php).'" required></label>
          </div>
          <div class="actions"><button class="btn ghost" type="submit">Add and verify</button></div>
        </form></div></details>
      <div class="card mt16"><div class="bd">
        <span class="f">Your installation key</span>
        <div class="path mt8"><code>'.h($installKey).'</code><button class="copy" type="button" data-copy="'.h($installKey).'">Copy key</button></div>
        <p class="help">You paste it once more on the next screen, where you create your account.</p>
      </div></div>
      <form method="post" action="?step=5"><input type="hidden" name="action" value="finish">
        <div class="actions"><button class="btn" type="submit">Continue to your account</button><span class="sub">This deletes the installer file and opens step 6 in Pharos. You can continue before the first run; Pharos keeps reminding you until it sees one.</span></div>
      </form>';
}

// ============================================================ helpers
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
/** PHP 7-safe stand-ins, so an old host gets the "switch to PHP 8.3" screen and not a fatal error. */
function pharos_has(string $hay, string $needle): bool { return $needle === '' || strpos($hay, $needle) !== false; }
function pharos_starts(string $hay, string $needle): bool { return substr($hay, 0, strlen($needle)) === $needle; }

function pharos_base_url(): string
{
    $https = (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $dir = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/');
    return ($https ? 'https' : 'http').'://'.$host.$dir;
}

/** Outside the web folder if at all possible: the home directory first, a sibling folder second. */
function pharos_app_dir(string $webRoot): string
{
    $home = $_SERVER['HOME'] ?? null;
    $suffix = substr(hash('sha256', realpath($webRoot) ?: $webRoot), 0, 12);
    $parents = [$home, dirname($webRoot)];
    foreach ($parents as $parent) {
        if (!$parent || !@is_dir($parent) || !@is_writable($parent)) { continue; }
        $parent = rtrim(realpath($parent) ?: $parent, '/');
        if ($parent === $webRoot || pharos_starts($parent.'/', rtrim($webRoot, '/').'/')) { continue; }
        $legacy = $parent.'/pharos-app';
        if (@is_file($legacy.'/.pharos-public') && trim((string)file_get_contents($legacy.'/.pharos-public')) === $webRoot) { return $legacy; }
        return $parent.'/pharos-app-'.$suffix;
    }
    return dirname($webRoot).'/pharos-app-'.$suffix;
}

/** Two-part minimum, because version_compare('8.3', '8.3.0', '>=') is false. */
function pharos_min_mm(): string
{
    $p = explode('.', PHAROS_MIN_PHP);
    return $p[0].'.'.(isset($p[1]) ? $p[1] : '0');
}

/** Which panel this is, from what it leaves on disk. Only used to word the advice. */
function pharos_panel(): string
{
    if (@is_dir('/usr/local/directadmin')) { return 'DirectAdmin'; }
    if (@is_dir('/usr/local/cpanel') || @is_dir('/opt/cpanel')) { return 'cPanel'; }
    if (@is_dir('/opt/psa') || @is_dir('/usr/local/psa')) { return 'Plesk'; }
    return '';
}

/**
 * Every PHP this server keeps on disk, newest first. Filesystem only, on purpose:
 * DirectAdmin ships php.conf.d/50-webapps.ini, which disables exec, shell_exec and
 * proc_open, so a binary can be found and pointed at but never run to confirm itself.
 * The version therefore comes from the path and is major.minor — no patch level.
 */
function pharos_php_installs(bool $onlySupported = false): array
{
    static $cache = [];
    $key = $onlySupported ? 1 : 0;
    if (isset($cache[$key])) { return $cache[$key]; }
    $patterns = [
        '/usr/local/php*/bin/php' => 'DirectAdmin',         // CustomBuild: php84, plus leftovers like php74-cli
        '/opt/alt/php*/usr/bin/php' => 'CloudLinux',        // alt-php
        '/opt/cpanel/ea-php*/root/usr/bin/php' => 'cPanel', // EasyApache
        '/opt/plesk/php/*/bin/php' => 'Plesk',
        '/usr/bin/php[78].[0-9]' => 'system',               // Debian, Ubuntu, Remi
        '/usr/local/bin/php[78].[0-9]' => 'system',
    ];
    $found = [];
    foreach ($patterns as $glob => $panel) {
        $hits = @glob($glob);
        if (!$hits) { continue; }
        foreach ($hits as $path) {
            $v = pharos_php_version_of($path);
            // no version in the path (/usr/local/php/bin/php is a moving symlink) — not something to pin a cron line to
            if ($v === '' || isset($found[$path]) || ! @is_executable($path)) { continue; }
            if ($onlySupported && version_compare($v, pharos_min_mm(), '<')) { continue; }
            $found[$path] = ['path' => $path, 'version' => $v, 'panel' => $panel];
        }
    }
    $list = array_values($found);
    // newest first; path as tiebreak, because usort is only stable from PHP 8.0
    usort($list, function ($a, $b) {
        $v = version_compare($b['version'], $a['version']);
        return $v !== 0 ? $v : strcmp($a['path'], $b['path']);
    });
    return $cache[$key] = $list;
}

/** major.minor out of a path: php84 → 8.4, /opt/plesk/php/8.3/ → 8.3, /usr/bin/php8.3 → 8.3, ea-php83 → 8.3. */
function pharos_php_version_of(string $path): string
{
    if (preg_match('~/(?:php|ea-php)/?(\d)\.(\d+)(?:/|$)~', $path, $m)) { return $m[1].'.'.$m[2]; }
    if (preg_match('~/(?:php|ea-php|alt-php)(\d)(\d+)~', $path, $m)) { return $m[1].'.'.$m[2]; }
    return '';
}

/**
 * The binary for the cron line. Cron does not go through the panel's PHP selector:
 * on DirectAdmin the domain answers over /usr/local/php84/bin/lsphp while cron gets
 * /usr/local/bin/php, a symlink the host repoints on every CustomBuild run. So pin a
 * full path, and prefer the major.minor the site itself runs on.
 */
function pharos_cron_php(): array
{
    static $cache = null;
    if ($cache !== null) { return $cache; }
    $web = PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;
    $installs = pharos_php_installs(true);
    // A box can carry the same version twice (cPanel's ea-php next to CloudLinux alt-php).
    // The one to pick is the install this very request is running out of: on DirectAdmin
    // the web SAPI is /usr/local/php84/bin/lsphp, whose neighbour is the CLI php we want.
    $sibling = defined('PHP_BINARY') && PHP_BINARY !== '' ? dirname(PHP_BINARY) : '';
    $exact = null;
    foreach ($installs as $i) {
        if ($i['version'] !== $web) { continue; }
        if ($exact === null) { $exact = $i; }
        if ($sibling !== '' && dirname($i['path']) === $sibling) { $exact = $i; break; }
    }
    if ($exact !== null) {
        return $cache = ['path' => $exact['path'], 'ok' => true, 'warn' => false, 'note' => 'Detected from the filesystem, not run. The first scheduler run below confirms it works.', 'detail' => $exact['path'].', PHP '.$exact['version'].', same as the site'];
    }
    if ($installs) {
        $i = $installs[0];
        return $cache = ['path' => $i['path'], 'ok' => true, 'warn' => true,
            'note' => 'The cron line runs PHP '.$i['version'].' while this site runs '.$web.'. This path has not been executed: confirm its extensions and a successful scheduler run before relying on monitoring.',
            'detail' => $i['path'].', PHP '.$i['version'].' while the site runs '.$web];
    }
    $self = defined('PHP_BINARY') ? PHP_BINARY : '';
    if ($self !== '' && pharos_php_version_of($self) !== '' && @is_executable($self) && ! pharos_has($self, 'php-fpm') && ! pharos_has($self, 'lsphp') && ! pharos_has($self, 'php-cgi')) {
        return $cache = ['path' => $self, 'ok' => true, 'warn' => false, 'note' => '', 'detail' => $self.', the binary running this request'];
    }
    return $cache = ['path' => 'php', 'ok' => false, 'warn' => true,
        'note' => 'No PHP binary could be found from the web, usually because of open_basedir. The line below says plain "php", which cron resolves to the server default. Check in your panel that it is '.pharos_min_mm().' or later, or replace it with a full path such as /usr/local/php'.str_replace('.', '', $web).'/bin/php.',
        'detail' => 'not found from the web, the cron line falls back to plain "php"'];
}

function pharos_checks(string $appDir, string $webRoot): array
{
    $c = [];
    $webOk = version_compare(PHP_VERSION, PHAROS_MIN_PHP, '>=');
    $c[] = ['group' => 'php', 'name' => 'PHP for the website', 'ok' => $webOk, 'detail' => PHP_VERSION.($webOk ? '' : ', need '.PHAROS_MIN_PHP.' or later')];
    $drivers = array_values(array_filter(['pdo_sqlite', 'pdo_mysql'], 'extension_loaded'));
    $c[] = ['group' => 'php', 'name' => 'Database driver', 'ok' => (bool) $drivers, 'detail' => $drivers ? implode(' and ', $drivers) : 'none: enable pdo_sqlite or pdo_mysql'];
    $where = pharos_panel() === 'DirectAdmin' ? 'pick a newer PHP in Domain Setup, or ask your host to build it into CustomBuild' : 'enable it under Select PHP Version (cPanel) or PHP Settings';
    foreach ([['pdo', 'PDO'], ['mbstring', 'mbstring'], ['openssl', 'openssl'], ['sodium', 'verifies the release signature'], ['zip', 'ZipArchive'], ['curl', 'curl'], ['fileinfo', 'fileinfo'], ['tokenizer', 'tokenizer'], ['xml', 'xml'], ['ctype', 'ctype']] as [$ext, $label]) {
        $c[] = ['group' => 'ext', 'label' => $ext, 'name' => "Extension $ext", 'ok' => extension_loaded($ext), 'detail' => extension_loaded($ext) ? 'loaded' : "missing ($label): $where"];
    }
    $parent = is_dir($appDir) ? $appDir : dirname($appDir);
    $c[] = ['group' => 'fs', 'name' => 'App folder', 'ok' => is_writable($parent), 'path' => $parent, 'detail' => $appDir.(is_dir($appDir) ? ' (exists)' : ' (will be created)').(is_writable($parent) ? '' : ', not writable')];
    $c[] = ['group' => 'fs', 'name' => 'Web folder', 'ok' => is_writable($webRoot), 'path' => $webRoot, 'detail' => (is_writable($webRoot) ? 'writable: ' : 'not writable: ').$webRoot];
    $c[] = ['group' => 'fs', 'name' => 'Existing install', 'ok' => ! is_file("$appDir/artisan"), 'detail' => is_file("$appDir/artisan") ? "found at $appDir" : 'none found'];
    try {
        $m = pharos_manifest();
        $c[] = ['group' => 'net', 'name' => 'Release server', 'ok' => true, 'detail' => parse_url(PHAROS_RELEASES, PHP_URL_HOST).' reachable, latest '.$m['version']];
    } catch (Throwable $e) {
        $c[] = ['group' => 'net', 'name' => 'Release server', 'ok' => false, 'detail' => $e->getMessage()];
    }
    // The scheduler does not run under the web PHP. On DirectAdmin the domain runs
    // /usr/local/php84/bin/lsphp while cron gets /usr/local/bin/php, a different,
    // often older version. Say now which binary the cron line on step 5 will use.
    $cli = pharos_cron_php();
    $c[] = ['group' => 'net', 'name' => 'PHP for cron', 'ok' => $cli['ok'], 'soft' => true, 'detail' => $cli['detail']];
    return $c;
}

/** @param callable|null $progress fn(int $total, int $now), called while a file downloads (curl only) */
function pharos_http_get(string $url, ?string $toFile = null, ?callable $progress = null): string
{
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        $fh = $toFile ? fopen($toFile, 'wb') : null;
        curl_setopt_array($ch, [CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_TIMEOUT => 300, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_USERAGENT => 'pharos-install/1', CURLOPT_SSL_VERIFYPEER => true] + ($fh ? [CURLOPT_FILE => $fh] : [CURLOPT_RETURNTRANSFER => true]));
        if ($progress !== null) {
            curl_setopt($ch, CURLOPT_NOPROGRESS, false);
            curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function ($ch, $total, $now) use ($progress) { $progress((int) $total, (int) $now); return 0; });
        }
        $out = curl_exec($ch);
        $code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $err = curl_error($ch);
        curl_close($ch);
        if ($fh) { fclose($fh); }
        if ($out === false || $code !== 200) { throw new RuntimeException("Could not fetch $url (".($err ?: "HTTP $code").')'); }
        return $toFile ? '' : (string) $out;
    }
    $ctx = stream_context_create(['http' => ['timeout' => 300, 'user_agent' => 'pharos-install/1']]);
    $data = @file_get_contents($url, false, $ctx);
    if ($data === false) { throw new RuntimeException("Could not fetch $url (allow_url_fopen is off and curl is missing?)"); }
    if ($toFile) { file_put_contents($toFile, $data); return ''; }
    return $data;
}

/** The signed manifest: "<base64url payload>.<base64url signature>", verified with the public key above. */
function pharos_manifest(): array
{
    static $cached = null;
    if ($cached) { return $cached; }
    $pin = PHAROS_PIN_VERSION ?: (preg_match('/^\d+\.\d+\.\d+$/', (string) ($_GET['version'] ?? '')) ? $_GET['version'] : '');
    $raw = trim(pharos_http_get(PHAROS_RELEASES.($pin ? "/pharos-$pin.json" : '/latest.json')));
    if (! pharos_has($raw, '.')) { throw new RuntimeException('latest.json is not a signed manifest'); }
    [$p, $s] = explode('.', $raw, 2);
    $b64 = fn (string $v) => base64_decode(strtr($v, '-_', '+/').str_repeat('=', (4 - strlen($v) % 4) % 4), true);
    $payload = $b64($p); $sig = $b64($s);
    if ($payload === false || $sig === false || strlen($sig) !== SODIUM_CRYPTO_SIGN_BYTES) { throw new RuntimeException('latest.json is malformed'); }
    if (! sodium_crypto_sign_verify_detached($sig, $payload, sodium_hex2bin(PHAROS_PUBKEY_HEX))) {
        throw new RuntimeException('The release manifest does not carry a valid SolutionMAX signature. Not installing.');
    }
    $m = json_decode($payload, true);
    if (! is_array($m) || ($m['purpose'] ?? null) !== 'pharos-release' || empty($m['version']) || empty($m['url']) || empty($m['sha256'])) {
        throw new RuntimeException('The manifest is not a Pharos release manifest');
    }
    if ($pin && $m['version'] !== $pin) {
        throw new RuntimeException("Asked for $pin but the manifest says {$m['version']}, refusing.");
    }
    return $cached = $m;
}

function pharos_tmp_dir(string $appDir): string
{
    $tmp = dirname($appDir).'/.pharos-install-tmp-'.substr(hash('sha256', $appDir), 0, 16);
    pharos_rmdir($tmp);
    if (! mkdir($tmp, 0755, true) && ! is_dir($tmp)) { throw new RuntimeException("Cannot create $tmp"); }
    return $tmp;
}

function pharos_download(string $url, string $to, ?callable $progress = null): void
{
    if (! pharos_starts($url, 'https://')) { throw new RuntimeException('The release URL is not https'); }
    pharos_http_get($url, $to, $progress);
    if (! is_file($to) || filesize($to) < 1000000) { throw new RuntimeException('Download is incomplete'); }
}

/** Returns the number of files unpacked; $progress(done, total) is called per batch of entries. */
function pharos_unpack(string $zipPath, string $tmp, string $appDir, ?callable $progress = null): int
{
    $zip = new ZipArchive;
    if ($zip->open($zipPath) !== true) { throw new RuntimeException('The archive cannot be opened'); }
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $name = (string) $zip->getNameIndex($i);
        if (pharos_has($name, '..') || pharos_starts($name, '/')) { throw new RuntimeException("Refusing unsafe archive entry $name"); }
        $zip->getExternalAttributesIndex($i, $opsys, $attr);
        if ($opsys === ZipArchive::OPSYS_UNIX && (($attr >> 16) & 0170000) === 0120000) { throw new RuntimeException("Refusing archive entry $name: symlinks are not allowed in a release"); }
    }
    $x = "$tmp/x"; mkdir($x, 0755, true);
    // In batches, so the page can show real progress; every entry was vetted above.
    $count = $zip->numFiles;
    $files = 0;
    for ($i = 0; $i < $count; $i += 250) {
        $names = [];
        for ($j = $i; $j < min($count, $i + 250); $j++) {
            $name = (string) $zip->getNameIndex($j);
            $names[] = $name;
            if (substr($name, -1) !== '/') { $files++; }
        }
        if (! $zip->extractTo($x, $names)) { throw new RuntimeException('Unpacking failed (disk full?)'); }
        if ($progress !== null) { $progress($files, $count); }
    }
    $zip->close();
    $entries = array_values(array_diff(scandir($x) ?: [], ['.', '..']));
    $root = count($entries) === 1 && is_dir("$x/{$entries[0]}") ? "$x/{$entries[0]}" : $x;
    if (! is_file("$root/artisan")) { throw new RuntimeException('That archive does not look like a Pharos release'); }
    if (is_dir($appDir)) { throw new RuntimeException("Refusing to replace existing folder $appDir"); }
    if (! @rename($root, $appDir)) { pharos_copy_tree($root, $appDir); }
    // belt and braces if this folder ever ends up under a document root
    file_put_contents("$appDir/.htaccess", "Require all denied\n");
    foreach (['storage', 'bootstrap/cache', 'database'] as $d) { @chmod("$appDir/$d", 0775); }
    return $files;
}

/** The web folder gets public/ only; index.php is rewritten to find the app where it really is. */
function pharos_publish_public(string $appDir, string $webRoot): void
{
    $pub = "$appDir/public";
    // cPanel keeps its PHP handler and ini blocks in .htaccess: keep them, add Laravel's rules underneath.
    $panel = is_file("$webRoot/.htaccess") ? file_get_contents("$webRoot/.htaccess") : '';
    $ours = is_file("$pub/.htaccess") ? file_get_contents("$pub/.htaccess") : '';
    if ($ours !== '' && strpos($panel, '# BEGIN Pharos') === false) {
        // DirectAdmin ships a placeholder index.html that Apache would serve before index.php
        file_put_contents("$webRoot/.htaccess", (trim($panel) === '' ? '' : rtrim($panel)."\n\n")."# BEGIN Pharos\nDirectoryIndex index.php\n".trim($ours)."\n# END Pharos\n");
    }
    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pub, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item) {
        $rel = substr($item->getPathname(), strlen($pub) + 1);
        if ($rel === '.htaccess') { continue; }
        $dest = "$webRoot/$rel";
        if ($item->isDir()) { @mkdir($dest, 0755, true); continue; }
        if (! copy($item->getPathname(), $dest)) { throw new RuntimeException("Cannot write $dest"); }
    }
    $index = file_get_contents("$webRoot/index.php");
    $index = str_replace("__DIR__.'/../", "'".addslashes($appDir)."/", $index);
    file_put_contents("$webRoot/index.php", $index);
    // the app must also know where its public folder is, for asset paths and storage:link
    file_put_contents("$appDir/.pharos-public", $webRoot);
}

function pharos_write_env(string $appDir, string $url, string $db, array $in, string $setupKey = ''): void
{
    $existing = is_file("$appDir/.env") ? file_get_contents("$appDir/.env") : '';
    $env = file_get_contents("$appDir/.env.example");
    $set = function (string $k, string $v) use (&$env) {
        $line = $k.'="'.strtr($v, ["\\"=>"\\\\", '"'=>'\\"', '$'=>'\\$', "\n"=>'\\n', "\r"=>'\\r']).'"';
        $env = preg_match("/^$k=.*$/m", $env) ? preg_replace_callback("/^$k=.*$/m", function () use ($line) { return $line; }, $env) : $env."\n$line";
    };
    $set('APP_ENV', 'production'); $set('APP_DEBUG', 'false'); $set('APP_URL', $url);
    // Preserve the literal existing line, including its quoting.
    if (preg_match('/^APP_KEY=(.+)$/m', $existing, $key)) {
        $env = preg_replace_callback('/^APP_KEY=.*$/m', function () use ($key) { return $key[0]; }, $env);
    } else { $set('APP_KEY', 'base64:'.base64_encode(random_bytes(32))); }
    if ($setupKey !== '') { $set('PHAROS_SETUP_KEY', $setupKey); }
    if ($db === 'mysql') {
        $set('DB_CONNECTION', 'mysql'); $set('DB_HOST', (string)($in['db_host'] ?? '127.0.0.1')); $set('DB_PORT', '3306');
        $set('DB_DATABASE', (string)($in['db_name'] ?? '')); $set('DB_USERNAME', (string)($in['db_user'] ?? '')); $set('DB_PASSWORD', (string)($in['db_pass'] ?? ''));
    } else {
        $set('DB_CONNECTION', 'sqlite');
        $env = preg_replace('/^DB_DATABASE=.*$/m', '', $env);
        if (!is_file("$appDir/database/database.sqlite")) { touch("$appDir/database/database.sqlite"); }
    }
    $tmp = "$appDir/.env.".bin2hex(random_bytes(6));
    if (file_put_contents($tmp, $env) === false) { throw new RuntimeException('Cannot write configuration.'); }
    chmod($tmp, 0600);
    if (!rename($tmp, "$appDir/.env")) { throw new RuntimeException('Cannot publish configuration.'); }
}

function pharos_setup_key(string $path): string
{
    $file = @fopen($path, 'c+');
    if (!$file || !flock($file, LOCK_EX)) { throw new RuntimeException('The private installation folder is not writable. Ask your host for a writable folder outside the document root.'); }
    try {
        @chmod($path, 0600);
        $key = trim(stream_get_contents($file));
        if ($key === '') { $key = bin2hex(random_bytes(32)); fwrite($file, $key); fflush($file); }
        return $key;
    } finally { flock($file, LOCK_UN); fclose($file); }
}

function pharos_shell_arg(string $value): string
{
    if (preg_match('/[\r\n%]/', $value)) { throw new RuntimeException('Cron paths cannot contain newlines or percent signs.'); }
    return "'".str_replace("'", "'\\''", $value)."'";
}

/** Runs an artisan command inside this request, without a shell. $boot sees the app first (to listen for events). */
function pharos_artisan(string $appDir, string $command, array $args = [], ?callable $boot = null): string
{
    require_once "$appDir/vendor/autoload.php";
    $app = require "$appDir/bootstrap/app.php";
    $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
    $kernel->bootstrap();
    if ($boot !== null) { $boot($app); }
    $out = new Symfony\Component\Console\Output\BufferedOutput;
    $code = $kernel->call($command, $args, $out);
    $text = $out->fetch();
    if ($code !== 0) { throw new RuntimeException("artisan $command failed: ".trim($text)); }
    return $text;
}

function pharos_link_storage(string $appDir, string $webRoot, array &$log): void
{
    $target = "$appDir/storage/app/public"; $link = "$webRoot/storage";
    if (is_link($link) || is_dir($link)) { $log[] = 'Storage link already present'; return; }
    if (function_exists('symlink') && @symlink($target, $link)) { $log[] = 'Storage linked, uploads will show'; return; }
    $log[] = 'No storage symlink allowed here; Pharos serves uploads through the app instead';
}

function pharos_copy_tree(string $from, string $to): void
{
    if (!is_dir($to) && !mkdir($to, 0755, true)) { throw new RuntimeException("Cannot create $to"); }
    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($from, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item) {
        $dest = $to.'/'.substr($item->getPathname(), strlen($from) + 1);
        $item->isDir() ? @mkdir($dest, 0755, true) : copy($item->getPathname(), $dest);
    }
}

function pharos_rmdir(string $dir): void
{
    if (! is_dir($dir)) { return; }
    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $item) {
        $item->isDir() ? @rmdir($item->getPathname()) : @unlink($item->getPathname());
    }
    @rmdir($dir);
}

/** Only this endpoint is used; panel credentials are neither persisted nor redirected. */
function pharos_da_request(string $panel, string $user, string $key, array $fields, string $panelType = 'directadmin'): array
{
    $parts = parse_url($panel);
    if (!$parts || ($parts['scheme'] ?? '') !== 'https' || empty($parts['host']) || isset($parts['user']) || isset($parts['pass']) || isset($parts['query']) || !in_array($parts['path'] ?? '', ['', '/'], true)) {
        throw new RuntimeException('Use your HTTPS panel origin, for example https://panel.example.com:2222 or :2083.');
    }
    if (!preg_match('/^[a-zA-Z0-9_-]+$/', $user) || $key === '' || preg_match('/[\r\n]/', $key)) { throw new RuntimeException('Enter a panel username and Login Key or API token.'); }
    $host = $parts['host'];
    $addresses = filter_var($host, FILTER_VALIDATE_IP) ? [$host] : (gethostbynamel($host) ?: []);
    if (!$addresses) { throw new RuntimeException('The panel hostname could not be resolved.'); }
    foreach ($addresses as $ip) {
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            throw new RuntimeException('Use a publicly reachable panel HTTPS address.');
        }
    }
    $port = $parts['port'] ?? 443;
    $creating = (bool)$fields;
    $endpoint = '/CMD_API_CRON_JOBS';
    if ($panelType === 'cpanel') {
        $endpoint = '/json-api/cpanel';
        $fields = ['cpanel_jsonapi_user'=>$user, 'cpanel_jsonapi_apiversion'=>2, 'cpanel_jsonapi_module'=>'Cron', 'cpanel_jsonapi_func'=>$creating ? 'add_line' : 'listcron'] + ($creating ? ['minute'=>'*', 'hour'=>'*', 'day'=>'*', 'month'=>'*', 'weekday'=>'*', 'command'=>$fields['command']] : []);
    }
    $ch = curl_init(rtrim($panel, '/').$endpoint);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_FOLLOWLOCATION=>false, CURLOPT_TIMEOUT=>15, CURLOPT_CONNECTTIMEOUT=>5, CURLOPT_SSL_VERIFYPEER=>true, CURLOPT_SSL_VERIFYHOST=>2, CURLOPT_USERPWD=>$user.':'.$key, CURLOPT_RESOLVE=>[$host.':'.$port.':'.$addresses[0]]]);
    if ($panelType === 'cpanel') { curl_setopt($ch, CURLOPT_USERPWD, null); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: cpanel '.$user.':'.$key]); }
    if ($fields) { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields)); }
    $raw = curl_exec($ch); $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch);
    if (!is_string($raw) || $status !== 200) { throw new RuntimeException('The hosting panel did not accept the request. Check the panel address, certificate and token permissions.'); }
    if ($panelType === 'cpanel') { return pharos_cpanel_result($raw, $creating); }
    parse_str($raw, $result);
    if ((isset($result['error']) && (string)$result['error'] !== '0') || strpos($raw, '<html') !== false || strpos($raw, '<!DOCTYPE') !== false) {
        throw new RuntimeException('DirectAdmin refused cron access. Existing jobs were not changed by Pharos.');
    }
    return $result;
}

/** Convert the documented API2 Cron response, including its final count-only row. */
function pharos_cpanel_result(string $raw, bool $creating): array
{
    $result = json_decode($raw, true)['cpanelresult'] ?? null;
    if (!is_array($result) || (int)($result['event']['result'] ?? 0) !== 1 || !is_array($result['data'] ?? null)) {
        throw new RuntimeException('cPanel cron access failed; use manual setup.');
    }
    if ($creating) {
        if ((int)($result['data'][0]['status'] ?? 0) !== 1) { throw new RuntimeException('cPanel did not save the cron task.'); }
        return ['error'=>'0'];
    }
    $jobs = [];
    foreach ($result['data'] as $row) {
        if (is_array($row) && array_keys($row) === ['count']) { continue; }
        $parts = [];
        foreach (['minute','hour','day','month','weekday','command'] as $field) {
            if (!isset($row[$field]) || !is_scalar($row[$field])) { throw new RuntimeException('Unknown cPanel cron response; use manual setup.'); }
            $parts[] = (string)$row[$field];
        }
        $jobs[] = implode(' ', $parts);
    }
    return $jobs;
}

/** Read before writing, never delete other tasks, and verify the saved task. */
function pharos_da_cron(string $panel, string $user, string $key, string $appDir, string $php, ?callable $request = null): string
{
    if ($php === 'php' || pharos_php_version_of($php) === '') { throw new RuntimeException('Choose a versioned absolute CLI PHP path before adding cron automatically.'); }
    $command = 'cd '.pharos_shell_arg($appDir).' && '.pharos_shell_arg($php).' artisan schedule:run >> /dev/null 2>&1';
    $request = $request ?: 'pharos_da_request';
    $contains = function (array $jobs) use ($command, $appDir): bool {
        foreach ($jobs as $id => $line) {
            if (!ctype_digit((string)$id)) { throw new RuntimeException('Unknown panel cron response; use manual setup.'); }
            if (!is_string($line)) { throw new RuntimeException('Unknown panel cron response; use manual setup.'); }
            $parts = preg_split('/\s+/', trim($line), 6);
            if (count($parts) !== 6) { throw new RuntimeException('Unknown panel cron response; use manual setup.'); }
            if ($parts[5] === $command && array_slice($parts, 0, 5) === ['*','*','*','*','*']) { return true; }
            if (strpos($parts[5], $appDir) !== false && strpos($parts[5], 'artisan schedule:run') !== false) { throw new RuntimeException('A cron task for this installation already exists with different settings. Review it in your panel; Pharos will not add a duplicate.'); }
        }
        return false;
    };
    $jobs = $request($panel, $user, $key, []);
    if ($contains($jobs)) { return 'The Pharos cron task already exists. Waiting for a successful scheduler run.'; }
    $request($panel, $user, $key, ['action'=>'create', 'minute'=>'*', 'hour'=>'*', 'dayofmonth'=>'*', 'month'=>'*', 'dayofweek'=>'*', 'command'=>$command]);
    if (!$contains($request($panel, $user, $key, []))) { throw new RuntimeException('The saved task could not be verified. Check your panel before retrying.'); }
    return 'Cron task saved and verified. Monitoring is confirmed by the first successful scheduler run.';
}


/* Embedded font licences (SIL Open Font License 1.1):
Copyright 2020 The Plus Jakarta Sans Project Authors (https://github.com/tokotype/PlusJakartaSans)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.


Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)

This Font Software is licensed under the SIL Open Font License, Version 1.1.

This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

*/
function pharos_installer_fonts(): string
{
    return <<<'PHAROS_FONTS'
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:400 800;font-display:swap;src:url(data:font/woff2;base64,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) format('woff2')}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 700;font-display:swap;src:url(data:font/woff2;base64,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) format('woff2')}
PHAROS_FONTS;
}

/** Embedded brand asset: the installer remains a single, offline-renderable file. */
function pharos_installer_logo(): string
{
    return <<<'PHAROS_LOGO'
<svg width="148" height="45" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1483 449" role="img" aria-label="Pharos">
<defs>
  <linearGradient id="pharosGradient" x1="0" y1="0" x2="1" y2="1">
    <stop offset="0%" stop-color="#0070F0"/>
    <stop offset="100%" stop-color="#0050B0"/>
  </linearGradient>
</defs>
<path fill="url(#pharosGradient)" fill-rule="evenodd" d="M 1287 290 L 1286 293 L 1288 293 Z M 827 287 L 827 306 L 829 304 L 828 289 Z M 213 265 L 213 267 L 215 267 Z M 1290 259 L 1290 263 L 1292 264 L 1292 272 L 1291 262 L 1292 260 Z M 209 253 L 209 255 L 211 255 L 211 253 Z M 991 231 L 990 233 L 990 238 L 991 240 L 992 238 Z M 1284 228 L 1283 231 L 1285 230 Z M 253 215 L 255 215 L 255 213 Z M 1439 208 L 1439 210 L 1441 209 Z M 193 206 L 195 205 L 195 203 Z M 1012 186 L 1010 188 L 1009 191 L 1009 237 L 1011 343 L 1061 343 L 1063 341 L 1062 256 L 1065 246 L 1071 240 L 1075 239 L 1076 236 L 1080 234 L 1087 232 L 1106 232 L 1108 231 L 1109 229 L 1109 189 L 1108 187 L 1106 186 L 1097 186 L 1089 187 L 1078 190 L 1068 195 L 1060 186 Z M 1067 196 L 1065 197 L 1063 199 L 1065 195 Z M 835 209 L 836 211 L 839 211 L 850 223 L 847 224 L 851 224 L 853 226 L 857 234 L 855 233 L 858 233 L 865 239 L 874 233 L 876 234 L 874 233 L 882 230 L 882 228 L 892 226 L 894 224 L 898 223 L 917 223 L 918 225 L 921 225 L 921 227 L 926 228 L 928 227 L 931 229 L 934 233 L 932 235 L 930 235 L 932 237 L 933 235 L 936 240 L 936 247 L 926 248 L 924 246 L 920 247 L 915 245 L 911 245 L 907 247 L 888 246 L 886 248 L 879 248 L 871 250 L 852 257 L 841 267 L 839 266 L 841 268 L 834 278 L 833 276 L 831 276 L 831 278 L 833 277 L 831 287 L 831 306 L 833 314 L 838 324 L 842 328 L 838 329 L 840 330 L 843 329 L 848 334 L 861 343 L 867 345 L 873 344 L 873 346 L 880 345 L 906 346 L 914 344 L 919 345 L 917 344 L 926 343 L 934 337 L 936 339 L 934 337 L 937 335 L 939 343 L 987 343 L 989 341 L 989 240 L 985 223 L 988 220 L 985 223 L 977 208 L 967 199 L 966 196 L 964 197 L 962 195 L 964 194 L 961 195 L 948 188 L 923 183 L 891 184 L 866 191 L 845 203 L 839 208 L 838 211 L 837 209 Z M 883 291 L 884 289 L 888 285 L 891 283 L 895 281 L 903 279 L 926 279 L 937 281 L 936 295 L 931 299 L 933 301 L 927 303 L 929 304 L 927 306 L 922 306 L 924 307 L 921 308 L 920 307 L 918 307 L 917 308 L 915 308 L 917 309 L 909 311 L 897 310 L 888 306 L 886 304 L 888 302 L 887 300 L 885 303 L 883 300 L 883 297 L 888 290 Z M 1111 189 L 1111 230 L 1116 232 L 1118 235 L 1115 243 L 1114 241 L 1113 243 L 1111 241 L 1111 245 L 1113 246 L 1114 242 L 1115 247 L 1113 253 L 1114 284 L 1111 286 L 1111 290 L 1113 289 L 1114 291 L 1115 288 L 1118 297 L 1115 301 L 1119 301 L 1122 306 L 1119 309 L 1122 308 L 1126 313 L 1123 315 L 1127 315 L 1129 320 L 1127 320 L 1130 319 L 1134 323 L 1132 324 L 1138 327 L 1135 328 L 1138 327 L 1142 330 L 1144 334 L 1146 333 L 1149 335 L 1149 337 L 1152 337 L 1156 341 L 1159 342 L 1160 341 L 1183 348 L 1210 349 L 1227 346 L 1249 339 L 1249 337 L 1264 329 L 1265 325 L 1266 326 L 1271 322 L 1269 321 L 1278 308 L 1285 291 L 1288 275 L 1288 257 L 1285 241 L 1279 228 L 1278 221 L 1280 221 L 1277 222 L 1271 213 L 1259 201 L 1261 200 L 1259 201 L 1242 190 L 1219 183 L 1213 182 L 1189 182 L 1177 184 L 1162 189 L 1152 195 L 1147 195 L 1149 196 L 1138 204 L 1134 209 L 1132 207 L 1134 209 L 1129 214 L 1127 212 L 1129 215 L 1123 217 L 1126 219 L 1122 224 L 1119 223 L 1121 227 L 1119 232 L 1116 232 L 1112 227 L 1111 222 Z M 1204 226 L 1216 228 L 1222 233 L 1220 234 L 1227 238 L 1230 246 L 1227 246 L 1229 248 L 1231 245 L 1233 249 L 1235 258 L 1235 270 L 1234 278 L 1232 285 L 1228 292 L 1223 297 L 1219 297 L 1221 298 L 1218 301 L 1216 300 L 1215 303 L 1209 305 L 1204 306 L 1197 306 L 1191 304 L 1193 303 L 1191 303 L 1190 302 L 1187 303 L 1184 302 L 1183 299 L 1181 300 L 1179 298 L 1180 296 L 1178 296 L 1176 295 L 1174 293 L 1176 290 L 1173 291 L 1171 289 L 1169 284 L 1171 284 L 1173 281 L 1172 279 L 1170 281 L 1169 283 L 1167 280 L 1166 276 L 1165 264 L 1167 251 L 1169 248 L 1170 250 L 1172 251 L 1173 249 L 1171 248 L 1170 249 L 1169 246 L 1172 241 L 1174 239 L 1176 242 L 1174 239 L 1176 237 L 1177 235 L 1180 236 L 1177 235 L 1181 231 L 1186 228 L 1188 229 L 1191 228 L 1193 226 Z M 1232 259 L 1233 269 L 1232 266 Z M 1327 188 L 1314 198 L 1308 200 L 1310 201 L 1302 212 L 1299 210 L 1301 211 L 1302 214 L 1301 216 L 1295 219 L 1297 225 L 1298 221 L 1299 226 L 1298 246 L 1297 242 L 1297 244 L 1295 245 L 1296 249 L 1297 247 L 1300 248 L 1302 254 L 1300 255 L 1300 257 L 1303 257 L 1313 267 L 1322 273 L 1348 282 L 1374 288 L 1379 291 L 1383 291 L 1386 293 L 1383 294 L 1387 293 L 1389 295 L 1390 302 L 1385 307 L 1385 305 L 1383 306 L 1385 307 L 1380 310 L 1361 311 L 1352 308 L 1354 307 L 1350 308 L 1350 306 L 1347 307 L 1335 301 L 1332 296 L 1328 296 L 1327 293 L 1319 292 L 1315 296 L 1312 296 L 1313 298 L 1310 301 L 1307 300 L 1310 301 L 1304 307 L 1301 307 L 1296 311 L 1298 313 L 1295 316 L 1295 321 L 1306 330 L 1308 334 L 1310 333 L 1311 336 L 1313 335 L 1323 342 L 1331 343 L 1331 345 L 1334 344 L 1334 346 L 1337 345 L 1337 347 L 1342 346 L 1356 349 L 1380 349 L 1397 345 L 1402 346 L 1400 345 L 1403 344 L 1405 345 L 1412 342 L 1413 340 L 1420 336 L 1424 335 L 1425 333 L 1426 334 L 1428 333 L 1426 332 L 1437 325 L 1434 323 L 1440 313 L 1443 299 L 1441 282 L 1435 270 L 1428 261 L 1425 261 L 1424 258 L 1422 259 L 1402 249 L 1385 246 L 1370 240 L 1364 240 L 1358 238 L 1351 233 L 1351 229 L 1350 231 L 1349 228 L 1354 222 L 1358 220 L 1377 219 L 1394 225 L 1401 229 L 1406 235 L 1411 236 L 1421 226 L 1424 226 L 1422 225 L 1431 218 L 1431 215 L 1436 211 L 1436 207 L 1416 191 L 1406 186 L 1388 181 L 1356 180 L 1340 183 Z M 181 170 L 183 170 L 183 167 Z M 210 159 L 209 161 L 210 163 Z M 385 219 L 290 219 L 284 226 L 282 223 L 283 225 L 278 231 L 280 230 L 281 232 L 279 236 L 274 237 L 276 240 L 274 245 L 270 244 L 273 247 L 269 254 L 266 252 L 268 256 L 265 261 L 262 259 L 265 262 L 261 267 L 258 266 L 259 273 L 257 276 L 254 273 L 257 277 L 253 283 L 249 282 L 252 286 L 250 286 L 249 289 L 246 288 L 248 291 L 244 294 L 242 293 L 244 294 L 241 296 L 228 296 L 225 294 L 227 293 L 222 290 L 223 288 L 220 289 L 215 282 L 196 222 L 199 215 L 195 217 L 188 197 L 191 193 L 190 190 L 186 190 L 184 185 L 185 182 L 187 182 L 186 179 L 184 183 L 176 157 L 175 158 L 174 155 L 172 156 L 161 176 L 157 175 L 161 177 L 156 185 L 156 182 L 154 182 L 156 186 L 147 203 L 141 205 L 143 211 L 137 221 L 134 219 L 136 223 L 130 226 L 132 227 L 130 229 L 126 229 L 128 230 L 120 233 L 41 233 L 41 406 L 47 411 L 132 411 L 146 404 L 145 400 L 148 393 L 151 398 L 148 393 L 148 337 L 151 333 L 154 333 L 153 331 L 155 329 L 260 328 L 284 324 L 306 317 L 306 315 L 310 313 L 314 313 L 314 311 L 339 297 L 337 296 L 343 290 L 351 286 L 349 285 L 352 282 L 355 282 L 352 281 L 355 277 L 363 273 L 360 272 L 368 261 L 370 263 L 368 261 L 380 240 L 383 242 L 381 235 L 386 224 Z M 623 154 L 620 154 L 610 147 L 609 144 L 607 145 L 607 143 L 605 144 L 605 142 L 599 141 L 599 139 L 594 139 L 591 137 L 574 133 L 568 133 L 567 131 L 466 132 L 464 134 L 465 343 L 518 343 L 523 341 L 524 338 L 524 285 L 523 284 L 562 283 L 583 280 L 599 275 L 609 270 L 620 261 L 624 262 L 620 261 L 629 252 L 631 253 L 629 252 L 636 240 L 640 241 L 638 239 L 637 241 L 636 238 L 640 229 L 641 232 L 643 232 L 643 229 L 642 227 L 641 230 L 640 226 L 642 219 L 642 196 L 640 190 L 641 185 L 642 188 L 643 184 L 641 184 L 640 186 L 636 176 L 638 176 L 639 174 L 636 176 L 633 169 L 635 167 L 633 169 L 628 162 L 621 155 Z M 585 196 L 585 198 L 584 200 L 582 202 L 582 215 L 585 218 L 585 220 L 582 225 L 578 229 L 574 229 L 576 230 L 573 232 L 571 231 L 569 234 L 559 236 L 539 235 L 520 235 L 522 231 L 523 234 L 524 187 L 523 183 L 525 181 L 564 180 L 571 183 L 569 184 L 573 184 L 576 186 L 574 188 L 577 187 L 581 191 L 578 193 L 581 191 Z M 654 131 L 656 131 L 657 343 L 709 342 L 709 253 L 710 249 L 712 247 L 715 247 L 715 244 L 713 242 L 720 234 L 722 235 L 723 232 L 730 229 L 740 230 L 746 229 L 751 234 L 755 234 L 756 238 L 754 239 L 757 237 L 761 243 L 758 245 L 759 249 L 761 248 L 762 245 L 764 250 L 765 343 L 819 342 L 818 238 L 820 237 L 819 230 L 818 233 L 816 232 L 814 227 L 815 218 L 812 218 L 810 216 L 810 212 L 812 211 L 809 213 L 801 203 L 803 201 L 801 200 L 800 201 L 798 199 L 799 198 L 796 198 L 783 189 L 767 184 L 749 183 L 734 185 L 722 189 L 710 196 L 709 129 L 658 128 Z M 38 74 L 38 80 L 39 77 Z M 44 66 L 41 79 L 41 195 L 111 194 L 120 179 L 121 181 L 123 180 L 120 178 L 122 174 L 127 173 L 124 170 L 131 158 L 135 159 L 132 156 L 136 150 L 139 151 L 136 149 L 144 134 L 147 136 L 144 133 L 148 127 L 151 129 L 148 126 L 154 115 L 159 115 L 159 113 L 155 113 L 159 106 L 163 107 L 160 107 L 160 103 L 167 99 L 165 98 L 171 97 L 168 96 L 171 94 L 177 93 L 188 96 L 193 101 L 190 103 L 193 102 L 196 106 L 209 144 L 208 148 L 206 147 L 206 150 L 208 151 L 209 148 L 221 180 L 220 183 L 218 183 L 219 186 L 221 182 L 226 196 L 224 198 L 222 195 L 221 197 L 229 205 L 229 211 L 226 207 L 225 209 L 226 211 L 228 210 L 231 212 L 234 221 L 232 227 L 237 230 L 238 235 L 244 227 L 245 229 L 247 228 L 244 226 L 254 208 L 259 207 L 256 204 L 264 191 L 267 192 L 264 191 L 268 187 L 271 188 L 269 187 L 272 184 L 276 185 L 274 184 L 278 182 L 392 181 L 391 159 L 388 148 L 389 142 L 391 142 L 390 138 L 388 143 L 377 111 L 379 110 L 376 112 L 364 93 L 367 91 L 364 92 L 353 80 L 354 77 L 352 78 L 339 66 L 321 54 L 292 42 L 259 36 L 82 36 L 71 39 L 60 45 L 51 54 Z"/>
</svg>
PHAROS_LOGO;
}

/** The release this installer is working with, when it already knows it without asking the network. */
function pharos_known_version(): string
{
    global $appDir;
    $m = is_string($appDir) ? json_decode((string) @file_get_contents("$appDir/.pharos-installer"), true) : null;
    if (is_array($m) && !empty($m['version'])) { return (string) $m['version']; }
    return PHAROS_PIN_VERSION;
}

function pharos_page(string $title, string $body, int $step, array $errors = [], array $log = [], array $opts = []): void
{
    $steps = [['Unlock', 'Prove this is your hosting'], ['Check the server', 'Nothing is written yet'], ['Download', 'Signed and verified'], ['Configure', 'Address and database'], ['Scheduler', 'One cron line'], ['Your account', 'In Pharos: name it, sign in'], ['Done', 'In Pharos: open it']];
    $fail = !empty($opts['fail']);
    $nav = '';
    foreach ($steps as $i => $s) {
        $n = $i + 1;
        $cls = $n < $step ? 'done' : ($n === $step ? 'now'.($fail ? ' fail' : '') : '');
        $mark = $n < $step ? '✓' : ($n === $step && $fail ? '!' : (string) $n);
        $nav .= '<li class="'.$cls.'"'.($n === $step ? ' aria-current="step"' : '').'><span class="n" aria-hidden="true">'.$mark.'</span><span><b>'.$s[0].'</b><small>'.$s[1].'</small></span></li>';
    }
    $err = $errors ? '<div class="note bad" role="alert"><span aria-hidden="true">×</span><span>'.implode('<br>', array_map('h', $errors)).'</span></div>' : '';
    $lg = $log ? '<ul class="donelog">'.implode('', array_map(function ($l) { return '<li><span class="st ok" aria-hidden="true">✓</span>'.h($l).'</li>'; }, $log)).'</ul>' : '';
    if (isset($_SESSION['pharos_csrf'])) {
        $body = preg_replace_callback('/(<form\b[^>]*>)/i', function ($m) { return $m[1].'<input type="hidden" name="csrf" value="'.h($_SESSION['pharos_csrf']).'">'; }, $body);
    }
    $kicker = $opts['kicker'] ?? 'Step '.$step.' of 7';
    $lede = isset($opts['lede']) ? '<p class="lede">'.h($opts['lede']).'</p>' : '';
    $version = pharos_known_version();
    header('Cache-Control: no-store');
    header('Referrer-Policy: no-referrer');
    header('X-Frame-Options: DENY');
    header('X-Content-Type-Options: nosniff');
    echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="light dark"><title>'.h($title).' · Pharos installer</title>
<script>document.documentElement.className+=" js"</script>
<style>'.pharos_installer_fonts().pharos_installer_css().'</style></head><body>
<div class="app"><aside class="rail"><div class="logo">'.pharos_installer_logo().'</div><div class="tag">Web installer'.($version !== '' ? ' · '.h($version) : '').'</div>
<ol class="steps" data-steps><span class="fill" data-fill aria-hidden="true"></span>'.$nav.'</ol>
<svg class="ekg" viewBox="0 0 268 40" preserveAspectRatio="none" aria-hidden="true"><path d="M0 20h96l8-14 10 28 9-20 7 6h138"/></svg>
<div class="foot">Every step can be repeated. If something stops halfway, open this page again and it continues where it was.</div></aside>
<main class="main" data-step="'.$step.'"><div class="kicker">'.h($kicker).'</div><h1>'.h($title).'</h1>'.$lede.'<div data-js-error></div>'.$err.$lg.$body.'</main></div>
<script>'.pharos_installer_js().'</script></body></html>';
}

function pharos_installer_css(): string
{
    return <<<'PHAROS_CSS'
:root{--bg:#f6f9fc;--tint:#eef3f9;--card:#fff;--line:#e3eaf2;--ink:#0e1726;--ink-2:#475467;--ink-3:#667085;--brand:#0079d2;--brand-soft:#e8f3fc;--green:#12b76a;--green-ink:#027a48;--green-soft:#e6f7ef;--amber:#f79009;--amber-ink:#b54708;--amber-soft:#fef4e6;--red:#f04438;--red-ink:#b42318;--red-soft:#fee4e2;--term:#0b1523;--term-ink:#c9d7e8;--btn:#0e1726;--btn-ink:#fff;--sans:'Plus Jakarta Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;--mono:'JetBrains Mono',ui-monospace,Menlo,monospace;--ease:cubic-bezier(.16,1,.3,1)}
@media(prefers-color-scheme:dark){:root{--bg:#070d17;--tint:#0b1523;--card:#0e1a2b;--line:#1c2b3f;--ink:#eaf0f7;--ink-2:#a7b6c7;--ink-3:#7d8fa3;--brand-soft:#0f2438;--green:#32d583;--green-ink:#6ce9a6;--green-soft:#0d2a1e;--amber-ink:#fec84b;--amber-soft:#2c2211;--red:#f97066;--red-ink:#fda29b;--red-soft:#2d1614;--term:#050a12;--btn:#0079d2;--btn-ink:#fff}}
*{box-sizing:border-box}html,body{min-height:100%}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased}
.app{min-height:100vh;display:grid;grid-template-columns:320px minmax(0,1fr)}
.rail{position:sticky;top:0;height:100vh;background:var(--card);border-right:1px solid var(--line);padding:32px 26px;display:flex;flex-direction:column;overflow-y:auto}
.logo svg{display:block;height:30px;width:auto}
.tag{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3);margin:10px 0 34px}
.steps{list-style:none;margin:0;padding:0;position:relative}
.steps::before{content:"";position:absolute;left:15px;top:16px;bottom:16px;width:2px;background:var(--line);border-radius:2px}
.steps .fill{position:absolute;left:15px;top:16px;width:2px;background:var(--brand);border-radius:2px;height:0;transition:height .6s var(--ease)}
.steps li{position:relative;display:grid;grid-template-columns:32px minmax(0,1fr);gap:12px;align-items:center;padding:9px 0}
.steps .n{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;font-size:12.5px;font-weight:700;background:var(--card);border:2px solid var(--line);color:var(--ink-3);position:relative;z-index:1}
.steps b{display:block;font-size:14px;color:var(--ink-3);font-weight:600}
.steps small{font-size:12px;color:var(--ink-3)}
.steps li.now .n{border-color:var(--brand);color:var(--brand);box-shadow:0 0 0 5px var(--brand-soft)}
.steps li.now b{color:var(--ink)}
.steps li.done .n{background:var(--green);border-color:var(--green);color:#fff}
.steps li.done b{color:var(--ink-2)}
.steps li.fail .n{background:var(--red);border-color:var(--red);color:#fff;box-shadow:0 0 0 5px var(--red-soft)}
.rail .ekg{margin-top:auto;width:100%;height:40px;overflow:visible;flex:none}
.rail .ekg path{fill:none;stroke:var(--brand);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:300;stroke-dashoffset:300;animation:trace 3s var(--ease) infinite}
@keyframes trace{0%{stroke-dashoffset:300}55%{stroke-dashoffset:0}100%{stroke-dashoffset:-300}}
.rail .foot{font-size:12px;color:var(--ink-3);margin-top:10px;line-height:1.5}
.main{padding:48px clamp(20px,5vw,72px) 96px;max-width:980px;width:100%;min-width:0;overflow-wrap:anywhere;animation:in .45s var(--ease)}
@keyframes in{from{opacity:0;transform:translateY(8px)}}
.kicker{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--brand);margin-bottom:10px}
h1{margin:0;font-size:clamp(28px,3vw,40px);letter-spacing:-.04em;line-height:1.08;color:var(--ink)}
.lede{margin:12px 0 28px;color:var(--ink-2);font-size:15.5px;line-height:1.6;max-width:62ch}
.card{background:var(--card);border:1px solid var(--line);border-radius:18px;box-shadow:0 1px 2px #1018280a,0 12px 32px -18px #10182826}
.card+.card,.card+form,.fix{margin-top:16px}
.card .hd{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.card .hd h3{margin:0;font-size:15px}
.card .hd .hint{margin-left:auto;font-size:12.5px;color:var(--ink-3)}
.card .bd{padding:20px}
.mt8{margin-top:8px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mt18{margin-top:18px}
.btn{font:inherit;font-size:15px;font-weight:700;border:0;border-radius:12px;padding:13px 20px;cursor:pointer;background:var(--btn);color:var(--btn-ink);display:inline-flex;align-items:center;gap:10px;text-decoration:none;transition:transform .15s,opacity .2s,box-shadow .2s}
.btn:hover{transform:translateY(-1px);box-shadow:0 8px 18px -10px #10182866}
.btn:active{transform:translateY(0)}
.btn[disabled]{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}
.btn.ghost{background:var(--card);color:var(--ink);border:1px solid var(--line)}
.actions{display:flex;gap:10px 14px;flex-wrap:wrap;align-items:center;margin-top:22px}
.actions .sub{font-size:12.5px;color:var(--ink-3);flex:1 1 240px}
:focus-visible{outline:2px solid var(--brand);outline-offset:3px;border-radius:8px}
.f{display:flex;flex-direction:column;gap:7px;font-size:12.5px;font-weight:600;color:var(--ink-2)}
.span2,.testrow{grid-column:1/-1}
.inp{width:100%;font:inherit;font-size:15px;font-weight:400;color:var(--ink);background:var(--tint);border:1px solid transparent;border-radius:12px;padding:12px 14px}
.inp:focus{outline:none;border-color:var(--brand);background:var(--card);box-shadow:0 0 0 4px var(--brand-soft)}
.grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.help{font-size:12.5px;color:var(--ink-3);margin:6px 0 18px;line-height:1.55}
.card .bd>.help:last-child{margin-bottom:0}
.path{display:flex;align-items:center;gap:8px;background:var(--tint);border-radius:12px;padding:6px 6px 6px 14px;font-family:var(--mono);font-size:12.5px;color:var(--ink-2)}
.path code{flex:1;min-width:0;overflow-x:auto;white-space:nowrap;padding:6px 0}
.copy{display:none;font:inherit;font-size:12px;font-weight:600;padding:7px 12px;border-radius:9px;border:1px solid var(--line);background:var(--card);color:var(--ink-2);cursor:pointer;flex:none;font-family:var(--sans)}
.js .copy{display:inline-block}
.copy.ok{color:var(--green-ink);border-color:var(--green)}
.cron .copy{background:transparent;color:#9fb3c8;border-color:#223449}
code{font-family:var(--mono);font-size:.9em}
.note{display:flex;gap:10px;padding:12px 14px;border-radius:12px;background:var(--tint);font-size:13.5px;color:var(--ink-2);line-height:1.55;margin:0 0 16px}
.note.warn{background:var(--amber-soft);color:var(--amber-ink)}
.note.bad{background:var(--red-soft);color:var(--red-ink)}
.note.good{background:var(--green-soft);color:var(--green-ink)}
.note>span:first-child{font-weight:800}
.note.mt12{margin:12px 0 0}
.donelog{list-style:none;margin:0 0 18px;padding:0;display:flex;flex-direction:column;gap:6px;font-size:13.5px;color:var(--ink-2)}
.donelog li{display:flex;gap:10px;align-items:center}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.sum{display:flex;align-items:center;gap:18px;padding:20px}
.ring{width:74px;height:74px;flex:none;position:relative}
.ring svg{transform:rotate(-90deg);display:block}
.ring circle{fill:none;stroke-width:7}
.ring .bg{stroke:var(--tint)}.ring .fg{stroke:var(--brand);stroke-linecap:round;transition:stroke-dashoffset .3s var(--ease),stroke .3s}
.ring[data-tone=ok] .fg{stroke:var(--green)}.ring[data-tone=warn] .fg{stroke:var(--amber)}.ring[data-tone=bad] .fg{stroke:var(--red)}
.ring.busy .fg{stroke:var(--brand)}
.ring b{position:absolute;inset:0;display:grid;place-items:center;font-size:17px;letter-spacing:-.02em}
.sum h3{margin:0;font-size:18px;letter-spacing:-.02em}
.sum p{margin:4px 0 0;color:var(--ink-3);font-size:13.5px}
.groups{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding:0 20px 20px}
.grp{border:1px solid var(--line);border-radius:14px;padding:14px;min-width:0}
.grp h4{margin:0 0 10px;font-family:var(--mono);font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--ink-3);font-weight:500}
.row{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;padding:6px 0;font-size:13.5px}
.row .k{flex:1 1 auto;font-weight:600;color:var(--ink)}
.row .d{color:var(--ink-3);font-size:12px;font-family:var(--mono);margin-left:28px;flex:1 1 100%;overflow-wrap:anywhere}
.st{width:18px;height:18px;border-radius:50%;display:inline-grid;place-items:center;font-size:10px;font-weight:800;border:1.5px solid var(--line);flex:none;line-height:1}
.st.run,.pend .st{background:none;border-color:var(--brand);border-top-color:transparent;color:transparent;animation:rot .7s linear infinite}
.st.ok{background:var(--green);border-color:var(--green);color:#fff}
.st.warn{background:var(--amber);border-color:var(--amber);color:#fff}
.st.bad{background:var(--red);border-color:var(--red);color:#fff}
@keyframes rot{to{transform:rotate(360deg)}}
.chips{display:flex;flex-wrap:wrap;gap:6px}
.chip{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11.5px;padding:4px 9px;border-radius:999px;background:var(--tint);color:var(--ink-3);transition:background .3s,color .3s}
.chip.ok{background:var(--green-soft);color:var(--green-ink)}
.chip.bad{background:var(--red-soft);color:var(--red-ink);font-weight:600}
.chip.warn{background:var(--amber-soft);color:var(--amber-ink)}
.chip.pend{background:var(--tint);color:var(--ink-3);font-weight:400}
.hold{display:none}
.tabs{display:none;gap:6px;margin:0 0 12px;flex-wrap:wrap}
.js .tabs{display:flex}
.tabs button{font:inherit;font-size:12.5px;font-weight:600;padding:6px 12px;border-radius:999px;border:1px solid var(--line);background:var(--card);color:var(--ink-2);cursor:pointer}
.tabs button[aria-selected="true"]{background:var(--brand-soft);color:var(--brand);border-color:transparent}
.tabpane{font-size:13.5px;color:var(--ink-2);line-height:1.6}
.tabpane+.tabpane{margin-top:14px}
.tabpane h5{margin:0 0 4px;font-size:13px;color:var(--ink)}
.js .tabpane{display:none}.js .tabpane.on{display:block;margin-top:0}.js .tabpane h5{display:none}
.tabpane ol{margin:0;padding-left:18px}.tabpane li+li{margin-top:4px}
.term{background:var(--term);color:var(--term-ink);border-radius:14px;padding:16px 18px;font-family:var(--mono);font-size:12.5px;line-height:1.8;min-height:150px}
.term .ok{color:#6ce9a6}.term .bad{color:#fda29b}.term .dim{color:#6f839b}
.bar{height:10px;border-radius:999px;background:var(--tint);overflow:hidden;margin:6px 0 4px}
.bar i{display:block;height:100%;width:0;border-radius:inherit;background:linear-gradient(90deg,var(--brand),#4fb3ff);transition:width .25s linear}
.bar.bad i{background:var(--red)}
.barl{display:flex;justify-content:space-between;gap:12px;font-size:12.5px;color:var(--ink-3);font-family:var(--mono)}
.proof{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}
.proof>div{border:1px solid var(--line);border-radius:14px;padding:12px 14px;font-size:13px;opacity:.45;transition:opacity .3s,border-color .3s}
.proof>div.on{opacity:1}
.proof>div:has(.st.bad){border-color:var(--red);background:var(--red-soft)}
.proof b{display:flex;align-items:center;gap:8px;font-size:13.5px;margin-bottom:3px}
.proof>div>span{color:var(--ink-3);font-size:12px}
.choice{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.choice label{display:flex;gap:12px;padding:16px;border:1px solid var(--line);border-radius:14px;cursor:pointer;transition:border-color .2s,background .2s}
.choice label:hover{border-color:color-mix(in srgb,var(--brand) 45%,var(--line))}
.choice label:has(input:checked){border-color:var(--brand);background:var(--brand-soft)}
.choice input{accent-color:var(--brand);margin-top:3px;flex:none}
.choice b{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:14.5px}
.choice small{display:block;color:var(--ink-3);font-size:12.5px;line-height:1.5;margin-top:3px}
.pill{font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:var(--green-soft);color:var(--green-ink)}
.pill.warn{background:var(--amber-soft);color:var(--amber-ink)}
.mysql{display:grid;margin-top:14px}
form:has(input[value=sqlite]:checked) .mysql{display:none}
.testrow{display:flex;gap:12px;align-items:center;flex-wrap:wrap;font-size:13px}
.jsonly{display:none}.js .jsonly{display:inline-flex}.js .nojs{display:none}
.good{color:var(--green-ink);font-weight:600}.badtxt{color:var(--red-ink);font-weight:600}
.migr{list-style:none;margin:16px 0 0;padding:0;display:none;flex-direction:column;gap:6px;font-family:var(--mono);font-size:12.5px;color:var(--ink-2)}
.migr.on{display:flex}
.migr li{display:flex;align-items:center;gap:10px;opacity:.35;transition:opacity .2s}
.migr li.on{opacity:1}
.cron{display:flex;align-items:center;gap:10px;background:var(--term);color:#d7e6f7;border-radius:14px;padding:14px 14px 14px 18px;font-family:var(--mono);font-size:13px}
.cron code{flex:1;min-width:0;overflow-x:auto;white-space:nowrap;font-size:inherit;padding:4px 0}
.waiting{display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:14px;border:1px dashed var(--line);margin-top:16px;font-size:13.5px;color:var(--ink-2);flex-wrap:wrap}
.waiting>span{flex:1 1 200px}
.waiting a{color:var(--brand);font-weight:600}
.waiting i{width:10px;height:10px;border-radius:50%;background:var(--amber);box-shadow:0 0 0 0 rgba(247,144,9,.5);animation:ping 1.6s infinite;flex:none}
.waiting.ok{border-style:solid;border-color:var(--green);background:var(--green-soft);color:var(--green-ink)}
.waiting.ok i{background:var(--green);animation:none}
@keyframes ping{70%{box-shadow:0 0 0 9px rgba(247,144,9,0)}100%{box-shadow:0 0 0 0 rgba(247,144,9,0)}}
details.more{border:1px solid var(--line);border-radius:14px;margin-top:16px;background:var(--card)}
details.more summary{cursor:pointer;padding:13px 16px;font-size:13.5px;font-weight:600;color:var(--ink-2);list-style:none}
details.more summary::-webkit-details-marker{display:none}
details.more summary::before{content:"› ";color:var(--ink-3)}
details.more[open] summary::before{content:"⌄ "}
details.more>div{padding:0 16px 16px}
@media(max-width:900px){
  .app{grid-template-columns:minmax(0,1fr)}
  .rail{position:static;height:auto;padding:18px 16px;border-right:0;border-bottom:1px solid var(--line);overflow:visible}
  .tag{margin:6px 0 14px}.rail .ekg,.rail .foot{display:none}
  .steps{display:flex;gap:4px;overflow-x:auto;padding-bottom:4px}.steps::before,.steps .fill{display:none}
  .steps li{grid-template-columns:auto;justify-items:center;text-align:center;min-width:64px;flex:1 0 64px;padding:0;gap:6px}.steps small{display:none}.steps b{font-size:11px;line-height:1.25}
  .steps li.now .n{box-shadow:0 0 0 3px var(--brand-soft)}.steps li.fail .n{box-shadow:0 0 0 3px var(--red-soft)}
  .main{padding:26px 16px 72px}
  .groups,.grid2,.choice,.proof{grid-template-columns:minmax(0,1fr)}
  .cron{flex-wrap:wrap}.cron code{flex-basis:100%;white-space:pre-wrap;overflow-wrap:anywhere}
}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
PHAROS_CSS;
}

function pharos_installer_js(): string
{
    return <<<'PHAROS_JS'
(function () {
  'use strict';
  var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
  var $ = function (s, r) { return (r || document).querySelector(s); };
  var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
  var wait = function (ms) { return new Promise(function (res) { setTimeout(res, reduce ? 0 : ms); }); };
  var base = location.pathname;
  function call(url, opts) {
    opts = opts || {};
    opts.headers = { 'X-Pharos-Installer': '1' };
    opts.credentials = 'same-origin';
    return fetch(url, opts).then(function (r) {
      return r.json().catch(function () { return { ok: false, errors: ['The server stopped answering (HTTP ' + r.status + '). Reload this page: the installer continues where it stopped.'] }; });
    }, function () { return { ok: false, errors: ['The connection dropped. Reload this page: the installer continues where it stopped.'] }; });
  }
  function esc(t) { var d = document.createElement('div'); d.textContent = t == null ? '' : String(t); return d.innerHTML; }
  function showError(msg) { var box = $('[data-js-error]'); if (box) box.innerHTML = msg ? '<div class="note bad" role="alert"><span aria-hidden="true">×</span><span>' + esc(msg) + '</span></div>' : ''; }
  function mark(el, st) { if (!el) return; el.className = 'st ' + st; el.textContent = st === 'ok' ? '✓' : st === 'bad' ? '×' : st === 'warn' ? '!' : ''; }

  // rail: the progress line runs from step 1 to the current one
  var steps = $$('[data-steps] li'), fill = $('[data-fill]'), now = $('[data-steps] li.now');
  if (fill && now) fill.style.height = (now.offsetTop - steps[0].offsetTop) + 'px';
  if (now && now.scrollIntoView && window.innerWidth <= 900) now.scrollIntoView({ block: 'nearest', inline: 'center' });
  function railFail(on) { if (!now) return; now.classList.toggle('fail', on); now.querySelector('.n').textContent = on ? '!' : String(steps.indexOf(now) + 1); }

  document.addEventListener('click', function (e) {
    var c = e.target.closest('[data-copy]');
    if (c && navigator.clipboard) {
      navigator.clipboard.writeText(c.getAttribute('data-copy')).then(function () {
        var t = c.textContent; c.classList.add('ok'); c.textContent = 'Copied';
        setTimeout(function () { c.textContent = t; c.classList.remove('ok'); }, 1400);
      });
    }
    var tab = e.target.closest('[data-tabs] [role=tab]');
    if (tab) {
      var box = tab.closest('.bd');
      $$('[role=tab]', box).forEach(function (b) { b.setAttribute('aria-selected', b === tab ? 'true' : 'false'); });
      $$('[data-pane]', box).forEach(function (p) { p.classList.toggle('on', p.getAttribute('data-pane') === tab.getAttribute('data-p')); });
    }
  });

  // step 2: reveal the real results one by one, then say what they mean
  var live = $('[data-live]');
  function reveal() {
    var items = $$('[data-state]', live), total = items.length, ring = $('.ring', live), n = $('[data-ring-n]', live), fg = $('.ring .fg', live);
    var h = $('[data-sum-h]', live), p = $('[data-sum-p]', live), fix = $('[data-fix]', live);
    items.forEach(function (el) { el.classList.add('pend'); });
    ring.classList.add('busy'); if (fix) fix.classList.add('hold');
    h.textContent = 'Checking your server'; p.textContent = 'This takes a few seconds.';
    var i = 0;
    function tick() { n.textContent = i; fg.style.strokeDashoffset = 195 - 195 * i / total; }
    tick();
    return items.reduce(function (chain, el) {
      return chain.then(function () { return wait(el.classList.contains('chip') ? 45 : 110); }).then(function () { el.classList.remove('pend'); i++; tick(); });
    }, Promise.resolve()).then(function () {
      ring.classList.remove('busy'); h.textContent = h.getAttribute('data-final'); p.textContent = p.getAttribute('data-final');
      if (fix) fix.classList.remove('hold');
    });
  }
  if (live) {
    reveal();
    var re = $('[data-recheck]'), go = $('[data-go]');
    re.addEventListener('click', function (e) {
      e.preventDefault(); re.setAttribute('aria-disabled', 'true'); go.disabled = true;
      $$('[data-state]', live).forEach(function (el) { el.classList.add('pend'); });
      call(base + '?step=2&do=checks').then(function (res) {
        re.removeAttribute('aria-disabled');
        if (!res.ok) { showError(res.errors && res.errors[0]); return; }
        showError(''); live.innerHTML = res.html; railFail(res.summary.bad > 0);
        return reveal().then(function () { go.disabled = res.summary.bad > 0; });
      });
    });
    $('[data-to-download]').addEventListener('submit', function (e) { e.preventDefault(); location.href = base + '?step=3&start=1' + (location.search.match(/&version=[0-9.]+/) || [''])[0]; });
  }

  // step 3: the download request does the work; this polls what it reports
  var dl = $('[data-download]');
  function proofStates(d) {
    var s = { signature: '', checksum: '', unpack: '' }, order = { manifest: 0, download: 1, checksum: 2, unpack: 3, publish: 4, done: 5 };
    if (d.stage === 'failed') {
      var f = d.failed || 'unpack';
      if (f === 'none') return s;
      s.signature = f === 'signature' ? 'bad' : 'ok'; s.checksum = f === 'checksum' ? 'bad' : f === 'unpack' ? 'ok' : ''; s.unpack = f === 'unpack' ? 'bad' : '';
      return s;
    }
    var at = order[d.stage] || 0;
    s.signature = at > 0 ? 'ok' : 'run'; s.checksum = at > 2 ? 'ok' : at === 2 ? 'run' : ''; s.unpack = at > 4 ? 'ok' : at >= 3 ? 'run' : '';
    return s;
  }
  function mb(b) { return (b / 1048576).toFixed(1); }
  function renderDownload(d) {
    var st = proofStates(d);
    Object.keys(st).forEach(function (k) { var c = $('[data-proof=' + k + ']', dl); c.classList.toggle('on', st[k] !== ''); mark($('.st', c), st[k]); });
    if (d.version) $('[data-dl-name]', dl).textContent = 'pharos-' + d.version + '.zip';
    var pct = d.total > 0 ? Math.min(100, d.bytes / d.total * 100) : (d.stage === 'done' ? 100 : 0);
    if (d.stage !== 'manifest' && d.stage !== 'download' && d.stage !== 'failed') pct = 100;
    $('[data-dl-bar]', dl).style.width = pct + '%';
    $('.bar', dl).setAttribute('aria-valuenow', Math.round(pct));
    $('.bar', dl).classList.toggle('bad', d.stage === 'failed' && d.failed === 'download');
    $('[data-dl-mb]', dl).textContent = d.total > 0 ? mb(d.bytes) + ' of ' + mb(d.total) + ' MB' : (d.stage === 'manifest' ? 'checking the signature' : d.bytes > 0 ? mb(d.bytes) + ' MB' : 'starting');
    if (d.total_files > 0) $('[data-dl-files]', dl).textContent = Number(d.files).toLocaleString('en') + (d.stage === 'unpack' ? ' of ' + Number(d.total_files).toLocaleString('en') : '') + ' files in place';
    var lines = '<div class="dim">$ fetch the signed manifest</div>';
    (d.log || []).forEach(function (l) { var bad = l.indexOf('Stopped: ') === 0; lines += '<div class="' + (bad ? 'bad' : 'ok') + '">' + (bad ? '× ' : '✓ ') + esc(bad ? l.slice(9) : l) + '</div>'; });
    if (d.stage === 'failed') lines += '<div class="dim">  nothing was unpacked, the download was deleted</div>';
    else if (d.stage !== 'done') lines += '<div class="dim">  ' + ({ manifest: 'verifying the signature', download: 'downloading', checksum: 'comparing the checksum', unpack: 'unpacking', publish: 'publishing the public folder' }[d.stage] || 'working') + '…</div>';
    $('[data-term]', dl).innerHTML = lines;
  }
  function runDownload() {
    var btn = $('[data-dl-go]', dl), sub = $('[data-dl-sub]', dl), done = false;
    btn.disabled = true; btn.textContent = 'Downloading…'; sub.textContent = 'Keep this page open.'; showError(''); railFail(false);
    renderDownload({ stage: 'manifest', bytes: 0, total: 0, log: [] });
    (function poll() {
      if (done) return;
      call(base + '?do=progress').then(function (p) { if (!done && p.op === 'download' && p.stage !== 'failed' && p.stage !== 'done') renderDownload(p); setTimeout(poll, 300); });
    })();
    call(dl.getAttribute('action'), { method: 'POST', body: new FormData(dl) }).then(function (res) {
      done = true;
      var d = res.download || { stage: 'failed', failed: 'none', log: ['Stopped: ' + ((res.errors && res.errors[0]) || 'unknown error')], bytes: 0, total: 0 };
      renderDownload(d);
      if (res.ok) {
        $('[data-dl-actions]', dl).innerHTML = '<a class="btn" href="' + base + '?step=4" data-next>Continue</a><span class="sub">Verified and unpacked.</span>';
        now.classList.remove('fail'); $('[data-next]', dl).focus();
      } else {
        railFail(true); btn.disabled = false; btn.textContent = 'Try again';
        sub.textContent = ((res.errors && res.errors[0]) || 'The download stopped.') + ' Try again; if it repeats, contact support.';
      }
    });
  }
  if (dl) {
    dl.addEventListener('submit', function (e) { e.preventDefault(); runDownload(); });
    if (dl.getAttribute('data-auto') === '1') { history.replaceState(null, '', base + '?step=3'); runDownload(); }
  }

  // step 4: test the connection, then show the real migration count while it runs
  var cf = $('[data-configure]');
  if (cf) {
    var tb = $('[data-testdb]', cf), res = $('[data-dbres]', cf);
    tb.addEventListener('click', function () {
      var fd = new FormData(cf); fd.set('action', 'testdb');
      res.textContent = 'Connecting…'; tb.disabled = true;
      call(cf.getAttribute('action'), { method: 'POST', body: fd }).then(function (r) {
        tb.disabled = false; var t = r.testdb || { ok: false, message: (r.errors && r.errors[0]) || 'No answer' };
        res.innerHTML = '<span class="' + (t.ok ? 'good' : 'badtxt') + '">' + (t.ok ? '✓ ' : '× ') + esc(t.message) + '</span>';
      });
    });
    cf.addEventListener('submit', function (e) {
      e.preventDefault();
      var go = $('[data-cf-go]', cf), m = $('[data-migr]', cf), done = false;
      var items = ['.env written, app key generated', 'Database tables', 'Storage linked'];
      m.innerHTML = items.map(function (t) { return '<li><span class="st"></span><span>' + esc(t) + '</span></li>'; }).join(''); m.classList.add('on');
      var li = $$('li', m);
      go.disabled = true; go.textContent = 'Setting up…'; showError(''); railFail(false);
      function paint(p) {
        var at = { env: 0, migrate: 1, link: 2, done: 3 }[p.stage]; if (at === undefined) return;
        li.forEach(function (el, i) { el.classList.toggle('on', i <= at); mark($('.st', el), i < at ? 'ok' : i === at ? 'run' : ''); });
        if (p.total_migrations) $('span:last-child', li[1]).textContent = 'Database tables: ' + (p.migrations || 0) + ' of ' + p.total_migrations + ' migrations';
      }
      paint({ stage: 'env' });
      (function poll() {
        if (done) return;
        call(base + '?do=progress').then(function (p) { if (!done && p.op === 'configure') paint(p); setTimeout(poll, 250); });
      })();
      call(cf.getAttribute('action'), { method: 'POST', body: new FormData(cf) }).then(function (r) {
        done = true;
        if (r.ok) {
          paint({ stage: 'done' });
          (r.log || []).slice(0, 3).forEach(function (t, i) { if (li[i]) $('span:last-child', li[i]).textContent = t; });
          return wait(700).then(function () { location.href = base + (r.next || '?step=5'); });
        }
        var running = $('.st.run', m); if (running) mark(running, 'bad');
        showError((r.errors && r.errors[0]) || 'Setting up the database stopped.'); railFail(true);
        go.disabled = false; go.textContent = 'Save settings and set up the database';
        window.scrollTo({ top: 0, behavior: reduce ? 'auto' : 'smooth' });
      });
    });
  }

  // step 5: notice the first scheduler run by itself
  var w = $('[data-waiting]');
  if (w && !w.classList.contains('ok')) {
    var started = Date.now();
    (function poll() {
      call(base + '?do=cron').then(function (s) {
        if (s && s.ran) {
          w.classList.add('ok');
          var t = new Date(s.at);
          $('[data-wait-text]', w).textContent = 'The scheduler ran at ' + (isNaN(t) ? s.label : t.toLocaleTimeString()) + '. Checks will start within a minute.';
          return;
        }
        setTimeout(poll, Date.now() - started > 300000 ? 15000 : 4000);
      });
    })();
  }
})();
PHAROS_JS;
}
