Files
xenia-pos-local/docker-compose.yml
T
bonaminandClaude Opus 5.5 76aac203d6 feat(backend): runtime LAN-IP detection (netinfo helper) + manual override
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>
2026-09-28 16:13:17 +03:00

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: