Compare commits
4 Commits
0a22eab4a8
...
aa546235fb
| Author | SHA1 | Date | |
|---|---|---|---|
| aa546235fb | |||
| c637b2783c | |||
| 77b2acb1ca | |||
| 10923164ad |
@@ -0,0 +1,13 @@
|
||||
# Booth reverse proxy. `:80` matches ANY hostname/IP, so the booth is reachable as
|
||||
# http://<booth-ip>/, http://localhost/, or http://parksystems.msai.al/ (the name pointed
|
||||
# at the booth's IP via hosts/DNS on-site) — with no domain baked into any image. The SPA
|
||||
# uses a relative /api base, so everything (HTTP + the /api/ws WebSocket, which Caddy
|
||||
# upgrades automatically) just flows through to the server container.
|
||||
#
|
||||
# TLS later: replace `:80` with the real hostname (e.g. `parksystems.msai.al`), uncomment
|
||||
# Caddy's :443 in docker-compose.prod.yml, and Caddy auto-provisions HTTPS. For a private
|
||||
# CA / internal cert, use `tls /path/cert.pem /path/key.pem`.
|
||||
:80 {
|
||||
encode gzip
|
||||
reverse_proxy server:3000
|
||||
}
|
||||
@@ -27,6 +27,12 @@ ENV CI=true
|
||||
COPY . .
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile --offline
|
||||
# Force the SPA to use a SAME-ORIGIN (relative) API base for THIS image. Vite auto-loads
|
||||
# apps/web/.env.production, which sets VITE_API_BASE=http://127.0.0.1:3000 for the TAURI
|
||||
# DESKTOP build — but here Fastify serves the SPA same-origin, so an absolute base would
|
||||
# make the browser hit 127.0.0.1:3000 cross-origin and fail CORS. `.env.production.local`
|
||||
# has higher precedence than `.env.production`, so this empties it for the server image only.
|
||||
RUN echo 'VITE_API_BASE=' > apps/web/.env.production.local
|
||||
# Builds shared/db/devices, the server dist, AND the web SPA dist (apps/web/dist).
|
||||
RUN pnpm turbo run build --filter=@parking/server --filter=@parking/web
|
||||
# `pnpm deploy` produces a SELF-CONTAINED prod bundle for the server in /deploy: a hoisted
|
||||
|
||||
+35
-4
@@ -1,14 +1,41 @@
|
||||
# PROD override: pull pinned registry images (no local build), restart always, real
|
||||
# recognizer, and keep vision INTERNAL (only the server port is published). Use with the
|
||||
# base file and pin TAG to the branch/SHA you deploy:
|
||||
# recognizer, and a CADDY reverse proxy in front so operators reach the booth on a clean
|
||||
# port-80 URL (no :3000) — and a path to real TLS later. Server + vision stay INTERNAL
|
||||
# (only Caddy publishes a port). Use with the base file and pin TAG to the branch you deploy:
|
||||
# REGISTRY=git.infra.msai.al/mca/parking_solution TAG=main \
|
||||
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
# See wiki/decisions/container-deployment.md.
|
||||
|
||||
services:
|
||||
server:
|
||||
# Reverse proxy: :80 → server:3000 (WebSocket /api/ws upgrades pass through natively).
|
||||
# Caddy is a single static binary with a one-line proxy config; swapping http:// for the
|
||||
# site's real hostname later enables automatic HTTPS. The booth is reached at
|
||||
# http://<name-or-ip>/ (the name set via hosts/DNS on-site — NOT baked into any image).
|
||||
proxy:
|
||||
image: caddy:2-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "80:80"
|
||||
# - "443:443" # uncomment when moving to TLS (and set a real hostname in Caddyfile)
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
depends_on:
|
||||
- server
|
||||
networks:
|
||||
- parking
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
server:
|
||||
restart: always
|
||||
# No published port — only the proxy reaches the server, over the private network.
|
||||
expose:
|
||||
- "3000"
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
@@ -26,3 +53,7 @@ services:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
|
||||
@@ -15,8 +15,19 @@ services:
|
||||
DATABASE_URL: /data/parking.sqlite
|
||||
# Reach the vision service over the private compose network by service name.
|
||||
VISION_URL: http://vision:8089
|
||||
VISION_ENABLED: ${VISION_ENABLED:-1}
|
||||
# JWT signing secret MUST be provided at deploy (no insecure default — see auth.ts).
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in the env/.env}
|
||||
# Dedicated ledger-signing key. Falls back to JWT_SECRET (with a warning) if empty;
|
||||
# set a distinct one in prod. See apps/server/.env.example + local-jwt-auth.
|
||||
EVENT_SIGNING_KEY: ${EVENT_SIGNING_KEY:-}
|
||||
# CRITICAL on the plain-HTTP booth LAN: cookies are Secure (HTTPS-only) by DEFAULT,
|
||||
# so without COOKIE_SECURE=0 the auth cookie is never sent over http and operators
|
||||
# CANNOT LOG IN. Leave unset only behind TLS. See disk-os-hardening "deploy-time runbook".
|
||||
COOKIE_SECURE: ${COOKIE_SECURE:-0}
|
||||
# The booth WS live feed checks the browser Origin — must list the address operators
|
||||
# actually hit (e.g. http://<booth-ip>:3000), or the live feed is rejected.
|
||||
WS_ALLOWED_ORIGINS: ${WS_ALLOWED_ORIGINS:-}
|
||||
volumes:
|
||||
- parking-data:/data
|
||||
depends_on:
|
||||
|
||||
@@ -72,6 +72,21 @@ The **desktop** app stays on its own tag-only `release.yml` (Tauri installers),
|
||||
THEN run `python -c "from fast_alpr import ALPR; ALPR()"` so weights land in `/home/vision/.cache`
|
||||
— exactly where the runtime reads. Verify the boot log shows NO "Downloading …onnx".
|
||||
|
||||
## Web access — relative API + Caddy proxy (2026-06-23)
|
||||
|
||||
- **The server-image SPA uses a RELATIVE `/api` base** (no baked origin), so the UI works loaded
|
||||
from any hostname/IP. The Dockerfile empties `VITE_API_BASE` via `apps/web/.env.production.local`
|
||||
before the web build — because Vite auto-loads `apps/web/.env.production`, which sets
|
||||
`VITE_API_BASE=http://127.0.0.1:3000` for the **Tauri desktop** build only. Without the override
|
||||
the browser bundle baked `127.0.0.1:3000` and failed Same-Origin Policy from any other host. **Do
|
||||
NOT bake the domain via a build var** — relative means naming is controlled by hosts/DNS at deploy,
|
||||
never a rebuild.
|
||||
- **A Caddy reverse proxy** (prod override) publishes `:80` → `server:3000` (server is `expose`-only,
|
||||
internal); `/api/ws` upgrades pass through. `Caddyfile` binds `:80` so it matches ANY host — booth
|
||||
IP, localhost, or `parksystems.msai.al` (pointed at the booth IP via hosts/DNS on-site). TLS later:
|
||||
swap `:80` for the real hostname + uncomment Caddy `:443` → auto-HTTPS.
|
||||
- `WS_ALLOWED_ORIGINS` (env) must list any REMOTE origin admins use (same-origin always passes).
|
||||
|
||||
## Invariants (must hold)
|
||||
|
||||
- **Never bake the live DB.** `.dockerignore` excludes `**/parking.sqlite*` (incl. `-wal`/`-shm`/
|
||||
|
||||
Reference in New Issue
Block a user