diff --git a/Makefile b/Makefile index 777935d..74c0b81 100644 --- a/Makefile +++ b/Makefile @@ -180,7 +180,7 @@ uninstall: case "$$ans" in \ y|Y) \ echo "Stopping containers and removing images..."; \ - for f in data/services/*/docker-compose.yml; do [ -f "$$f" ] && PUID=$$(id -u) PGID=$$(id -g) docker compose -f "$$f" down 2>/dev/null || true; done; \ + for f in data/api/services/*/docker-compose.yml; do [ -f "$$f" ] && PUID=$$(id -u) PGID=$$(id -g) docker compose -f "$$f" down 2>/dev/null || true; done; \ PUID=$$(id -u) PGID=$$(id -g) $(DCF) --profile core down --rmi all 2>/dev/null || true; \ docker network rm cell-network 2>/dev/null || true; \ echo "Deleting config/ and data/..."; \ @@ -189,7 +189,7 @@ uninstall: ;; \ n|N|"") \ echo "Stopping and removing containers (keeping images and data)..."; \ - for f in data/services/*/docker-compose.yml; do [ -f "$$f" ] && PUID=$$(id -u) PGID=$$(id -g) docker compose -f "$$f" down 2>/dev/null || true; done; \ + for f in data/api/services/*/docker-compose.yml; do [ -f "$$f" ] && PUID=$$(id -u) PGID=$$(id -g) docker compose -f "$$f" down 2>/dev/null || true; done; \ PUID=$$(id -u) PGID=$$(id -g) $(DCF) --profile core down 2>/dev/null || true; \ echo "Done. Images, config/ and data/ are untouched. Run 'make start' to bring it back up."; \ ;; \ diff --git a/api/routes/peer_dashboard.py b/api/routes/peer_dashboard.py index 0ed8416..1e95f84 100644 --- a/api/routes/peer_dashboard.py +++ b/api/routes/peer_dashboard.py @@ -65,10 +65,11 @@ def peer_services(): wg_port = 51820 server_endpoint = '' try: + from routes.wireguard import _effective_endpoint + from app import config_manager server_public_key = wireguard_manager.get_keys().get('public_key', '') wg_port = config_manager.configs.get('_identity', {}).get('wireguard_port', 51820) - srv = wireguard_manager.get_server_config() - server_endpoint = srv.get('endpoint') or '' + server_endpoint = _effective_endpoint(wireguard_manager, config_manager) except Exception: pass diff --git a/api/routes/wireguard.py b/api/routes/wireguard.py index 820df4d..ddfccf4 100644 --- a/api/routes/wireguard.py +++ b/api/routes/wireguard.py @@ -4,6 +4,20 @@ from flask import Blueprint, request, jsonify logger = logging.getLogger('picell') bp = Blueprint('wireguard', __name__) + +def _effective_endpoint(wireguard_manager, config_manager) -> str: + """Return the WireGuard endpoint to embed in peer configs. + + Uses wireguard_endpoint from identity config when set (admin override), + falling back to get_external_ip() detection. + """ + srv = wireguard_manager.get_server_config() + override = (config_manager.get_identity().get('wireguard_endpoint') or '').strip() + if override: + port = srv.get('port', 51820) + return override if ':' in override else f'{override}:{port}' + return srv.get('endpoint') or '' + @bp.route('/api/wireguard/keys', methods=['GET']) def get_wireguard_keys(): try: @@ -171,8 +185,8 @@ def get_peer_config(): server_endpoint = data.get('server_endpoint', '') if not server_endpoint: - srv = wireguard_manager.get_server_config() - server_endpoint = srv.get('endpoint') or '' + from app import config_manager + server_endpoint = _effective_endpoint(wireguard_manager, config_manager) allowed_ips = data.get('allowed_ips') or None if not allowed_ips and registered: @@ -198,12 +212,40 @@ def get_peer_config(): @bp.route('/api/wireguard/server-config', methods=['GET']) def get_server_config(): try: - from app import wireguard_manager - return jsonify(wireguard_manager.get_server_config()) + from app import wireguard_manager, config_manager + cfg = wireguard_manager.get_server_config() + cfg['endpoint_override'] = (config_manager.get_identity().get('wireguard_endpoint') or '').strip() + cfg['effective_endpoint'] = _effective_endpoint(wireguard_manager, config_manager) + return jsonify(cfg) except Exception as e: logger.error(f"Error getting server config: {e}") return jsonify({"error": str(e)}), 500 +@bp.route('/api/wireguard/endpoint', methods=['GET']) +def get_wireguard_endpoint(): + try: + from app import wireguard_manager, config_manager + return jsonify({ + 'endpoint_override': (config_manager.get_identity().get('wireguard_endpoint') or '').strip(), + 'detected_endpoint': wireguard_manager.get_server_config().get('endpoint'), + 'effective_endpoint': _effective_endpoint(wireguard_manager, config_manager), + }) + except Exception as e: + logger.error(f"Error getting wireguard endpoint: {e}") + return jsonify({"error": str(e)}), 500 + +@bp.route('/api/wireguard/endpoint', methods=['PUT']) +def set_wireguard_endpoint(): + try: + from app import config_manager + data = request.get_json(silent=True) or {} + override = (data.get('endpoint_override') or '').strip() + config_manager.set_identity_field('wireguard_endpoint', override) + return jsonify({'endpoint_override': override, 'ok': True}) + except Exception as e: + logger.error(f"Error setting wireguard endpoint: {e}") + return jsonify({"error": str(e)}), 500 + @bp.route('/api/wireguard/refresh-ip', methods=['GET', 'POST']) def refresh_external_ip(): try: diff --git a/webui/src/pages/WireGuard.jsx b/webui/src/pages/WireGuard.jsx index 3e6574f..6470a16 100644 --- a/webui/src/pages/WireGuard.jsx +++ b/webui/src/pages/WireGuard.jsx @@ -20,6 +20,10 @@ function WireGuard() { const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [peerStatuses, setPeerStatuses] = useState({}); const [tunnelMode, setTunnelMode] = useState('full'); // 'split' or 'full' + const [endpointOverride, setEndpointOverride] = useState(''); + const [endpointOverrideDraft, setEndpointOverrideDraft] = useState(''); + const [isSavingEndpoint, setIsSavingEndpoint] = useState(false); + const [endpointSaved, setEndpointSaved] = useState(false); useEffect(() => { fetchWireGuardData(); @@ -51,6 +55,22 @@ function WireGuard() { return () => clearInterval(interval); }, []); + const saveEndpointOverride = async () => { + setIsSavingEndpoint(true); + try { + await wireguardAPI.setEndpointOverride(endpointOverrideDraft); + setEndpointOverride(endpointOverrideDraft); + setEndpointSaved(true); + setTimeout(() => setEndpointSaved(false), 3000); + const sc = await fetch('/api/wireguard/server-config', { credentials: 'include' }).then(r => r.json()); + setServerConfig(prev => ({ ...prev, ...sc })); + } catch (e) { + console.error('Failed to save endpoint override:', e); + } finally { + setIsSavingEndpoint(false); + } + }; + const refreshExternalIp = async () => { setIsRefreshingIp(true); try { @@ -81,6 +101,9 @@ function WireGuard() { setStatus(statusResponse.data); if (serverConfigResponse) { setServerConfig({ ...serverConfigResponse, port_open: 'checking' }); + const override = serverConfigResponse.endpoint_override || ''; + setEndpointOverride(override); + setEndpointOverrideDraft(override); // Check port asynchronously so page loads fast fetch('/api/wireguard/check-port', { credentials: 'include' }) .then(r => r.json()) @@ -446,10 +469,38 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;

- {serverConfig && !serverConfig.external_ip && ( +
+

Endpoint Override

+

+ Force a specific endpoint (IP or hostname) for peer configs. Leave blank to use auto-detected IP above. + Useful when peers connect via LAN or a different IP than what's detected. +

+
+ setEndpointOverrideDraft(e.target.value)} + placeholder="e.g. 192.168.1.100 or myvpn.example.com" + className="flex-1 input text-sm font-mono" + /> + +
+ {serverConfig?.effective_endpoint && ( +

+ Effective endpoint used in peer configs: {serverConfig.effective_endpoint} +

+ )} +
+ {serverConfig && !serverConfig.external_ip && !endpointOverride && (
- External IP could not be detected. Check internet connectivity, then click Refresh IP. + External IP could not be detected. Set an endpoint override above or check internet connectivity.
)} {serverConfig && serverConfig.port_open === false && ( diff --git a/webui/src/services/api.js b/webui/src/services/api.js index 3441672..4694e75 100644 --- a/webui/src/services/api.js +++ b/webui/src/services/api.js @@ -137,6 +137,8 @@ export const wireguardAPI = { updatePeerIP: (data) => api.put('/api/wireguard/peers/ip', data), getPeerConfig: (data) => api.post('/api/wireguard/peers/config', data), getPeerStatuses: () => api.get('/api/wireguard/peers/statuses'), + getEndpoint: () => api.get('/api/wireguard/endpoint'), + setEndpointOverride: (endpoint_override) => api.put('/api/wireguard/endpoint', { endpoint_override }), }; // Peer Registry API