From 3690c6d95520fa5ffd284fad4395db4dd82819a6 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Sun, 26 Apr 2026 17:11:21 -0400 Subject: [PATCH] fix: correct DNS records, peer dashboard field names, and services API response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - network_manager: api/webui DNS records now point to Caddy (172.20.0.2) instead of their container IPs so Caddy can reverse-proxy correctly - ip_utils: add webui.dev block to generated Caddyfile - config/caddy/Caddyfile: regenerated with webui.dev block - config/dns/Corefile: simplify to single forward zone (remove duplicate) - app.py peer_dashboard: rename peer_name→name, rx_bytes→transfer_rx, tx_bytes→transfer_tx to match PeerDashboard.jsx; add service_urls dict - app.py peer_services: fix DNS (10.0.0.1→real CoreDNS IP), CalDAV URL (radicale.dev:5232→calendar.dev), email structure (flat→nested smtp/imap objects), rename webdav→files, add WireGuard config text, add username field - PeerDashboard.jsx: render service icon links from service_urls Co-Authored-By: Claude Sonnet 4.6 --- api/app.py | 58 +++++++++++---- api/ip_utils.py | 4 ++ api/network_manager.py | 14 +++- config/api/caddy/Caddyfile | 113 +++++++++++------------------- config/dns/Corefile | 4 -- webui/src/pages/PeerDashboard.jsx | 34 ++++++++- 6 files changed, 132 insertions(+), 95 deletions(-) diff --git a/api/app.py b/api/app.py index a3732db..9e4cb76 100644 --- a/api/app.py +++ b/api/app.py @@ -32,7 +32,7 @@ import contextvars API_START_TIME = time.time() from network_manager import NetworkManager -from wireguard_manager import WireGuardManager +from wireguard_manager import WireGuardManager, _resolve_peer_dns from peer_registry import PeerRegistry from email_manager import EmailManager from calendar_manager import CalendarManager @@ -3086,14 +3086,27 @@ def peer_dashboard(): peer_ip = peer.get('ip', '') allowed_ips = f"{peer_ip.split('/')[0]}/32" if peer_ip else '' + domain = _configured_domain() + _svc_url_map = { + 'calendar': f'http://calendar.{domain}', + 'files': f'http://files.{domain}', + 'mail': f'http://mail.{domain}', + 'webdav': f'http://webdav.{domain}', + } + service_urls = { + svc: _svc_url_map[svc] + for svc in peer.get('service_access', []) + if svc in _svc_url_map + } return jsonify({ - 'peer_name': peer_name, + 'name': peer_name, 'ip': peer_ip, 'service_access': peer.get('service_access', []), + 'service_urls': service_urls, 'online': wg_stats.get('online'), - 'rx_bytes': wg_stats.get('transfer_rx', 0), - 'tx_bytes': wg_stats.get('transfer_tx', 0), + 'transfer_rx': wg_stats.get('transfer_rx', 0), + 'transfer_tx': wg_stats.get('transfer_tx', 0), 'last_handshake': wg_stats.get('last_handshake'), 'allowed_ips': peer.get('allowed_ips', allowed_ips), }) @@ -3112,32 +3125,51 @@ def peer_services(): server_public_key = '' wg_port = 51820 + server_endpoint = '' try: server_public_key = wireguard_manager.get_keys().get('public_key', '') wg_port = config_manager.configs.get('_identity', {}).get('wireguard_port', 51820) + srv = wireguard_manager.get_server_config() + server_endpoint = srv.get('endpoint') or '' except Exception: pass + wg_config = '' + peer_private_key = peer.get('private_key', '') + if peer_private_key: + try: + internet_access = peer.get('internet_access', True) + allowed_ips = wireguard_manager.FULL_TUNNEL_IPS if internet_access else wireguard_manager.get_split_tunnel_ips() + wg_config = wireguard_manager.get_peer_config( + peer_name=peer_name, + peer_ip=peer_ip, + peer_private_key=peer_private_key, + server_endpoint=server_endpoint, + allowed_ips=allowed_ips, + ) + except Exception: + pass + return jsonify({ + 'username': peer_name, 'wireguard': { 'ip': peer_ip, 'server_public_key': server_public_key, 'endpoint_port': wg_port, - 'dns': '10.0.0.1', + 'dns': _resolve_peer_dns(), + 'config': wg_config, }, 'email': { - 'username': f'{peer_name}@{domain}', - 'imap_host': f'mail.{domain}', - 'smtp_host': f'mail.{domain}', - 'imap_port': 993, - 'smtp_port': 587, + 'address': f'{peer_name}@{domain}', + 'smtp': {'host': f'mail.{domain}', 'port': 587}, + 'imap': {'host': f'mail.{domain}', 'port': 993}, }, 'caldav': { - 'url': f'http://radicale.{domain}:5232', + 'url': f'http://calendar.{domain}', 'username': peer_name, }, - 'webdav': { - 'url': f'http://webdav.{domain}', + 'files': { + 'url': f'http://files.{domain}', 'username': peer_name, }, }) diff --git a/api/ip_utils.py b/api/ip_utils.py index 2f98cd9..d6dda4b 100644 --- a/api/ip_utils.py +++ b/api/ip_utils.py @@ -189,6 +189,10 @@ http://api.{domain} {{ reverse_proxy cell-api:3000 }} +http://webui.{domain} {{ + reverse_proxy cell-webui:80 +}} + # Catch-all for direct IP / localhost :80 {{ handle /api/* {{ diff --git a/api/network_manager.py b/api/network_manager.py index a64a76e..be2efe3 100644 --- a/api/network_manager.py +++ b/api/network_manager.py @@ -150,13 +150,21 @@ class NetworkManager(BaseServiceManager): return {'restarted': restarted, 'warnings': warnings} def _build_dns_records(self, cell_name: str, ip_range: str) -> List[Dict]: - """Build the standard set of DNS A records for the given subnet.""" + """Build the standard set of DNS A records for the given subnet. + + All user-facing names resolve to the Caddy reverse proxy (caddy IP) so + the Host header is passed through and Caddy routes based on it. + Exception: calendar/files/mail/webdav use dedicated virtual IPs so that + iptables per-service firewall rules can target them by destination IP. + api and webui also go through Caddy — they don't have their own VIPs and + their containers don't serve HTTP on port 80. + """ import ip_utils ips = ip_utils.get_service_ips(ip_range) return [ {'name': cell_name, 'type': 'A', 'value': ips['caddy']}, - {'name': 'api', 'type': 'A', 'value': ips['api']}, - {'name': 'webui', 'type': 'A', 'value': ips['webui']}, + {'name': 'api', 'type': 'A', 'value': ips['caddy']}, + {'name': 'webui', 'type': 'A', 'value': ips['caddy']}, {'name': 'calendar', 'type': 'A', 'value': ips['vip_calendar']}, {'name': 'files', 'type': 'A', 'value': ips['vip_files']}, {'name': 'mail', 'type': 'A', 'value': ips['vip_mail']}, diff --git a/config/api/caddy/Caddyfile b/config/api/caddy/Caddyfile index b5fe71c..b41bc24 100644 --- a/config/api/caddy/Caddyfile +++ b/config/api/caddy/Caddyfile @@ -1,92 +1,57 @@ -# Personal Internet Cell - Caddy Configuration -# This serves as the main reverse proxy and TLS termination point - -# Global settings { - # Auto-generate certificates for .cell domains - auto_https disable_redirects + auto_https off } -# Main cell domain - replace 'mycell' with your cell name -mycell.cell { - # TLS with internal CA - tls internal - - # API endpoints +# Main cell domain — no service-IP restriction needed +http://pic0.dev, http://172.20.0.2:80 { handle /api/* { reverse_proxy cell-api:3000 } - - # Web UI - handle / { - reverse_proxy cell-webui:80 - } - - # Email web interface - handle /mail { - reverse_proxy cell-mail:80 - } - - # Calendar and contacts - handle /calendar { + handle /calendar* { reverse_proxy cell-radicale:5232 } - - # File storage - handle /files { - reverse_proxy cell-webdav:80 - } - - # DNS management interface - handle /dns { - reverse_proxy cell-dns:8080 - } - - # RainLoop Webmail - handle_path /webmail/* { - reverse_proxy cell-rainloop:8888 - } - - # FileGator File Browser - handle /files-ui* { + handle /files* { reverse_proxy cell-filegator:8080 } + handle /webmail* { + reverse_proxy cell-rainloop:8888 + } + handle { + reverse_proxy cell-webui:80 + } } -# Peer cell domains (will be dynamically added) -# Example: bob.cell { -# reverse_proxy cell-wireguard:51820 -# } +# Per-service virtual IPs — each gets its own IP so iptables can target them +http://calendar.dev, http://172.20.0.21:80 { + reverse_proxy cell-radicale:5232 +} -# Local development -localhost { - # API endpoints +http://files.dev, http://172.20.0.22:80 { + reverse_proxy cell-filegator:8080 +} + +http://mail.dev, http://webmail.dev, http://172.20.0.23:80 { + reverse_proxy cell-rainloop:8888 +} + +http://webdav.dev, http://172.20.0.24:80 { + reverse_proxy cell-webdav:80 +} + +http://api.dev { + reverse_proxy cell-api:3000 +} + +http://webui.dev { + reverse_proxy cell-webui:80 +} + +# Catch-all for direct IP / localhost +:80 { handle /api/* { reverse_proxy cell-api:3000 } - - # Web UI - handle / { + handle { reverse_proxy cell-webui:80 } - - # Email web interface - handle /mail { - reverse_proxy cell-mail:80 - } - - # Calendar and contacts - handle /calendar { - reverse_proxy cell-radicale:5232 - } - - # File storage - handle /files { - reverse_proxy cell-webdav:80 - } - - # DNS management interface - handle /dns { - reverse_proxy cell-dns:8080 - } -} \ No newline at end of file +} diff --git a/config/dns/Corefile b/config/dns/Corefile index ad1f4c2..e30d384 100644 --- a/config/dns/Corefile +++ b/config/dns/Corefile @@ -10,7 +10,3 @@ dev { log } -local.dev { - file /data/local.zone - log -} diff --git a/webui/src/pages/PeerDashboard.jsx b/webui/src/pages/PeerDashboard.jsx index 87b0ee3..9a89bae 100644 --- a/webui/src/pages/PeerDashboard.jsx +++ b/webui/src/pages/PeerDashboard.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; -import { Wifi, ArrowDown, ArrowUp, Clock } from 'lucide-react'; +import { Wifi, ArrowDown, ArrowUp, Clock, Calendar, FolderOpen, Mail, Globe } from 'lucide-react'; import { peerAPI } from '../services/api'; function formatBytes(bytes) { @@ -114,6 +114,38 @@ export default function PeerDashboard() {

Quick Access

+ {peer.service_urls && Object.keys(peer.service_urls).length > 0 ? ( +
+ {peer.service_urls.calendar && ( + + + Calendar + + )} + {peer.service_urls.files && ( + + + Files + + )} + {peer.service_urls.mail && ( + + + Mail + + )} + {peer.service_urls.webdav && ( + + + WebDAV + + )} +
+ ) : null}