Copilot pipeline fix v5
This commit is contained in:
parent
2f4c8fb2cf
commit
5e277b9f94
@ -27,7 +27,6 @@ steps:
|
||||
trivy image --scanners vuln --ignore-unfixed --ignorefile .trivyignore --exit-code 1 --severity CRITICAL $REG/server3:$IMG_TAG
|
||||
trivy image --scanners vuln --ignore-unfixed --ignorefile .trivyignore --exit-code 1 --severity CRITICAL $REG/nginx:$IMG_TAG
|
||||
|
||||
|
||||
- name: staging-deploy
|
||||
image: google/cloud-sdk:latest
|
||||
entrypoint:
|
||||
@ -38,7 +37,6 @@ steps:
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
|
||||
# ── Inject known-hosts and SSH key ───────────────────────────────
|
||||
gcloud secrets versions access latest \
|
||||
--secret=STAGING_KNOWN_HOSTS --project=aptivaai-dev \
|
||||
| base64 -d > ~/.ssh/known_hosts
|
||||
@ -51,7 +49,6 @@ steps:
|
||||
|
||||
echo "🔑 SSH prerequisites installed"
|
||||
|
||||
# ── SSH into staging and deploy ──────────────────────────────────
|
||||
ssh -o StrictHostKeyChecking=yes \
|
||||
-i ~/.ssh/id_ed25519 \
|
||||
jcoakley@10.128.0.12 \
|
||||
@ -109,24 +106,24 @@ 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\"; \
|
||||
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 -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'
|
||||
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"; \
|
||||
else \
|
||||
echo \"⚠️ WRAPPED_DEK_dev returned empty; skipping copy\"; \
|
||||
echo "⚠️ WRAPPED_DEK_dev returned empty; skipping copy"; \
|
||||
fi; \
|
||||
else \
|
||||
echo \"ℹ️ WRAPPED_DEK_dev not found; leaving existing staging DEK alone\"; \
|
||||
echo "ℹ️ WRAPPED_DEK_dev not found; leaving existing staging DEK alone"; \
|
||||
fi; \
|
||||
\
|
||||
cd /home/jcoakley/aptiva-staging-app; \
|
||||
sudo --preserve-env=IMG_TAG,FROM_SECRETS_MANAGER,JWT_SECRET,OPENAI_API_KEY,ONET_USERNAME,ONET_PASSWORD,STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WH_SECRET,STRIPE_PRICE_PREMIUM_MONTH,STRIPE_PRICE_PREMIUM_YEAR,STRIPE_PRICE_PRO_MONTH,STRIPE_PRICE_PRO_YEAR,DB_NAME,DB_HOST,DB_PORT,DB_USER,DB_PASSWORD,DB_SSL_CA,DB_SSL_CERT,DB_SSL_KEY,TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN,TWILIO_MESSAGING_SERVICE_SID,KMS_KEY_NAME,DEK_PATH \
|
||||
docker compose pull; \
|
||||
sudo --preserve-env=IMG_TAG,FROM_SECRETS_MANAGER,JWT_SECRET,OPENAI_API_KEY,ONET_USERNAME,ONET_PASSWORD,STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WH_SECRET,STRIPE_PRICE_PREMIUM_MONTH,STRIPE_PRICE_PREMIUM_YEAR,STRIPE_PRICE_PRO_MONTH,STRIPE_PRICE_PRO_YEAR,DB_NAME,DB_HOST,DB_PORT,DB_USER,DB_PASSWORD,DB_SSL_CA,DB_SSL_CERT,DB_SSL_KEY,TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN,TWILIO_MESSAGING_SERVICE_SID,KMS_KEY_NAME,DEK_PATH \
|
||||
sudo --preserve-env=IMG_TAG,FROM_SECRETS_MANAGER,JWT_SECRET,OPENAI_API_KEY,ONET_USERNAME,ONET_PASSWORD,STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WH_SECRET,STRIPE_PRICE_PREMIUM_MONTH,STRIPE_PRICE_PREMIUM_YEAR,STRIPE
|
||||
docker compose up -d --force-recreate --remove-orphans; \
|
||||
echo \"✅ Staging stack refreshed with tag \$IMG_TAG\"'
|
||||
|
||||
|
@ -8,14 +8,14 @@ const __dirname = path.dirname(__filename);
|
||||
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import fs from 'fs/promises';
|
||||
import { readFile } from 'fs/promises'; // <-- add this
|
||||
import multer from 'multer';
|
||||
import fetch from 'node-fetch';
|
||||
import mammoth from 'mammoth';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import pkg from 'pdfjs-dist';
|
||||
import pool from './config/mysqlPool.js'; // adjust path if needed
|
||||
import pool from './config/mysqlPool.js';
|
||||
|
||||
import OpenAI from 'openai';
|
||||
import Fuse from 'fuse.js';
|
||||
@ -25,19 +25,14 @@ import { createReminder } from './utils/smsService.js';
|
||||
import { initEncryption, verifyCanary } from './shared/crypto/encryption.js';
|
||||
import { hashForLookup } from './shared/crypto/encryption.js';
|
||||
|
||||
await initEncryption();
|
||||
await pool.query('SELECT 1');
|
||||
await verifyCanary(pool);
|
||||
|
||||
import './jobs/reminderCron.js';
|
||||
import { cacheSummary } from "./utils/ctxCache.js";
|
||||
|
||||
const rootPath = path.resolve(__dirname, '..'); // one level up
|
||||
const env = (process.env.NODE_ENV || 'production'); // production in prod
|
||||
const stage = env === 'staging' ? 'development' : env;
|
||||
const envPath = path.resolve(rootPath, `.env.${env}`); // => /app/.env.production
|
||||
const rootPath = path.resolve(__dirname, '..');
|
||||
const env = (process.env.NODE_ENV || 'production');
|
||||
const envPath = path.resolve(rootPath, `.env.${env}`);
|
||||
if (!process.env.FROM_SECRETS_MANAGER) {
|
||||
dotenv.config({ path: envPath });
|
||||
dotenv.config({ path: envPath, override: false });
|
||||
}
|
||||
|
||||
const PORT = process.env.SERVER3_PORT || 5002;
|
||||
@ -45,31 +40,38 @@ const API_BASE = `http://localhost:${PORT}/api`;
|
||||
|
||||
/* ─── helper: canonical public origin ─────────────────────────── */
|
||||
const PUBLIC_BASE = (
|
||||
process.env.APTIVA_AI_BASE // ← preferred
|
||||
|| process.env.REACT_APP_API_URL // ← old name, tolerated
|
||||
process.env.APTIVA_AI_BASE
|
||||
|| process.env.REACT_APP_API_URL
|
||||
|| ''
|
||||
).replace(/\/+$/, ''); // strip trailing “/”
|
||||
).replace(/\/+$/, '');
|
||||
|
||||
/* allow‑list for redirects to block open‑redirect attacks */
|
||||
const ALLOWED_REDIRECT_HOSTS = new Set([
|
||||
new URL(PUBLIC_BASE || 'http://localhost').host
|
||||
]);
|
||||
|
||||
function isSafeRedirect(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return ALLOWED_REDIRECT_HOSTS.has(u.host) && u.protocol === 'https:';
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return ALLOWED_REDIRECT_HOSTS.has(u.host) && u.protocol === 'https:';
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
const app = express();
|
||||
const { getDocument } = pkg;
|
||||
const bt = "`".repeat(3);
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-04-10' });
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2024-04-10',
|
||||
});
|
||||
// ── Use raw pool for canary/db checks (avoid DAO wrapper noise) ──
|
||||
const db = pool.raw || pool;
|
||||
|
||||
// Bootstrap: unwrap DEK, check DB, verify canary
|
||||
try {
|
||||
await initEncryption();
|
||||
await db.query('SELECT 1');
|
||||
await verifyCanary(db);
|
||||
} catch (e) {
|
||||
console.error('FATAL during crypto/DB bootstrap:', e?.message || e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function fprPathFromEnv() {
|
||||
const p = (process.env.DEK_PATH || '').trim();
|
||||
@ -82,8 +84,8 @@ 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
|
||||
await initEncryption();
|
||||
await verifyCanary(db);
|
||||
return res.type('text').send('OK');
|
||||
} catch (e) {
|
||||
console.error('[READYZ]', e.message);
|
||||
@ -91,17 +93,17 @@ app.get('/readyz', async (_req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Health: detailed JSON (you can curl this to “see everything”)
|
||||
// 3) Health: detailed JSON you can curl
|
||||
app.get('/healthz', async (_req, res) => {
|
||||
const out = {
|
||||
service: process.env.npm_package_name || 'server',
|
||||
service: process.env.npm_package_name || 'server3',
|
||||
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
|
||||
live: { ok: true },
|
||||
crypto: { ok: false, fp: null },
|
||||
db: { ok: false, ping_ms: null },
|
||||
db: { ok: false, ping_ms: null },
|
||||
canary: { ok: false }
|
||||
}
|
||||
};
|
||||
@ -112,8 +114,7 @@ app.get('/healthz', async (_req, res) => {
|
||||
out.checks.crypto.ok = true;
|
||||
const p = fprPathFromEnv();
|
||||
if (p) {
|
||||
try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); }
|
||||
catch { /* fp optional */ }
|
||||
try { out.checks.crypto.fp = (await readFile(p, 'utf8')).trim(); } catch {}
|
||||
}
|
||||
} catch (e) {
|
||||
out.checks.crypto.error = e.message;
|
||||
@ -122,7 +123,7 @@ app.get('/healthz', async (_req, res) => {
|
||||
// DB ping
|
||||
const t0 = Date.now();
|
||||
try {
|
||||
await pool.query('SELECT 1');
|
||||
await db.query('SELECT 1');
|
||||
out.checks.db.ok = true;
|
||||
out.checks.db.ping_ms = Date.now() - t0;
|
||||
} catch (e) {
|
||||
@ -131,7 +132,7 @@ app.get('/healthz', async (_req, res) => {
|
||||
|
||||
// canary
|
||||
try {
|
||||
await verifyCanary(pool);
|
||||
await verifyCanary(db);
|
||||
out.checks.canary.ok = true;
|
||||
} catch (e) {
|
||||
out.checks.canary.error = e.message;
|
||||
@ -141,6 +142,9 @@ app.get('/healthz', async (_req, res) => {
|
||||
return res.status(ready ? 200 : 503).json(out);
|
||||
});
|
||||
|
||||
// …rest of your routes and app.listen(PORT)
|
||||
|
||||
|
||||
function internalFetch(req, urlPath, opts = {}) {
|
||||
return fetch(`${API_BASE}${urlPath}`, {
|
||||
...opts,
|
||||
|
Loading…
Reference in New Issue
Block a user