Files
vacuum-wall/AGENTS.md
T
mteehan e2f56b8cc8 Initial commit: SSL proxy / firewall appliance
Flask WebUI behind nginx reverse proxy with zone-based firewall, DHCP,
WireGuard, and ACME certificate management.
2026-05-07 22:24:24 +00:00

3.5 KiB

Vacuum Wall — Agent Instructions

What This Is

SSL proxy / firewall appliance. Python 3 Flask WebUI behind nginx reverse proxy. Deploys on Debian 13 (trixie). Target system: /home/wall/vacuum-wall.

Architecture

Client ──→ nginx (SSL + basic auth) ──→ Flask (127.0.0.1:9090)
Flask ──→ lib/*.py ──→ sudo <cmd> ──→ system service
  • webui/server.py — Flask app entry point. Only file that creates the app.
  • webui/api/*.py — Flask blueprints, one per subsystem. Routes prefix /api/<subsystem>/.
  • lib/*.py — Backend modules. Wrap system commands via subprocess.run(["sudo", ...]).
  • data/ — Declarative JSON configs (source of truth). Generated .conf in data/nginx/sites-enabled/.
  • system/ — System file templates. systemd/ (service units installed to /etc/systemd/system/), sudoers.d/, nginx/.

Project uses .venv. Install deps with pip install -e . (from pyproject.toml). __init__.py files in webui/ and lib/ are intentionally empty — no sys.path boilerplate needed.

Fixed Path

Every module hardcodes /home/wall/vacuum-wall. Changing it requires updating lib/*.py, system/systemd/*.service, install.sh, and system/sudoers.d/vacuum-wall.

Local Dev

.venv/bin/python webui/server.py          # binds 127.0.0.1:9090

In production the systemd unit runs as the vacuum-wall system user (NoNewPrivileges, ProtectSystem=strict, loopback-only networking).

Blueprint ↔ lib Mapping (Naming Is Not 1:1)

Blueprint URL prefix Backend module
webui/api/firewall /api/firewall/ lib.firewall
webui/api/dhcp /api/dhcp/ lib.dnsmasq
webui/api/proxy /api/proxy/ lib.nginx
webui/api/certs /api/certs/ lib.acme
webui/api/wireguard /api/wireguard/ lib.wireguard

Privileged Operations

lib/ modules call sudo for everything that touches system services. Whitelist is system/sudoers.d/vacuum-wall.

Pattern for mutations: write JSON → render native config → sudo <cmd> to apply. Adding a new privileged command requires a sudoers entry and the lib/ code.

API Response Contract

  • Success: {"ok": true, "data": <value>} — helper _ok(data)
  • Error: {"ok": false, "error": "msg"} — helper _error(msg, code=400)
  • HTTP codes: 400 bad request, 404 not found, 500 internal failure
  • Full spec: docs/api.md

Page Routes vs API

server.py serves HTML pages with Jinja templates. All data is wrapped in _safely(fn, default) so page routes never 500 — they render with fallback values instead.

Deploy

install.sh is the single deploy script. Run as root, requires MGMT_DOMAIN, MGMT_PASS, ACME_EMAIL env vars.

Lint and Tests

Linter / formatter: Ruff (ruff check + ruff format). Config in pyproject.toml under [tool.ruff].

Tests: pytest in tests/. Run with python -m pytest. Tests mock out subprocess calls (firewalld, acme.sh, WireGuard, nginx, dnsmasq) — no system services required.

.venv/bin/ruff check lib/ webui/ tests/   # lint
.venv/bin/ruff format lib/ webui/ tests/   # format
.venv/bin/python -m pytest tests/ -v       # test (149 tests)

Install dev tooling with pip install -e ".[dev]".

Docs

docs/ contains the authoritative reference. docs/architecture.md covers request flow, zone model, and data directory layout in detail.