diff --git a/README.md b/README.md index 75c3a25..0540dfd 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ DATA_DIR=/opt/netbird-instances DOCKER_NETWORK=npm-network LOG_LEVEL=INFO WEB_UI_PORT=8000 +HOST_IP= ``` > **All application settings** (domain, NPM credentials, Docker images, branding, etc.) are stored in the SQLite database and editable via the Web UI under **Settings**. diff --git a/app/services/npm_service.py b/app/services/npm_service.py index b990e59..47a525e 100644 --- a/app/services/npm_service.py +++ b/app/services/npm_service.py @@ -13,7 +13,7 @@ Also manages NPM streams for STUN/TURN relay UDP ports. """ import logging -import socket +import os from typing import Any import httpx @@ -25,27 +25,24 @@ NPM_TIMEOUT = 30 def _get_forward_host() -> str: - """Detect the host machine's real IP address. + """Get the host machine's real IP address for NPM forwarding. NPM proxy hosts must forward to the actual host IP where Docker port mappings are exposed — NOT a container name or Docker gateway. - Uses a UDP socket to determine the primary outbound IP address - of the host (works inside Docker containers). + Reads the HOST_IP environment variable set during installation + (detected via ``hostname -I`` on the host and stored in .env). Returns: - The host's primary IP address (e.g. ``192.168.26.191``). + The host's IP address (e.g. ``192.168.26.191``). """ - try: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect(("8.8.8.8", 80)) - host_ip = s.getsockname()[0] - s.close() - logger.info("Detected host IP: %s", host_ip) + host_ip = os.environ.get("HOST_IP", "").strip() + if host_ip: + logger.info("Using HOST_IP from environment: %s", host_ip) return host_ip - except Exception: - logger.warning("Could not detect host IP, falling back to 172.17.0.1") - return "172.17.0.1" + + logger.warning("HOST_IP not set in environment — please add HOST_IP= to .env") + return "127.0.0.1" async def _npm_login(client: httpx.AsyncClient, api_url: str, email: str, password: str) -> str: @@ -164,7 +161,7 @@ async def create_proxy_host( } try: - async with httpx.AsyncClient(timeout=NPM_TIMEOUT) as client: + async with httpx.AsyncClient(timeout=180) as client: # Long timeout for LE cert # Step 1: Login to NPM token = await _npm_login(client, api_url, npm_email, npm_password) headers = { @@ -208,6 +205,9 @@ async def _request_ssl( ) -> None: """Request a Let's Encrypt SSL certificate for a proxy host. + Let's Encrypt validation can take up to 120 seconds, so we use + a longer timeout for certificate requests. + Args: client: httpx client (already authenticated). api_url: NPM API base URL. @@ -227,20 +227,28 @@ async def _request_ssl( }, } try: + logger.info("Requesting Let's Encrypt certificate for %s ...", domain) resp = await client.post( - f"{api_url}/nginx/certificates", json=ssl_payload, headers=headers + f"{api_url}/nginx/certificates", + json=ssl_payload, + headers=headers, + timeout=120, # LE validation can be slow ) if resp.status_code in (200, 201): cert_id = resp.json().get("id") - # Assign certificate to proxy host - await client.put( + logger.info("Certificate created (id=%s), assigning to proxy host %s", cert_id, proxy_id) + assign_resp = await client.put( f"{api_url}/nginx/proxy-hosts/{proxy_id}", json={"certificate_id": cert_id}, headers=headers, ) - logger.info("SSL certificate %s assigned to proxy host %s", cert_id, proxy_id) + if assign_resp.status_code in (200, 201): + logger.info("SSL certificate %s assigned to proxy host %s", cert_id, proxy_id) + else: + logger.warning("Failed to assign cert to proxy host: %s %s", + assign_resp.status_code, assign_resp.text[:200]) else: - logger.warning("SSL request returned %s: %s", resp.status_code, resp.text[:200]) + logger.warning("SSL cert request returned %s: %s", resp.status_code, resp.text[:500]) except Exception as exc: logger.warning("SSL certificate request failed: %s", exc) diff --git a/docker-compose.yml b/docker-compose.yml index 36dc302..748cf9c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: - LOG_LEVEL=${LOG_LEVEL:-INFO} - DATA_DIR=${DATA_DIR:-/opt/netbird-instances} - DOCKER_NETWORK=${DOCKER_NETWORK:-npm-network} + - HOST_IP=${HOST_IP:-} networks: - npm-network healthcheck: diff --git a/install.sh b/install.sh index f81c93e..3015a26 100644 --- a/install.sh +++ b/install.sh @@ -338,6 +338,10 @@ echo "Generating encryption keys..." SECRET_KEY=$(openssl rand -base64 32) echo -e "${GREEN}✓ Encryption keys generated${NC}" +# Detect host IP for NPM forwarding +HOST_IP=$(hostname -I | awk '{print $1}') +echo -e "Host IP: ${CYAN}${HOST_IP}${NC}" + # Create MINIMAL .env — only container-level vars needed by docker-compose.yml # All application config goes into the DATABASE, not here! echo "Creating minimal container environment..." @@ -350,6 +354,7 @@ DATA_DIR=$DATA_DIR DOCKER_NETWORK=$DOCKER_NETWORK LOG_LEVEL=INFO WEB_UI_PORT=8000 +HOST_IP=$HOST_IP ENVEOF chmod 600 "$INSTALL_DIR/.env" @@ -482,8 +487,9 @@ if [ -n "$MSP_DOMAIN" ]; then if [ -n "$PROXY_ID" ] && [ "$PROXY_ID" != "None" ] && [ "$PROXY_ID" != "" ]; then echo -e "${GREEN}✓ NPM proxy host created (ID: ${PROXY_ID})${NC}" - # Step 3: Request Let's Encrypt certificate - CERT_RESULT=$(curl -s -X POST "${NPM_API_URL}/nginx/certificates" \ + # Step 3: Request Let's Encrypt certificate (can take up to 120s) + echo -e "${CYAN}Requesting Let's Encrypt certificate (this may take a minute)...${NC}" + CERT_RESULT=$(curl -s --max-time 120 -X POST "${NPM_API_URL}/nginx/certificates" \ -H "Authorization: Bearer ${NPM_TOKEN}" \ -H "Content-Type: application/json" \ -d "{