- log_manager: add collect_container_logs (appends docker logs to container_<name>.log),
get_container_log_lines, rotate_container_log, get_all_log_file_infos
- app.py: new endpoints /api/logs/files (all log file sizes), /api/logs/containers/<name>
(collect+return stored container logs); rotate endpoint now handles both service and container logs
- Logs page: split into API Service Logs tab (python manager logs) and Container Logs tab
(persistent docker stdout/stderr); Statistics tab shows both kinds with per-row rotate;
each tab has a description explaining what it shows and where files live
- wireguard_manager: test_connectivity peer_ip=None guard (already in previous commit, now rebuilt)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WireGuardManager.test_connectivity: make peer_ip optional so health_check
can call it without args (was logging ERROR on every health poll)
- Logs page: add ALL option to service selector (uses search across all services)
- Logs page: show service tag on each log line when in ALL/search mode
- Logs page: require window.confirm before rotating logs to prevent accidental data loss
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The connectivity endpoint was calling routing_manager.test_connectivity()
(no args, internal health check) instead of test_routing_connectivity(target_ip).
Also ping/traceroute aren't installed in the API container; run them via
docker exec cell-wireguard instead.
Updated test_api_endpoints to mock test_routing_connectivity and cover
the new DELETE /firewall/<id> and GET /live-iptables endpoints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added a path guard: if the config file resolves to /tmp/ or a pytest
temp dir, _syncconf bails out immediately. Without this, tests calling
add_peer/remove_peer with a temp-dir WireGuardManager would connect to
the live cell-wireguard container and remove production peers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server-side access control:
- firewall_manager.py: per-peer iptables FORWARD rules in WireGuard container;
virtual IPs on Caddy (172.20.0.21-24) for per-service DROP/ACCEPT targeting
- CoreDNS Corefile regenerated with ACL blocks for blocked services per peer
- POST /api/wireguard/apply-enforcement re-applies rules after WireGuard restart;
wg0.conf PostUp calls it via curl so rules restore automatically on container start
WireGuard fixes:
- _syncconf uses `wg set peer` instead of `wg syncconf` to avoid resetting ListenPort
- add_peer validates AllowedIPs must be /32 — rejects full/split tunnel CIDRs that
would route internet or LAN traffic to that peer
- _config_file() checks for linuxserver wg_confs/ subdirectory first
UI:
- Peers page fetches /api/wireguard/peers/statuses for live handshake data;
status badge now shows real Online/Offline + seconds since last handshake
- IP field removed from Add Peer form (auto-assigned from 10.0.0.0/24)
Tests (246 pass):
- test_firewall_manager.py: 22 tests for ACL generation, iptables rule correctness,
comment tagging, clear_peer_rules filter logic
- test_peer_wg_integration.py: 10 tests for /32 enforcement, IP auto-assignment,
syncconf called on add/remove
- test_wireguard_manager.py: updated to reflect correct IPs and /32 requirement
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Peer creation/edit form now configures:
- Tunnel mode: full (0.0.0.0/0) or split (PIC only)
- Per-service access toggles (calendar, files, mail, webdav)
- Peer-to-peer communication toggle
- Optional calendar account creation
- Access capability badges in peer list
Bug fixes:
- DNS in client configs was 8.8.8.8 / 172.20.0.2 — now 172.20.0.3 (CoreDNS)
This was why .cell domains didn't resolve on connected VPN peers
- get_peer_config API uses stored internet_access to set AllowedIPs
- New PUT /api/peers/<name> endpoint with config_changed detection
- POST /api/peers/<name>/clear-reinstall clears reinstall flag after download
- Routing page reads real host routes via /proc/1/net/route (pid: host)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WireGuard default changed to full tunnel (0.0.0.0/0) — all peer traffic
routes through PIC server so internet latency matches server's clean 41ms
- UI tunnel toggle now defaults to Full tunnel
- API /peers/config accepts allowed_ips param so UI toggle wires through
- Routing page reads real host routes via /proc/1/net/route (pid: host)
instead of mock data; shows ens18/192.168.31.1 correctly
- Add iproute2 + util-linux to API Dockerfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wg show outputs "listening port" not "listen port" — substring mismatch
caused port status to always show Blocked. Add webdav.cell, webmail.cell,
api.cell to Caddyfile and cell.zone so VPN peers can reach all services.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Assign static IPs to all 13 containers (172.20.0.2–13) so DNS zone
records match actual container IPs regardless of start order.
- Update cell.zone: all .cell domains now point to cell-caddy (172.20.0.2)
which is the correct single entry point via Caddy reverse proxy.
- Create config/radicale/config so the calendar container actually starts.
- Fix webdav: replace empty users.passwd with USERNAME/PASSWORD env vars.
- Fix DNS fallback IP in wireguard_manager: 172.20.0.2→172.20.0.3 (cell-dns).
- Remove duplicate http://ui.cell from Caddyfile.
- Add persistent data volumes for rainloop and filegator.
- Fix mail domainname placeholder (yourdomain.com→cell.local).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- check_port_open now checks if wg0 interface is actually listening (via
'wg show wg0') instead of requiring a live peer handshake. This means
the port shows 'Open' whenever WireGuard is running, not only when a
peer has connected recently.
- get_peer_config defaults to split-tunnel AllowedIPs (10.0.0.0/24,
172.20.0.0/16) so VPN clients only route cell service traffic through
the tunnel. Local LAN traffic (192.168.x.x etc.) stays direct, fixing
the 60-120ms penalty when pinging local hosts while on VPN.
- Peer config modal now uses cell DNS (172.20.0.2) so .cell domains
resolve correctly with both split and full tunnel.
- Added split/full tunnel toggle in the peer config modal so users can
download either config variant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a full-tunnel VPN client pings the server's own public IP, traffic
loops out through Docker's external interface and back, causing 60-120ms
jitter. The DNAT PostUp rule intercepts packets from wg0 destined for the
public IP and redirects them to 10.0.0.1 (the VPN interface), keeping
traffic entirely inside the tunnel.
Also updates SERVER_ADDRESS from 172.20.0.1/16 to 10.0.0.1/24 to avoid
routing conflict with the Docker bridge network on eth0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix CoreDNS not loading .cell zones (wrong Corefile path, now uses -conf flag)
- Fix WireGuard server address conflict (172.20.0.1/16 overlapped with Docker
network; changed to 10.0.0.1/24 to eliminate duplicate routes)
- Add SERVERMODE=true and sysctls to WireGuard docker-compose for server mode
- Fix DNS zone file parser to handle 4-field records (name IN type value)
- Add get_dns_records() to NetworkManager; mount data/dns into API container
- Fix peer config endpoint: look up IP/key from registry, use real endpoint
- Add bulk peer statuses endpoint keyed by public_key
- Normalize snake_case API fields to camelCase in WireGuard UI
- Add port check endpoint (checks via live handshake, not unreliable TCP probe)
- Add Caddy virtual hosts for ui/calendar/files/mail .cell domains (HTTP only)
- Fix cell config domain default from cell.local to cell
- Fix Routing Network Config tab (was calling hardcoded localhost:3000)
- Fix DNS records display (record.value not record.ip)
- Move service access guide to top of Dashboard with login hints
- Add /api/routing/setup endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WireGuardManager: get_external_ip() (cached 1h), check_port_open(),
get_server_config() returning public_key + detected endpoint
- API: /api/wireguard/server-config returns real external IP;
/api/wireguard/refresh-ip forces re-detection;
/api/wireguard/peers/config now looks up peer IP + private key from
registry and uses real server endpoint automatically
- Fix doubled port in Endpoint (178.x:51820:51820 → 178.x:51820)
- Fix Address=/32 when peer_ip already has mask
- WebUI nginx: proxy /api/ and /health to cell-api (fixes localhost:3000
hardcode — UI now works from any machine)
- api.js: baseURL='' so all calls go through nginx proxy
- WireGuard page: show Server Endpoint card with external IP, endpoint,
public key, and Refresh IP button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>