chore(deploy): build & (re)start microservices on release tag
All checks were successful
PR checks / checks (pull_request) Successful in 42s
PR checks / integration (pull_request) Successful in 31s

The v* tag deploy previously only updated the Next app (ppms); GstService /
EpfoService / PdfService were never built or restarted by automation. Now the
same deploy manages them.

- ecosystem.config.js (root): pm2 definitions for gst-service (3003) /
  epfo-service (3004) / pdf-service (3005). Registers only services whose source
  is checked out (keyed on package.json), so a not-yet-merged service is skipped
  and adopted automatically once its PR lands. Secrets come from the env at pm2
  invocation; ports are fixed here.
- deploy.yml: after the app restart, export the few service secrets out of
  App/.env (never PORT or the ephemeral FORGEJO_TOKEN), npm install + playwright
  install chromium + build each present service, then
  `pm2 startOrReload ecosystem.config.js --update-env` (create on first release,
  reload after) + pm2 save, and health-check :3003/:3004/:3005.
- automation/README.md: documents the flow + the one-time alignment for any
  pre-existing differently-named pm2 process.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hardik 2026-06-24 02:59:36 +05:30
parent 144d44ccca
commit 6b0210078a
3 changed files with 120 additions and 0 deletions

View file

@ -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

View file

@ -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 <old-name> && 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

43
ecosystem.config.js Normal file
View file

@ -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 },
})),
};