From d15dec5e6c244e9c58d4ce3eb1890b674328d8a5 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Wed, 8 Jul 2026 15:59:48 -0400 Subject: [PATCH] infra: deploy.sh renders ops nginx.conf from versioned template w/ server-side token injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - apps/ops/infra/nginx.conf brought back in sync with the live working conf (was missing the /hub proxy + /auth/whoami → deploying the old template would have broken OPS). Tokens are placeholders (__ERP_API_TOKEN__ / __HUB_TOKEN__), never real values. - deploy.sh now renders nginx.conf on every deploy: substitutes the tokens from the server's /opt/targo-hub/.env (ERP_TOKEN = service account, not Administrator; HUB_SERVICE_TOKEN), validates via throwaway nginx -t, then restarts ops-frontend. Fixes the "nginx.conf deleted from host → OPS down on restart" landmine and makes token rotation a one-command redeploy. Co-Authored-By: Claude Opus 4.8 --- apps/ops/deploy.sh | 23 +++++++++++++++++++++ apps/ops/infra/nginx.conf | 42 ++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/apps/ops/deploy.sh b/apps/ops/deploy.sh index 5c18f60..bf5c669 100755 --- a/apps/ops/deploy.sh +++ b/apps/ops/deploy.sh @@ -62,6 +62,29 @@ else rm -f /tmp/ops-build.tar.gz + # ── Render nginx.conf from the versioned template, injecting tokens SERVER-SIDE ── + # Tokens live only on the server (/opt/targo-hub/.env): ERP_TOKEN = ERPNext service account + # (hub-service@targo.ca, NOT Administrator) · HUB_SERVICE_TOKEN = hub Bearer. They are never + # in the repo, never in the JS bundle. This makes the conf reproducible (fixes the past + # "nginx.conf deleted from host → ops down on restart" landmine) and rotation a one-command redeploy. + echo "==> Rendering nginx.conf (server-side token injection)..." + scp -i "$SSH_KEY" infra/nginx.conf "$SERVER":/tmp/ops-nginx.tmpl + ssh -i "$SSH_KEY" "$SERVER" ' + set -e + ERP=$(grep -E "^ERP_TOKEN=" /opt/targo-hub/.env | cut -d= -f2-) + HUB=$(grep -E "^HUB_SERVICE_TOKEN=" /opt/targo-hub/.env | cut -d= -f2-) + [ -z "$HUB" ] && HUB=$(grep -E "^HUB_TOKEN=" /opt/targo-hub/.env | cut -d= -f2-) + if [ -z "$ERP" ] || [ -z "$HUB" ]; then echo "ABORT: ERP_TOKEN/HUB token missing in /opt/targo-hub/.env"; exit 1; fi + sed -e "s#__ERP_API_TOKEN__#${ERP}#g" -e "s#__HUB_TOKEN__#${HUB}#g" /tmp/ops-nginx.tmpl > '"$DEST"'/nginx.conf + rm -f /tmp/ops-nginx.tmpl + # validate against a throwaway nginx before touching the live container + docker run --rm -v '"$DEST"'/nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine nginx -t + # restart re-binds the file (avoids stale //deleted inode); ~1s blip + docker restart ops-frontend >/dev/null + sleep 2 + docker exec ops-frontend sh -c "wget -qO- http://127.0.0.1/api/method/frappe.auth.get_logged_user 2>/dev/null" | head -c 80; echo + ' + echo "" echo "Done! Targo Ops ($BUILD_MODE): https://erp.gigafibre.ca/ops/" fi diff --git a/apps/ops/infra/nginx.conf b/apps/ops/infra/nginx.conf index c8ae2da..fe81bc9 100644 --- a/apps/ops/infra/nginx.conf +++ b/apps/ops/infra/nginx.conf @@ -6,23 +6,51 @@ server { resolver 127.0.0.11 valid=30s; - # ERPNext API proxy — token injected server-side (never in JS bundle) - # To rotate: edit this file + docker restart ops-frontend + # Return Authentik user identity (email injected by Traefik forward-auth) + location = /auth/whoami { + default_type application/json; + return 200 '{"email":"$http_x_authentik_email","username":"$http_x_authentik_username","groups":"$http_x_authentik_groups"}'; + } + + # ERPNext API proxy — token injected SERVER-SIDE at deploy (never in the JS bundle, never committed). + # deploy.sh fills the placeholder below from the server's /opt/targo-hub/.env ERP_TOKEN + # (the dedicated service account hub-service@targo.ca — NOT Administrator). Rotate = re-run deploy.sh. location /api/ { proxy_pass https://erp.gigafibre.ca; proxy_ssl_verify off; proxy_set_header Host erp.gigafibre.ca; - proxy_set_header Authorization "token __ERP_API_TOKEN__"; # injected at deploy — never commit a real token + proxy_set_header Authorization "token __ERP_API_TOKEN__"; proxy_set_header X-Authentik-Email $http_x_authentik_email; proxy_set_header X-Authentik-Username $http_x_authentik_username; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } - # NOTE: Ollama Vision proxy removed 2026-04-22 — invoice OCR and all - # barcode/equipment scans now go directly to targo-hub (Gemini 2.5 Flash). - # See docs/features/vision-ocr.md. The hub handles CORS + rate-limit, so no - # nginx pass-through is needed here. + # Ollama Vision API proxy (kept for legacy vision paths; primary OCR goes through the hub) + location /ollama/ { + set $ollama_upstream http://ollama:11434; + proxy_pass $ollama_upstream/; + proxy_set_header Host $host; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + client_max_body_size 20m; + } + + # targo-hub proxy — service token injected SERVER-SIDE at deploy (never bundled/committed). + # deploy.sh fills the placeholder below from the server's /opt/targo-hub/.env HUB_SERVICE_TOKEN. + location /hub/ { + proxy_pass https://msg.gigafibre.ca/; + proxy_ssl_verify off; + proxy_set_header Host msg.gigafibre.ca; + proxy_set_header Authorization "Bearer __HUB_TOKEN__"; + proxy_set_header X-Authentik-Email $http_x_authentik_email; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_read_timeout 3600s; + } # SPA fallback — all routes serve index.html location / {