Copilot pipeline rewrite v4
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Josh 2025-08-09 18:57:51 +00:00
parent d6e9b1f489
commit 333dbe3f02
2 changed files with 50 additions and 43 deletions

View File

@ -128,7 +128,8 @@ steps:
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 \
docker compose up -d --force-recreate --remove-orphans; \
echo \"✅ Staging stack refreshed with tag $IMG_TAG\"'
echo \"✅ Staging stack refreshed with tag \$IMG_TAG\"'
secrets:

View File

@ -5,43 +5,40 @@
import express from 'express';
import axios from 'axios';
import cors from 'cors';
import helmet from 'helmet'; // For HTTP security headers
import helmet from 'helmet';
import dotenv from 'dotenv';
import xlsx from 'xlsx'; // Keep for CIP->SOC mapping only
import xlsx from 'xlsx';
import path from 'path';
import { fileURLToPath } from 'url';
import { open } from 'sqlite';
import sqlite3 from 'sqlite3';
import pool from './config/mysqlPool.js'; // adjust path if needed
import pool from './config/mysqlPool.js'; // exports { query, execute, raw, ... }
import fs from 'fs';
import { readFile } from 'fs/promises'; // <-- add this
import readline from 'readline';
import chatFreeEndpoint from "./utils/chatFreeEndpoint.js";
import { OpenAI } from 'openai';
import rateLimit from 'express-rate-limit';
import authenticateUser from './utils/authenticateUser.js';
import { vectorSearch } from "./utils/vectorSearch.js";
import { initEncryption, verifyCanary } from './shared/crypto/encryption.js';
import { initEncryption, verifyCanary, SENTINEL } from './shared/crypto/encryption.js';
// --- Basic file init ---
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const rootPath = path.resolve(__dirname, '..'); // Up one level
const rootPath = path.resolve(__dirname, '..');
const env = process.env.NODE_ENV?.trim() || 'development';
const envPath = path.resolve(rootPath, `.env.${env}`);
dotenv.config({ path: envPath }); // Load .env
dotenv.config({ path: envPath, override: false }); // don't clobber compose-injected env
const ROOT_DIR = path.resolve(__dirname, '..'); // repo root
const PUBLIC_DIR = path.join(ROOT_DIR, 'public'); // static json files
const ROOT_DIR = path.resolve(__dirname, '..');
const PUBLIC_DIR = path.join(ROOT_DIR, 'public');
const CIP_TO_SOC_PATH = path.join(PUBLIC_DIR, 'CIP_to_ONET_SOC.xlsx');
const INSTITUTION_DATA_PATH= path.join(PUBLIC_DIR, 'Institution_data.json');
const SALARY_DB_PATH = path.join(ROOT_DIR, 'salary_info.db');
const USER_PROFILE_DB_PATH = path.join(ROOT_DIR, 'user_profile.db');
for (const p of [CIP_TO_SOC_PATH, INSTITUTION_DATA_PATH,
SALARY_DB_PATH, USER_PROFILE_DB_PATH]) {
for (const p of [CIP_TO_SOC_PATH, INSTITUTION_DATA_PATH, SALARY_DB_PATH, USER_PROFILE_DB_PATH]) {
if (!fs.existsSync(p)) {
console.error(`FATAL Required data file not found → ${p}`);
process.exit(1);
@ -56,12 +53,20 @@ const chatLimiter = rateLimit({
keyGenerator: req => req.user?.id || req.ip
});
// Institution data
// Load institution data (kept for existing routes)
const institutionData = JSON.parse(fs.readFileSync(INSTITUTION_DATA_PATH, 'utf8'));
// ── DEK + canary bootstrap (use raw pool to avoid DAO interception) ──
const db = pool.raw || pool;
try {
await initEncryption();
await pool.query('SELECT 1');
await verifyCanary(pool);
await db.query('SELECT 1');
await verifyCanary(db);
} catch (e) {
console.error('FATAL during crypto/DB bootstrap:', e?.message || e);
process.exit(1);
}
// Create Express app
const app = express();
@ -72,14 +77,14 @@ function fprPathFromEnv() {
return p ? path.join(path.dirname(p), 'dek.fpr') : null;
}
// 1) Liveness: process is up and event loop responsive
// 1) Liveness: process up
app.get('/livez', (_req, res) => res.type('text').send('OK'));
// 2) Readiness: crypto + canary are good
// 2) Readiness: DEK + canary OK
app.get('/readyz', async (_req, res) => {
try {
await initEncryption(); // load/unlock DEK
await verifyCanary(pool); // DB + decrypt sentinel
await initEncryption();
await verifyCanary(db); // <-- use raw pool
return res.type('text').send('OK');
} catch (e) {
console.error('[READYZ]', e.message);
@ -87,15 +92,15 @@ 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 || 'server2',
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 },
canary: { ok: false }
@ -108,8 +113,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;
@ -118,7 +122,7 @@ app.get('/healthz', async (_req, res) => {
// DB ping
const t0 = Date.now();
try {
await pool.query('SELECT 1');
await db.query('SELECT 1'); // <-- use raw pool
out.checks.db.ok = true;
out.checks.db.ping_ms = Date.now() - t0;
} catch (e) {
@ -127,7 +131,7 @@ app.get('/healthz', async (_req, res) => {
// canary
try {
await verifyCanary(pool);
await verifyCanary(db); // <-- use raw pool
out.checks.canary.ok = true;
} catch (e) {
out.checks.canary.error = e.message;
@ -138,15 +142,14 @@ app.get('/healthz', async (_req, res) => {
});
/**************************************************
* DB connections
* DB connections (SQLite)
**************************************************/
let db;
let dbSqlite;
let userProfileDb;
async function initDatabases() {
try {
db = await open({
dbSqlite = await open({
filename: SALARY_DB_PATH,
driver : sqlite3.Database,
mode : sqlite3.OPEN_READONLY
@ -160,12 +163,15 @@ async function initDatabases() {
console.log('✅ Connected to user_profile.db');
} catch (err) {
console.error('❌ DB init failed →', err);
process.exit(1); // let Docker restart the service
process.exit(1);
}
}
await initDatabases();
// …rest of your routes and app.listen(PORT)
/*
* SECURITY, CORS, JSON Body
* */