Refusing to install over plain HTTP (your key would travel unencrypted). '
. 'Use https, or set $ALLOW_HTTP = true if this is an internal test box.
');
exit;
}
/* ── preflight checks ────────────────────────────────────────────────── */
$errors = [];
if (!is_file($AGENT_FILE)) {
$errors[] = 'wordfort-agent.php is not in this directory. Upload it next to install.php.';
}
if (!is_writable(__DIR__)) {
$errors[] = 'This directory is not writable by PHP (' . __DIR__ . '). '
. 'The installer needs to write .user.ini here.';
}
// .user.ini only works under PHP-FPM / CGI, not mod_php.
$sapi = php_sapi_name();
if (stripos($sapi, 'fpm') === false && stripos($sapi, 'cgi') === false) {
$errors[] = 'This host runs PHP as "' . htmlspecialchars($sapi) . '", not FPM/CGI. '
. '.user.ini auto_prepend_file only works on PHP-FPM/CGI. '
. 'Use the .htaccess or FPM-pool method instead (see docs).';
}
if (!function_exists('curl_init')) {
$errors[] = 'PHP cURL extension is not available — needed for verification and reporting.';
}
if ($errors) {
render('Cannot install yet', ''
. implode('', array_map(fn($e) => '- ' . htmlspecialchars($e) . '
', $errors))
. '
Fix the above and reload this page.
');
exit;
}
/* ── collect the WordFort key from the form ──────────────────────────── */
$submittedKey = trim($_POST['wf_key'] ?? $_GET['wf_key'] ?? '');
/* ── ACTION: only proceed on explicit confirm with a key ─────────────── */
$doInstall = (($_POST['go'] ?? $_GET['go'] ?? '') === '1');
$keyLooksValid = (bool)preg_match('/^(wf|akg)_[A-Za-z0-9_\-]{8,}$/', $submittedKey);
if (!$doInstall || !$keyLooksValid) {
$abs = $AGENT_FILE;
$keyErr = ($doInstall && !$keyLooksValid)
? 'That doesn\'t look like an ACKgeo key (it should start with '
. 'akg_). Copy it from your ACKgeo dashboard and try again.
'
: '';
render('WordFort — install', '
Paste your ACKgeo key (from the WordFort page in your dashboard), then install.
This is the only thing you need — the key both configures the agent and authorizes
this install.
' . $keyErr . '
This creates .user.ini with
auto_prepend_file=' . htmlspecialchars($abs) . ', verifies the agent loads,
and deletes this installer on success.
SAPI: ' . htmlspecialchars($sapi) . ' ·
Dir writable: yes · HTTPS: ' . ($isHttps ? 'yes' : 'no (allowed)') . '
');
exit;
}
$WORDFORT_KEY = $submittedKey; // validated above
/* ── STEP 1 — inject key into the agent ──────────────────────────────── */
$changed = [];
$agentSrc = file_get_contents($AGENT_FILE);
if (strpos($agentSrc, 'wf_live_REPLACE_ME') !== false) {
$agentSrc = str_replace('wf_live_REPLACE_ME', addslashes($WORDFORT_KEY), $agentSrc);
file_put_contents($AGENT_FILE, $agentSrc);
$changed[] = 'Wrote key into wordfort-agent.php';
} else {
$changed[] = 'Agent already had a key (left as-is)';
}
/* ── STEP 2 — health token ───────────────────────────────────────────── */
$healthToken = bin2hex(random_bytes(16));
file_put_contents($TOKENFILE, $healthToken);
@chmod($TOKENFILE, 0640);
$changed[] = 'Created health token';
/* ── STEP 3 — .user.ini ──────────────────────────────────────────────── */
$abs = str_replace('\\', '/', $AGENT_FILE);
$iniLine = 'auto_prepend_file=' . $abs;
$existing = is_file($USERINI) ? file_get_contents($USERINI) : '';
if (strpos($existing, 'auto_prepend_file') === false) {
$out = rtrim($existing) . (strlen($existing) ? "\n" : '')
. "; Added by WordFort installer\n" . $iniLine . "\n";
file_put_contents($USERINI, $out);
$changed[] = 'Wrote auto_prepend_file into .user.ini';
} else {
$changed[] = '.user.ini already had auto_prepend_file (left as-is)';
}
/* ── STEP 4 — wait for .user.ini cache, then verify by loopback ──────── */
// PHP caches .user.ini for user_ini.cache_ttl seconds (default 300). We can't
// force a flush, but a freshly-created .user.ini in a dir with no prior cache
// entry is usually picked up on the next request. We poll for up to ~8s; if it
// hasn't taken effect we tell the user it may take up to 5 min (the TTL).
$ttl = (int) ini_get('user_ini.cache_ttl');
$selfUrl = build_self_url($healthToken);
$verified = false;
$lastBody = '';
for ($i = 0; $i < 8; $i++) {
[$code, $body, $hdr] = http_get($selfUrl);
$lastBody = $body;
if ($code === 200 && stripos($hdr, 'x-wordfort: active') !== false) {
$verified = true;
break;
}
sleep(1);
}
/* ── STEP 5 — result ─────────────────────────────────────────────────── */
if ($verified) {
// Success — clean up: remove health token + self-delete.
@unlink($TOKENFILE);
$selfGone = @unlink($SELF);
render('WordFort is protecting this site ✓', '
The agent is loaded and running on live requests.
' . implode('', array_map(fn($c)=>'- '.htmlspecialchars($c).'
', $changed)) . '
' . ($selfGone
? 'This installer has deleted itself. Nothing else to do.'
: 'Could not auto-delete install.php — please delete it manually now.')
. '
Blocked requests will start appearing in your ACKgeo dashboard.
To uninstall later: remove the auto_prepend_file line from
.user.ini and delete wordfort-agent.php.
');
exit;
}
// Not verified yet — do NOT self-delete. Likely the .user.ini cache TTL.
render('Installed — waiting for PHP to pick it up', '
Files are in place, but the agent has not answered a live request yet.
' . implode('', array_map(fn($c)=>'- '.htmlspecialchars($c).'
', $changed)) . '
PHP caches .user.ini for ' . ($ttl ?: 300) . 's.
Wait that long, then reload:
Re-check now →
The installer was kept (not deleted) so you can retry.
If it never verifies, confirm this host uses PHP-FPM and that
' . htmlspecialchars($abs) . ' is readable.
');
exit;
/* ───────────────────────── helpers ─────────────────────────────────── */
function self_base() {
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| ($_SERVER['SERVER_PORT'] ?? '') == 443
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
$scheme = $https ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$path = strtok($_SERVER['REQUEST_URI'] ?? '/install.php', '?');
return $scheme . '://' . $host . $path;
}
function build_self_url($healthToken) {
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| ($_SERVER['SERVER_PORT'] ?? '') == 443
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
$scheme = $https ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
// Hit a PHP endpoint (this installer script itself) with the health param.
// The agent prepends onto every PHP request, so it answers here and exits
// BEFORE install.php's own logic runs. We must NOT hit '/' — on a static-HTML
// site the root serves index.html without invoking PHP, so the prepend never
// fires and verification would fail even when the agent is correctly installed.
$self = basename($_SERVER['SCRIPT_NAME'] ?? '/install.php');
return $scheme . '://' . $host . '/' . $self . '?__wordfort_health=' . urlencode($healthToken);
}
function http_get($url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_TIMEOUT => 4,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_SSL_VERIFYPEER => false, // loopback to self; cert may be internal
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_FOLLOWLOCATION => false,
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$hsz = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$hdr = substr((string)$resp, 0, $hsz);
$body = substr((string)$resp, $hsz);
return [$code, $body, $hdr];
}
function render($title, $bodyHtml) {
echo ''
. ''
. ''
. '' . htmlspecialchars($title) . ' — WordFort' . htmlspecialchars($title) . '
' . $bodyHtml
. 'WordFort · part of ACKgeo
'
. '';
}