mteehan 6476695d29 fix: ACME cert list self-heals when account.conf is left owner-only
The startup normalize and _run_acme_preflight covered daemon startup and issue/renew, but the recurring collector poll called lib.acme.list_certs() without normalizing ACME_HOME. A non-daemon run (e.g. a manual run as the WebUI user) re-creating account.conf owner-only made every acme.sh --list exit 2, so the collector returned certs=[] and the UI showed no certs until the next issue/renew or daemon restart.

- collector: normalize_acme_home() before list_certs() so the poll self-heals
- issue pre-check: normalize before the direct lib.acme.list_certs()
- _parse_account_conf: read acme.sh v3 account.conf (not just .account.conf)
- _collect_acme: actionable status.error for the account.conf perm case
- install.sh: chown ACME_HOME conf files to the daemon user
2026-09-04 21:38:55 +00:00

Vacuum Wall

A zone-based firewall appliance with a built-in SSL reverse proxy. Combines firewalld policy control, DHCP/DNS, WireGuard VPN, and automated certificate provisioning into a single device managed through one web UI.

Tech Stack

  • Debian 13 (trixie) target platform
  • Python 3.13+, Flask 3.x web UI
  • firewalld (nftables backend), dnsmasq, nginx, WireGuard
  • acme.sh for ACME certificates (ZeroSSL)

For Operators

Prerequisites

  • Clean Debian 13 system with root access
  • git installed
  • One public-facing NIC (external) and at least one LAN NIC (internal)
  • A DNS A record pointing to the appliance's public IP for the management domain
  • Minimum hardware: 1 CPU, 512 MB RAM, 4 GB disk

Install (Production)

MGMT_DOMAIN=wall.example.com \
MGMT_PASS="strongpassword" \
MGMT_USER="admin" \
ACME_EMAIL="admin@example.com" \
bash scripts/install.sh

Install (Development)

./scripts/install.sh --dev --mgmt-pass strongpassword --acme-email "admin@example.com"

--dev auto-detects the repo's file owner as the service user, skips the safety warning about running as a regular user, and keeps file ownership dev-friendly.

Flag Env Var Required Description
-- MGMT_DOMAIN No Public domain for the management WebUI (auto-detected as hostname.local)
--mgmt-pass MGMT_PASS Yes HTTP basic auth password for the WebUI
--mgmt-user MGMT_USER No WebUI username (defaults to admin)
--acme-email ACME_EMAIL Yes ACME registration email (ZeroSSL by default)
--user, -u USER_NAME No System user for service (default: vacuum-wall)
--path, -p INSTALL_DIR No Install directory (default: repo root)
--dev -- No Auto-detect repo owner as service user, skip safety warning
--mgmt-domain MGMT_DOMAIN No (alias for env var)
--wan-iface WAN_IFACE No WAN interface (auto-detected)
--lan-ifaces LAN_IFACES No LAN interfaces, comma-separated (auto-detected)

CLI flags take precedence over environment variables. Run ./scripts/install.sh --help for full usage.

After installation, access the WebUI at https://<MGMT_DOMAIN>. The initial certificate is self-signed — use the Certs tab to issue a real one once DNS is propagating.

Next Steps

  1. Assign interfaces to zones from the Interfaces tab
  2. Configure DHCP ranges for your LAN
  3. Add proxy domains with ACME certificates
  4. Set up WireGuard (optional)

See docs/deployment.md for the full guide, including troubleshooting.


For Developers

Local Development

git clone <repo-url> && cd vacuum-wall
bash scripts/update-vendor.sh
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"

Start the WebUI locally (binds to 127.0.0.1:9090):

.venv/bin/python webui/server.py

Lint and Format

.venv/bin/ruff check lib/ webui/ tests/
.venv/bin/ruff format lib/ webui/ tests/

All lib/ modules share lib.common utilities (run, run_proc, load_json, save_json, deep_merge, ensure_dirs) and have full type hints and __all__ exports. API blueprints share _ok/_error from webui.api.common.

Tests

.venv/bin/python -m pytest tests/ -v

Tests mock all subprocess calls (firewalld, acme.sh, WireGuard, nginx, dnsmasq) — no system services required. 192 tests across 5 test modules.

Documentation MCP Server

This project uses docs-mcp-server for grounded, real-time documentation lookups during development.

pipx install docs-mcp-server
docs-mcp init

Then run with Claude Code or Opencode to activate it. It automatically checks live docs before answering technical questions — no manual doc search needed.

Architecture

Client ──→ nginx (SSL + basic auth) ──→ Flask (127.0.0.1:9090)
Flask ──→ lib/*.py ──→ sudo <cmd> ──→ system service
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

See docs/architecture.md for detailed request flow, zone model, and shared utility patterns.


Documentation

S
Description
No description provided
Readme 2.8 MiB
Languages
Python 73.1%
JavaScript 24%
Shell 1.6%
CSS 1.3%