Fix NPM forwarding: use HOST_IP env var instead of socket detection
Socket detection inside Docker returns the container IP (172.18.0.x), not the host IP. Now: - install.sh detects host IP via hostname -I and stores in .env - docker-compose.yml passes HOST_IP to the container - npm_service.py reads HOST_IP from environment - Increased SSL cert timeout to 120s (LE validation is slow) - Added better logging for SSL cert creation/assignment - README updated with HOST_IP in .env example Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -257,6 +257,7 @@ DATA_DIR=/opt/netbird-instances
|
|||||||
DOCKER_NETWORK=npm-network
|
DOCKER_NETWORK=npm-network
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
WEB_UI_PORT=8000
|
WEB_UI_PORT=8000
|
||||||
|
HOST_IP=<your-server-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**.
|
> **All application settings** (domain, NPM credentials, Docker images, branding, etc.) are stored in the SQLite database and editable via the Web UI under **Settings**.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Also manages NPM streams for STUN/TURN relay UDP ports.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -25,27 +25,24 @@ NPM_TIMEOUT = 30
|
|||||||
|
|
||||||
|
|
||||||
def _get_forward_host() -> str:
|
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
|
NPM proxy hosts must forward to the actual host IP where Docker
|
||||||
port mappings are exposed — NOT a container name or Docker gateway.
|
port mappings are exposed — NOT a container name or Docker gateway.
|
||||||
|
|
||||||
Uses a UDP socket to determine the primary outbound IP address
|
Reads the HOST_IP environment variable set during installation
|
||||||
of the host (works inside Docker containers).
|
(detected via ``hostname -I`` on the host and stored in .env).
|
||||||
|
|
||||||
Returns:
|
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:
|
host_ip = os.environ.get("HOST_IP", "").strip()
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
if host_ip:
|
||||||
s.connect(("8.8.8.8", 80))
|
logger.info("Using HOST_IP from environment: %s", host_ip)
|
||||||
host_ip = s.getsockname()[0]
|
|
||||||
s.close()
|
|
||||||
logger.info("Detected host IP: %s", host_ip)
|
|
||||||
return host_ip
|
return host_ip
|
||||||
except Exception:
|
|
||||||
logger.warning("Could not detect host IP, falling back to 172.17.0.1")
|
logger.warning("HOST_IP not set in environment — please add HOST_IP=<your-server-ip> to .env")
|
||||||
return "172.17.0.1"
|
return "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
async def _npm_login(client: httpx.AsyncClient, api_url: str, email: str, password: str) -> str:
|
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:
|
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
|
# Step 1: Login to NPM
|
||||||
token = await _npm_login(client, api_url, npm_email, npm_password)
|
token = await _npm_login(client, api_url, npm_email, npm_password)
|
||||||
headers = {
|
headers = {
|
||||||
@@ -208,6 +205,9 @@ async def _request_ssl(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Request a Let's Encrypt SSL certificate for a proxy host.
|
"""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:
|
Args:
|
||||||
client: httpx client (already authenticated).
|
client: httpx client (already authenticated).
|
||||||
api_url: NPM API base URL.
|
api_url: NPM API base URL.
|
||||||
@@ -227,20 +227,28 @@ async def _request_ssl(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
|
logger.info("Requesting Let's Encrypt certificate for %s ...", domain)
|
||||||
resp = await client.post(
|
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):
|
if resp.status_code in (200, 201):
|
||||||
cert_id = resp.json().get("id")
|
cert_id = resp.json().get("id")
|
||||||
# Assign certificate to proxy host
|
logger.info("Certificate created (id=%s), assigning to proxy host %s", cert_id, proxy_id)
|
||||||
await client.put(
|
assign_resp = await client.put(
|
||||||
f"{api_url}/nginx/proxy-hosts/{proxy_id}",
|
f"{api_url}/nginx/proxy-hosts/{proxy_id}",
|
||||||
json={"certificate_id": cert_id},
|
json={"certificate_id": cert_id},
|
||||||
headers=headers,
|
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:
|
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:
|
except Exception as exc:
|
||||||
logger.warning("SSL certificate request failed: %s", exc)
|
logger.warning("SSL certificate request failed: %s", exc)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ services:
|
|||||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
- DATA_DIR=${DATA_DIR:-/opt/netbird-instances}
|
- DATA_DIR=${DATA_DIR:-/opt/netbird-instances}
|
||||||
- DOCKER_NETWORK=${DOCKER_NETWORK:-npm-network}
|
- DOCKER_NETWORK=${DOCKER_NETWORK:-npm-network}
|
||||||
|
- HOST_IP=${HOST_IP:-}
|
||||||
networks:
|
networks:
|
||||||
- npm-network
|
- npm-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
10
install.sh
10
install.sh
@@ -338,6 +338,10 @@ echo "Generating encryption keys..."
|
|||||||
SECRET_KEY=$(openssl rand -base64 32)
|
SECRET_KEY=$(openssl rand -base64 32)
|
||||||
echo -e "${GREEN}✓ Encryption keys generated${NC}"
|
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
|
# Create MINIMAL .env — only container-level vars needed by docker-compose.yml
|
||||||
# All application config goes into the DATABASE, not here!
|
# All application config goes into the DATABASE, not here!
|
||||||
echo "Creating minimal container environment..."
|
echo "Creating minimal container environment..."
|
||||||
@@ -350,6 +354,7 @@ DATA_DIR=$DATA_DIR
|
|||||||
DOCKER_NETWORK=$DOCKER_NETWORK
|
DOCKER_NETWORK=$DOCKER_NETWORK
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
WEB_UI_PORT=8000
|
WEB_UI_PORT=8000
|
||||||
|
HOST_IP=$HOST_IP
|
||||||
ENVEOF
|
ENVEOF
|
||||||
|
|
||||||
chmod 600 "$INSTALL_DIR/.env"
|
chmod 600 "$INSTALL_DIR/.env"
|
||||||
@@ -482,8 +487,9 @@ if [ -n "$MSP_DOMAIN" ]; then
|
|||||||
if [ -n "$PROXY_ID" ] && [ "$PROXY_ID" != "None" ] && [ "$PROXY_ID" != "" ]; then
|
if [ -n "$PROXY_ID" ] && [ "$PROXY_ID" != "None" ] && [ "$PROXY_ID" != "" ]; then
|
||||||
echo -e "${GREEN}✓ NPM proxy host created (ID: ${PROXY_ID})${NC}"
|
echo -e "${GREEN}✓ NPM proxy host created (ID: ${PROXY_ID})${NC}"
|
||||||
|
|
||||||
# Step 3: Request Let's Encrypt certificate
|
# Step 3: Request Let's Encrypt certificate (can take up to 120s)
|
||||||
CERT_RESULT=$(curl -s -X POST "${NPM_API_URL}/nginx/certificates" \
|
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 "Authorization: Bearer ${NPM_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{
|
-d "{
|
||||||
|
|||||||
Reference in New Issue
Block a user