feat: cell-to-cell (PIC mesh) connection feature

Site-to-site WireGuard tunnels between PIC cells with automatic DNS forwarding.
Each cell generates an invite JSON (public key, endpoint, VPN subnet, DNS IP,
domain); the remote cell imports it to establish a bidirectional tunnel and
CoreDNS forwarding block so each cell's domain resolves across the mesh.

Backend:
- CellLinkManager: invite generation, add/remove connections, live WireGuard
  handshake status; stores links in data/cell_links.json
- WireGuardManager: add_cell_peer() accepts subnet CIDRs (not /32) and an
  optional endpoint for site-to-site peers; _read_iface_field() reads port,
  address, and network directly from wg0.conf at runtime instead of constants
- NetworkManager: add/remove CoreDNS forwarding blocks per remote cell domain
- app.py: /api/cells/* routes; _next_peer_ip() derives VPN range from
  configured address so peer allocation follows any address change

Frontend:
- CellNetwork page: invite panel (JSON + QR), connect form (paste JSON),
  connected cells list (green/red status, disconnect button)
- App.jsx: Cell Network nav entry and route

Tests: 25 new tests across test_wireguard_manager, test_network_manager,
test_cell_link_manager (263 total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 08:34:21 -04:00
parent 3912452fd6
commit 848f8cfc7c
10 changed files with 872 additions and 1 deletions
+5 -1
View File
@@ -13,7 +13,8 @@ import {
Server,
Key,
Package2,
Settings as SettingsIcon
Settings as SettingsIcon,
Link2
} from 'lucide-react';
import { healthAPI } from './services/api';
import { ConfigProvider } from './contexts/ConfigContext';
@@ -30,6 +31,7 @@ import Logs from './pages/Logs';
import Settings from './pages/Settings';
import Vault from './pages/Vault';
import ContainerDashboard from './components/ContainerDashboard';
import CellNetwork from './pages/CellNetwork';
function App() {
const [isOnline, setIsOnline] = useState(false);
@@ -65,6 +67,7 @@ function App() {
{ name: 'Routing', href: '/routing', icon: Wifi },
{ name: 'Vault', href: '/vault', icon: Key },
{ name: 'Containers', href: '/containers', icon: Package2 },
{ name: 'Cell Network', href: '/cell-network', icon: Link2 },
{ name: 'Logs', href: '/logs', icon: Activity },
{ name: 'Settings', href: '/settings', icon: SettingsIcon },
];
@@ -121,6 +124,7 @@ function App() {
<Route path="/routing" element={<Routing />} />
<Route path="/vault" element={<Vault />} />
<Route path="/containers" element={<ContainerDashboard />} />
<Route path="/cell-network" element={<CellNetwork />} />
<Route path="/logs" element={<Logs />} />
<Route path="/settings" element={<Settings />} />
</Routes>