From a1d759167d59fc2aaf8f8e5ea2e662e5eee5a938 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 9 Aug 2025 18:50:17 +0000 Subject: [PATCH] Copilot pipeline fix v2 --- .woodpecker.yml | 14 +------- Dockerfile.server1 | 13 +++----- Dockerfile.server2 | 13 +++----- Dockerfile.server3 | 13 +++----- backend/server1.js | 80 +++++++++++++++++++++++++++++++++++++++------- backend/server2.js | 79 ++++++++++++++++++++++++++++++++++++++------- backend/server3.js | 79 ++++++++++++++++++++++++++++++++++++++------- docker-compose.yml | 21 ++++++------ 8 files changed, 230 insertions(+), 82 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 509b5af..2d1074e 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -109,25 +109,13 @@ steps: DEK_PATH=$(gcloud secrets versions access latest --secret=DEK_PATH_$ENV --project=$PROJECT); \ export DEK_PATH; \ export FROM_SECRETS_MANAGER=true; \ - \ # ── NEW: sync dev DEK into staging volume (uses Secret Manager) ── \ if gcloud secrets describe WRAPPED_DEK_dev --project=$PROJECT >/dev/null 2>&1; then \ echo \"πŸ” Syncing dev DEK into staging volume\"; \ gcloud secrets versions access latest --secret=WRAPPED_DEK_dev --project=$PROJECT > /tmp/dev_dek.enc; \ if [ -s /tmp/dev_dek.enc ]; then \ docker volume ls -q | grep -qx aptiva_dek_staging || docker volume create aptiva_dek_staging >/dev/null; \ - sudo docker run --rm -v aptiva_dek_staging:/v -v /tmp:/host busybox sh <<'EOF' - set -e - mkdir -p /v/staging - cp -f /host/dev_dek.enc /v/staging/dek.enc - chown 1000:1000 /v/staging/dek.enc - chmod 400 /v/staging/dek.enc - rm -f /v/staging/dek.fpr - echo -n "staging dek.enc bytes: "; wc -c res.type('text').send('OK')); +function fprPathFromEnv() { + const p = (process.env.DEK_PATH || '').trim(); + return p ? path.join(path.dirname(p), 'dek.fpr') : null; +} - // READINESS: KMS/DEK/DB are correct - app.get('/readyz', async (req, res) => { - try { - await verifyCanary(pool); // throws if bad - return res.type('text').send('READY'); - } catch (e) { - console.error('[READYZ]', e.message); - return res.status(503).type('text').send('NOT_READY'); - } - }); +// 1) Liveness: process is up and event loop responsive +app.get('/livez', (_req, res) => res.type('text').send('OK')); + +// 2) Readiness: crypto + canary are good +app.get('/readyz', async (_req, res) => { + try { + await initEncryption(); // load/unlock DEK + await verifyCanary(pool); // DB + decrypt sentinel + return res.type('text').send('OK'); + } catch (e) { + console.error('[READYZ]', e.message); + return res.status(500).type('text').send('FAIL'); + } +}); + +// 3) Health: detailed JSON (you can curl this to β€œsee everything”) +app.get('/healthz', async (_req, res) => { + const out = { + service: process.env.npm_package_name || 'server', + version: process.env.IMG_TAG || null, + uptime_s: Math.floor(process.uptime()), + now: new Date().toISOString(), + checks: { + live: { ok: true }, // if we reached here, process is up + crypto: { ok: false, fp: null }, + db: { ok: false, ping_ms: null }, + canary: { ok: false } + } + }; + + // crypto / DEK + try { + await initEncryption(); + out.checks.crypto.ok = true; + const p = fprPathFromEnv(); + if (p) { + try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); } + catch { /* fp optional */ } + } + } catch (e) { + out.checks.crypto.error = e.message; + } + + // DB ping + const t0 = Date.now(); + try { + await pool.query('SELECT 1'); + out.checks.db.ok = true; + out.checks.db.ping_ms = Date.now() - t0; + } catch (e) { + out.checks.db.error = e.message; + } + + // canary + try { + await verifyCanary(pool); + out.checks.canary.ok = true; + } catch (e) { + out.checks.canary.error = e.message; + } + + const ready = out.checks.crypto.ok && out.checks.db.ok && out.checks.canary.ok; + return res.status(ready ? 200 : 503).json(out); +}); // Enable CORS with dynamic origin checking app.use( diff --git a/backend/server2.js b/backend/server2.js index ecfe083..2cca847 100755 --- a/backend/server2.js +++ b/backend/server2.js @@ -67,20 +67,75 @@ await verifyCanary(pool); const app = express(); const PORT = process.env.SERVER2_PORT || 5001; - // LIVENESS: process is up; no external deps - app.get('/healthz', (req, res) => res.type('text').send('OK')); +function fprPathFromEnv() { + const p = (process.env.DEK_PATH || '').trim(); + return p ? path.join(path.dirname(p), 'dek.fpr') : null; +} - // READINESS: KMS/DEK/DB are correct - app.get('/readyz', async (req, res) => { - try { - await verifyCanary(pool); // throws if bad - return res.type('text').send('READY'); - } catch (e) { - console.error('[READYZ]', e.message); - return res.status(503).type('text').send('NOT_READY'); - } - }); +// 1) Liveness: process is up and event loop responsive +app.get('/livez', (_req, res) => res.type('text').send('OK')); +// 2) Readiness: crypto + canary are good +app.get('/readyz', async (_req, res) => { + try { + await initEncryption(); // load/unlock DEK + await verifyCanary(pool); // DB + decrypt sentinel + return res.type('text').send('OK'); + } catch (e) { + console.error('[READYZ]', e.message); + return res.status(500).type('text').send('FAIL'); + } +}); + +// 3) Health: detailed JSON (you can curl this to β€œsee everything”) +app.get('/healthz', async (_req, res) => { + const out = { + service: process.env.npm_package_name || 'server', + version: process.env.IMG_TAG || null, + uptime_s: Math.floor(process.uptime()), + now: new Date().toISOString(), + checks: { + live: { ok: true }, // if we reached here, process is up + crypto: { ok: false, fp: null }, + db: { ok: false, ping_ms: null }, + canary: { ok: false } + } + }; + + // crypto / DEK + try { + await initEncryption(); + out.checks.crypto.ok = true; + const p = fprPathFromEnv(); + if (p) { + try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); } + catch { /* fp optional */ } + } + } catch (e) { + out.checks.crypto.error = e.message; + } + + // DB ping + const t0 = Date.now(); + try { + await pool.query('SELECT 1'); + out.checks.db.ok = true; + out.checks.db.ping_ms = Date.now() - t0; + } catch (e) { + out.checks.db.error = e.message; + } + + // canary + try { + await verifyCanary(pool); + out.checks.canary.ok = true; + } catch (e) { + out.checks.canary.error = e.message; + } + + const ready = out.checks.crypto.ok && out.checks.db.ok && out.checks.canary.ok; + return res.status(ready ? 200 : 503).json(out); +}); /************************************************** * DB connections diff --git a/backend/server3.js b/backend/server3.js index 95ad6ad..12f813d 100644 --- a/backend/server3.js +++ b/backend/server3.js @@ -71,20 +71,75 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-04-10', }); - // LIVENESS: process is up; no external deps - app.get('/healthz', (req, res) => res.type('text').send('OK')); +function fprPathFromEnv() { + const p = (process.env.DEK_PATH || '').trim(); + return p ? path.join(path.dirname(p), 'dek.fpr') : null; +} - // READINESS: KMS/DEK/DB are correct - app.get('/readyz', async (req, res) => { - try { - await verifyCanary(pool); // throws if bad - return res.type('text').send('READY'); - } catch (e) { - console.error('[READYZ]', e.message); - return res.status(503).type('text').send('NOT_READY'); - } - }); +// 1) Liveness: process is up and event loop responsive +app.get('/livez', (_req, res) => res.type('text').send('OK')); +// 2) Readiness: crypto + canary are good +app.get('/readyz', async (_req, res) => { + try { + await initEncryption(); // load/unlock DEK + await verifyCanary(pool); // DB + decrypt sentinel + return res.type('text').send('OK'); + } catch (e) { + console.error('[READYZ]', e.message); + return res.status(500).type('text').send('FAIL'); + } +}); + +// 3) Health: detailed JSON (you can curl this to β€œsee everything”) +app.get('/healthz', async (_req, res) => { + const out = { + service: process.env.npm_package_name || 'server', + version: process.env.IMG_TAG || null, + uptime_s: Math.floor(process.uptime()), + now: new Date().toISOString(), + checks: { + live: { ok: true }, // if we reached here, process is up + crypto: { ok: false, fp: null }, + db: { ok: false, ping_ms: null }, + canary: { ok: false } + } + }; + + // crypto / DEK + try { + await initEncryption(); + out.checks.crypto.ok = true; + const p = fprPathFromEnv(); + if (p) { + try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); } + catch { /* fp optional */ } + } + } catch (e) { + out.checks.crypto.error = e.message; + } + + // DB ping + const t0 = Date.now(); + try { + await pool.query('SELECT 1'); + out.checks.db.ok = true; + out.checks.db.ping_ms = Date.now() - t0; + } catch (e) { + out.checks.db.error = e.message; + } + + // canary + try { + await verifyCanary(pool); + out.checks.canary.ok = true; + } catch (e) { + out.checks.canary.error = e.message; + } + + const ready = out.checks.crypto.ok && out.checks.db.ok && out.checks.canary.ok; + return res.status(ready ? 200 : 503).json(out); +}); function internalFetch(req, urlPath, opts = {}) { return fetch(`${API_BASE}${urlPath}`, { diff --git a/docker-compose.yml b/docker-compose.yml index 5dc64b0..2b6d698 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,10 +51,11 @@ services: - ./user_profile.db:/app/user_profile.db - dek-vol:/run/secrets/dev:rw healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"] - interval: 30s + test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER1_PORT}/livez || exit 1"] + interval: 15s timeout: 5s - retries: 3 + retries: 5 + start_period: 25s # ───────────────────────────── server2 ───────────────────────────── server2: @@ -96,10 +97,11 @@ services: - ./user_profile.db:/app/user_profile.db - dek-vol:/run/secrets/dev:ro healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:${SERVER2_PORT}/healthz || exit 1"] - interval: 30s + test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER2_PORT}/livez || exit 1"] + interval: 15s timeout: 5s - retries: 3 + retries: 5 + start_period: 25s # ───────────────────────────── server3 ───────────────────────────── server3: @@ -148,10 +150,11 @@ services: - ./user_profile.db:/app/user_profile.db - dek-vol:/run/secrets/dev:ro healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:${SERVER3_PORT}/healthz || exit 1"] - interval: 30s + test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER3_PORT}/livez || exit 1"] + interval: 15s timeout: 5s - retries: 3 + retries: 5 + start_period: 25s # ───────────────────────────── nginx ─────────────────────────────── nginx: