refactor: overhaul daemon server, client, and handlers

This commit is contained in:
2026-06-16 03:35:58 +00:00
parent c5813d68b3
commit 4fc0fb3f72
10 changed files with 683 additions and 195 deletions
+43 -18
View File
@@ -8,6 +8,20 @@ from typing import Any
from jinja2 import Environment, FileSystemLoader
from daemon.iface import (
DELETE_NGINX_DOMAINS_REMOVE,
GET_NGINX_CONFIG,
GET_NGINX_DOMAINS,
PATCH_NGINX_CONFIG,
POST_NGINX_APPLY,
POST_NGINX_CONFIG,
POST_NGINX_DOMAINS_ADD,
POST_NGINX_DOMAINS_UPDATE,
POST_NGINX_MANAGEMENT,
POST_NGINX_RELOAD,
POST_NGINX_SSL_APPLY,
POST_NGINX_TEST,
)
from daemon.server import NotFoundError, refresh_state, registry
from lib.common import ensure_dirs, load_json, run, run_proc, save_json
@@ -125,7 +139,7 @@ def _write_include_file() -> None:
"""Write the system include file that references all per-site configs."""
tmpl = ENV.get_template("nginx/include.conf")
content = tmpl.render(sites_glob=str(SITES_DIR / "*.conf"))
tmp = INCLUDE_FILE.with_suffix(".tmp")
tmp = Path("/tmp") / "vacuum-wall-include.tmp"
with open(tmp, "w") as f:
f.write(content)
os.chmod(tmp, 0o644)
@@ -143,7 +157,7 @@ def _write_ssl_snippet() -> None:
ssl_cfg.setdefault("ciphers", DEFAULT_SSL["ciphers"])
tmpl = ENV.get_template("nginx/ssl_snippet.conf")
content = tmpl.render(ssl=ssl_cfg)
tmp = SSL_SNIPPET.with_suffix(".tmp")
tmp = Path("/tmp") / "vacuum-wall-ssl-snippet.tmp"
with open(tmp, "w") as f:
f.write(content)
os.chmod(tmp, 0o644)
@@ -225,6 +239,20 @@ def _write_all_sites() -> None:
os.replace(tmp, site)
def _hash_password(password: str) -> str:
"""Hash *password* using SHA-256 crypt via passlib.
Args:
password: Plain-text password to hash.
Returns:
The hashed password string suitable for ``.htpasswd``.
"""
from passlib.hash import sha256_crypt
return sha256_crypt.hash(password)
def _write_htpasswd(user: str, password: str) -> None:
"""Add or update a user entry in the .htpasswd file using SHA-256 hashing.
@@ -233,10 +261,7 @@ def _write_htpasswd(user: str, password: str) -> None:
password: Plain-text password to hash.
"""
ensure_dirs(DATA_DIR)
import crypt
salt = os.urandom(16).hex()[:16]
hashed = crypt.crypt(password, f"$5${salt}")
hashed = _hash_password(password)
existing: dict[str, str] = {}
if HTPASSWD_FILE.exists():
with open(HTPASSWD_FILE) as f:
@@ -268,7 +293,7 @@ def _get_nginx_state() -> dict[str, Any]:
# Routes
@registry.register("GET", "/nginx/config")
@registry.register(GET_NGINX_CONFIG)
def get_config(_request: Any, _body: Any) -> dict[str, Any]:
"""GET /nginx/config — return current nginx config.
@@ -281,7 +306,7 @@ def get_config(_request: Any, _body: Any) -> dict[str, Any]:
return _get_config()
@registry.register("POST", "/nginx/config")
@registry.register(POST_NGINX_CONFIG)
def save_config_handler(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"""POST /nginx/config — replace the entire nginx config and refresh state.
@@ -295,7 +320,7 @@ def save_config_handler(_request: Any, body: dict[str, Any] | None) -> dict[str,
return {"config_saved": True}
@registry.register("PATCH", "/nginx/config")
@registry.register(PATCH_NGINX_CONFIG)
def patch_config(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"""PATCH /nginx/config — deep-merge partial updates into current config.
@@ -313,7 +338,7 @@ def patch_config(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
return {"config_saved": True}
@registry.register("GET", "/nginx/domains")
@registry.register(GET_NGINX_DOMAINS)
def get_domains(_request: Any, _body: Any) -> list[dict[str, Any]]:
"""GET /nginx/domains — return the list of configured proxy domains.
@@ -326,7 +351,7 @@ def get_domains(_request: Any, _body: Any) -> list[dict[str, Any]]:
return []
@registry.register("POST", "/nginx/domains/add")
@registry.register(POST_NGINX_DOMAINS_ADD)
def add_domain(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"""POST /nginx/domains/add — add a new reverse-proxy domain entry.
@@ -369,7 +394,7 @@ def add_domain(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
return {"domain": domain}
@registry.register("DELETE", "/nginx/domains/remove")
@registry.register(DELETE_NGINX_DOMAINS_REMOVE)
def remove_domain(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"""DELETE /nginx/domains/remove — remove a domain from the proxy config.
@@ -394,7 +419,7 @@ def remove_domain(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
return {"domain": domain}
@registry.register("POST", "/nginx/domains/update")
@registry.register(POST_NGINX_DOMAINS_UPDATE)
def update_domain(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"""POST /nginx/domains/update — patch fields of an existing domain entry.
@@ -422,7 +447,7 @@ def update_domain(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
return {"domain": domain}
@registry.register("POST", "/nginx/apply")
@registry.register(POST_NGINX_APPLY)
def apply(_request: Any, _body: Any) -> dict[str, Any]:
"""POST /nginx/apply — render all configs, test, and reload nginx.
@@ -440,7 +465,7 @@ def apply(_request: Any, _body: Any) -> dict[str, Any]:
return {"applied": True}
@registry.register("POST", "/nginx/test")
@registry.register(POST_NGINX_TEST)
def test(_request: Any, _body: Any) -> dict[str, Any]:
"""POST /nginx/test — dry-run validate the live nginx config without applying.
@@ -451,7 +476,7 @@ def test(_request: Any, _body: Any) -> dict[str, Any]:
return {"valid": valid, "output": output}
@registry.register("POST", "/nginx/ssl-apply")
@registry.register(POST_NGINX_SSL_APPLY)
def ssl_apply(_request: Any, _body: Any) -> dict[str, Any]:
"""POST /nginx/ssl-apply — re-render and install only the SSL snippet."""
_write_ssl_snippet()
@@ -459,7 +484,7 @@ def ssl_apply(_request: Any, _body: Any) -> dict[str, Any]:
return {"applied": True}
@registry.register("POST", "/nginx/management")
@registry.register(POST_NGINX_MANAGEMENT)
def set_management_proxy(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"""POST /nginx/management — configure the management UI reverse proxy.
@@ -490,7 +515,7 @@ def set_management_proxy(_request: Any, body: dict[str, Any] | None) -> dict[str
return {"domain": domain}
@registry.register("POST", "/nginx/reload")
@registry.register(POST_NGINX_RELOAD)
def reload_nginx(_request: Any, _body: Any) -> dict[str, Any]:
"""POST /nginx/reload — trigger an nginx reload (SIGHUP)."""
_reload_nginx()