PatchPanel Architecture
System architecture of PatchPanel — components, data flow, and the state-driven render pipeline that produces a running HAProxy configuration.
System overview
PatchPanel sits next to HAProxy on the same host. It does not proxy traffic — HAProxy does that. PatchPanel manages HAProxy’s configuration: it renders haproxy.cfg from a single canonical state document, validates the result, atomically replaces the on-disk file, and asks HAProxy to reload via the master CLI socket. It also handles certificate lifecycle (Let’s Encrypt + bring-your-own), trusted CA / CRL storage, and live observability via HAProxy’s stats socket.
Architecture diagram
graph LR
subgraph "Clients"
WB[Web Browser<br/>React SPA]
API[API Clients<br/>Bearer tokens]
HA[Home Assistant<br/>ingress proxy]
end
subgraph "Authentication"
AUTH{Auth<br/>Middleware}
SESS[Session Cookie]
KEYS[API Keys<br/>bcrypt]
HAUSR[X-Remote-User<br/>headers]
end
subgraph "Server Core"
EXPRESS[Express 5<br/>HTTPS Server]
ROUTES[REST API<br/>Routes]
end
subgraph "State Machine"
STATE[state.json<br/>Zod-validated]
RENDER[cfg renderer]
VALIDATE[haproxy -c<br/>validator]
APPLY[atomic swap<br/>+ reload]
end
subgraph "Background Services"
CRON[Renewal cron<br/>certbot]
STATS[Stats sampler]
AUDIT[Audit log<br/>SQLite]
end
subgraph "Data Layer"
ST[(state.json)]
AU[(audit.sqlite)]
CERTS[/etc/letsencrypt/]
TC[trusted-cas/<br/>trusted-crls/]
end
subgraph "HAProxy"
HCFG[haproxy.cfg]
HMASTER[master.sock<br/>CLI]
HSTATS[admin.sock<br/>runtime API]
end
%% Client flows
WB -->|HTTPS| EXPRESS
API -->|Bearer Token| EXPRESS
HA -->|X-Ingress-Path| EXPRESS
%% Authentication
EXPRESS --> AUTH
AUTH --> SESS
AUTH --> KEYS
AUTH --> HAUSR
%% Core processing
AUTH --> ROUTES
ROUTES --> STATE
STATE --> RENDER
RENDER --> VALIDATE
VALIDATE --> APPLY
APPLY --> HCFG
APPLY -->|reload| HMASTER
%% Background
CRON -->|writes PEMs| CERTS
CRON -->|triggers render| RENDER
STATS -->|reads| HSTATS
STATS --> ROUTES
%% Data persistence
ROUTES --> ST
ROUTES --> AUDIT
AUDIT --> AU
ROUTES --> TC
%% Reads
RENDER --> ST
RENDER --> CERTS
RENDER --> TC
Component details
Client layer
- Web browser — React 19 SPA. Renders the management UI, polls the stats endpoint for live observability, server-sent-events for log streaming.
- API clients — Anything that holds a bcrypt-issued API key. Billing / orchestration systems, CLI tools, monitoring exporters.
- Home Assistant ingress — When PatchPanel runs as an HA add-on, HA’s ingress proxy is the only client. HA performs auth; PatchPanel reads
X-Remote-User-*from the request for audit attribution.
Authentication
- Session cookies (browser admin) — issued by
/api/auth/loginafter the first-run wizard creates the local admin. Sessions stored in the audit SQLite database. - API keys — bcrypt-hashed tokens with per-key permission scopes (
read,state-write,cert-renew, etc.) and optional expiry up to one year. Used asAuthorization: Bearer <token>. - HA-ingress mode — bypasses local auth entirely; trusts HA’s ingress headers and the supervisor proxy IP whitelist. No first-run wizard runs in this mode.
Server core
- Express 5 with
helmet,cors, session middleware,luscaCSRF on cookie-authenticated routes, rate limiting on auth + write endpoints. - Routes — REST handlers for state CRUD, cert lifecycle, runtime stats, audit history, trusted CAs / CRLs, ACME accounts, and the master-socket-level operations (
reload,start,stop).
State machine
This is PatchPanel’s core loop. Every write request flows through it:
- Validate — Zod schema check on the incoming candidate state.
- Render — deterministically produce
haproxy.cfgfrom the candidate state plus the bootstrap config (paths, ports, SSL). - Pre-validate — write to a temp file, run
haproxy -c -f <tmp>, parse the stderr into structured hints if it fails. - Snapshot — copy the current
haproxy.cfgto a.bakfor rollback. - Atomic swap —
writeAtomicthe rendered cfg to its target path. - Reload — send
reloadto the master CLI socket. On failure, restore the.bakand reload again. - Persist state — only after a successful reload, write the new state to disk.
- Snapshot history — append to
/data/snapshots/for time-machine rollback. - Audit — record the change in the SQLite audit log with editor, reason, and a summary of what changed.
Background services
- Renewal cron — Croner-scheduled
certbot renewruns. Configurable per-account schedule, default Mon/Thu 08:05 local time. On success, triggers a re-render so the new PEM is picked up by HAProxy’scrt-listreload. - Stats sampler — Polls HAProxy’s
admin.sockevery 5s; maintains a one-hour rolling window of frontend/backend metrics for dashboard charts. - Audit log — Append-only SQLite table, every state mutation recorded with actor, category, action, target, outcome, and timestamp.
Data layer
state.json—/data/state.json(HA addon) or/var/lib/patchpanel/state.json(standalone). The single source of truth for everything HAProxy-related.audit.sqlite— better-sqlite3, WAL-mode. Sessions + audit log share this database./etc/letsencrypt/— Certbot’s account + cert store, unchanged from its standard layout. Symlinks underlive/are referenced from the rendered HAProxycrt-list.trusted-cas/+trusted-crls/— User-uploaded PEM bundles, one file per entry, referenced from bind ssl + server lines.
HAProxy integration
haproxy.cfg— The only file HAProxy reads. Generated, never hand-edited. PatchPanel’s render output is the canonical version.master.sock— HAProxy’s master CLI. Used forreload, start/stop, and configuration introspection.admin.sock— HAProxy’s stats / runtime socket. Used for live metrics and per-server actions (drain, set weight, etc.) without requiring a reload.
Configuration
- Bootstrap config —
/etc/patchpanel/config.yaml. Paths, ports, SSL, auth strategy, log directory. Edited rarely; metadata-wrapped YAML drives a generated Settings UI. - Runtime state —
state.json. The HAProxy data model. Edited through the UI on every change.
Frontend
- React 19 + Vite + react-bootstrap. SPA served from the same Express server (
web/dist/static dir +*splatfallback). - Highcharts for live charts, @xyflow/react for topology, diff for the rendered-cfg side-by-side view.
- Uses relative asset paths so it renders correctly behind HA’s ingress subpath as well as at root.