From 32082165b38d4935ccd7b0f878292bfdb14e6274 Mon Sep 17 00:00:00 2001 From: rajashish147 Date: Fri, 3 Apr 2026 01:46:45 +0530 Subject: [PATCH 1/2] fix(deploy): harden VPS paths and remove monorepo residue - Standardize deploy root handling to DEPLOY_ROOT= in deploy workflow and environment loader scripts - Add fail-fast DEPLOY_ROOT directory guards in deploy paths - Add mandatory deploy debug visibility (USER/HOME/PWD + ls of HOME/api) to SSH deployment steps in deploy.yml - Remove hardcoded DEPLOY_ROOT=/api in sync-infra and rollback jobs - Keep env contract gate mandatory before deploy: ./scripts/validate-env.sh --check-monitoring - Normalize rollback and observability docs to use C:\Users\rajas/api and api.conf - Remove legacy apps/api and fieldtrack.conf references from tracked source, docs, and rollback SQL comments - Add optional safe legacy VPS repo cleanup in vps-setup.sh (AUTO_CLEAN_LEGACY_REPO=true) - Keep docker/network naming aligned: api-blue, api-green, api_network --- .github/workflows/deploy.yml | 40 ++++++++-- docs/OBSERVABILITY_ARCHITECTURE.md | 2 +- docs/ROLLBACK_QUICKREF.md | 4 +- docs/ROLLBACK_SYSTEM.md | 10 +-- docs/walkthrough.md | 76 +++++++++---------- scripts/deploy-bluegreen.sh | 4 +- scripts/load-env.sh | 14 +++- scripts/validate-env.sh | 2 +- scripts/vps-setup.sh | 20 ++++- src/config/env.ts | 4 +- src/routes/events.routes.ts | 2 +- .../20260329000200_feat1_rollback.sql | 24 +++--- 12 files changed, 130 insertions(+), 72 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 837e621..07458d9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -525,7 +525,13 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/api" + export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + echo "USER=$(whoami)" + echo "HOME=$HOME" + echo "PWD=$(pwd)" + ls -la "$HOME" + ls -la "$HOME/api" + [ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } cd "$DEPLOY_ROOT" git fetch origin git reset --hard origin/master @@ -543,7 +549,14 @@ jobs: script: | set -euo pipefail T0=$(date +%s) - export DEPLOY_ROOT="/api" + export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + echo "USER=$(whoami)" + echo "HOME=$HOME" + echo "PWD=$(pwd)" + ls -la "$HOME" + ls -la "$HOME/api" + ls -la "$DEPLOY_ROOT" + [ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } cd "$DEPLOY_ROOT" chmod +x scripts/*.sh # Environment already validated in previous step @@ -589,7 +602,14 @@ jobs: script: | set -euo pipefail T0=$(date +%s) - export DEPLOY_ROOT="/api" + export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + echo "USER=$(whoami)" + echo "HOME=$HOME" + echo "PWD=$(pwd)" + ls -la "$HOME" + ls -la "$HOME/api" + [ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } + cd "$DEPLOY_ROOT" INFRA_DIR="$DEPLOY_ROOT/infra" NGINX_LIVE="/etc/nginx/sites-enabled/api.conf" ACTIVE_SLOT_FILE="/var/run/api/active-slot" @@ -647,7 +667,8 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/api" + export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + [ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } cd "$DEPLOY_ROOT" source scripts/load-env.sh echo "=== Checking /health via VPS (API_HOSTNAME=$API_HOSTNAME) ===" @@ -679,7 +700,8 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/api" + export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + [ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } cd "$DEPLOY_ROOT" source scripts/load-env.sh echo "=== Final health check via public endpoint (API_HOSTNAME=$API_HOSTNAME) ===" @@ -771,7 +793,13 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/api" + export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + echo "USER=$(whoami)" + echo "HOME=$HOME" + echo "PWD=$(pwd)" + ls -la "$HOME" + ls -la "$HOME/api" + [ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } cd "$DEPLOY_ROOT" chmod +x scripts/*.sh ./scripts/rollback.sh --auto diff --git a/docs/OBSERVABILITY_ARCHITECTURE.md b/docs/OBSERVABILITY_ARCHITECTURE.md index 01de609..8404eef 100644 --- a/docs/OBSERVABILITY_ARCHITECTURE.md +++ b/docs/OBSERVABILITY_ARCHITECTURE.md @@ -288,7 +288,7 @@ Nginx references LetsEncrypt certificates at `/etc/letsencrypt/live/ ``` ### Rollback to Previous Version ```bash -cd /api +cd "$HOME/api" ./scripts/rollback.sh ``` diff --git a/docs/ROLLBACK_SYSTEM.md b/docs/ROLLBACK_SYSTEM.md index 0704866..0aad560 100644 --- a/docs/ROLLBACK_SYSTEM.md +++ b/docs/ROLLBACK_SYSTEM.md @@ -62,7 +62,7 @@ The history is maintained using a rolling window: Deploy the latest image from CI: ```bash -cd /api +cd "$HOME/api" ./scripts/deploy-bluegreen.sh a4f91c2 ``` @@ -71,7 +71,7 @@ cd /api Instantly restore the last working deployment: ```bash -cd /api +cd "$HOME/api" ./scripts/rollback.sh ``` @@ -184,7 +184,7 @@ sudo systemctl reload nginx # Reload only if valid - name: Deploy to VPS run: | ssh ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} \ - "cd /api && \ + "cd \"$HOME/api\" && \ ./scripts/deploy-bluegreen.sh ${{ env.SHA_SHORT }}" ``` @@ -204,7 +204,7 @@ The history maintains the last 5 deployments in chronological order (newest firs ## File Locations ``` -/api/ +$HOME/api/ ├── scripts/ │ ├── deploy-bluegreen.sh # Blue-green deployment │ └── rollback.sh # Rollback automation @@ -225,7 +225,7 @@ chmod +x scripts/rollback.sh ``` ERROR: No deployment history found. -File not found: /api/.deploy_history +File not found: $HOME/api/.deploy_history ``` **Solution:** Deploy at least once before attempting rollback. diff --git a/docs/walkthrough.md b/docs/walkthrough.md index 54ef5b4..2f07725 100644 --- a/docs/walkthrough.md +++ b/docs/walkthrough.md @@ -14,10 +14,10 @@ Fastify + TypeScript backend scaffold with JWT, structured logging, modular rout | File | Action | Purpose | |------|--------|---------| -| [jwt.ts](file:///d:/Codebase/api/apps/api/src/types/jwt.ts) | **NEW** | Zod v4 schema for JWT payload (`sub`, `role`, `organization_id`) | -| [global.d.ts](file:///d:/Codebase/api/apps/api/src/types/global.d.ts) | **MODIFIED** | Wires `JwtPayload` into Fastify types + adds `organizationId` to request | -| [auth.ts](file:///d:/Codebase/api/apps/api/src/middleware/auth.ts) | **MODIFIED** | JWT verify → Zod validate → attach tenant context (or 401) | -| [tenant.ts](file:///d:/Codebase/api/apps/api/src/utils/tenant.ts) | **NEW** | `enforceTenant()` — scopes any query to `request.organizationId` | +| [jwt.ts](file:///d:/Codebase/api/src/types/jwt.ts) | **NEW** | Zod v4 schema for JWT payload (`sub`, `role`, `organization_id`) | +| [global.d.ts](file:///d:/Codebase/api/src/types/global.d.ts) | **MODIFIED** | Wires `JwtPayload` into Fastify types + adds `organizationId` to request | +| [auth.ts](file:///d:/Codebase/api/src/middleware/auth.ts) | **MODIFIED** | JWT verify → Zod validate → attach tenant context (or 401) | +| [tenant.ts](file:///d:/Codebase/api/src/utils/tenant.ts) | **NEW** | `enforceTenant()` — scopes any query to `request.organizationId` | ### How Tenant Enforcement Works @@ -71,14 +71,14 @@ flowchart LR | File | Layer | Purpose | |------|-------|---------| -| [attendance.schema.ts](file:///d:/Codebase/api/apps/api/src/modules/attendance/attendance.schema.ts) | Types | DB row type, Zod pagination schema, response interfaces | -| [attendance.repository.ts](file:///d:/Codebase/api/apps/api/src/modules/attendance/attendance.repository.ts) | Repository | Supabase queries — all scoped via `enforceTenant()` | -| [attendance.service.ts](file:///d:/Codebase/api/apps/api/src/modules/attendance/attendance.service.ts) | Service | Business rules: no duplicate check-in, no check-out without open session | -| [attendance.controller.ts](file:///d:/Codebase/api/apps/api/src/modules/attendance/attendance.controller.ts) | Controller | Extract request data, call service, return `{ success, data }` | -| [attendance.routes.ts](file:///d:/Codebase/api/apps/api/src/modules/attendance/attendance.routes.ts) | Routes | 4 endpoints with auth middleware, ADMIN guard on org-sessions | -| [supabase.ts](file:///d:/Codebase/api/apps/api/src/config/supabase.ts) | Config | Supabase client singleton (service role key) | -| [role-guard.ts](file:///d:/Codebase/api/apps/api/src/middleware/role-guard.ts) | Middleware | Reusable `requireRole()` factory — 403 on role mismatch | -| [errors.ts](file:///d:/Codebase/api/apps/api/src/utils/errors.ts) | Utils | Added `ForbiddenError` (403) | +| [attendance.schema.ts](file:///d:/Codebase/api/src/modules/attendance/attendance.schema.ts) | Types | DB row type, Zod pagination schema, response interfaces | +| [attendance.repository.ts](file:///d:/Codebase/api/src/modules/attendance/attendance.repository.ts) | Repository | Supabase queries — all scoped via `enforceTenant()` | +| [attendance.service.ts](file:///d:/Codebase/api/src/modules/attendance/attendance.service.ts) | Service | Business rules: no duplicate check-in, no check-out without open session | +| [attendance.controller.ts](file:///d:/Codebase/api/src/modules/attendance/attendance.controller.ts) | Controller | Extract request data, call service, return `{ success, data }` | +| [attendance.routes.ts](file:///d:/Codebase/api/src/modules/attendance/attendance.routes.ts) | Routes | 4 endpoints with auth middleware, ADMIN guard on org-sessions | +| [supabase.ts](file:///d:/Codebase/api/src/config/supabase.ts) | Config | Supabase client singleton (service role key) | +| [role-guard.ts](file:///d:/Codebase/api/src/middleware/role-guard.ts) | Middleware | Reusable `requireRole()` factory — 403 on role mismatch | +| [errors.ts](file:///d:/Codebase/api/src/utils/errors.ts) | Utils | Added `ForbiddenError` (403) | ### Endpoints @@ -132,11 +132,11 @@ curl "http://localhost:3000/attendance/org-sessions?page=1&limit=20" \ | File | Layer | Purpose | |------|-------|---------| -| [locations.schema.ts](file:///d:/Codebase/api/apps/api/src/modules/locations/locations.schema.ts) | Types | DB row type, Zod schema (`latitude`, `longitude`, `accuracy`, `recorded_at`), response interfaces | -| [locations.repository.ts](file:///d:/Codebase/api/apps/api/src/modules/locations/locations.repository.ts) | Repository | Supabase `createLocation` and `findLocationsBySession`, scoped via `enforceTenant()` | -| [locations.service.ts](file:///d:/Codebase/api/apps/api/src/modules/locations/locations.service.ts) | Service | Business rules: verify open attendance session before insertion | -| [locations.controller.ts](file:///d:/Codebase/api/apps/api/src/modules/locations/locations.controller.ts) | Controller | Extract request data, Zod payload validation, delegate to service, format responses | -| [locations.routes.ts](file:///d:/Codebase/api/apps/api/src/modules/locations/locations.routes.ts) | Routes | 2 endpoints, both restricted to `EMPLOYEE` via role guard | +| [locations.schema.ts](file:///d:/Codebase/api/src/modules/locations/locations.schema.ts) | Types | DB row type, Zod schema (`latitude`, `longitude`, `accuracy`, `recorded_at`), response interfaces | +| [locations.repository.ts](file:///d:/Codebase/api/src/modules/locations/locations.repository.ts) | Repository | Supabase `createLocation` and `findLocationsBySession`, scoped via `enforceTenant()` | +| [locations.service.ts](file:///d:/Codebase/api/src/modules/locations/locations.service.ts) | Service | Business rules: verify open attendance session before insertion | +| [locations.controller.ts](file:///d:/Codebase/api/src/modules/locations/locations.controller.ts) | Controller | Extract request data, Zod payload validation, delegate to service, format responses | +| [locations.routes.ts](file:///d:/Codebase/api/src/modules/locations/locations.routes.ts) | Routes | 2 endpoints, both restricted to `EMPLOYEE` via role guard | ### Endpoints @@ -1308,7 +1308,7 @@ When `GITHUB_SHA` is absent (local dev, manual deploy), the value is `"manual"`. |------|--------| | `src/server.ts` | Added `SIGTERM`/`SIGINT` graceful shutdown handlers; added boot log marker with `GITHUB_SHA` | | `src/workers/distance.worker.ts` | Added `workerStarted` flag; `startDistanceWorker` returns `Worker \| null` and is idempotent | -| `apps/api/Dockerfile` | Added `apk add curl`; added `HEALTHCHECK` directive | +| `Dockerfile` | Added `apk add curl`; added `HEALTHCHECK` directive | --- @@ -1589,15 +1589,15 @@ Phase 16 performed a clean database reset, replacing all `TEXT` columns that car | File | Action | Purpose | |------|--------|---------| | `supabase/migrations/20260309000000_phase16_schema.sql` | **NEW** | Full schema DDL with ENUM types, all 7 tables, indexes, RLS policies | -| `apps/api/src/types/database.ts` | **REGENERATED** | Supabase-generated TypeScript types (removed from `.gitignore` so Docker can build) | -| `apps/api/src/types/db.ts` | **NEW** | Human-readable type aliases — `AttendanceSession`, `Expense`, `GpsLocation`, etc. | +| `src/types/database.ts` | **REGENERATED** | Supabase-generated TypeScript types (removed from `.gitignore` so Docker can build) | +| `src/types/db.ts` | **NEW** | Human-readable type aliases — `AttendanceSession`, `Expense`, `GpsLocation`, etc. | | Attendance / expenses / locations / analytics schema files | **UPDATED** | Import row types from `db.ts` instead of long `Database["public"]["Tables"][...]["Row"]` paths | ### Migration Steps ``` supabase db reset --linked --yes -supabase gen types typescript --linked > apps/api/src/types/database.ts +supabase gen types typescript --linked > src/types/database.ts ``` --- @@ -1794,7 +1794,7 @@ Phase 13 moved FieldTrack 2.0 from a locally-runnable service to a fully operati ### 13.1 — VPS Setup Script -**File:** `apps/api/scripts/vps-setup.sh` +**File:** `scripts/vps-setup.sh` A single idempotent script provisions a fresh Ubuntu VPS from zero to production-ready: @@ -1810,7 +1810,7 @@ A single idempotent script provisions a fresh Ubuntu VPS from zero to production ### 13.2 — Nginx Reverse Proxy -**File:** `infra/nginx/fieldtrack.conf` +**File:** `infra/nginx/api.conf` - Terminates TLS (HTTPS → HTTP to backend containers) - Upstream block points to the active blue/green container port @@ -1859,12 +1859,12 @@ Dashboard is automatically loaded on container start via `infra/grafana/provisio | File | Purpose | |------|----------| -| `apps/api/scripts/vps-setup.sh` | Full VPS provisioning from scratch | +| `scripts/vps-setup.sh` | Full VPS provisioning from scratch | | `infra/docker-compose.monitoring.yml` | Prometheus, Grafana, Loki, Promtail, Tempo | | `infra/grafana/dashboards/fieldtrack.json` | Application dashboard (auto-provisioned) | | `infra/grafana/provisioning/dashboards/dashboard.yml` | Dashboard provisioning config | | `infra/grafana/provisioning/datasources/prometheus.yml` | Prometheus datasource provisioning | -| `infra/nginx/fieldtrack.conf` | Nginx reverse proxy and TLS termination | +| `infra/nginx/api.conf` | Nginx reverse proxy and TLS termination | | `infra/prometheus/prometheus.yml` | Scrape config targeting backend `/metrics` | --- @@ -1890,7 +1890,7 @@ Phase 14 connected the three pillars of observability — **metrics**, **logs**, ### 14.1 — OpenTelemetry Tracing -**File:** `apps/api/src/tracing.ts` +**File:** `src/tracing.ts` Must be the **first import in `server.ts`** so the SDK wraps all subsequently-loaded modules. @@ -1923,7 +1923,7 @@ Every HTTP request, BullMQ job, and Supabase query produces a span automatically ### 14.2 — Pino Log Correlation -**File:** `apps/api/src/config/logger.ts` +**File:** `src/config/logger.ts` An `otelMixin` function injects the active trace's `trace_id`, `span_id`, and `trace_flags` into **every Pino log line** as top-level fields: @@ -1948,7 +1948,7 @@ When no active span exists (background workers, startup), the mixin returns `{}` ### 14.3 — Prometheus Exemplars -**File:** `apps/api/src/plugins/prometheus.ts` +**File:** `src/plugins/prometheus.ts` The `http_request_duration_seconds` histogram is upgraded to attach trace IDs as exemplars to each observation: @@ -1971,13 +1971,13 @@ Infrastructure requirements enabled in `docker-compose.monitoring.yml`: | File | Action | |------|--------| -| `apps/api/src/tracing.ts` | **NEW** — OpenTelemetry SDK bootstrap; OTLP exporter to Tempo | -| `apps/api/src/server.ts` | **MODIFIED** — `import "./tracing.js"` as the very first import | -| `apps/api/src/config/logger.ts` | **MODIFIED** — `otelMixin` injects trace/span IDs into every log line | -| `apps/api/src/plugins/prometheus.ts` | **MODIFIED** — exemplar support on duration histogram | +| `src/tracing.ts` | **NEW** — OpenTelemetry SDK bootstrap; OTLP exporter to Tempo | +| `src/server.ts` | **MODIFIED** — `import "./tracing.js"` as the very first import | +| `src/config/logger.ts` | **MODIFIED** — `otelMixin` injects trace/span IDs into every log line | +| `src/plugins/prometheus.ts` | **MODIFIED** — exemplar support on duration histogram | | `infra/docker-compose.monitoring.yml` | **MODIFIED** — Tempo OTLP ports 4317/4318; Prometheus exemplar storage | | `infra/prometheus/prometheus.yml` | **MODIFIED** — OpenMetrics scrape protocol for backend jobs | -| `apps/api/src/app.ts` | **MODIFIED** — `onRequest` hook enriches active span with route pattern and request ID | +| `src/app.ts` | **MODIFIED** — `onRequest` hook enriches active span with route pattern and request ID | --- @@ -2372,7 +2372,7 @@ The pipeline is split into two jobs: ### Multi-Version Rollback System -**Files:** `apps/api/scripts/deploy-bluegreen.sh`, `apps/api/scripts/rollback.sh` +**Files:** `scripts/deploy-bluegreen.sh`, `scripts/rollback.sh` #### Deployment History @@ -2415,9 +2415,9 @@ Any SHA from `.deploy_history` (or any valid GHCR tag) can be targeted directly. | File | Action | |------|--------| | `.github/workflows/deploy.yml` | **MODIFIED** — Split into `test` + `build-and-deploy` jobs; `npm ci`; `tsc --noEmit`; GHA cache | -| `apps/api/scripts/deploy-bluegreen.sh` | **MODIFIED** — Appends SHA to `.deploy_history`; maintains 5-entry window | -| `apps/api/scripts/rollback.sh` | **NEW** — Reads history, confirms, re-deploys previous image | -| `apps/api/.gitignore` | **MODIFIED** — `.deploy_history` excluded | +| `scripts/deploy-bluegreen.sh` | **MODIFIED** — Appends SHA to `.deploy_history`; maintains 5-entry window | +| `scripts/rollback.sh` | **NEW** — Reads history, confirms, re-deploys previous image | +| `.gitignore` | **MODIFIED** — `.deploy_history` excluded | | `docs/ROLLBACK_SYSTEM.md` | **NEW** — Architecture, usage, troubleshooting guide | | `docs/ROLLBACK_QUICKREF.md` | **NEW** — Fast reference card for operators | @@ -2433,4 +2433,4 @@ Any SHA from `.deploy_history` (or any valid GHCR tag) can be targeted directly. | Docker layer cache | `cache-from/to: type=gha` in `docker/build-push-action` | | Rollback requires 2+ deployments | Script exits with error if history is insufficient | | Rollback time | < 10 s (image already in GHCR) | -| SHA tag on each image | `ghcr.io/.../fieldtrack-api:` retained permanently | \ No newline at end of file +| SHA tag on each image | `ghcr.io/.../fieldtrack-api:` retained permanently | diff --git a/scripts/deploy-bluegreen.sh b/scripts/deploy-bluegreen.sh index 4a210f7..1bbf8f8 100644 --- a/scripts/deploy-bluegreen.sh +++ b/scripts/deploy-bluegreen.sh @@ -153,7 +153,9 @@ APP_PORT=3000 NETWORK="api_network" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" +DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" +[ -d "$DEPLOY_ROOT" ] || { echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT"; exit 1; } +REPO_DIR="$DEPLOY_ROOT" # Slot state directory and file. # /var/run/api/ is chosen over /tmp (world-writable, cleaned by tmpwatch) diff --git a/scripts/load-env.sh b/scripts/load-env.sh index c5c5b00..a00b5af 100644 --- a/scripts/load-env.sh +++ b/scripts/load-env.sh @@ -25,8 +25,18 @@ _LES_REPO="$(cd "$_LES_DIR/.." && pwd)" # ── DEPLOY_ROOT ───────────────────────────────────────────────────────────── # Prefer an already-exported value (e.g. set explicitly by the CI SSH step); -# fall back to the path inferred from this script's own location on the VPS. -export DEPLOY_ROOT="${DEPLOY_ROOT:-$_LES_REPO}" +# default to the canonical VPS deployment path under the current user's home. +export DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/api}" + +[ -d "$DEPLOY_ROOT" ] || { + echo "❌ DEPLOY_ROOT not found: $DEPLOY_ROOT" + echo " Expected repository root at: $HOME/api" + echo " If your repo is elsewhere, export DEPLOY_ROOT before running scripts." + if [ -d "$_LES_REPO" ]; then + echo " Detected script-relative repo candidate: $_LES_REPO" + fi + exit 1 +} # ── ENV_FILE ───────────────────────────────────────────────────────────────── export ENV_FILE="$DEPLOY_ROOT/.env" diff --git a/scripts/validate-env.sh b/scripts/validate-env.sh index 0624459..50e5b11 100644 --- a/scripts/validate-env.sh +++ b/scripts/validate-env.sh @@ -93,7 +93,7 @@ header "Forbidden variable check (API_DOMAIN)" # Hard error: API_DOMAIN assignment must not appear ANYWHERE in the repository. # Repository-wide scan (excluding node_modules) to catch drift across scripts # and generated env files, not only the two primary env files. -if grep -r "API_DOMAIN" . --exclude-dir=node_modules 2>/dev/null | grep -E "API_DOMAIN[[:space:]]*="; then +if grep -r "API_DOMAIN" "$REPO_ROOT" --exclude-dir=node_modules 2>/dev/null | grep -E "API_DOMAIN[[:space:]]*="; then fail "API_DOMAIN assignment found in repository — this variable has been REMOVED" fail " Replace with: API_BASE_URL=https://your-domain.com (in .env)" fail " API_HOSTNAME=your-domain.com (in infra/.env.monitoring)" diff --git a/scripts/vps-setup.sh b/scripts/vps-setup.sh index 1f360d2..9fd7ea7 100644 --- a/scripts/vps-setup.sh +++ b/scripts/vps-setup.sh @@ -26,7 +26,11 @@ GH_PAT="" # GitHub Personal Access Token (pack DEPLOY_USER="ashish" # Non-root user for deployment DEPLOY_USER_SSH_PUBLIC_KEY="" # Required public key for deploy user (ssh-ed25519 ...) REPO_URL="https://github.com/fieldtrack-tech/api.git" -REPO_DIR="/api" +DEPLOY_HOME="/home/${DEPLOY_USER}" +DEPLOY_ROOT="${DEPLOY_ROOT:-${DEPLOY_HOME}/api}" +REPO_DIR="$DEPLOY_ROOT" +LEGACY_REPO_DIR="${DEPLOY_HOME}/FieldTrack-2.0" +AUTO_CLEAN_LEGACY_REPO="${AUTO_CLEAN_LEGACY_REPO:-false}" NETWORK="api_network" NGINX_SITE_LINK="/etc/nginx/conf.d/api.conf" @@ -237,6 +241,20 @@ log "Fail2Ban configured." # ============================================================================ log "Phase 8: Cloning repository..." +if [ -d "$LEGACY_REPO_DIR" ] && [ "$LEGACY_REPO_DIR" != "$REPO_DIR" ]; then + warn "Legacy deployment directory detected: $LEGACY_REPO_DIR" + if [ "$AUTO_CLEAN_LEGACY_REPO" = "true" ]; then + if [ -L "$LEGACY_REPO_DIR" ]; then + err "Refusing to remove symlinked legacy path: $LEGACY_REPO_DIR" + fi + rm -rf "$LEGACY_REPO_DIR" + log "Removed legacy deployment directory: $LEGACY_REPO_DIR" + else + warn "Leaving legacy directory untouched (AUTO_CLEAN_LEGACY_REPO=false)." + warn "Set AUTO_CLEAN_LEGACY_REPO=true to auto-remove $LEGACY_REPO_DIR" + fi +fi + if [ -d "$REPO_DIR" ]; then warn "Repository directory already exists, pulling latest..." cd "$REPO_DIR" diff --git a/src/config/env.ts b/src/config/env.ts index f1f123b..4f5327d 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -1,8 +1,8 @@ // ⚠️ DO NOT add env variables without updating ALL of the following: -// - apps/api/.env.example (developer template — every variable must appear here) +// - .env.example (developer template — every variable must appear here) // - .github/workflows/pr.yml (CI env: block — non-secret defaults for container bootstrap) // - docs/env-contract.md (source-of-truth documentation table) -// - apps/api/scripts/validate-env.sh (if monitoring-layer or cross-file validation needed) +// - scripts/validate-env.sh (if monitoring-layer or cross-file validation needed) // // Failure to keep these in sync causes config drift, silent CI failures, and // environment contract violations caught only at production deploy time. diff --git a/src/routes/events.routes.ts b/src/routes/events.routes.ts index d6d7ace..0392fbf 100644 --- a/src/routes/events.routes.ts +++ b/src/routes/events.routes.ts @@ -26,7 +26,7 @@ const MAX_SSE_CONNECTIONS_PER_ORG = 20; * keep the connection alive through proxies and load balancers. * * Auth: ADMIN only. - * Nginx: requires `proxy_buffering off` — already configured in fieldtrack.conf. + * Nginx: requires `proxy_buffering off` — already configured in infra/nginx/api.conf. * Limit: max 20 concurrent connections per org (M4 — FD exhaustion protection). */ export async function eventsRoutes(app: FastifyInstance): Promise { diff --git a/supabase/migrations/20260329000200_feat1_rollback.sql b/supabase/migrations/20260329000200_feat1_rollback.sql index 1c29da4..5f69bc7 100644 --- a/supabase/migrations/20260329000200_feat1_rollback.sql +++ b/supabase/migrations/20260329000200_feat1_rollback.sql @@ -70,18 +70,18 @@ DROP TABLE IF EXISTS public.employee_last_state CASCADE; -- -- git revert -- # or --- git checkout -- apps/api/src/workers/snapshot.queue.ts --- git checkout -- apps/api/src/workers/snapshot.worker.ts --- git checkout -- apps/api/src/workers/startup.ts --- git checkout -- apps/api/src/modules/attendance/attendance.service.ts --- git checkout -- apps/api/src/modules/locations/locations.service.ts --- git checkout -- apps/api/src/modules/expenses/expenses.service.ts --- git checkout -- apps/api/src/modules/expenses/expenses.controller.ts --- git checkout -- apps/api/src/modules/expenses/expenses.repository.ts --- git checkout -- apps/api/src/modules/employees/employees.repository.ts --- git checkout -- apps/api/src/modules/employees/employees.controller.ts --- git checkout -- apps/api/src/modules/profile/profile.repository.ts --- git checkout -- apps/api/src/modules/profile/profile.service.ts +-- git checkout -- src/workers/snapshot.queue.ts +-- git checkout -- src/workers/snapshot.worker.ts +-- git checkout -- src/workers/startup.ts +-- git checkout -- src/modules/attendance/attendance.service.ts +-- git checkout -- src/modules/locations/locations.service.ts +-- git checkout -- src/modules/expenses/expenses.service.ts +-- git checkout -- src/modules/expenses/expenses.controller.ts +-- git checkout -- src/modules/expenses/expenses.repository.ts +-- git checkout -- src/modules/employees/employees.repository.ts +-- git checkout -- src/modules/employees/employees.controller.ts +-- git checkout -- src/modules/profile/profile.repository.ts +-- git checkout -- src/modules/profile/profile.service.ts -- -- The BullMQ "snapshot-engine" queue in Redis will drain naturally. -- Any jobs still in the queue when workers are stopped can be safely From d23f954f434cf47a3ddd2795fba1b2425dea0c80 Mon Sep 17 00:00:00 2001 From: rajashish147 Date: Fri, 3 Apr 2026 02:19:22 +0530 Subject: [PATCH 2/2] feat(deploy): ensure log directory exists with fallback to home directory --- scripts/deploy-bluegreen.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/deploy-bluegreen.sh b/scripts/deploy-bluegreen.sh index 1bbf8f8..7a35b51 100644 --- a/scripts/deploy-bluegreen.sh +++ b/scripts/deploy-bluegreen.sh @@ -63,6 +63,14 @@ trap '_ft_trap_err "$LINENO"' ERR _FT_STATE="INIT" DEPLOY_LOG_FILE="${DEPLOY_LOG_FILE:-/var/log/api/deploy.log}" +# Ensure log directory exists with fallback to home directory +LOG_DIR="$(dirname "$DEPLOY_LOG_FILE")" +if ! mkdir -p "$LOG_DIR" 2>/dev/null; then + LOG_DIR="$HOME/api/logs" + DEPLOY_LOG_FILE="$LOG_DIR/deploy.log" + mkdir -p "$LOG_DIR" +fi + _ft_log() { { set +x; } 2>/dev/null local log_entry