332 lines
9.9 KiB
TypeScript
332 lines
9.9 KiB
TypeScript
|
|
#!/usr/bin/env bun
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Management CLI for NGINX Proxy Manager
|
||
|
|
* Usage: bun manage.ts <command> [options]
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { program } from 'commander';
|
||
|
|
import { database } from './src/database/index.js';
|
||
|
|
import { UserModel } from './src/models/User.js';
|
||
|
|
import { ProxyModel } from './src/models/Proxy.js';
|
||
|
|
import { CertificateModel } from './src/models/Certificate.js';
|
||
|
|
import { SSLService } from './src/services/SSLService.js';
|
||
|
|
import { NginxService } from './src/services/NginxService.js';
|
||
|
|
import bcrypt from 'bcryptjs';
|
||
|
|
import logger from './src/utils/logger.js';
|
||
|
|
|
||
|
|
program
|
||
|
|
.name('nginx-proxy-manager')
|
||
|
|
.description('NGINX Proxy Manager CLI')
|
||
|
|
.version('1.0.0');
|
||
|
|
|
||
|
|
// User management commands
|
||
|
|
const userCmd = program.command('user').description('User management commands');
|
||
|
|
|
||
|
|
userCmd
|
||
|
|
.command('create')
|
||
|
|
.description('Create a new admin user')
|
||
|
|
.requiredOption('-u, --username <username>', 'Username')
|
||
|
|
.requiredOption('-p, --password <password>', 'Password')
|
||
|
|
.action(async (options) => {
|
||
|
|
try {
|
||
|
|
const hashedPassword = await bcrypt.hash(options.password, 10);
|
||
|
|
const user = await UserModel.create({
|
||
|
|
username: options.username,
|
||
|
|
password: hashedPassword
|
||
|
|
});
|
||
|
|
console.log(`✅ User created: ${user.username} (ID: ${user.id})`);
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to create user: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
userCmd
|
||
|
|
.command('change-password')
|
||
|
|
.description('Change user password')
|
||
|
|
.requiredOption('-u, --username <username>', 'Username')
|
||
|
|
.requiredOption('-p, --password <password>', 'New password')
|
||
|
|
.action(async (options) => {
|
||
|
|
try {
|
||
|
|
const user = await UserModel.findByUsername(options.username);
|
||
|
|
if (!user) {
|
||
|
|
console.error(`❌ User not found: ${options.username}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const hashedPassword = await bcrypt.hash(options.password, 10);
|
||
|
|
await UserModel.updatePassword(user.id!, hashedPassword);
|
||
|
|
console.log(`✅ Password updated for user: ${options.username}`);
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to update password: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Proxy management commands
|
||
|
|
const proxyCmd = program.command('proxy').description('Proxy management commands');
|
||
|
|
|
||
|
|
proxyCmd
|
||
|
|
.command('list')
|
||
|
|
.description('List all proxies')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
const proxies = await ProxyModel.findAll();
|
||
|
|
console.log(`\n📋 Found ${proxies.length} proxy(ies):\n`);
|
||
|
|
|
||
|
|
proxies.forEach(proxy => {
|
||
|
|
console.log(`🔗 ${proxy.domain} → ${proxy.target}`);
|
||
|
|
console.log(` SSL: ${proxy.ssl_type}`);
|
||
|
|
console.log(` ID: ${proxy.id}`);
|
||
|
|
console.log(` Created: ${proxy.created_at}\n`);
|
||
|
|
});
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to list proxies: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
proxyCmd
|
||
|
|
.command('delete')
|
||
|
|
.description('Delete a proxy by ID')
|
||
|
|
.requiredOption('-i, --id <id>', 'Proxy ID')
|
||
|
|
.action(async (options) => {
|
||
|
|
try {
|
||
|
|
const id = parseInt(options.id);
|
||
|
|
const proxy = await ProxyModel.findById(id);
|
||
|
|
|
||
|
|
if (!proxy) {
|
||
|
|
console.error(`❌ Proxy not found with ID: ${id}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove NGINX config
|
||
|
|
await NginxService.removeConfig(proxy.domain);
|
||
|
|
|
||
|
|
// Delete from database
|
||
|
|
await ProxyModel.delete(id);
|
||
|
|
|
||
|
|
// Reload NGINX
|
||
|
|
const result = await NginxService.reload();
|
||
|
|
if (result.success) {
|
||
|
|
console.log(`✅ Proxy deleted: ${proxy.domain}`);
|
||
|
|
} else {
|
||
|
|
console.log(`⚠️ Proxy deleted but NGINX reload failed: ${result.output}`);
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to delete proxy: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Certificate management commands
|
||
|
|
const certCmd = program.command('cert').description('Certificate management commands');
|
||
|
|
|
||
|
|
certCmd
|
||
|
|
.command('list')
|
||
|
|
.description('List all certificates')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
const certificates = await CertificateModel.findAll();
|
||
|
|
console.log(`\n🔐 Found ${certificates.length} certificate(s):\n`);
|
||
|
|
|
||
|
|
certificates.forEach(cert => {
|
||
|
|
console.log(`📜 ${cert.domain}`);
|
||
|
|
console.log(` Type: ${cert.type}`);
|
||
|
|
console.log(` Status: ${cert.status}`);
|
||
|
|
console.log(` Expiry: ${cert.expiry || 'N/A'}`);
|
||
|
|
console.log(` ID: ${cert.id}\n`);
|
||
|
|
});
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to list certificates: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
certCmd
|
||
|
|
.command('renew')
|
||
|
|
.description('Renew certificates expiring soon')
|
||
|
|
.option('-d, --days <days>', 'Days before expiry to renew', '30')
|
||
|
|
.action(async (options) => {
|
||
|
|
try {
|
||
|
|
const days = parseInt(options.days);
|
||
|
|
console.log(`🔍 Checking for certificates expiring within ${days} days...`);
|
||
|
|
|
||
|
|
await SSLService.autoRenewCertificates();
|
||
|
|
console.log('✅ Certificate renewal process completed');
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Certificate renewal failed: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// NGINX management commands
|
||
|
|
const nginxCmd = program.command('nginx').description('NGINX management commands');
|
||
|
|
|
||
|
|
nginxCmd
|
||
|
|
.command('test')
|
||
|
|
.description('Test NGINX configuration')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
const result = await NginxService.testConfig();
|
||
|
|
if (result.success) {
|
||
|
|
console.log('✅ NGINX configuration is valid');
|
||
|
|
} else {
|
||
|
|
console.log('❌ NGINX configuration test failed:');
|
||
|
|
console.log(result.output);
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to test NGINX: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
nginxCmd
|
||
|
|
.command('reload')
|
||
|
|
.description('Reload NGINX configuration')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
const result = await NginxService.reload();
|
||
|
|
if (result.success) {
|
||
|
|
console.log('✅ NGINX reloaded successfully');
|
||
|
|
} else {
|
||
|
|
console.log('❌ NGINX reload failed:');
|
||
|
|
console.log(result.output);
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to reload NGINX: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
nginxCmd
|
||
|
|
.command('status')
|
||
|
|
.description('Get NGINX status')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
const result = await NginxService.getStatus();
|
||
|
|
if (result.success) {
|
||
|
|
console.log('✅ NGINX Status:');
|
||
|
|
console.log(result.output);
|
||
|
|
} else {
|
||
|
|
console.log('❌ Failed to get NGINX status:');
|
||
|
|
console.log(result.output);
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to get NGINX status: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Database management commands
|
||
|
|
const dbCmd = program.command('db').description('Database management commands');
|
||
|
|
|
||
|
|
dbCmd
|
||
|
|
.command('init')
|
||
|
|
.description('Initialize database')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
console.log('🗄️ Initializing database...');
|
||
|
|
// Database initialization happens automatically when imported
|
||
|
|
console.log('✅ Database initialized successfully');
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Database initialization failed: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
dbCmd
|
||
|
|
.command('backup')
|
||
|
|
.description('Create database backup')
|
||
|
|
.option('-o, --output <path>', 'Output file path', `./backups/backup-${new Date().toISOString().split('T')[0]}.db`)
|
||
|
|
.action(async (options) => {
|
||
|
|
try {
|
||
|
|
const fs = await import('fs');
|
||
|
|
const path = await import('path');
|
||
|
|
|
||
|
|
// Ensure backup directory exists
|
||
|
|
const backupDir = path.dirname(options.output);
|
||
|
|
if (!fs.existsSync(backupDir)) {
|
||
|
|
fs.mkdirSync(backupDir, { recursive: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copy database file
|
||
|
|
fs.copyFileSync('./data/proxy_manager.db', options.output);
|
||
|
|
console.log(`✅ Database backed up to: ${options.output}`);
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Backup failed: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Status command
|
||
|
|
program
|
||
|
|
.command('status')
|
||
|
|
.description('Show application status')
|
||
|
|
.action(async () => {
|
||
|
|
try {
|
||
|
|
console.log('📊 NGINX Proxy Manager Status\n');
|
||
|
|
|
||
|
|
// Check database
|
||
|
|
console.log('🗄️ Database:');
|
||
|
|
const proxies = await ProxyModel.findAll();
|
||
|
|
const certificates = await CertificateModel.findAll();
|
||
|
|
console.log(` Proxies: ${proxies.length}`);
|
||
|
|
console.log(` Certificates: ${certificates.length}`);
|
||
|
|
|
||
|
|
// Check NGINX
|
||
|
|
console.log('\n🔧 NGINX:');
|
||
|
|
const nginxStatus = await NginxService.getStatus();
|
||
|
|
if (nginxStatus.success) {
|
||
|
|
console.log(' Status: ✅ Running');
|
||
|
|
console.log(` Version: ${nginxStatus.output.trim()}`);
|
||
|
|
} else {
|
||
|
|
console.log(' Status: ❌ Not running or error');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check config
|
||
|
|
const configTest = await NginxService.testConfig();
|
||
|
|
console.log(` Config: ${configTest.success ? '✅ Valid' : '❌ Invalid'}`);
|
||
|
|
|
||
|
|
// Check expiring certificates
|
||
|
|
console.log('\n🔐 Certificates:');
|
||
|
|
const expiring = await SSLService.checkExpiringCertificates(30);
|
||
|
|
console.log(` Expiring soon (30 days): ${expiring.length}`);
|
||
|
|
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error(`❌ Failed to get status: ${error.message}`);
|
||
|
|
} finally {
|
||
|
|
await database.close();
|
||
|
|
process.exit();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Parse arguments
|
||
|
|
program.parse();
|
||
|
|
|
||
|
|
// If no command provided, show help
|
||
|
|
if (!process.argv.slice(2).length) {
|
||
|
|
program.outputHelp();
|
||
|
|
process.exit();
|
||
|
|
}
|