This commit is contained in:
parent
d6e9b1f489
commit
333dbe3f02
@ -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:
|
||||
|
@ -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 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'));
|
||||
|
||||
await initEncryption();
|
||||
await pool.query('SELECT 1');
|
||||
await verifyCanary(pool);
|
||||
// ── DEK + canary bootstrap (use raw pool to avoid DAO interception) ──
|
||||
const db = pool.raw || pool;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
* ────────────────────────────────────────────────────────────── */
|
||||
|
Loading…
Reference in New Issue
Block a user