fix: WG address change now queues pending restart + heals cell connections
Three issues fixed together: 1. WireGuard address changes now go through the pending-restart queue (shown in the UI banner) instead of restarting cell-wireguard immediately. Only private_key changes still restart immediately; address and port changes both defer to the user-initiated Apply flow. Previously the address change was silently applied and never appeared in Settings → Pending Configuration. 2. When the WG address changes, the API spawns a background thread that pushes the updated invite to all connected cells (over LAN, before the WG tunnel is back up). This lets remote cells automatically update their dns_ip, AllowedIPs, and CoreDNS forwarding rules without manual re-pairing. 3. accept_invite now handles the "already connected but changed" case: if the remote cell re-sends an invite with a different dns_ip, vpn_subnet or endpoint, we update the stored link, the WG AllowedIPs, and the CoreDNS forward rule in place — no delete/re-add required. Previously the endpoint was ignored and returned the stale record unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -461,9 +461,52 @@ class CellLinkManager:
|
||||
links = self._load()
|
||||
name = invite['cell_name']
|
||||
|
||||
# Idempotent: already connected
|
||||
# Already connected — check whether the remote's endpoint or subnet changed
|
||||
# (e.g. the remote cell changed its WireGuard address) and heal if so.
|
||||
existing = next((l for l in links if l['cell_name'] == name), None)
|
||||
if existing:
|
||||
dns_changed = existing.get('dns_ip') != invite['dns_ip']
|
||||
subnet_changed = existing.get('vpn_subnet') != invite['vpn_subnet']
|
||||
endpoint_changed = (invite.get('endpoint') and
|
||||
invite['endpoint'] != existing.get('endpoint'))
|
||||
if dns_changed or subnet_changed or endpoint_changed:
|
||||
logger.info(
|
||||
f"accept_invite: updating existing cell '{name}' "
|
||||
f"(dns_ip: {existing.get('dns_ip')} → {invite['dns_ip']}, "
|
||||
f"vpn_subnet: {existing.get('vpn_subnet')} → {invite['vpn_subnet']})"
|
||||
)
|
||||
old_subnet = existing.get('vpn_subnet', '')
|
||||
existing['dns_ip'] = invite['dns_ip']
|
||||
existing['vpn_subnet'] = invite['vpn_subnet']
|
||||
existing['remote_api_url'] = f"http://{invite['dns_ip']}:3000"
|
||||
if invite.get('endpoint'):
|
||||
existing['endpoint'] = invite['endpoint']
|
||||
self._save(links)
|
||||
|
||||
# Update WG peer AllowedIPs to the new subnet
|
||||
if subnet_changed and old_subnet:
|
||||
self.wireguard_manager.update_peer_ip(
|
||||
existing['public_key'], invite['vpn_subnet'])
|
||||
|
||||
# Update DNS forward rule (remove old, add new)
|
||||
if dns_changed:
|
||||
try:
|
||||
self.network_manager.remove_cell_dns_forward(existing['domain'])
|
||||
except Exception:
|
||||
pass
|
||||
self.network_manager.add_cell_dns_forward(
|
||||
domain=existing['domain'], dns_ip=invite['dns_ip'])
|
||||
|
||||
# Reapply firewall rules with new subnet
|
||||
if subnet_changed:
|
||||
try:
|
||||
import firewall_manager as _fm
|
||||
inbound_list = [s for s, v in
|
||||
existing.get('permissions', {}).get('inbound', {}).items() if v]
|
||||
_fm.apply_cell_rules(name, invite['vpn_subnet'], inbound_list)
|
||||
except Exception as e:
|
||||
logger.warning(f"apply_cell_rules after subnet update failed: {e}")
|
||||
|
||||
return existing
|
||||
|
||||
# Conflict check (exclude by name since we're adding for the first time)
|
||||
|
||||
Reference in New Issue
Block a user