Refactor nginx to path-based domain model with config migration

Replace the legacy top-level management key with a unified paths-based
model. Each domain now contains a paths map where each entry defines its
own backend, auth, headers, and flags (is_management, is_websocket).

- Add _migrate_config() to auto-migrate legacy formats on first load
- Remove set_management_proxy() and POST_NGINX_MANAGEMENT endpoint
- Update server_block.conf template to iterate paths with per-location auth
- Update daemon handler, API blueprint, state collector, and install script
- Add server config generation tests for paths, WebSocket, auth inheritance
- Update frontend proxy page to display per-path rows with flags
This commit is contained in:
2026-06-27 23:34:06 +00:00
parent 8feb56faf6
commit 835326311b
14 changed files with 851 additions and 558 deletions
+35 -67
View File
@@ -17,7 +17,6 @@ from daemon.iface import (
POST_NGINX_CONFIG,
POST_NGINX_DOMAINS_ADD,
POST_NGINX_DOMAINS_UPDATE,
POST_NGINX_MANAGEMENT,
POST_NGINX_SSL_APPLY,
POST_NGINX_TEST,
)
@@ -140,7 +139,13 @@ def add_domain_bp():
POST /api/proxy/domains
Body fields:
Body fields (paths mode):
domain: Domain name.
paths: Path-to-config map (e.g. ``{"/": {"backend": {...}}, "/app": {...}}``).
cert: Optional certificate type.
force_ssl: Optional SSL redirect flag (default ``true``).
Body fields (legacy mode):
domain: Domain name.
backend_host: Upstream host.
backend_port: Upstream port.
@@ -153,29 +158,37 @@ def add_domain_bp():
"""
body = request.get_json(silent=True) or {}
domain = body.get("domain", "").strip()
backend_host = body.get("backend_host", "").strip()
backend_port = body.get("backend_port")
backend_proto = body.get("backend_proto", "http").strip() or "http"
cert = body.get("cert")
extra_headers = body.get("extra_headers")
if not domain:
return _error("'domain' is required", 400)
if not backend_host:
return _error("'backend_host' is required", 400)
if backend_port is None:
return _error("'backend_port' is required", 400)
paths = body.get("paths")
if paths is not None:
payload = {
"domain": domain,
"paths": paths,
"cert": body.get("cert"),
"force_ssl": body.get("force_ssl", True),
}
else:
backend_host = body.get("backend_host", "").strip()
backend_port = body.get("backend_port")
backend_proto = body.get("backend_proto", "http").strip() or "http"
cert = body.get("cert")
extra_headers = body.get("extra_headers")
if not backend_host:
return _error("'backend_host' is required", 400)
if backend_port is None:
return _error("'backend_port' is required", 400)
payload = {
"domain": domain,
"backend_host": backend_host,
"backend_port": int(backend_port),
"backend_proto": backend_proto,
"cert": cert,
"extra_headers": extra_headers,
}
try:
post(
POST_NGINX_DOMAINS_ADD,
{
"domain": domain,
"backend_host": backend_host,
"backend_port": int(backend_port),
"backend_proto": backend_proto,
"cert": cert,
"extra_headers": extra_headers,
},
)
post(POST_NGINX_DOMAINS_ADD, payload)
logger.info("Proxy domain added via API: %s", domain)
return _ok({"domain": domain})
except BadRequest as exc:
@@ -272,48 +285,3 @@ def test_bp():
except RuntimeError as exc:
logger.error("nginx config test failed: %s", exc)
return _error(str(exc), 500)
@bp.route("/management", methods=["POST"])
def management_bp():
"""Configure the management reverse proxy for the WebUI.
POST /api/proxy/management
Body fields:
domain: Management domain name.
flask_host: Upstream Flask host (default ``127.0.0.1``).
flask_port: Upstream Flask port (default 9090).
auth_user: Optional basic-auth username.
auth_pass: Optional basic-auth password.
Returns:
``{"ok": true}`` on success.
"""
body = request.get_json(silent=True) or {}
domain = body.get("domain", "").strip()
if not domain:
return _error("'domain' is required", 400)
flask_host = body.get("flask_host", "127.0.0.1").strip() or "127.0.0.1"
flask_port = body.get("flask_port", 9090)
auth_user = body.get("auth_user")
auth_pass = body.get("auth_pass")
try:
post(
POST_NGINX_MANAGEMENT,
{
"domain": domain,
"flask_host": flask_host,
"flask_port": int(flask_port),
"auth_user": auth_user,
"auth_pass": auth_pass,
},
)
logger.info("Management proxy configured via API: %s", domain)
return _ok(None)
except BadRequest as exc:
logger.info("Management proxy config rejected: %s", exc)
return _error(str(exc), 400)
except RuntimeError as exc:
logger.error("Failed to set management proxy: %s", exc)
return _error(str(exc), 500)