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:
@@ -354,6 +354,35 @@ class WireGuardManager(BaseServiceManager):
|
||||
logger.error(f'add_peer failed: {e}')
|
||||
return False
|
||||
|
||||
def add_cell_peer(self, name: str, public_key: str, endpoint: str, vpn_subnet: str) -> bool:
|
||||
"""Add a site-to-site [Peer] block for another PIC cell.
|
||||
|
||||
Unlike add_peer(), allows a subnet CIDR as AllowedIPs (whole remote VPN range).
|
||||
The endpoint is expected to already include the port (e.g. '1.2.3.4:51820').
|
||||
"""
|
||||
import ipaddress
|
||||
try:
|
||||
ipaddress.ip_network(vpn_subnet, strict=False)
|
||||
except ValueError as e:
|
||||
logger.error(f'add_cell_peer: invalid vpn_subnet {vpn_subnet!r}: {e}')
|
||||
return False
|
||||
try:
|
||||
content = self._read_config()
|
||||
peer_block = (
|
||||
f'\n[Peer]\n'
|
||||
f'# cell:{name}\n'
|
||||
f'PublicKey = {public_key}\n'
|
||||
f'AllowedIPs = {vpn_subnet}\n'
|
||||
f'PersistentKeepalive = 25\n'
|
||||
)
|
||||
if endpoint:
|
||||
peer_block += f'Endpoint = {endpoint}\n'
|
||||
self._write_config(content + peer_block)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f'add_cell_peer failed: {e}')
|
||||
return False
|
||||
|
||||
def remove_peer(self, public_key: str) -> bool:
|
||||
"""Remove the [Peer] block matching public_key from wg0.conf."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user