Ctrl K
ComunidadGuías…

host_agent.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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()