host_agent.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Host-side nginx control agent for nginx-webui.
nginx-webui running in a container edits the host's /etc/nginx (mounted), but
it cannot signal or restart the host nginx master itself. Run this agent ON
THE HOST and point nginx-webui at it via NGINX_CTL_URL — then status, `nginx -t`,
reload and restart all act on the *host* nginx.
Run as root, or give the service account passwordless sudo for nginx and
systemctl (see README). Example:
sudo python3 host_agent.py --bind 127.0.0.1 --port 9401
API (JSON):
GET /status host nginx version / running / pid / workers / config
POST /check nginx -t
POST /reload nginx -s reload
POST /restart systemctl restart nginx
"""
import json
import os
import re
import subprocess
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse
TIMEOUT = 30
NGINX_PIDFILE = "/run/nginx.pid"
def _run(cmd, timeout=TIMEOUT):
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return r.stdout.strip(), r.stderr.strip(), r.returncode
except subprocess.TimeoutExpired:
return "", "command timed out", 124
def _sudo(cmd):
return cmd if os.geteuid() == 0 else f"sudo {cmd}"
def _status():
version = ""
out, err, _ = _run("nginx -v")
version = (err or out).strip().replace("nginx version:", "").replace("nginx/", "", 1).strip()
master_pid = ""
if os.path.exists(NGINX_PIDFILE):
try:
master_pid = open(NGINX_PIDFILE).read().strip()
except OSError:
pass
running = bool(master_pid and os.path.exists(f"/proc/{master_pid}"))
workers = 0
try:
workers = int(_run("pgrep -fc 'nginx: worker'")[0] or 0)
except (ValueError, RuntimeError):
workers = 0
config = ""
out, _, _ = _run("nginx -T 2>/dev/null")
m = re.search(r"configuration file: (\S+)", out)
if m:
config = m.group(1)
return {
"version": version or "(unknown)",
"running": running,
"master_pid": master_pid,
"workers": workers,
"config_file": config or "/etc/nginx/nginx.conf",
"conf_dir": "/etc/nginx",
"status_source": "agent",
}
ACTIONS = {
"check": "nginx -t",
"reload": "nginx -s reload",
"restart": "systemctl restart nginx",
}
class Handler(BaseHTTPRequestHandler):
def _json(self, obj, code=200):
body = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if urlparse(self.path).path == "/status":
self._json(_status())
else:
self._json({"ok": False, "error": "not found"}, 404)
def do_POST(self):
action = urlparse(self.path).path.strip("/")
if action not in ACTIONS:
self._json({"ok": False, "error": f"unknown action '{action}'"}, 404)
return
out, err, rc = _run(_sudo(ACTIONS[action]))
self._json({"ok": rc == 0, "output": out, "error": err, "valid": rc == 0})
def log_message(self, fmt, *args):
sys.stderr.write("%s - - [%s] %s\n" % (self.address_string(), self.log_date_time_string(), fmt % args))
def main():
bind, port = "127.0.0.1", 9401
args = sys.argv[1:]
if "--bind" in args:
bind = args[args.index("--bind") + 1]
if "--port" in args:
port = int(args[args.index("--port") + 1])
print(f"nginx host control agent on http://{bind}:{port} (pid {os.getpid()})", flush=True)
ThreadingHTTPServer((bind, port), Handler).serve_forever()
if __name__ == "__main__":
main()