fix: wireguard port/subnet/domain propagate to peer configs and new peer IPs

Backend:
- wireguard_manager: _get_configured_port/address/network() read from wg0.conf
  instead of module-level constants; get_split_tunnel_ips() derives VPN network
  from configured Address; get_server_config() returns configured port, dns_ip,
  split_tunnel_ips, vpn_network
- add_peer() and get_peer_config() use configured port (not hardcoded 51820)
- _next_peer_ip() derives subnet from wireguard_manager._get_configured_address()
  so new peers are allocated IPs from the correct VPN range after address change
- refresh-ip and check-port API endpoints return configured port, not 51820
- PUT /api/config: when wireguard port/address changes, all peers are marked
  config_needs_reinstall so users know to re-download tunnel configs
- get_peer_config endpoint: uses configured split tunnel IPs (not hardcoded)

Frontend:
- Peers.jsx: SERVICES domains use live domain from ConfigContext; generateConfig()
  uses serverConf.dns_ip and serverConf.split_tunnel_ips; vpn_network shown in
  peer-access description; DNS hint uses live domain; server config loaded at
  mount time so it is available without re-fetching on every peer action;
  handleUpdatePeer uses /32 for server-side AllowedIPs (was incorrectly using
  full/split tunnel CIDRs which the backend rejects)
- WireGuard.jsx: generateWireGuardConfig() uses serverConfig.dns_ip,
  split_tunnel_ips from server-config API; split-tunnel description shows
  live IPs

Tests: 9 new tests in TestWireGuardConfigReads verify all config reads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 07:47:19 -04:00
parent 5c89687fab
commit 3912452fd6
5 changed files with 198 additions and 41 deletions
+19 -9
View File
@@ -434,6 +434,13 @@ def update_config():
'service': service,
'config': config
})
# VPN port or subnet change → all peer client configs are stale
if service == 'wireguard' and ('port' in config or 'address' in config):
for p in peer_registry.list_peers():
peer_registry.update_peer(p['peer'], {'config_needs_reinstall': True})
n = len(peer_registry.list_peers())
if n:
all_warnings.append(f'WireGuard endpoint changed — {n} peer(s) must reinstall VPN config')
# Apply cell identity domain to network and email services
if identity_updates.get('domain'):
@@ -1028,7 +1035,7 @@ def get_peer_config():
allowed_ips = data.get('allowed_ips') or None
if not allowed_ips and registered:
internet_access = registered.get('internet_access', True)
allowed_ips = wireguard_manager.FULL_TUNNEL_IPS if internet_access else wireguard_manager.SPLIT_TUNNEL_IPS
allowed_ips = wireguard_manager.FULL_TUNNEL_IPS if internet_access else wireguard_manager.get_split_tunnel_ips()
result = wireguard_manager.get_peer_config(
peer_name=peer_name,
@@ -1055,10 +1062,11 @@ def get_server_config():
def refresh_external_ip():
try:
ip = wireguard_manager.get_external_ip(force_refresh=True)
port = wireguard_manager._get_configured_port()
return jsonify({
'external_ip': ip,
'port': 51820,
'endpoint': f'{ip}:51820' if ip else None,
'port': port,
'endpoint': f'{ip}:{port}' if ip else None,
})
except Exception as e:
logger.error(f"Error refreshing external IP: {e}")
@@ -1079,7 +1087,7 @@ def apply_wireguard_enforcement():
def check_wireguard_port():
try:
port_open = wireguard_manager.check_port_open()
return jsonify({'port_open': port_open, 'port': 51820})
return jsonify({'port_open': port_open, 'port': wireguard_manager._get_configured_port()})
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -1095,17 +1103,19 @@ def get_peers():
return jsonify({"error": str(e)}), 500
def _next_peer_ip() -> str:
"""Auto-assign the next free 10.0.0.x address (starts at .2, skips .1 = server)."""
"""Auto-assign the next free host address from the configured VPN subnet."""
import ipaddress
server_addr = wireguard_manager._get_configured_address() # e.g. '10.0.0.1/24'
network = ipaddress.ip_network(server_addr, strict=False)
server_ip = str(ipaddress.ip_interface(server_addr).ip)
used = {p.get('ip', '').split('/')[0] for p in peer_registry.list_peers()}
network = ipaddress.ip_network('10.0.0.0/24')
for host in network.hosts():
ip = str(host)
if ip == '10.0.0.1':
continue # server address
if ip == server_ip:
continue
if ip not in used:
return ip
raise ValueError('No free IPs left in 10.0.0.0/24')
raise ValueError(f'No free IPs left in {network}')
@app.route('/api/peers', methods=['POST'])