diff --git a/docker-compose.yml b/docker-compose.yml index e5efafb..94d004a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - DATABASE_URL=sqlite:////app/data/pos.db - VERSION=${VERSION:-0.0.0} - HOST_IP=${HOST_IP:-} + - TLS_PORT=${TLS_PORT:-8443} # published port of the proxy's TLS entry (advertised to the app) - MASTER_USERNAME=${MASTER_USERNAME:-} - MASTER_PASSWORD=${MASTER_PASSWORD:-} volumes: @@ -18,6 +19,14 @@ services: - ${LOGO_PATH}:/app/logo.png:ro - ${FISCAL_PATH}:/mnt/fiscal - netinfo:/netinfo:ro + # "Healthy" = app started, which also means the TLS key/cert for the + # proxy's :8443 exist (created at startup by services/tls_identity.py) + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/system/health', timeout=3)"] + interval: 5s + timeout: 5s + retries: 12 + start_period: 20s # 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 @@ -50,12 +59,18 @@ services: - "443:443" - "4443:4443" - "8081:8081" # manager over plain HTTP (LAN only) + - "8443:8443" # native app over TLS with a pinned key (LAN only) volumes: - ./nginx-proxy/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./certs:/etc/nginx/certs:ro + - ${DATA_PATH}/tls:/etc/nginx/xenia-tls:ro depends_on: - - waiter_pwa - - manager_dashboard + backend: + condition: service_healthy + waiter_pwa: + condition: service_started + manager_dashboard: + condition: service_started restart: unless-stopped volumes: diff --git a/install.sh b/install.sh index cea52c6..3565e7b 100644 --- a/install.sh +++ b/install.sh @@ -108,6 +108,7 @@ cat > "$SCRIPT_DIR/nginx-proxy/nginx.conf" << 'EOF' # :8081 → manager dashboard over plain HTTP, LAN only # :443 waiter.* / manager.* / bare IP → https (needs certs/cert.pem + key.pem) # :4443 → manager over https +# :8443 → native app over TLS (backend-managed key, pinned), LAN only # # Every proxied location forwards WebSocket upgrades (/api/ws/connect) and # disables buffering (SSE), otherwise live events never reach phones / KDS. @@ -279,6 +280,44 @@ server { } } +server { + listen 8443 ssl default_server; + server_name _; + + # Encrypted LAN entry for the native app (plan step 7). Key + self-signed cert + # are created and renewed by the backend (services/tls_identity.py) under + # ${DATA_PATH}/tls; phones pin the public key, so renewals need no action. + ssl_certificate /etc/nginx/xenia-tls/cert.pem; + ssl_certificate_key /etc/nginx/xenia-tls/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + # LAN only: plain HTTP must never be reachable from the internet, even if the + # client forwards ports on their router. Relies on Docker preserving the real + # client IP (default iptables port publishing on Linux). + allow 10.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + allow 127.0.0.0/8; + allow fc00::/7; + allow fe80::/10; + allow ::1; + deny all; + + location / { + proxy_pass http://waiter_pwa:80; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_buffering off; + } +} + server { listen 4443 ssl default_server; server_name _; diff --git a/nginx-proxy/nginx.conf b/nginx-proxy/nginx.conf index 8c5fb12..4bfb3d2 100644 --- a/nginx-proxy/nginx.conf +++ b/nginx-proxy/nginx.conf @@ -8,6 +8,7 @@ # :8081 → manager dashboard over plain HTTP, LAN only # :443 waiter.* / manager.* / bare IP → https (needs certs/cert.pem + key.pem) # :4443 → manager over https +# :8443 → native app over TLS (backend-managed key, pinned), LAN only # # Every proxied location forwards WebSocket upgrades (/api/ws/connect) and # disables buffering (SSE), otherwise live events never reach phones / KDS. @@ -179,6 +180,44 @@ server { } } +server { + listen 8443 ssl default_server; + server_name _; + + # Encrypted LAN entry for the native app (plan step 7). Key + self-signed cert + # are created and renewed by the backend (services/tls_identity.py) under + # ${DATA_PATH}/tls; phones pin the public key, so renewals need no action. + ssl_certificate /etc/nginx/xenia-tls/cert.pem; + ssl_certificate_key /etc/nginx/xenia-tls/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + # LAN only: plain HTTP must never be reachable from the internet, even if the + # client forwards ports on their router. Relies on Docker preserving the real + # client IP (default iptables port publishing on Linux). + allow 10.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + allow 127.0.0.0/8; + allow fc00::/7; + allow fe80::/10; + allow ::1; + deny all; + + location / { + proxy_pass http://waiter_pwa:80; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_buffering off; + } +} + server { listen 4443 ssl default_server; server_name _;