#!/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)..." # VITE_ERP_TOKEN is still needed by a few API calls that hit ERPNext # directly (catalog, invoice PDFs). TODO: migrate these behind the hub. VITE_ERP_TOKEN="b273a666c86d2d0:06120709db5e414" 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/"