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:
@@ -157,6 +157,54 @@ class TestCellLinkManagerConnections(unittest.TestCase):
|
||||
self.mgr.add_connection(second)
|
||||
self.assertEqual(len(self.mgr.list_connections()), 2)
|
||||
|
||||
# accept_invite — new connection
|
||||
def test_accept_invite_adds_new_connection(self):
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
self.mgr.accept_invite(SAMPLE_INVITE)
|
||||
links = self.mgr.list_connections()
|
||||
self.assertEqual(len(links), 1)
|
||||
self.assertEqual(links[0]['cell_name'], 'office')
|
||||
|
||||
def test_accept_invite_idempotent_no_change(self):
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
self.mgr.accept_invite(SAMPLE_INVITE)
|
||||
self.wg.reset_mock()
|
||||
self.mgr.accept_invite(SAMPLE_INVITE)
|
||||
# No WG update for identical invite
|
||||
self.wg.update_peer_ip.assert_not_called()
|
||||
|
||||
def test_accept_invite_updates_dns_ip_on_existing(self):
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
self.mgr.accept_invite(SAMPLE_INVITE)
|
||||
|
||||
updated_invite = {**SAMPLE_INVITE, 'dns_ip': '10.1.0.2'}
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
result = self.mgr.accept_invite(updated_invite)
|
||||
|
||||
self.assertEqual(result['dns_ip'], '10.1.0.2')
|
||||
self.assertEqual(result['remote_api_url'], 'http://10.1.0.2:3000')
|
||||
self.nm.remove_cell_dns_forward.assert_called()
|
||||
self.nm.add_cell_dns_forward.assert_called_with(
|
||||
domain='office.cell', dns_ip='10.1.0.2')
|
||||
|
||||
def test_accept_invite_updates_vpn_subnet_on_existing(self):
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
self.mgr.accept_invite(SAMPLE_INVITE)
|
||||
|
||||
self.wg.update_peer_ip = MagicMock(return_value=True)
|
||||
updated_invite = {**SAMPLE_INVITE, 'vpn_subnet': '10.5.0.0/24'}
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
result = self.mgr.accept_invite(updated_invite)
|
||||
|
||||
self.assertEqual(result['vpn_subnet'], '10.5.0.0/24')
|
||||
self.wg.update_peer_ip.assert_called_once_with('officepubkey=', '10.5.0.0/24')
|
||||
|
||||
def test_accept_invite_does_not_duplicate_link(self):
|
||||
with patch('firewall_manager.apply_cell_rules'):
|
||||
self.mgr.accept_invite(SAMPLE_INVITE)
|
||||
self.mgr.accept_invite({**SAMPLE_INVITE, 'dns_ip': '10.1.0.99'})
|
||||
self.assertEqual(len(self.mgr.list_connections()), 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user