diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 40b7e2a..57cc197 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -40,6 +40,56 @@ jobs: pm2 restart ppms echo "=== Deployed $TAG ===" + - name: Build & (re)start microservices + run: | + set -euo pipefail + export NVM_DIR="$HOME/.nvm" + . "$NVM_DIR/nvm.sh" + + cd "$HOME/pms" + + # Pull only the few keys the services need out of the app's .env (the + # single source of truth on the host). Never import PORT (each service's + # port is fixed in ecosystem.config.js) or the runner's ephemeral + # FORGEJO_TOKEN. Missing keys → empty, which the services tolerate. + envget() { grep -E "^$1=" App/.env 2>/dev/null | head -1 | sed -E 's/^[^=]+=//; s/^"//; s/"$//'; } + export PDF_SERVICE_TOKEN="$(envget PDF_SERVICE_TOKEN)" + export ALLOWED_ORIGIN="$(envget ALLOWED_ORIGIN)" + export EPFO_LIVE="$(envget EPFO_LIVE)" + + # Build each present service (skip any not yet in the tree, e.g. before + # its feature PR has merged). npm install (not ci) — not every service + # carries a lockfile. Playwright's postinstall fetches the browser; the + # explicit install is a cached, idempotent backstop. + for svc in GstService EpfoService PdfService; do + [ -f "$svc/package.json" ] || { echo "skip $svc (absent)"; continue; } + echo "=== Building $svc ===" + ( cd "$svc" && npm install --no-audit --no-fund && npx playwright install chromium && npm run build ) + done + + # Create on first release, zero-downtime reload thereafter. The + # ecosystem registers only services whose dirs exist. + pm2 startOrReload ecosystem.config.js --update-env + pm2 save + pm2 list + echo "=== Microservices up ===" + + - name: Verify services respond + run: | + sleep 3 + cd "$HOME/pms" + check() { + local dir="$1" port="$2" + [ -f "$dir/package.json" ] || { echo "skip $dir (absent)"; return 0; } + local code + code=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$port/health" || echo "000") + echo "$dir on :$port /health → HTTP $code" + test "$code" = "200" + } + check GstService 3003 + check EpfoService 3004 + check PdfService 3005 + - name: Verify portal responds run: | sleep 5 diff --git a/automation/README.md b/automation/README.md index 76e3c03..042a36a 100644 --- a/automation/README.md +++ b/automation/README.md @@ -178,6 +178,33 @@ The runner checks out the tag in `~/pms`, runs `pnpm install` + `build` + `prisma migrate deploy`, `pm2 restart ppms`, and verifies `/login` returns 200. Watch progress under **Actions** on the Forgejo repo, or `pm2 logs forgejo-runner` on pms1. +## Microservices (GstService / EpfoService / PdfService) + +The standalone Playwright services are deployed by the **same `v*` tag** as the app. +After the app restart, `deploy.yml`: + +1. exports the few secrets the services need out of `~/pms/App/.env` + (`PDF_SERVICE_TOKEN`, `ALLOWED_ORIGIN`, `EPFO_LIVE`) — never `PORT` or the + runner's ephemeral `FORGEJO_TOKEN`; +2. for each service folder present, runs `npm install` + `npx playwright install + chromium` + `npm run build`; +3. runs `pm2 startOrReload ecosystem.config.js --update-env` — which **creates** + the pm2 processes on the first release and **reloads** them on every release + after — then `pm2 save`; +4. health-checks `:3003` / `:3004` / `:3005` (`/health` → 200). + +`ecosystem.config.js` (repo root) is the source of truth: canonical pm2 names +`gst-service` / `epfo-service` / `pdf-service`, fixed ports, and it registers +**only services whose folder is checked out** (so a not-yet-merged service is +skipped, and adopted automatically once its PR lands). + +**One-time alignment:** if a service is already running on pms1 under a *different* +pm2 name, delete it once (`pm2 delete && pm2 save`) so the canonical +process can bind its port — otherwise the new one fails on a port clash. PdfService +additionally needs Chromium system libs the first time (`npx playwright install +--with-deps chromium`, which needs sudo); the deploy's plain `playwright install +chromium` only fetches the browser binary. + ## Operational notes - The watcher runs Claude Code with `--dangerously-skip-permissions` inside the diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..47a536f --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,43 @@ +// pm2 process definitions for the Pelagia microservices. +// +// The Next app (pm2 `ppms`) is deployed/restarted separately by +// .forgejo/workflows/deploy.yml — deliberately, so its delicate env handling +// (avoiding the runner's ephemeral FORGEJO_TOKEN) is untouched. This file covers +// only the standalone Playwright services. The deploy builds each, then runs +// pm2 startOrReload ecosystem.config.js --update-env +// which CREATES them on first release and RELOADS them on subsequent ones. +// +// Resilient to services that aren't in the tree yet (e.g. PdfService lands with +// its feature PR): only directories that exist are registered. Ports are fixed +// here (matching the app's *_SERVICE_URL defaults). Secrets are read from the +// deploy environment at `pm2` invocation time — the deploy exports the few keys +// these services need out of App/.env first (never PORT or FORGEJO_*). Unset → +// harmless defaults (GST/EPFO stay stub-capable, PdfService skips token/origin checks). +const path = require("path"); +const fs = require("fs"); +const root = __dirname; + +const DEFS = [ + { name: "gst-service", dir: "GstService", env: { PORT: "3003" } }, + { name: "epfo-service", dir: "EpfoService", env: { PORT: "3004", EPFO_LIVE: process.env.EPFO_LIVE || "" } }, + { + name: "pdf-service", + dir: "PdfService", + env: { + PORT: "3005", + PDF_SERVICE_TOKEN: process.env.PDF_SERVICE_TOKEN || "", + ALLOWED_ORIGIN: process.env.ALLOWED_ORIGIN || "", + }, + }, +]; + +module.exports = { + // Key on package.json — present only when the service source is checked out + // (a lingering gitignored node_modules/ dir must not register a phantom app). + apps: DEFS.filter((d) => fs.existsSync(path.join(root, d.dir, "package.json"))).map((d) => ({ + name: d.name, + cwd: path.join(root, d.dir), + script: "dist/index.js", + env: { NODE_ENV: "production", ...d.env }, + })), +};