refactor: introduce two-user daemon architecture with socket-based communication
- Add daemon/ module with aiohttp server, sync client, and handler registry - Add daemon/handlers/ for privileged operations (acme, dnsmasq, firewall, logs, nginx, wireguard) - Add system/acme-deploy.py, vacuum-walld sudoers and systemd service - Update API routes to use daemon client instead of lib/ directly - Update lib/, tests/, and webui/ for new architecture - Update docs and deployment scripts
This commit is contained in:
+28
-55
@@ -1,36 +1,24 @@
|
||||
"""ACME certificate management API blueprint.
|
||||
|
||||
Exposed at /api/certs/* and delegates to lib.acme.
|
||||
Exposed at /api/certs/* and delegates to vacuum-walld.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from flask import Blueprint, request
|
||||
|
||||
from lib.acme import (
|
||||
get_cert_info,
|
||||
issue,
|
||||
list_certs,
|
||||
remove,
|
||||
renew,
|
||||
set_email,
|
||||
)
|
||||
from daemon.client import BadRequest, NotFound, delete, get, post
|
||||
from webui.api.common import _error, _ok
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
bp = Blueprint("certs", __name__)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Certificate listing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@bp.route("/list", methods=["GET"])
|
||||
def list_certs_bp():
|
||||
try:
|
||||
return _ok(list_certs())
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
return _ok(get("/acme/list"))
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to list certificates: %s", exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
@@ -38,20 +26,15 @@ def list_certs_bp():
|
||||
@bp.route("/<domain>", methods=["GET"])
|
||||
def cert_details(domain: str):
|
||||
try:
|
||||
info = get_cert_info(domain)
|
||||
return _ok(info)
|
||||
except ValueError as exc:
|
||||
return _ok(get("/acme/info", {"domain": domain}))
|
||||
except NotFound as exc:
|
||||
logger.info("Cert for '%s' not found: %s", domain, exc)
|
||||
return _error(str(exc), 404)
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to get cert info for '%s': %s", domain, exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Issue
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@bp.route("/issue", methods=["POST"])
|
||||
def issue_bp():
|
||||
body = request.get_json(silent=True) or {}
|
||||
@@ -62,59 +45,46 @@ def issue_bp():
|
||||
email = body.get("email", "").strip() or None
|
||||
try:
|
||||
logger.info("Certificate issuance requested for '%s' via API", domain)
|
||||
issue(domain, webroot=webroot, email=email)
|
||||
post("/acme/issue", {"domain": domain, "webroot": webroot, "email": email})
|
||||
logger.info("Certificate issued for '%s'", domain)
|
||||
return _ok(None)
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
except BadRequest as exc:
|
||||
logger.info("Cert issue for '%s' rejected: %s", domain, exc)
|
||||
return _error(str(exc), 400)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to issue cert for '%s': %s", domain, exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Renew
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@bp.route("/<domain>/renew", methods=["POST"])
|
||||
def renew_bp(domain: str):
|
||||
try:
|
||||
logger.info("Certificate renewal requested for '%s' via API", domain)
|
||||
renew(domain)
|
||||
post("/acme/renew", {"domain": domain})
|
||||
logger.info("Certificate renewed for '%s'", domain)
|
||||
return _ok(None)
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
except BadRequest as exc:
|
||||
logger.info("Cert renew for '%s' rejected: %s", domain, exc)
|
||||
return _error(str(exc), 400)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to renew cert for '%s': %s", domain, exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@bp.route("/<domain>", methods=["DELETE"])
|
||||
def remove_bp(domain: str):
|
||||
try:
|
||||
get_cert_info(domain)
|
||||
except ValueError as exc:
|
||||
return _error(str(exc), 404)
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
logger.error("Failed to verify cert '%s': %s", domain, exc)
|
||||
return _error(str(exc), 500)
|
||||
try:
|
||||
remove(domain)
|
||||
delete("/acme/remove", {"domain": domain})
|
||||
logger.info("Certificate removed for '%s' via API", domain)
|
||||
return _ok(None)
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
except NotFound as exc:
|
||||
logger.info("Cert '%s' not found: %s", domain, exc)
|
||||
return _error(str(exc), 404)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to remove cert '%s': %s", domain, exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Contact email
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@bp.route("/email", methods=["POST"])
|
||||
def set_email_bp():
|
||||
body = request.get_json(silent=True) or {}
|
||||
@@ -122,9 +92,12 @@ def set_email_bp():
|
||||
if not email:
|
||||
return _error("'email' is required", 400)
|
||||
try:
|
||||
set_email(email)
|
||||
post("/acme/email", {"email": email})
|
||||
logger.info("ACME email set via API: %s", email)
|
||||
return _ok({"email": email})
|
||||
except (RuntimeError, FileNotFoundError) as exc:
|
||||
except BadRequest as exc:
|
||||
logger.info("ACME email set rejected: %s", exc)
|
||||
return _error(str(exc), 400)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to set ACME email: %s", exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
Reference in New Issue
Block a user