#!/usr/bin/env php ['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);