The backend's bridge network can't see the host's NICs, so HOST_IP from install.sh went stale silently after a DHCP change. Now: - services/netinfo_helper.py runs as a new `netinfo` service (same backend image, network_mode: host): every 60s it picks the PHYSICAL LAN address (main-table default-route NIC if real hardware, else first real NIC with IPv4; never WireGuard/Tailscale/ZeroTier/bridges/veths, ignores 169.254) and writes it to the shared `netinfo` volume. Stdlib only. On Docker Desktop (linuxkit/WSL2 kernel) it reports "unsupported" instead of the VM's meaningless address. - services/lan_ip.py: one resolver used by /api/system/status (lan_ip + lan_ip_info), the pairing QR and the cloud heartbeat's local_ip: override (pos_settings network.lan_ip_override) → live detection (ignored when older than 5 min) → HOST_IP. Flags `mismatch` when a pinned address is no longer on any of the machine's NICs. - PUT /api/system/lan-ip-override (manager): set a private IPv4 or null to return to automatic; public/loopback/link-local/IPv6 rejected (422). - cloud_sync._get_local_ip uses the resolver (no more socket trick that returned the container IP). Tested: helper selection on a fake sysfs/route table (8 cases incl. VPN default routes) + real ioctl/route parsing on a Linux kernel; resolver priority/staleness/mismatch/validation (18 cases); isolated full stack: HOST_IP fallback on Docker Desktop, override save/validate/auth, simulated Linux detection incl. DHCP change and dead helper, heartbeat IP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
1.8 KiB
YAML
63 lines
1.8 KiB
YAML
services:
|
|
backend:
|
|
image: ${REGISTRY}/pos-backend:${VERSION:-latest}
|
|
restart: unless-stopped
|
|
environment:
|
|
- SITE_ID=${SITE_ID}
|
|
- SITE_KEY=${SITE_KEY}
|
|
- CLOUD_URL=${CLOUD_URL}
|
|
- SECRET_KEY=${SECRET_KEY}
|
|
- LICENSE_GRACE_HOURS=${LICENSE_GRACE_HOURS:-24}
|
|
- DATABASE_URL=sqlite:////app/data/pos.db
|
|
- VERSION=${VERSION:-0.0.0}
|
|
- HOST_IP=${HOST_IP:-}
|
|
- MASTER_USERNAME=${MASTER_USERNAME:-}
|
|
- MASTER_PASSWORD=${MASTER_PASSWORD:-}
|
|
volumes:
|
|
- ${DATA_PATH}:/app/data
|
|
- ${LOGO_PATH}:/app/logo.png:ro
|
|
- ${FISCAL_PATH}:/mnt/fiscal
|
|
- netinfo:/netinfo:ro
|
|
|
|
# Finds the server's LAN IP on the PHYSICAL network (never a VPN) every minute
|
|
# and shares it with the backend (pairing QR, manager, heartbeat). Needs the
|
|
# host's network namespace because the backend's bridge network can't see
|
|
# real NICs. Same image as the backend — see services/netinfo_helper.py.
|
|
netinfo:
|
|
image: ${REGISTRY}/pos-backend:${VERSION:-latest}
|
|
restart: unless-stopped
|
|
network_mode: host
|
|
command: ["python", "-m", "services.netinfo_helper"]
|
|
volumes:
|
|
- netinfo:/netinfo
|
|
|
|
waiter_pwa:
|
|
image: ${REGISTRY}/pos-waiter:${VERSION:-latest}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- backend
|
|
|
|
manager_dashboard:
|
|
image: ${REGISTRY}/pos-manager:${VERSION:-latest}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- backend
|
|
|
|
proxy:
|
|
image: nginx:alpine
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "4443:4443"
|
|
- "8081:8081" # manager over plain HTTP (LAN only)
|
|
volumes:
|
|
- ./nginx-proxy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
- ./certs:/etc/nginx/certs:ro
|
|
depends_on:
|
|
- waiter_pwa
|
|
- manager_dashboard
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
netinfo:
|