2f215793e9
Add comprehensive docstrings to firewall, DHCP, proxy, wireguard, certs, and logs API endpoints. Document parameters, return values, and error cases for the documentation system.
307 lines
9.0 KiB
Python
307 lines
9.0 KiB
Python
"""Nginx proxy domain management API blueprint.
|
|
|
|
Exposed at /api/proxy/* and delegates to vacuum-walld.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from flask import Blueprint, request
|
|
|
|
from daemon.client import BadRequest, NotFound, delete, get, patch, post
|
|
from webui.api.common import _error, _ok
|
|
|
|
logger = logging.getLogger(__name__)
|
|
bp = Blueprint("proxy", __name__)
|
|
|
|
|
|
@bp.route("/ssl-apply", methods=["POST"])
|
|
def ssl_apply_bp():
|
|
"""Apply SSL snippet config.
|
|
|
|
POST /api/proxy/ssl-apply
|
|
|
|
Returns:
|
|
``{"ok": true}`` on success.
|
|
|
|
Raises:
|
|
RuntimeError: If nginx SSL snippet write fails.
|
|
"""
|
|
try:
|
|
post("/nginx/ssl-apply")
|
|
logger.info("SSL snippet written via API")
|
|
return _ok(None)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to write SSL snippet: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/config", methods=["GET"])
|
|
def get_config_bp():
|
|
"""Get the current nginx proxy configuration.
|
|
|
|
GET /api/proxy/config
|
|
|
|
Returns:
|
|
Current config dict from the daemon.
|
|
"""
|
|
try:
|
|
return _ok(get("/nginx/config"))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to read proxy config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/config", methods=["POST"])
|
|
def post_config():
|
|
"""Save the nginx proxy configuration.
|
|
|
|
POST /api/proxy/config
|
|
|
|
Body:
|
|
Any JSON object to merge into the config.
|
|
|
|
Returns:
|
|
``{"ok": true}`` on success.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
if not isinstance(body, dict):
|
|
return _error("Request body must be a JSON object", 400)
|
|
try:
|
|
post("/nginx/config", body)
|
|
logger.info("Proxy config saved: %s", sorted(body.keys()))
|
|
return _ok(None)
|
|
except BadRequest as exc:
|
|
logger.info("Proxy config save rejected: %s", exc)
|
|
return _error(str(exc), 400)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to save proxy config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/config", methods=["PATCH"])
|
|
def patch_config():
|
|
"""Partially update the nginx proxy configuration.
|
|
|
|
PATCH /api/proxy/config
|
|
|
|
Body:
|
|
JSON object with fields to patch.
|
|
|
|
Returns:
|
|
``{"ok": true}`` on success.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
if not isinstance(body, dict):
|
|
return _error("Request body must be a JSON object", 400)
|
|
try:
|
|
patch("/nginx/config", body)
|
|
logger.info("Proxy config patched: %s", sorted(body.keys()))
|
|
return _ok(None)
|
|
except BadRequest as exc:
|
|
logger.info("Proxy config patch rejected: %s", exc)
|
|
return _error(str(exc), 400)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to patch proxy config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/domains", methods=["GET"])
|
|
def list_domains():
|
|
"""List all configured proxy domains.
|
|
|
|
GET /api/proxy/domains
|
|
|
|
Returns:
|
|
List of domain dicts from the daemon.
|
|
"""
|
|
try:
|
|
return _ok(get("/nginx/domains"))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to list proxy domains: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/domains", methods=["POST"])
|
|
def add_domain_bp():
|
|
"""Add a new proxy domain.
|
|
|
|
POST /api/proxy/domains
|
|
|
|
Body fields:
|
|
domain: Domain name.
|
|
backend_host: Upstream host.
|
|
backend_port: Upstream port.
|
|
backend_proto: Protocol (``http`` or ``https``, default ``http``).
|
|
cert: Optional certificate type.
|
|
extra_headers: Optional extra headers dict.
|
|
|
|
Returns:
|
|
``{"domain": ...}`` on success.
|
|
"""
|
|
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)
|
|
try:
|
|
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,
|
|
},
|
|
)
|
|
logger.info("Proxy domain added via API: %s", domain)
|
|
return _ok({"domain": domain})
|
|
except BadRequest as exc:
|
|
logger.info("Add proxy domain '%s' rejected: %s", domain, exc)
|
|
return _error(str(exc), 400)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to add proxy domain '%s': %s", domain, exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/domains/<domain>", methods=["PUT"])
|
|
def update_domain_bp(domain):
|
|
"""Update an existing proxy domain in-place.
|
|
|
|
PUT /api/proxy/domains/<domain>
|
|
|
|
Body fields:
|
|
Fields to merge into the domain config.
|
|
|
|
Returns:
|
|
``{"domain": ...}`` on success.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
if not body:
|
|
return _error("Request body must be a JSON object with fields to update", 400)
|
|
try:
|
|
post("/nginx/domains/update", {"domain": domain, **body})
|
|
logger.info("Proxy domain '%s' updated via API", domain)
|
|
return _ok({"domain": domain})
|
|
except BadRequest as exc:
|
|
logger.info("Update domain '%s' rejected: %s", domain, exc)
|
|
return _error(str(exc), 400)
|
|
except NotFound as exc:
|
|
logger.info("Domain '%s' not found: %s", domain, exc)
|
|
return _error(str(exc), 404)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to update domain '%s': %s", domain, exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/domains/<domain>", methods=["DELETE"])
|
|
def remove_domain_bp(domain):
|
|
"""Remove a proxy domain.
|
|
|
|
DELETE /api/proxy/domains/<domain>
|
|
|
|
Returns:
|
|
``{"domain": ...}`` on success.
|
|
"""
|
|
try:
|
|
delete("/nginx/domains/remove", {"domain": domain})
|
|
logger.info("Proxy domain removed via API: %s", domain)
|
|
return _ok({"domain": domain})
|
|
except NotFound as exc:
|
|
logger.info("Domain '%s' not found: %s", domain, exc)
|
|
return _error(str(exc), 404)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to remove domain '%s': %s", domain, exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/apply", methods=["POST"])
|
|
def apply_bp():
|
|
"""Generate all nginx configs and reload nginx.
|
|
|
|
POST /api/proxy/apply
|
|
|
|
Returns:
|
|
``{"ok": true}`` on success.
|
|
"""
|
|
try:
|
|
post("/nginx/apply")
|
|
logger.info("nginx config applied via API")
|
|
return _ok(None)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to apply nginx config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/test", methods=["POST"])
|
|
def test_bp():
|
|
"""Test nginx configuration without reloading.
|
|
|
|
POST /api/proxy/test
|
|
|
|
Returns:
|
|
``{"valid": true, "output": ...}`` on success. Returns 400 if test fails.
|
|
"""
|
|
try:
|
|
result = post("/nginx/test")
|
|
if result.get("valid"):
|
|
return _ok({"valid": True, "output": result.get("output", "")})
|
|
return _error(result.get("output", "unknown error"), 400)
|
|
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(
|
|
"/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)
|