Project Structure¶
Repository Layout¶
control-center/
├── .gitignore
├── Dockerfile # Python 3.12 + iptables + wireguard-tools
├── docker-compose.yml # Host network, NET_ADMIN
├── requirements.txt # fastapi, uvicorn, aiohttp, pyyaml
├── data/
│ ├── .gitkeep
│ ├── rules.json # Firewall config (runtime, gitignored)
│ └── audit.jsonl # Audit events (runtime, gitignored)
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app, lifespan, router mounts
│ ├── config.py # Environment variables, paths, constants
│ ├── models.py # Pydantic models
│ ├── static/
│ │ └── index.html # Single-page frontend
│ ├── services/
│ │ ├── __init__.py
│ │ ├── iptables.py # iptables operations, rule application
│ │ ├── pihole.py # Pi-hole v5/v6 API client
│ │ ├── traefik.py # Traefik route management (YAML)
│ │ ├── vpn.py # WireGuard monitoring
│ │ ├── audit.py # Structured audit logging
│ │ └── config_store.py # Config persistence (JSON)
│ └── routers/
│ ├── __init__.py
│ ├── status.py # GET /api/status
│ ├── firewall.py # Rules CRUD, profiles, killswitch
│ ├── dns.py # Pi-hole endpoints
│ ├── routes.py # Traefik route management
│ ├── vpn.py # GET /api/vpn/status
│ ├── audit.py # GET /api/audit/events
│ └── debug.py # iptables dump, blocked traffic
Architecture¶
main.py (FastAPI app + lifespan)
├── routers/ (API endpoints)
│ ├── status.py → services/iptables, pihole, vpn, traefik, config_store
│ ├── firewall.py → services/iptables, audit, config_store
│ ├── dns.py → services/pihole, audit
│ ├── routes.py → services/traefik, audit
│ ├── vpn.py → services/vpn
│ ├── secrets.py → services/secrets
│ ├── audit.py → services/audit
│ └── debug.py → services/iptables
└── services/ (business logic)
├── iptables.py (subprocess: iptables, ping)
├── pihole.py (aiohttp: Pi-hole API)
├── traefik.py (file I/O: vm-services.yml)
├── vpn.py (subprocess: wg show all dump)
├── secrets.py (file I/O: secrets.yml + SSH deploy)
├── audit.py (file I/O: audit.jsonl)
└── config_store.py (file I/O: rules.json)
Key Design Patterns¶
Service Layer¶
All business logic lives in services/. Routers are thin — they validate input, call services, and return responses. This makes services testable independently of HTTP.
Config as Code¶
All configuration is in config.py using environment variables with sensible defaults. No hardcoded values in service files.
Audit Integration¶
Every router that modifies state calls audit.log_event():
audit.log_event("firewall.rule_added", {
"rule_id": r.id, "description": r.description,
"protocol": r.protocol, "port": r.port,
}, severity="info")
Legacy Compatibility¶
The config loader handles old config files: