Files
MageSail/Magesail/scripts/nginx-reload-probe.php
T
2026-03-21 01:36:59 -05:00

188 lines
7.6 KiB
PHP

#!/usr/bin/env php
<?php
/**
* Standalone probe: which nginx test/reload strategy works for THIS user (e.g. PHP-FPM / www-data)?
*
* Run on the app server as the same user that runs Magento (important):
* sudo -u www-data php /path/to/Magesail/scripts/nginx-reload-probe.php
*
* Or from SSH (shows your user — not the same as web PHP unless you match):
* php nginx-reload-probe.php
*
* Options:
* --json Machine-readable output
* --map=/path Global map file for minimal nginx -t (default: /etc/nginx/conf.d/global-map.conf)
* --bin=/path nginx binary (default: /usr/sbin/nginx)
* --try-reload Actually attempt reload for strategies that look safe (default: off for probe-only)
*/
declare(strict_types=1);
$opts = getopt('', ['json', 'map:', 'bin:', 'try-reload', 'help']);
if (isset($opts['help'])) {
fwrite(STDERR, "Usage: php nginx-reload-probe.php [--json] [--map=PATH] [--bin=PATH] [--try-reload]\n");
exit(0);
}
$asJson = isset($opts['json']);
$mapPath = $opts['map'] ?? '/etc/nginx/conf.d/global-map.conf';
$nginxBin = $opts['bin'] ?? '/usr/sbin/nginx';
$tryReload = isset($opts['try-reload']);
function runArgv(array $argv): array
{
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$cmd = implode(' ', array_map('escapeshellarg', $argv));
$process = @proc_open($cmd, $descriptorspec, $pipes, null, null);
if (!is_resource($process)) {
return [127, '', 'proc_open failed for: ' . $cmd];
}
fclose($pipes[0]);
$out = stream_get_contents($pipes[1]);
$err = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($process);
$combined = trim(($out !== '' ? rtrim($out) : '') . ($err !== '' ? "\n" . rtrim($err) : ''));
return [$code, $combined, $cmd];
}
function minimalConfPath(string $mapInclude, string $pidFile): string
{
$map = str_replace('\\', '/', $mapInclude);
$mapQ = preg_match('/^[a-zA-Z0-9._\/-]+$/', $map) ? $map : '"' . addcslashes($map, '"\\') . '"';
$pidQ = preg_match('/^[a-zA-Z0-9._\/-]+$/', $pidFile) ? $pidFile : '"' . addcslashes($pidFile, '"\\') . '"';
$body = "error_log stderr;\npid {$pidQ};\nevents {\n worker_connections 1;\n}\nhttp {\n include {$mapQ};\n}\n";
$tmp = sys_get_temp_dir() . '/magesail-probe-' . getmypid() . '.conf';
file_put_contents($tmp, $body, LOCK_EX);
return $tmp;
}
$user = function_exists('posix_getpwuid') && function_exists('posix_geteuid')
? (posix_getpwuid(posix_geteuid())['name'] ?? 'unknown')
: 'unknown';
$uid = function_exists('posix_geteuid') ? posix_geteuid() : -1;
$results = [
'meta' => [
'user' => $user,
'uid' => $uid,
'sapi' => PHP_SAPI,
'map_path' => $mapPath,
'nginx_bin' => $nginxBin,
],
'tests' => [],
];
// 1) Minimal nginx -t (same idea as MageSail)
// Unique pid path per run: a fixed /tmp/magesail-probe-pid.pid may exist root-owned and break for the PHP user.
$pidFile = sys_get_temp_dir() . '/magesail-probe-pid-' . getmypid() . '-' . bin2hex(random_bytes(4)) . '.pid';
$tmpConf = null;
if (is_readable($mapPath)) {
$tmpConf = minimalConfPath($mapPath, $pidFile);
[$c, $o, $shown] = runArgv([$nginxBin, '-t', '-c', $tmpConf]);
$results['tests']['minimal_nginx_t'] = ['exit' => $c, 'ok' => $c === 0, 'output' => $o, 'cmd' => $shown];
@unlink($tmpConf);
@unlink($pidFile);
} else {
$results['tests']['minimal_nginx_t'] = [
'exit' => -1,
'ok' => false,
'output' => 'Map file not readable: ' . $mapPath,
'cmd' => null,
];
}
$reloadStrategies = [
'direct_nginx_reload' => [$nginxBin, '-s', 'reload'],
'sudo_n_nginx_reload' => ['sudo', '-n', $nginxBin, '-s', 'reload'],
'systemctl_reload_nginx' => ['systemctl', 'reload', 'nginx'],
'service_nginx_reload' => ['service', 'nginx', 'reload'],
];
foreach ($reloadStrategies as $name => $argv) {
if (!$tryReload) {
$results['tests'][$name] = [
'skipped' => true,
'hint' => 'Re-run with --try-reload to execute (may reload production nginx).',
];
continue;
}
if ($argv[0] === 'sudo' && !is_executable('/usr/bin/sudo') && !is_executable('/bin/sudo')) {
$results['tests'][$name] = ['exit' => -1, 'ok' => false, 'output' => 'sudo not found', 'skipped_binary' => true];
continue;
}
if ($argv[0] === 'systemctl' && !is_executable('/usr/bin/systemctl') && !is_executable('/bin/systemctl')) {
$results['tests'][$name] = ['exit' => -1, 'ok' => false, 'output' => 'systemctl not found', 'skipped_binary' => true];
continue;
}
if ($argv[0] === 'service' && !is_executable('/usr/sbin/service') && !is_executable('/usr/bin/service')) {
$results['tests'][$name] = ['exit' => -1, 'ok' => false, 'output' => 'service not found', 'skipped_binary' => true];
continue;
}
[$c, $o, $shown] = runArgv($argv);
$results['tests'][$name] = ['exit' => $c, 'ok' => $c === 0, 'output' => $o, 'cmd' => $shown];
}
$results['recommendation'] = [];
if (!empty($results['tests']['minimal_nginx_t']['ok'])) {
$results['recommendation'][] = 'minimal nginx -t works — MageSail map syntax validation can succeed.';
} else {
$results['recommendation'][] = 'Fix minimal_nginx_t first (map path, nginx binary, map file syntax).';
}
if ($tryReload) {
$reloadOk = false;
foreach (['direct_nginx_reload', 'sudo_n_nginx_reload', 'systemctl_reload_nginx', 'service_nginx_reload'] as $k) {
if (!empty($results['tests'][$k]['ok'])) {
$results['recommendation'][] = "WORKING RELOAD: {$k} — use a wrapper or MageSail reload_wrapper_script (see README-NGINX-RELOAD.md).";
$reloadOk = true;
break;
}
}
if (!$reloadOk) {
$results['recommendation'][] = 'Reload: none succeeded as user ' . $user . ' (typical: master is root → kill EPERM; sudo/systemctl need a TTY or NOPASSWD). '
. 'Use a root-owned helper script + /etc/sudoers.d/ allowing ' . $user . ' NOPASSWD for that path only, point MageSail “Reload wrapper script” at a small script that runs sudo -n on the helper. '
. 'See Magesail/scripts/README-NGINX-RELOAD.md and magesail-nginx-reload.sh.example.';
}
} else {
$results['recommendation'][] = 'Run as your PHP user, e.g.: sudo -u ' . $user . ' php ' . __FILE__ . ' --try-reload --map=' . escapeshellarg($mapPath) . ' --bin=' . escapeshellarg($nginxBin);
$results['recommendation'][] = 'If direct_nginx_reload fails (kill EPERM), use sudoers NOPASSWD for a fixed wrapper script (README-NGINX-RELOAD.md).';
}
if ($asJson) {
echo json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
} else {
echo "MageSail nginx probe\n";
echo "====================\n";
echo "User: {$user} (uid {$uid}) SAPI: " . PHP_SAPI . "\n\n";
foreach ($results['tests'] as $name => $t) {
echo "[{$name}]\n";
if (!empty($t['skipped'])) {
echo " skipped: {$t['hint']}\n\n";
continue;
}
$ok = $t['ok'] ?? ($t['exit'] === 0);
echo ' ok: ' . ($ok ? 'YES' : 'NO') . ' exit: ' . ($t['exit'] ?? 'n/a') . "\n";
if (!empty($t['output'])) {
echo ' output: ' . str_replace("\n", "\n ", $t['output']) . "\n";
}
if (!empty($t['cmd'])) {
echo " cmd: {$t['cmd']}\n";
}
echo "\n";
}
echo "Recommendation\n";
echo "--------------\n";
foreach ($results['recommendation'] as $line) {
echo "- {$line}\n";
}
}
exit(0);