gigafibre-fsm/apps/client/deploy.sh
louispaulb 041df48ac3 security: harden payments/hub auth + remove leaked ERPNext token from source
Auth hardening:
- payments: per-customer JWT authorization on dual-use /payments routes
  (balance/methods/invoice/checkout/setup/portal/toggle-ppa) via authorizeCustomer
  + PAYMENTS_AUTH=enforce; portal retains+sends magic-link JWT (sessionStorage)
- hub: fail-closed Stripe webhook, /accept/doc-pdf IDOR gate, telephony field-name
  guard (SQLi), modem-bridge private-IP guard (SSRF), ALWAYS_ENFORCE expansion

Leaked-credential cleanup (token already rotated in ERPNext):
- de-hardcode ERPNext API token -> env in bulk_submit.py, import_items.py,
  apps/client/deploy.sh; placeholder in apps/ops/infra/nginx.conf (nginx injects)
- ops prod build no longer bakes VITE_ERP_TOKEN (.env.production empty)
- de-hardcode legacy DB password -> env in import_items.py
- gitignore legacy migration PII exports (tsv/json)

Conversations/UI:
- floating (un-docked) conversation panel; full-width mailbox
- per-message real sender from email headers; unified scroll; header spacing
- campaign pre-send review (subject/from/channel), Gmail send channel, clone-as-draft

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 06:17:17 -04:00

76 lines
3.2 KiB
Bash
Executable File

#!/bin/bash
# ─────────────────────────────────────────────────────────────────────────────
# deploy.sh — Build Gigafibre Customer Portal and ship to portal.gigafibre.ca
#
# Topology (2026-04-22):
# portal.gigafibre.ca → standalone nginx:alpine container `client-portal`
# serving /opt/client-app/ on erp.gigafibre.ca.
# client.gigafibre.ca → Traefik 302 → portal.gigafibre.ca (legacy alias).
#
# We build the PWA with base=/ (the portal host serves from root, not from
# /assets/client-app/ like the old ERPNext-embedded deployment) and rsync
# the dist/pwa/ output into /opt/client-app/ on the server. The nginx
# container bind-mounts /opt/client-app/ read-only, so files appear live
# with no container restart.
#
# Usage:
# ./deploy.sh # build + ship to production (portal.gigafibre.ca)
# ./deploy.sh local # build only (dist/pwa/) — no deploy
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
SERVER="root@96.125.196.67"
SSH_KEY="$HOME/.ssh/proxmox_vm"
DEST="/opt/client-app"
echo "==> Installing dependencies..."
npm ci --silent
echo "==> Building PWA (base=/ for portal.gigafibre.ca)..."
# Do NOT bake an ERPNext token into the portal bundle. Any direct ERPNext
# calls (catalog, invoice PDFs) must go through a server-side token proxy.
DEPLOY_BASE=/ npx quasar build -m pwa
if [ "${1:-}" = "local" ]; then
echo ""
echo "Local build done. Output: dist/pwa/"
exit 0
fi
echo "==> Packaging..."
tar czf /tmp/client-pwa.tar.gz -C dist/pwa .
echo "==> Shipping to $SERVER:$DEST ..."
# We deploy to a staging dir and flip atomically — this avoids serving
# a half-written index.html referencing new hashed assets that haven't
# finished uploading (would 404 the SPA for a second or two).
cat /tmp/client-pwa.tar.gz | ssh -i "$SSH_KEY" "$SERVER" "bash -s" <<'REMOTE'
set -euo pipefail
cat > /tmp/client.tar.gz
STAGE=$(mktemp -d /opt/client-app.new.XXXXXX)
tar xzf /tmp/client.tar.gz -C "$STAGE"
# Preserve docker-compose.yml + nginx.conf from live dir (they live alongside the SPA)
cp /opt/client-app/docker-compose.yml "$STAGE/" 2>/dev/null || true
cp /opt/client-app/nginx.conf "$STAGE/" 2>/dev/null || true
# Atomic flip
BACKUP=/opt/client-app.bak.$(date +%s)
mv /opt/client-app "$BACKUP"
mv "$STAGE" /opt/client-app
# Keep last 3 backups, prune the rest
ls -dt /opt/client-app.bak.* 2>/dev/null | tail -n +4 | xargs -r rm -rf
rm -f /tmp/client.tar.gz
# nginx bind-mount follows the original inode, so the `mv` swap above
# leaves the container pointed at the backup dir. Restart to re-bind.
docker restart client-portal >/dev/null
echo " Deployed. Backup: $BACKUP"
REMOTE
rm -f /tmp/client-pwa.tar.gz
echo ""
echo "Done! Customer Portal: https://portal.gigafibre.ca/"
echo "Legacy alias (302): https://client.gigafibre.ca/"