feat(proxy): plain-HTTP LAN entry for waiter (:80) and manager (:8081), no certs required

Phones can now open the waiter app at http://<LAN IP> with no domain, DNS
record or certificate (works around DNS-rebinding failures, KI-001). The
manager gets http://<LAN IP>:8081, with http://<LAN IP>/manager redirecting
there. waiter.*/manager.* hostnames on :80 still redirect to https, so
legacy domain sites behave as before.

- Both plain-HTTP servers are LAN-only (allow RFC1918/loopback/ULA/link-local,
  deny all -> 403), so a router port-forward can't expose an unencrypted POS
- nginx-proxy/nginx.conf and the install.sh heredoc are now byte-identical
  (one canonical config, routing map in its header)
- install.sh generates a 10-year self-signed cert when certs/ is empty (nginx
  won't start its TLS listeners without one), detects HOST_IP via
  'ip route get', prompts for it on fresh installs and backfills it into an
  existing .env, always starts the stack, prints the LAN URLs
- docker-compose publishes 8081; .env.example documents HOST_IP
- pack README: ports/request path updated, CS-5 byte-identical check

Verified: nginx -t; install.sh in Debian (fresh / upgrade without HOST_IP /
re-run - no duplicate HOST_IP, cert SAN includes HOST_IP, key 600); full
stack from freshly built images: every entry point returns the expected
200/301/302, and removing the gateway's range from the allow list yields 403
on :80 and :8081.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 11:50:29 +03:00
co-authored by Claude Opus 5.5
parent 18012c2c95
commit 9fcb4df30e
5 changed files with 330 additions and 54 deletions
+4
View File
@@ -9,6 +9,10 @@ CLOUD_URL=https://xenia-admin.bonamin.gr
SECRET_KEY=generate-with-openssl-rand-hex-32
LICENSE_GRACE_HOURS=24
# This machine's LAN IP — the address phones open (http://<HOST_IP>) and the
# pairing QR code encodes. install.sh detects it; reserve it in the router's DHCP.
HOST_IP=
# Break-glass support account (leave blank to disable)
MASTER_USERNAME=
MASTER_PASSWORD=