188 lines
7.5 KiB
PHP
188 lines
7.5 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 jetrails.
|
|
$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);
|