$meta) { if (!isset($meta['changed']) || !$meta['changed']) { continue; } // Process and detect if actual change occurred $changed |= processDomain($name, $meta); // Reset 'changed' flag after processing $domains[$name]['changed'] = 0; } // Restart nginx if any updates were made and save new state if ($changed) { shell_exec("sudo nginx -t && sudo systemctl reload nginx"); echo "Reloaded nginx configuration.\n"; // Save updated meta.json back to file if (!saveJson(METAFILE, $domains)) { exit(1); } } echo "All applicable domains processed.\n"; } /** * Modify the proxy_pass value inside a specific location block */ function updateProxyPassInLocation(string $nginxConf, string $locationPath, string $newProxyPass): string { $lines = explode("\n", $nginxConf); // Split config into lines $inBlock = false; // Flag: inside target location block $locationStart = false; $braceLevel = 0; // Tracks nested braces foreach ($lines as $i => $line) { $escapedLocation = preg_quote($locationPath, '/'); // Escape location for regex if (preg_match("/^\s*location\s+$escapedLocation\s*\{\s*$/", $line)) { $inBlock = true; $locationStart = true; $braceLevel = 1; continue; } if ($inBlock) { if (strpos($line, '{') !== false) $braceLevel++; if (strpos($line, '}') !== false) $braceLevel--; // Replace existing proxy_pass directive if (preg_match("/proxy_pass\\s+.+;/", $line)) { $lines[$i] = preg_replace("/proxy_pass\\s+.+;/", "proxy_pass $newProxyPass;", $line); } // Exit block if all braces are closed if ($braceLevel === 0) { $inBlock = false; } } } return implode("\n", $lines); // Return modified config } /** * Processes a single domain configuration from meta.json. * * - Verifies all required fields are present and non-empty. * - Constructs the expected proxy_pass value from protocol, domain, and port. * - Replaces only the proxy_pass directive within the specified location block. * - If the config changes, it is saved and NGINX will be marked for reload. * - Even if no actual config differences are found, a restart can still be triggered * by manually setting `"changed": 1` in meta.json. * * @param string $name The key in meta.json (usually target vhost name) * @param array $meta The domain's metadata including IP, protocol, port, location, etc. * @return bool True if a reload should be performed, false otherwise */ function processDomain(string $name, array $meta): bool { $requiredFields = ['domain', 'ip', 'protocol', 'port', 'location']; // Required keys $missing = []; $result = false; // Collect any missing or empty required fields foreach ($requiredFields as $field) { if (isEmpty($meta[$field])) { $missing[] = $field; } } // Skip this domain if required fields are missing if (!isEmpty($missing)) { echo "Skipping '$name': missing or empty field(s): " . implode(', ', $missing) . "\n"; return false; } // Allow forced reloads even without content change by setting "changed": 1 in meta.json $result = boolval($meta['changed'] ?? 1); if (!$result) { echo "No changes for $name\n"; return false; } // Full path to nginx config file for this domain $systemConfPath = VHOST_BASE . '/' . $name . '/conf/' . NGINX_CONF_NAME; // Check file existence and permissions if (!file_exists($systemConfPath)) { echo "System config not found for $name\n"; return false; } if (!is_readable($systemConfPath) || !is_writable($systemConfPath)) { echo "Permission denied for $systemConfPath\n"; return false; } // Read current nginx config $original = file_get_contents($systemConfPath); // Build new proxy_pass target $proxyTarget = $meta['protocol'] . '://' . $meta['domain'] . ':' . $meta['port']; // Apply transformation inside matching location block $modified = updateProxyPassInLocation($original, $meta['location'], $proxyTarget); // Write only if content changed if ($original !== $modified) { $result = (file_put_contents($systemConfPath, $modified) !== false); echo "Updated proxy_pass for $name\n"; } else { echo "No changes for $name\n"; } return $result; }