diff --git a/api/routes/peers.py b/api/routes/peers.py index 253c5b4..7c011ee 100644 --- a/api/routes/peers.py +++ b/api/routes/peers.py @@ -328,17 +328,24 @@ def unregister_peer(peer_name): @bp.route('/api/peers//update-ip', methods=['PUT']) def update_peer_ip_registry(peer_name): try: - from app import peer_registry, routing_manager + from app import peer_registry, routing_manager, wireguard_manager data = request.get_json(silent=True) new_ip = data.get('ip') if data else None if not new_ip: return jsonify({"error": "Missing ip"}), 400 + peer = peer_registry.get_peer(peer_name) success = peer_registry.update_peer_ip(peer_name, new_ip) if success: try: routing_manager.update_peer_ip(peer_name, new_ip) except Exception as e: logger.warning(f"RoutingManager update_peer_ip failed: {e}") + if peer and peer.get('public_key'): + try: + wg_ip = new_ip if '/' in new_ip else f'{new_ip}/32' + wireguard_manager.update_peer_ip(peer['public_key'], wg_ip) + except Exception as e: + logger.warning(f"WireGuard update_peer_ip failed: {e}") return jsonify({"message": f"IP update received for {peer_name}"}) return jsonify({"error": f"Peer {peer_name} not found"}), 404 except Exception as e: @@ -349,7 +356,7 @@ def update_peer_ip_registry(peer_name): @bp.route('/api/ip-update', methods=['POST']) def ip_update(): try: - from app import peer_registry, routing_manager + from app import peer_registry, routing_manager, wireguard_manager data = request.get_json(silent=True) if data is None: return jsonify({"error": "No data provided"}), 400 @@ -357,12 +364,19 @@ def ip_update(): new_ip = data.get('ip') if not peer_name or not new_ip: return jsonify({"error": "Missing peer or ip"}), 400 + peer = peer_registry.get_peer(peer_name) success = peer_registry.update_peer_ip(peer_name, new_ip) if success: try: routing_manager.update_peer_ip(peer_name, new_ip) except Exception as e: logger.warning(f"RoutingManager update_peer_ip failed: {e}") + if peer and peer.get('public_key'): + try: + wg_ip = new_ip if '/' in new_ip else f'{new_ip}/32' + wireguard_manager.update_peer_ip(peer['public_key'], wg_ip) + except Exception as e: + logger.warning(f"WireGuard update_peer_ip failed: {e}") return jsonify({"message": f"IP update received for {peer_name}"}) return jsonify({"error": f"Peer {peer_name} not found"}), 404 except Exception as e: diff --git a/api/wireguard_manager.py b/api/wireguard_manager.py index dc48d99..9cdb969 100644 --- a/api/wireguard_manager.py +++ b/api/wireguard_manager.py @@ -296,7 +296,7 @@ class WireGuardManager(BaseServiceManager): Docker bridge subnet would cause routing conflicts when cells share the same range. """ local_net = self._get_configured_network() - cell_links_file = os.path.join(self.data_dir, 'api', 'cell_links.json') + cell_links_file = os.path.join(self.data_dir, 'cell_links.json') cell_nets = [] try: with open(cell_links_file) as f: @@ -449,8 +449,8 @@ class WireGuardManager(BaseServiceManager): """ import subprocess, re real_conf = self._config_file() - if '/tmp/' in real_conf or 'pytest' in real_conf: - logger.debug('_syncconf: skipping — config path looks like a test dir') + if '/tmp/' in real_conf or 'pytest' in real_conf or 'wg_confs' not in real_conf: + logger.debug('_syncconf: skipping — not running inside container') return try: # Parse desired peers from config file @@ -634,7 +634,7 @@ class WireGuardManager(BaseServiceManager): wg-quick would do this automatically, but we manage WG live via 'wg set'. """ real_conf = self._config_file() - if '/tmp/' in real_conf or 'pytest' in real_conf: + if '/tmp/' in real_conf or 'pytest' in real_conf or 'wg_confs' not in real_conf: return try: subprocess.run( @@ -653,7 +653,7 @@ class WireGuardManager(BaseServiceManager): are ephemeral; only the WG peer config in wg0.conf persists). """ real_conf = self._config_file() - if '/tmp/' in real_conf or 'pytest' in real_conf: + if '/tmp/' in real_conf or 'pytest' in real_conf or 'wg_confs' not in real_conf: return try: content = self._read_config() @@ -715,7 +715,7 @@ class WireGuardManager(BaseServiceManager): treated as success. """ real_conf = self._config_file() - if '/tmp/' in real_conf or 'pytest' in real_conf: + if '/tmp/' in real_conf or 'pytest' in real_conf or 'wg_confs' not in real_conf: return True try: def _wg(cmd): @@ -738,7 +738,7 @@ class WireGuardManager(BaseServiceManager): def remove_peer_route_via(self, peer_ip: str, table: int = 100) -> None: """Remove the ip rule for peer_ip added by apply_peer_route_via. Non-fatal.""" real_conf = self._config_file() - if '/tmp/' in real_conf or 'pytest' in real_conf: + if '/tmp/' in real_conf or 'pytest' in real_conf or 'wg_confs' not in real_conf: return try: subprocess.run( @@ -836,7 +836,7 @@ class WireGuardManager(BaseServiceManager): def _apply_peer_allowed_ips_live(self, public_key: str, new_ips: str) -> None: """Apply AllowedIPs for one peer via wg set (no spaces — wg rejects them).""" real_conf = self._config_file() - if '/tmp/' in real_conf or 'pytest' in real_conf: + if '/tmp/' in real_conf or 'pytest' in real_conf or 'wg_confs' not in real_conf: return try: ips = new_ips.replace(' ', '') diff --git a/tests/test_wireguard_manager.py b/tests/test_wireguard_manager.py index ce8e317..0d5272d 100644 --- a/tests/test_wireguard_manager.py +++ b/tests/test_wireguard_manager.py @@ -828,7 +828,7 @@ class TestCellRoutes(unittest.TestCase): def test_ensure_cell_route_calls_ip_route_add(self): """Outside test dirs, _ensure_cell_route calls docker exec ip route add.""" - with patch.object(self.wg, '_config_file', return_value='/app/config/wireguard/wg0.conf'): + with patch.object(self.wg, '_config_file', return_value='/app/config/wireguard/wg_confs/wg0.conf'): with patch('subprocess.run') as mock_run: mock_run.return_value = MagicMock(returncode=0) self.wg._ensure_cell_route('10.1.0.0/24') @@ -849,7 +849,7 @@ class TestCellRoutes(unittest.TestCase): '[Peer]\n# alice\nPublicKey = YWxpY2VwdWJrZXlfZm9yX3Rlc3RzX3dndGVzdDEyMyE=\n' 'AllowedIPs = 10.0.0.2/32\nPersistentKeepalive = 25\n' ) - with patch.object(self.wg, '_config_file', return_value='/app/config/wireguard/wg0.conf'): + with patch.object(self.wg, '_config_file', return_value='/app/config/wireguard/wg_confs/wg0.conf'): with patch.object(self.wg, '_read_config', return_value=conf): with patch('subprocess.run') as mock_run: mock_run.return_value = MagicMock(returncode=0)