@codai/cbd
Version:
Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server
291 lines (245 loc) • 10.1 kB
JavaScript
// CBD Let's Encrypt SSL Certificate Manager
// Independent SSL solution without cloud provider dependencies
const express = require('express');
const fs = require('fs').promises;
const path = require('path');
const { spawn } = require('child_process');
const https = require('https');
const http = require('http');
class CBDSSLManager {
constructor() {
this.domain = 'cbd.memorai.ro';
this.certPath = '/etc/letsencrypt/live/cbd.memorai.ro';
this.certbotPath = 'certbot'; // Adjust if needed
this.webRoot = path.join(__dirname, 'ssl-challenges');
this.app = express();
this.httpServer = null;
this.httpsServer = null;
this.setupRoutes();
}
setupRoutes() {
// Serve ACME challenges
this.app.use('/.well-known/acme-challenge', express.static(this.webRoot + '/.well-known/acme-challenge'));
// Health check
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
ssl: {
certificateExists: this.certificateExists(),
domain: this.domain,
renewalDate: this.getCertificateExpiry()
}
});
});
// Redirect HTTP to HTTPS (when HTTPS is available)
this.app.use((req, res, next) => {
if (req.headers.host === this.domain && !req.secure && this.httpsServer) {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
// Proxy to CBD service
this.app.use('/', (req, res) => {
const options = {
hostname: 'localhost',
port: 4180,
path: req.url,
method: req.method,
headers: req.headers
};
const proxyReq = http.request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res, { end: true });
});
proxyReq.on('error', (err) => {
console.error('Proxy error:', err.message);
res.status(502).json({ error: 'CBD service unavailable' });
});
req.pipe(proxyReq, { end: true });
});
}
async certificateExists() {
try {
await fs.access(path.join(this.certPath, 'fullchain.pem'));
await fs.access(path.join(this.certPath, 'privkey.pem'));
return true;
} catch {
return false;
}
}
async getCertificateExpiry() {
try {
const cert = await fs.readFile(path.join(this.certPath, 'fullchain.pem'));
// Simple parsing - in production, use a proper certificate parser
const certData = cert.toString();
const match = certData.match(/Not After : (.+)/);
return match ? match[1] : 'Unknown';
} catch {
return 'No certificate';
}
}
async requestCertificate() {
console.log('🔒 Requesting SSL certificate from Let\'s Encrypt...');
// Ensure web root exists
await fs.mkdir(path.join(this.webRoot, '.well-known/acme-challenge'), { recursive: true });
return new Promise((resolve, reject) => {
const certbotArgs = [
'certonly',
'--webroot',
'--webroot-path', this.webRoot,
'--email', 'codaiecosystem@gmail.com',
'--agree-tos',
'--non-interactive',
'--domain', this.domain,
'--keep-until-expiring'
];
console.log('📋 Running certbot with args:', certbotArgs.join(' '));
const certbot = spawn(this.certbotPath, certbotArgs, {
stdio: ['inherit', 'pipe', 'pipe']
});
let output = '';
let error = '';
certbot.stdout.on('data', (data) => {
const message = data.toString();
output += message;
console.log('📜 Certbot:', message.trim());
});
certbot.stderr.on('data', (data) => {
const message = data.toString();
error += message;
console.log('⚠️ Certbot error:', message.trim());
});
certbot.on('close', (code) => {
if (code === 0) {
console.log('✅ SSL certificate obtained successfully!');
resolve({ success: true, output });
} else {
console.log('❌ SSL certificate request failed with code:', code);
reject({ success: false, code, output, error });
}
});
});
}
async setupHTTPSServer() {
if (!await this.certificateExists()) {
console.log('⚠️ SSL certificate not found. Running HTTP only.');
return false;
}
try {
const privateKey = await fs.readFile(path.join(this.certPath, 'privkey.pem'), 'utf8');
const certificate = await fs.readFile(path.join(this.certPath, 'fullchain.pem'), 'utf8');
const credentials = { key: privateKey, cert: certificate };
this.httpsServer = https.createServer(credentials, this.app);
this.httpsServer.listen(443, () => {
console.log('🔒 HTTPS server running on port 443');
console.log(`🌐 Secure access: https://${this.domain}`);
});
return true;
} catch (error) {
console.error('❌ Failed to setup HTTPS server:', error.message);
return false;
}
}
async startHTTPServer() {
this.httpServer = http.createServer(this.app);
this.httpServer.listen(80, () => {
console.log('🌐 HTTP server running on port 80');
console.log(`🔗 HTTP access: http://${this.domain}`);
console.log('📁 ACME challenge directory:', this.webRoot);
});
}
async renewCertificate() {
console.log('🔄 Renewing SSL certificate...');
return new Promise((resolve, reject) => {
const renewArgs = ['renew', '--quiet'];
const certbot = spawn(this.certbotPath, renewArgs, {
stdio: ['inherit', 'pipe', 'pipe']
});
certbot.on('close', (code) => {
if (code === 0) {
console.log('✅ SSL certificate renewed successfully!');
resolve(true);
} else {
console.log('❌ SSL certificate renewal failed with code:', code);
reject(false);
}
});
});
}
async start() {
console.log('🚀 Starting CBD SSL Manager...');
console.log(`🎯 Domain: ${this.domain}`);
// Start HTTP server for ACME challenges
await this.startHTTPServer();
// Check if certificate exists
if (await this.certificateExists()) {
console.log('✅ SSL certificate found, setting up HTTPS...');
await this.setupHTTPSServer();
} else {
console.log('⏳ No SSL certificate found. Request one with: npm run ssl:request');
}
// Setup automatic renewal (check every 12 hours)
setInterval(async () => {
try {
await this.renewCertificate();
// Restart HTTPS server with new certificate
if (this.httpsServer) {
this.httpsServer.close();
await this.setupHTTPSServer();
}
} catch (error) {
console.error('🔄 Auto-renewal failed:', error);
}
}, 12 * 60 * 60 * 1000); // 12 hours
console.log('🎉 CBD SSL Manager is ready!');
console.log('📋 Available commands:');
console.log(' - Request certificate: npm run ssl:request');
console.log(' - Renew certificate: npm run ssl:renew');
console.log(' - Check status: curl http://cbd.memorai.ro/health');
}
async requestCertificateCommand() {
try {
await this.requestCertificate();
console.log('🔄 Setting up HTTPS server...');
await this.setupHTTPSServer();
console.log('🎉 SSL setup complete!');
} catch (error) {
console.error('❌ SSL request failed:', error);
process.exit(1);
}
}
async renewCertificateCommand() {
try {
await this.renewCertificate();
console.log('🎉 SSL renewal complete!');
} catch (error) {
console.error('❌ SSL renewal failed:', error);
process.exit(1);
}
}
}
// CLI interface
if (require.main === module) {
const sslManager = new CBDSSLManager();
const command = process.argv[2];
switch (command) {
case 'start':
sslManager.start();
break;
case 'request':
sslManager.requestCertificateCommand();
break;
case 'renew':
sslManager.renewCertificateCommand();
break;
default:
console.log('🔒 CBD SSL Manager');
console.log('Usage:');
console.log(' node cbd-ssl-manager.js start - Start SSL proxy server');
console.log(' node cbd-ssl-manager.js request - Request new SSL certificate');
console.log(' node cbd-ssl-manager.js renew - Renew existing certificate');
process.exit(1);
}
}
module.exports = CBDSSLManager;