Services box LAN IP is now a setting (gear icon → Network section),
not a panel in the Services tab. Set it once, saved via API on blur
when authenticated. Loaded on app startup from /api/services/config.
Removed: Services Host panel, caddyIpInput state, saveCaddyIp function
Added: Network section in SettingsPanel with caddy_ip field
- Auto-saves to backend on blur (requires active TOTP session)
- Loaded from /api/services/config on mount
- Services tab checklist reads from status.caddy_ip
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
The management computer (VLAN 99) only runs the switch manager tool
and holds SSH keys/TOTP secrets. Caddy and services (Plex, etc.) run
on a SEPARATE computer on LAN.
Backend:
- New /api/services/config endpoint to store services box LAN IP
- services-config.json persists caddy_ip separately from mgmt_ip
- Port forward creation targets caddy_ip (LAN services box), not
mgmt_ip (VLAN 99 management computer)
- Deploy endpoint uses caddy_ip for all Caddy/NAT references
- _get_caddy_ip() helper reads from services config
Frontend:
- New "Services Host" panel: configure Caddy box LAN IP
- Checklist shows caddy_ip status, not mgmt_ip
- Port forward and deploy pass caddy_ip to backend
- Clear labels: "Services box" vs "Management computer"
Architecture:
VLAN 99: management computer (this tool, SSH keys, TOTP)
LAN: services computer (Caddy, Plex, Docker containers)
WAN port forward 443 → services computer LAN IP
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
- DNS profile no longer hardcoded to "house"
- When selecting a single VLAN for a WG peer, auto-sets the DNS
profile to that VLAN's name (e.g. select "Staff" VLAN → profile
becomes "staff")
- Multiple VLANs: keeps current profile, user can change manually
- Input shows context: auto-set vs manual vs needs VLAN selection
- Profile name matches ctrld upstream names so the correct ControlD
profile applies to VPN DNS queries
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
When creating a WireGuard peer on OPNsense:
- Client config DNS now points to OPNsense's IP (not tunnel gateway)
so DNS flows: client → OPNsense → Unbound → ctrld → ControlD
- New dns_profile field: select which ControlD profile applies to
VPN clients (default: "house" for VLAN 99)
- Generates ctrld.toml instructions for WireGuard tunnel subnet
routing — tells user what to add so ctrld routes VPN DNS queries
to the correct ControlD profile
- QR modal now shows ControlD setup instructions alongside the
WireGuard config
This solves the Android Private DNS conflict: WireGuard's DNS setting
overrides Android's Private DNS, pointing to OPNsense which runs
Unbound → ctrld. No Private DNS toggle needed on the phone.
Multi-VLAN access for VPN peers works because the peer is on the
WireGuard interface (not on any VLAN). OPNsense routes between the
tunnel and VLANs per firewall rules. VLAN isolation preserved.
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
Removed duplicate firewall policy endpoints (kept existing ones at
/api/firewall/* which match the frontend).
Port Forwarding tab:
- Create/delete OPNsense NAT port forwards via API
- Track rule UUIDs for clean removal
- Form: protocol, WAN port, target IP:port, description
- Table: active forwards with one-click remove
- Note: for HTTP services, use Services tab (Caddy) instead
PoE Budget tab:
- Visual power bar: used/total/remaining watts with percentage
- Color-coded thresholds: green (<75%), orange (75-90%), red (>90%)
- Warning banner when budget exceeds 85%
- Per-port power draw grid with status indicators
- Auto-parsed from cached switch PoE status
Network Topology tab:
- Auto-generated from live switch + OPNsense data
- Router node: IP, version, online/offline status
- Switch node: hostname, IP, port up/down counts
- Trunk link visualization between router and switch
- VLAN fan-out cards: port counts, device counts, subnets
- One-click refresh
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
Previous approach tried to put a reverse proxy on OPNsense or poke
firewall holes — both wrong. Correct architecture:
- Caddy stays on LAN management computer (where it already is)
- WAN: port 443 forwarded to Caddy. Only port exposed externally.
- LAN devices reach services directly via Caddy
- Isolated VLANs (IoT, Guest) use public FQDNs (plex.mydomain.com)
- OPNsense NAT reflection handles this internally — traffic never
leaves the network, but IoT is treated exactly like an external user
- Zero cross-VLAN access. No pinholes. Full isolation preserved.
IoT = untrusted = same access as someone on the internet. This is the
correct security model — no exceptions for "just one port."
Deploy endpoint now: writes Caddyfile entries, checks NAT reflection
status, provides setup checklist for port forward + reflection toggle.
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
Service proxy fix:
- DNS now resolves service FQDNs to OPNsense gateway IP (not mgmt box)
- Devices reach services through their own gateway — never touch other
VLANs. Full VLAN isolation preserved.
- No new firewall rules needed — devices can already reach their gateway
- Deploy tries Caddy on OPNsense first, then HAProxy plugin, then gives
manual setup instructions
- Removed "allowed VLANs" selector — all VLANs can reach services
automatically through the gateway reverse proxy
VLAN time-based schedules:
- New vlan_enable/vlan_disable scheduler actions
- Creates/removes OPNsense firewall allow-outbound rules on schedule
- Switch ports stay up so devices reconnect when re-enabled
- Tracked rule UUIDs for clean enable/disable cycles
- VlanScheduleWizard UI component with paired off/on times
- Quick presets: Guest WiFi midnight-6am, Business 6pm-8am weekdays,
Kids 9pm-7am, IoT 11pm-5am
- ntfy notifications on VLAN enable/disable events
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
Firewall inter-VLAN policy matrix:
- Visual VLAN-to-VLAN matrix with click-to-set policies
- Policy types: block, allow, one-way, printer, service-ports
- Printer template: other VLANs reach ports 9100/631/443/515, printers
cannot initiate back — solves the "printer VLAN" use case
- Generates both switch ACLs AND OPNsense firewall rules
- Preview commands before pushing, auto-backup before changes
Service proxy (LAN services via FQDN without inter-VLAN access):
- Register services with FQDN + backend URL + allowed VLANs
- Deploy generates Caddyfile entries, Unbound DNS overrides, and
firewall rules allowing only port 443 to the proxy
- Pattern: device on VLAN 30 → DNS resolves to mgmt box → Caddy
proxies to actual LAN server — no VLAN-to-VLAN access needed
ntfy push notifications:
- Configure ntfy.sh or self-hosted ntfy server
- Alert events: connectivity lost/restored, PoE budget >85%,
backup failures, push failures
- Integrated into poll loop — alerts fire on state transitions
- Test notification button
Scheduled operations:
- Cron-like scheduler for automated backups and connectivity checks
- Background thread checks every 60 seconds
- Per-schedule: name, action, hour, minute, days (mon,wed,fri or *)
- Run-now button for manual trigger
- ntfy notifications on scheduled task completion/failure
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
New capabilities:
- Unified Network tab: provision VLANs across switch + OPNsense in one
operation — select ports, set PoE per-port, auto-configure DHCP and
firewall rules on OPNsense
- Automatic backup before every change: switch running-config via SSH,
OPNsense full XML config export via API
- Backup/Restore tab: manual backups, download, restore with safety net
(creates backup of current state before restoring)
- Connectivity safety checks: pre-change and post-change SSH/API probes
to both devices — warns if connectivity lost after push
- Safe push endpoint (/api/switch/push-safe) wraps existing push with
auto-backup and connectivity verification
- Backup pruning (keeps last 50 per device)
https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
New ACL template: "IoT VLAN — isolated + access local services via FQDN"
- Permits DNS to the configured resolver (so FQDNs resolve to LAN IPs)
- Permits traffic to the servers VLAN subnet only (NAS, Home Assistant, etc.)
- Blocks all other RFC1918 — users, cameras, management stay isolated
- Permits internet
- serverSubnet param auto-suggested from the vlans list if a VLAN named
"Servers" exists; otherwise user enters it
Template description explains the full picture:
- How FQDN access works through the ACL (DNS → LAN IP → ACL permits it)
- Offline resilience: ctrld must be on OPNsense (not management PC) to
survive internet outages; split-horizon in DNS tab makes *.lan resolve
from local dnsmasq without any internet dependency
- Servers VLAN security: lock down servers VLAN inbound ACL by port
so IoT can only reach specific service ports, not all server traffic
Strict IoT template description tightened — now clearly says "zero LAN
access" so users pick the right template for their use case.
Modal: serverSubnet field added alongside existing ctrldIp/nvrIp/pbxIp
params; preview correctly passes serverSubnet to the build function.
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
Dashboard tab (now default landing page):
- Service status cards: Switch / OPNsense / ctrld DNS / WireGuard
each clickable to navigate to the relevant tab
- Setup checklist with progress bar: 8 steps from switch connection
through WireGuard, each uncompleted step is clickable and navigates
to the tab where that step is configured
- VLAN health grid: per-VLAN card showing device count (from DHCP
leases), Relay / DNS / ACL status dots, and inline "+ add" nudges
for anything not yet configured
- Quick Access buttons to all tabs
- All data loaded in parallel from existing API endpoints — no new
backend endpoints needed
ACL template fixes:
- VoIP template label updated: "local desk phones" clarifies when to use it
- VoIP description now explains: ACL is subnet-based (MAC randomization
irrelevant), switch IS the L3 enforcement point (no OPNsense inter-VLAN
rules needed), and gives the correct mobile-softphone recommendation:
configure with public FQDN via OPNsense port forwarding + NAT reflection
- IoT template label/description updated to explicitly list smart TVs,
printers, IoT as targets and mention it blocks PBX + all LAN services
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
ACL backend:
- AclRule gains optional port_end field; build_acl generates
"range X Y" when both port and port_end are set (needed for RTP)
New ACL template — "SIP Phone VLAN — Asterisk / FreePBX access":
- Permits SIP signaling UDP/TCP 5060 to PBX IP
- Permits SIP/TLS TCP 5061 to PBX IP
- Permits RTP audio UDP range 10000-20000 to PBX IP (uses new range syntax)
- Blocks management VLAN 99
- Permits internet and all other traffic
- Requires entering the Asterisk server IP (restricts SIP/RTP to that
exact host, not the whole VLAN subnet)
Template description explains:
- Why OPNsense firewall rules are also needed (inter-VLAN routing)
- Exactly which OPNsense rules to add (including return RTP)
- Remote access options: WebRTC via Caddy reverse proxy (recommended)
and SIP/TLS with fail2ban for traditional SIP clients
Template modal:
- New pbxIp param field shown for VoIP template
- Description box scrollable for longer template descriptions
- Preview renders "range X Y" for port range rules
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
When OPNsense WG plugin is detected:
- Show recommendation banner at top explaining why router-level VPN
is better than running it on the management PC
- OPNsense WG section appears first (recommended path)
- Local WG panels relabeled as "backup / this machine only"
- Local WG description changes to explain the limitation (VLAN 99 only)
When OPNsense is not connected:
- Local WG shown normally as the primary option
- Tip nudge added pointing to DHCP tab to unlock router-level VPN
- OPNsense WG section shown below as a setup prompt
In both cases:
- SSH Tunnel renamed to "Emergency Fallback" with clearer description
- Pre-select servers VLAN when OPNsense WG server becomes available
(most users want remote access to services, not IoT/cameras)
- Connected Peers / Clients panels prefixed "Local WG —" for clarity
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
Moves WireGuard off the management computer and onto OPNsense so any
device can VPN home without touching the management PC. Each peer is
restricted to only the VLANs you select (e.g. phone gets VLAN 10 only,
laptop gets VLAN 10 + 20). Private keys are generated on the mgmt PC
and never sent to OPNsense — only the public key is registered.
Backend (switch_backend.py):
- /api/opnsense/wireguard/status — check plugin, server, peers
- /api/opnsense/wireguard/setup-server — create wg1 on OPNsense via API
- DELETE /api/opnsense/wireguard/server — tear down server
- /api/opnsense/wireguard/add-peer — generate keypair, register peer,
link to server, return .conf
- DELETE /api/opnsense/wireguard/peer/{uuid} — revoke peer
- /api/opnsense/wireguard/peer-config/{name} — fetch saved .conf
Frontend (ers5952-manager.jsx):
- New OPNsenseWGSection component added to VPN tab below local WireGuard
- Progressive UI: not configured → plugin missing → server setup →
peer management (VLAN checkboxes) → QR/.conf download
- Firewall rules guidance panel auto-generated from active peers showing
exactly which OPNsense rules to add per VLAN
- vlans prop threaded through to WireGuardTab so VLAN names/colors
appear on peer badges and in the VLAN selector
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
- Backend: _get_relay_status() reads current ip helper-address per VLAN
- Backend: _build_relay_cmds() generates ERS 5952 relay CLI commands
- Backend: /api/dhcp/relay/status and /api/dhcp/relay/configure endpoints
- Backend: dhcp_overview now includes relay status in response
- Frontend: VLAN_MAP + VlanBadge + vlanFromIp() helpers for consistent labelling
- Frontend: RelayPanel shows per-VLAN relay status grid with push button;
VLAN 99 always shown as locked/local, VLANs 10/20/30/40/50 show live
relay target and purpose note
- Frontend: Reservations table gains VLAN column and inline purpose note
(from descr/notes or VLAN_MAP fallback)
VLAN 99 is excluded from relay at both backend and UI level — it is the
switch management / OPNsense recovery path.
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
Features added:
- Port 53 conflict resolution: auto-detect/fix systemd-resolved stub listener
on Linux; instructions for OPNsense Unbound (ctrld auto-terminates it)
- DNS enforcement ACLs: generate ERS 5952 ACL commands that permit DNS only
to ctrld IP and block all other port 53/853 traffic per VLAN
- Inter-VLAN routing ACL templates: Staff, IoT, Guest, Camera profiles with
live preview and parameter inputs (ctrld IP, NVR IP, subnet)
- Local hostname resolution: dnsmasq Docker service for .lan split-horizon DNS;
manage hostname→IP mappings via UI; generates dnsmasq.conf and ctrld.toml
upstream.local block
- Fix ctrld.toml format: correct [listener.0], [network.N], [upstream.N] table
notation (was using wrong [[array]] notation); matches official docs format
- Backend docstrings: added docstrings to all previously undocumented functions
- README: new sections for port 53 conflict resolution, DNS enforcement ACLs,
ACL templates, and local hostname resolution (dnsmasq)
- Fix Python 3.11 f-string syntax errors in Avaya_5952_setup.py (backslash
in f-string expressions, same-type quote in dict access); embed now succeeds
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6