Files
Deep-Package-Inspection/scripts/verify-deployment.js
T

149 lines
7.2 KiB
JavaScript

// scripts/verify-deployment.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Post-Deployment Verification Script
// Tests: frontend, backend, security headers, proxy, MongoDB
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const https = require('https');
const http = require('http');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
// HTTP request helper
function httpGet(url) {
return new Promise((resolve) => {
const mod = url.startsWith('https') ? https : http;
const req = mod.get(url, { timeout: 10000 }, (res) => {
let body = '';
res.on('data', (d) => body += d);
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: body.substring(0, 300) }));
});
req.on('error', (e) => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
const SSH_COMMANDS = [
// Check PM2 process list
`echo "=== PM2 STATUS ===" && ${PM2} list`,
// Check backend health
`echo "=== BACKEND HEALTH ===" && curl -s http://127.0.0.1:3001/api/health`,
// Check frontend pages
`echo "=== FRONTEND / ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/`,
`echo "=== FRONTEND /login ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
// Security audit: ensure backend and proxy ports are NOT publicly accessible
`echo "=== SECURITY: Port 3001 external test ===" && ss -tlnp | grep 3001`,
`echo "=== SECURITY: Port 4000 external test ===" && ss -tlnp | grep 4000`,
// Check upload directory permissions
`echo "=== UPLOAD DIR PERMS ===" && ls -la ${ROOT}/backend/public/api/uploads/ 2>/dev/null || echo "Upload dir does not exist yet (will be created on first upload)"`,
// Check proxy MongoDB connection
`echo "=== PROXY STATUS ===" && ${PM2} logs backone-proxy --lines 5 --nostream 2>&1 | grep -E "(MongoDB|Connected|Capacity|Scheduler)" | tail -10`,
// Verify no secrets in Next.js build output (sanity check)
`echo "=== SECURITY AUDIT: Check no API key in build ===" && grep -r "sk_db_live" ${ROOT}/.next/standalone/ 2>/dev/null | head -3 || echo "CLEAN: No Netify API key found in build output"`,
`echo "=== SECURITY AUDIT: Check no MongoDB URI in build ===" && grep -r "backone_user" ${ROOT}/.next/standalone/ 2>/dev/null | head -3 || echo "CLEAN: No MongoDB credentials in build output"`,
// Check frontend log for readiness
`echo "=== FRONTEND READY LOG ===" && ${PM2} logs backone-frontend --lines 5 --nostream 2>&1 | grep -E "(Ready|Error|Next.js)" | tail -10`,
// Check disk usage for uploads dir
`echo "=== DISK USAGE ===" && df -h /home/adminbackend/web/ 2>/dev/null | tail -2`,
// Final summary
`echo ""`,
`echo "╔════════════════════════════════════════════╗"`,
`echo "║ BackOne DPI Production — Service Status ║"`,
`echo "╠════════════════════════════════════════════╣"`,
`${PM2} list 2>/dev/null | grep -E "(online|error|stopped)" | awk '{printf "║ %-12s %-10s %-8s ║\\n", $2, $10, $8}'`,
`echo "╚════════════════════════════════════════════╝"`,
];
function runSshChecks(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected for verification!\n');
let index = 0;
function runNext() {
if (index >= commands.length) {
ssh.end();
return;
}
const cmd = commands[index++];
console.log(`\n$ ${cmd.substring(0, 120)}${cmd.length > 120 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error(`[ERROR] ${err.message}`); runNext(); return; }
stream.on('data', (d) => process.stdout.write(d.toString()));
stream.stderr.on('data', (d) => {
const t = d.toString();
if (!t.includes('npm warn') && !t.includes('npm notice')) process.stdout.write(t);
});
stream.on('close', runNext);
});
}
runNext();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Post-Deployment Verification ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// ── SSH internal checks ───────────────────────────────────────────────────
console.log('▶ Running internal server checks via SSH...');
await runSshChecks(SSH_COMMANDS);
// ── External HTTP checks (from this machine) ──────────────────────────────
console.log('\n\n▶ Running external HTTP checks from local machine...\n');
const checks = [
{ url: 'http://demoplace.my.id/', label: 'Domain root (redirect expected)' },
{ url: 'http://demoplace.my.id/login', label: 'Login page' },
{ url: 'http://demoplace.my.id/api/health', label: 'Backend health via domain' },
];
for (const { url, label } of checks) {
const result = await httpGet(url);
const status = result.status;
const icon = (status >= 200 && status < 400) ? '✅' : (status === 0 ? '⚠️' : '❌');
console.log(` ${icon} ${label}: HTTP ${status} ${result.error || ''}`);
if (result.body && status >= 200 && status < 300) {
console.log(` Body preview: ${result.body.substring(0, 100)}`);
}
}
console.log('\n══════════════════════════════════════════════════════════');
console.log(' Production deployment verification complete!');
console.log(' URL: https://demoplace.my.id');
console.log('══════════════════════════════════════════════════════════\n');
}
main().catch((err) => {
console.error('[FATAL]', err.message);
process.exit(1);
});