Files
vacuum-wall/daemon/handlers/logs.py
T
mteehan dc96e15643 feat: pre-computed state store and async ACME issuance (fixes timeout mismatch)
- Add lib/state.py: in-memory state store with subsystem collectors
  (firewall, dnsmasq, nginx, acme, wireguard)
- Refactor all handlers: read from state on GET, call refresh_state()
  after mutations instead of invoking subprocesses per request
- daemon/server.py: add refresh_state(), /status/all, /status/refresh;
  populate state at startup
- webui/api/certs.py: async step-by-step ACME issuance (validate,
  issue with request_id, poll status) replacing blocking endpoint
- webui/server.py: render pages from state instead of direct lib calls
- Update templates, JS for async cert issuance with polling UI
- Update tests for state-based mocking; add test_state.py
- Fix SIM105 lint issue (contextlib.suppress)
- Add TODO.md with certificate issuance issue tracking

Resolves: WebUI 30s timeout freeze during cert issuance (Problem 1)
2026-05-30 05:46:09 +00:00

71 lines
1.9 KiB
Python

"""Logs daemon handler.
Reads system logs and journal entries.
"""
import logging
from pathlib import Path
from daemon.server import registry
from lib.common import run_proc
logger = logging.getLogger(__name__)
PROJECT_DIR = Path(__file__).resolve().parent.parent.parent
_APP_LOG_FILE = PROJECT_DIR / "data" / "logs" / "vacuum-wall.log"
_MAX_LINES = 200
def _tail_file(path: str, n: int = _MAX_LINES, sudo: bool = False) -> str:
try:
if sudo:
result = run_proc(["cat", path], sudo=True)
lines = result.stdout.splitlines(keepends=True)
else:
with open(path) as f:
lines = f.readlines()
return "".join(lines[-n:])
except FileNotFoundError:
return "(log file not found)\n"
except PermissionError:
return "(permission denied)\n"
def _sudo_journalctl(unit: str, n: int = _MAX_LINES) -> str:
try:
result = run_proc(
["journalctl", "--unit=" + unit, "-n", str(n)],
sudo=True,
check=False,
timeout=10,
)
output = result.stdout.strip()
return output if output else f"(no journal entries for {unit})\n"
except Exception as exc:
return f"(error reading journal: {exc})\n"
@registry.register("GET", "/logs/journal")
def journal(_request, _body) -> str:
return _sudo_journalctl("vacuum-wall")
@registry.register("GET", "/logs/nginx/access")
def nginx_access(_request, _body) -> str:
return _tail_file("/var/log/nginx/access.log", sudo=True)
@registry.register("GET", "/logs/nginx/error")
def nginx_error(_request, _body) -> str:
return _tail_file("/var/log/nginx/error.log", sudo=True)
@registry.register("GET", "/logs/dnsmasq")
def dnsmasq_log(_request, _body) -> str:
return _sudo_journalctl("dnsmasq")
@registry.register("GET", "/logs/app")
def app_log(_request, _body) -> str:
return _tail_file(str(_APP_LOG_FILE))