fix: correct DNS records, peer dashboard field names, and services API response
- 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 <noreply@anthropic.com>
This commit is contained in:
+45
-13
@@ -32,7 +32,7 @@ import contextvars
|
|||||||
API_START_TIME = time.time()
|
API_START_TIME = time.time()
|
||||||
|
|
||||||
from network_manager import NetworkManager
|
from network_manager import NetworkManager
|
||||||
from wireguard_manager import WireGuardManager
|
from wireguard_manager import WireGuardManager, _resolve_peer_dns
|
||||||
from peer_registry import PeerRegistry
|
from peer_registry import PeerRegistry
|
||||||
from email_manager import EmailManager
|
from email_manager import EmailManager
|
||||||
from calendar_manager import CalendarManager
|
from calendar_manager import CalendarManager
|
||||||
@@ -3086,14 +3086,27 @@ def peer_dashboard():
|
|||||||
|
|
||||||
peer_ip = peer.get('ip', '')
|
peer_ip = peer.get('ip', '')
|
||||||
allowed_ips = f"{peer_ip.split('/')[0]}/32" if peer_ip else ''
|
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({
|
return jsonify({
|
||||||
'peer_name': peer_name,
|
'name': peer_name,
|
||||||
'ip': peer_ip,
|
'ip': peer_ip,
|
||||||
'service_access': peer.get('service_access', []),
|
'service_access': peer.get('service_access', []),
|
||||||
|
'service_urls': service_urls,
|
||||||
'online': wg_stats.get('online'),
|
'online': wg_stats.get('online'),
|
||||||
'rx_bytes': wg_stats.get('transfer_rx', 0),
|
'transfer_rx': wg_stats.get('transfer_rx', 0),
|
||||||
'tx_bytes': wg_stats.get('transfer_tx', 0),
|
'transfer_tx': wg_stats.get('transfer_tx', 0),
|
||||||
'last_handshake': wg_stats.get('last_handshake'),
|
'last_handshake': wg_stats.get('last_handshake'),
|
||||||
'allowed_ips': peer.get('allowed_ips', allowed_ips),
|
'allowed_ips': peer.get('allowed_ips', allowed_ips),
|
||||||
})
|
})
|
||||||
@@ -3112,32 +3125,51 @@ def peer_services():
|
|||||||
|
|
||||||
server_public_key = ''
|
server_public_key = ''
|
||||||
wg_port = 51820
|
wg_port = 51820
|
||||||
|
server_endpoint = ''
|
||||||
try:
|
try:
|
||||||
server_public_key = wireguard_manager.get_keys().get('public_key', '')
|
server_public_key = wireguard_manager.get_keys().get('public_key', '')
|
||||||
wg_port = config_manager.configs.get('_identity', {}).get('wireguard_port', 51820)
|
wg_port = config_manager.configs.get('_identity', {}).get('wireguard_port', 51820)
|
||||||
|
srv = wireguard_manager.get_server_config()
|
||||||
|
server_endpoint = srv.get('endpoint') or '<SERVER_IP>'
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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({
|
return jsonify({
|
||||||
|
'username': peer_name,
|
||||||
'wireguard': {
|
'wireguard': {
|
||||||
'ip': peer_ip,
|
'ip': peer_ip,
|
||||||
'server_public_key': server_public_key,
|
'server_public_key': server_public_key,
|
||||||
'endpoint_port': wg_port,
|
'endpoint_port': wg_port,
|
||||||
'dns': '10.0.0.1',
|
'dns': _resolve_peer_dns(),
|
||||||
|
'config': wg_config,
|
||||||
},
|
},
|
||||||
'email': {
|
'email': {
|
||||||
'username': f'{peer_name}@{domain}',
|
'address': f'{peer_name}@{domain}',
|
||||||
'imap_host': f'mail.{domain}',
|
'smtp': {'host': f'mail.{domain}', 'port': 587},
|
||||||
'smtp_host': f'mail.{domain}',
|
'imap': {'host': f'mail.{domain}', 'port': 993},
|
||||||
'imap_port': 993,
|
|
||||||
'smtp_port': 587,
|
|
||||||
},
|
},
|
||||||
'caldav': {
|
'caldav': {
|
||||||
'url': f'http://radicale.{domain}:5232',
|
'url': f'http://calendar.{domain}',
|
||||||
'username': peer_name,
|
'username': peer_name,
|
||||||
},
|
},
|
||||||
'webdav': {
|
'files': {
|
||||||
'url': f'http://webdav.{domain}',
|
'url': f'http://files.{domain}',
|
||||||
'username': peer_name,
|
'username': peer_name,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -189,6 +189,10 @@ http://api.{domain} {{
|
|||||||
reverse_proxy cell-api:3000
|
reverse_proxy cell-api:3000
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
http://webui.{domain} {{
|
||||||
|
reverse_proxy cell-webui:80
|
||||||
|
}}
|
||||||
|
|
||||||
# Catch-all for direct IP / localhost
|
# Catch-all for direct IP / localhost
|
||||||
:80 {{
|
:80 {{
|
||||||
handle /api/* {{
|
handle /api/* {{
|
||||||
|
|||||||
+11
-3
@@ -150,13 +150,21 @@ class NetworkManager(BaseServiceManager):
|
|||||||
return {'restarted': restarted, 'warnings': warnings}
|
return {'restarted': restarted, 'warnings': warnings}
|
||||||
|
|
||||||
def _build_dns_records(self, cell_name: str, ip_range: str) -> List[Dict]:
|
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
|
import ip_utils
|
||||||
ips = ip_utils.get_service_ips(ip_range)
|
ips = ip_utils.get_service_ips(ip_range)
|
||||||
return [
|
return [
|
||||||
{'name': cell_name, 'type': 'A', 'value': ips['caddy']},
|
{'name': cell_name, 'type': 'A', 'value': ips['caddy']},
|
||||||
{'name': 'api', 'type': 'A', 'value': ips['api']},
|
{'name': 'api', 'type': 'A', 'value': ips['caddy']},
|
||||||
{'name': 'webui', 'type': 'A', 'value': ips['webui']},
|
{'name': 'webui', 'type': 'A', 'value': ips['caddy']},
|
||||||
{'name': 'calendar', 'type': 'A', 'value': ips['vip_calendar']},
|
{'name': 'calendar', 'type': 'A', 'value': ips['vip_calendar']},
|
||||||
{'name': 'files', 'type': 'A', 'value': ips['vip_files']},
|
{'name': 'files', 'type': 'A', 'value': ips['vip_files']},
|
||||||
{'name': 'mail', 'type': 'A', 'value': ips['vip_mail']},
|
{'name': 'mail', 'type': 'A', 'value': ips['vip_mail']},
|
||||||
|
|||||||
+39
-74
@@ -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 off
|
||||||
auto_https disable_redirects
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main cell domain - replace 'mycell' with your cell name
|
# Main cell domain — no service-IP restriction needed
|
||||||
mycell.cell {
|
http://pic0.dev, http://172.20.0.2:80 {
|
||||||
# TLS with internal CA
|
|
||||||
tls internal
|
|
||||||
|
|
||||||
# API endpoints
|
|
||||||
handle /api/* {
|
handle /api/* {
|
||||||
reverse_proxy cell-api:3000
|
reverse_proxy cell-api:3000
|
||||||
}
|
}
|
||||||
|
handle /calendar* {
|
||||||
# Web UI
|
|
||||||
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
|
reverse_proxy cell-radicale:5232
|
||||||
}
|
}
|
||||||
|
handle /files* {
|
||||||
# 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* {
|
|
||||||
reverse_proxy cell-filegator:8080
|
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)
|
# Per-service virtual IPs — each gets its own IP so iptables can target them
|
||||||
# Example: bob.cell {
|
http://calendar.dev, http://172.20.0.21:80 {
|
||||||
# reverse_proxy cell-wireguard:51820
|
reverse_proxy cell-radicale:5232
|
||||||
# }
|
}
|
||||||
|
|
||||||
# Local development
|
http://files.dev, http://172.20.0.22:80 {
|
||||||
localhost {
|
reverse_proxy cell-filegator:8080
|
||||||
# API endpoints
|
}
|
||||||
|
|
||||||
|
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/* {
|
handle /api/* {
|
||||||
reverse_proxy cell-api:3000
|
reverse_proxy cell-api:3000
|
||||||
}
|
}
|
||||||
|
handle {
|
||||||
# Web UI
|
|
||||||
handle / {
|
|
||||||
reverse_proxy cell-webui:80
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,3 @@ dev {
|
|||||||
log
|
log
|
||||||
}
|
}
|
||||||
|
|
||||||
local.dev {
|
|
||||||
file /data/local.zone
|
|
||||||
log
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
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';
|
import { peerAPI } from '../services/api';
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
@@ -114,6 +114,38 @@ export default function PeerDashboard() {
|
|||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2 className="text-base font-semibold text-gray-900 mb-3">Quick Access</h2>
|
<h2 className="text-base font-semibold text-gray-900 mb-3">Quick Access</h2>
|
||||||
|
{peer.service_urls && Object.keys(peer.service_urls).length > 0 ? (
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
|
||||||
|
{peer.service_urls.calendar && (
|
||||||
|
<a href={peer.service_urls.calendar} target="_blank" rel="noopener noreferrer"
|
||||||
|
className="flex flex-col items-center gap-1.5 p-3 rounded-lg border border-gray-200 hover:border-primary-400 hover:bg-primary-50 transition-colors text-sm text-gray-700 hover:text-primary-700">
|
||||||
|
<Calendar className="h-6 w-6" />
|
||||||
|
Calendar
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{peer.service_urls.files && (
|
||||||
|
<a href={peer.service_urls.files} target="_blank" rel="noopener noreferrer"
|
||||||
|
className="flex flex-col items-center gap-1.5 p-3 rounded-lg border border-gray-200 hover:border-primary-400 hover:bg-primary-50 transition-colors text-sm text-gray-700 hover:text-primary-700">
|
||||||
|
<FolderOpen className="h-6 w-6" />
|
||||||
|
Files
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{peer.service_urls.mail && (
|
||||||
|
<a href={peer.service_urls.mail} target="_blank" rel="noopener noreferrer"
|
||||||
|
className="flex flex-col items-center gap-1.5 p-3 rounded-lg border border-gray-200 hover:border-primary-400 hover:bg-primary-50 transition-colors text-sm text-gray-700 hover:text-primary-700">
|
||||||
|
<Mail className="h-6 w-6" />
|
||||||
|
Mail
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{peer.service_urls.webdav && (
|
||||||
|
<a href={peer.service_urls.webdav} target="_blank" rel="noopener noreferrer"
|
||||||
|
className="flex flex-col items-center gap-1.5 p-3 rounded-lg border border-gray-200 hover:border-primary-400 hover:bg-primary-50 transition-colors text-sm text-gray-700 hover:text-primary-700">
|
||||||
|
<Globe className="h-6 w-6" />
|
||||||
|
WebDAV
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<Link
|
<Link
|
||||||
to="/my-services"
|
to="/my-services"
|
||||||
className="inline-flex items-center gap-2 btn btn-primary"
|
className="inline-flex items-center gap-2 btn btn-primary"
|
||||||
|
|||||||
Reference in New Issue
Block a user