Copilot pipeline fix v2
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Josh 2025-08-09 18:50:17 +00:00
parent 5109c4a596
commit c4ce80c5c2
8 changed files with 230 additions and 82 deletions

View File

@ -109,25 +109,13 @@ steps:
DEK_PATH=$(gcloud secrets versions access latest --secret=DEK_PATH_$ENV --project=$PROJECT); \ DEK_PATH=$(gcloud secrets versions access latest --secret=DEK_PATH_$ENV --project=$PROJECT); \
export DEK_PATH; \ export DEK_PATH; \
export FROM_SECRETS_MANAGER=true; \ export FROM_SECRETS_MANAGER=true; \
\
# ── NEW: sync dev DEK into staging volume (uses Secret Manager) ── \ # ── NEW: sync dev DEK into staging volume (uses Secret Manager) ── \
if gcloud secrets describe WRAPPED_DEK_dev --project=$PROJECT >/dev/null 2>&1; then \ if gcloud secrets describe WRAPPED_DEK_dev --project=$PROJECT >/dev/null 2>&1; then \
echo \"🔁 Syncing dev DEK into staging volume\"; \ echo \"🔁 Syncing dev DEK into staging volume\"; \
gcloud secrets versions access latest --secret=WRAPPED_DEK_dev --project=$PROJECT > /tmp/dev_dek.enc; \ gcloud secrets versions access latest --secret=WRAPPED_DEK_dev --project=$PROJECT > /tmp/dev_dek.enc; \
if [ -s /tmp/dev_dek.enc ]; then \ if [ -s /tmp/dev_dek.enc ]; then \
docker volume ls -q | grep -qx aptiva_dek_staging || docker volume create aptiva_dek_staging >/dev/null; \ 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' sudo docker run --rm -v aptiva_dek_staging:/v -v /tmp:/host busybox sh -c '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 </v/staging/dek.enc; ls -l /v/staging'
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 </v/staging/dek.enc
ls -l /v/staging
EOF
\"; \
else \ else \
echo \"⚠️ WRAPPED_DEK_dev returned empty; skipping copy\"; \ echo \"⚠️ WRAPPED_DEK_dev returned empty; skipping copy\"; \
fi; \ fi; \

View File

@ -1,15 +1,13 @@
FROM node:20-bookworm-slim AS base FROM node:20-bookworm-slim AS base
RUN groupadd -r app && useradd -r -g app app RUN groupadd -r app && useradd -r -g app app
WORKDIR /app WORKDIR /app
# ---- native build deps ---- # add curl for healthchecks (+ CA bundle)
RUN apt-get update -y && \ RUN apt-get update -y && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
build-essential python3 pkg-config && \ build-essential python3 pkg-config curl ca-certificates && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# ---------------------------
COPY package*.json ./ COPY package*.json ./
COPY public/ /app/public/ COPY public/ /app/public/
@ -17,7 +15,6 @@ RUN npm ci --unsafe-perm
COPY . . COPY . .
RUN mkdir -p /run/secrets && chown -R app:app /run/secrets RUN mkdir -p /run/secrets && chown -R app:app /run/secrets
USER app USER app
CMD ["node", "backend/server1.js"] CMD ["node", "backend/server1.js"]

View File

@ -1,15 +1,13 @@
FROM node:20-bookworm-slim AS base FROM node:20-bookworm-slim AS base
RUN groupadd -r app && useradd -r -g app app RUN groupadd -r app && useradd -r -g app app
WORKDIR /app WORKDIR /app
# ---- native build deps ---- # add curl for healthchecks (+ CA bundle)
RUN apt-get update -y && \ RUN apt-get update -y && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
build-essential python3 pkg-config && \ build-essential python3 pkg-config curl ca-certificates && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# ---------------------------
COPY package*.json ./ COPY package*.json ./
COPY public/ /app/public/ COPY public/ /app/public/
@ -17,6 +15,5 @@ RUN npm ci --unsafe-perm
COPY . . COPY . .
RUN mkdir -p /run/secrets && chown -R app:app /run/secrets RUN mkdir -p /run/secrets && chown -R app:app /run/secrets
USER app USER app
CMD ["node", "backend/server2.js"] CMD ["node", "backend/server2.js"]

View File

@ -1,15 +1,13 @@
FROM node:20-bookworm-slim AS base FROM node:20-bookworm-slim AS base
RUN groupadd -r app && useradd -r -g app app RUN groupadd -r app && useradd -r -g app app
WORKDIR /app WORKDIR /app
# ---- native build deps ---- # add curl for healthchecks (+ CA bundle)
RUN apt-get update -y && \ RUN apt-get update -y && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
build-essential python3 pkg-config && \ build-essential python3 pkg-config curl ca-certificates && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# ---------------------------
COPY package*.json ./ COPY package*.json ./
COPY public/ /app/public/ COPY public/ /app/public/
@ -17,7 +15,6 @@ RUN npm ci --unsafe-perm
COPY . . COPY . .
RUN mkdir -p /run/secrets && chown -R app:app /run/secrets RUN mkdir -p /run/secrets && chown -R app:app /run/secrets
USER app USER app
CMD ["node", "backend/server3.js"] CMD ["node", "backend/server3.js"]

View File

@ -100,20 +100,76 @@ app.use(
}) })
); );
// LIVENESS: process is up; no external deps function fprPathFromEnv() {
app.get('/healthz', (req, res) => res.type('text').send('OK')); const p = (process.env.DEK_PATH || '').trim();
return p ? path.join(path.dirname(p), 'dek.fpr') : null;
}
// READINESS: KMS/DEK/DB are correct // 1) Liveness: process is up and event loop responsive
app.get('/readyz', async (req, res) => { app.get('/livez', (_req, res) => res.type('text').send('OK'));
// 2) Readiness: crypto + canary are good
app.get('/readyz', async (_req, res) => {
try { try {
await verifyCanary(pool); // throws if bad await initEncryption(); // load/unlock DEK
return res.type('text').send('READY'); await verifyCanary(pool); // DB + decrypt sentinel
return res.type('text').send('OK');
} catch (e) { } catch (e) {
console.error('[READYZ]', e.message); console.error('[READYZ]', e.message);
return res.status(503).type('text').send('NOT_READY'); 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 // Enable CORS with dynamic origin checking
app.use( app.use(
cors({ cors({

View File

@ -67,20 +67,75 @@ await verifyCanary(pool);
const app = express(); const app = express();
const PORT = process.env.SERVER2_PORT || 5001; const PORT = process.env.SERVER2_PORT || 5001;
// LIVENESS: process is up; no external deps function fprPathFromEnv() {
app.get('/healthz', (req, res) => res.type('text').send('OK')); const p = (process.env.DEK_PATH || '').trim();
return p ? path.join(path.dirname(p), 'dek.fpr') : null;
}
// READINESS: KMS/DEK/DB are correct // 1) Liveness: process is up and event loop responsive
app.get('/readyz', async (req, res) => { app.get('/livez', (_req, res) => res.type('text').send('OK'));
// 2) Readiness: crypto + canary are good
app.get('/readyz', async (_req, res) => {
try { try {
await verifyCanary(pool); // throws if bad await initEncryption(); // load/unlock DEK
return res.type('text').send('READY'); await verifyCanary(pool); // DB + decrypt sentinel
return res.type('text').send('OK');
} catch (e) { } catch (e) {
console.error('[READYZ]', e.message); console.error('[READYZ]', e.message);
return res.status(503).type('text').send('NOT_READY'); 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 * DB connections

View File

@ -71,20 +71,75 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-04-10', apiVersion: '2024-04-10',
}); });
// LIVENESS: process is up; no external deps function fprPathFromEnv() {
app.get('/healthz', (req, res) => res.type('text').send('OK')); const p = (process.env.DEK_PATH || '').trim();
return p ? path.join(path.dirname(p), 'dek.fpr') : null;
}
// READINESS: KMS/DEK/DB are correct // 1) Liveness: process is up and event loop responsive
app.get('/readyz', async (req, res) => { app.get('/livez', (_req, res) => res.type('text').send('OK'));
// 2) Readiness: crypto + canary are good
app.get('/readyz', async (_req, res) => {
try { try {
await verifyCanary(pool); // throws if bad await initEncryption(); // load/unlock DEK
return res.type('text').send('READY'); await verifyCanary(pool); // DB + decrypt sentinel
return res.type('text').send('OK');
} catch (e) { } catch (e) {
console.error('[READYZ]', e.message); console.error('[READYZ]', e.message);
return res.status(503).type('text').send('NOT_READY'); 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 = {}) { function internalFetch(req, urlPath, opts = {}) {
return fetch(`${API_BASE}${urlPath}`, { return fetch(`${API_BASE}${urlPath}`, {

View File

@ -51,10 +51,11 @@ services:
- ./user_profile.db:/app/user_profile.db - ./user_profile.db:/app/user_profile.db
- dek-vol:/run/secrets/dev:rw - dek-vol:/run/secrets/dev:rw
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"] test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER1_PORT}/livez || exit 1"]
interval: 30s interval: 15s
timeout: 5s timeout: 5s
retries: 3 retries: 5
start_period: 25s
# ───────────────────────────── server2 ───────────────────────────── # ───────────────────────────── server2 ─────────────────────────────
server2: server2:
@ -96,10 +97,11 @@ services:
- ./user_profile.db:/app/user_profile.db - ./user_profile.db:/app/user_profile.db
- dek-vol:/run/secrets/dev:ro - dek-vol:/run/secrets/dev:ro
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER2_PORT}/healthz || exit 1"] test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER2_PORT}/livez || exit 1"]
interval: 30s interval: 15s
timeout: 5s timeout: 5s
retries: 3 retries: 5
start_period: 25s
# ───────────────────────────── server3 ───────────────────────────── # ───────────────────────────── server3 ─────────────────────────────
server3: server3:
@ -148,10 +150,11 @@ services:
- ./user_profile.db:/app/user_profile.db - ./user_profile.db:/app/user_profile.db
- dek-vol:/run/secrets/dev:ro - dek-vol:/run/secrets/dev:ro
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER3_PORT}/healthz || exit 1"] test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER3_PORT}/livez || exit 1"]
interval: 30s interval: 15s
timeout: 5s timeout: 5s
retries: 3 retries: 5
start_period: 25s
# ───────────────────────────── nginx ─────────────────────────────── # ───────────────────────────── nginx ───────────────────────────────
nginx: nginx: