update
This commit is contained in:
448
ts/cli.ts
Normal file
448
ts/cli.ts
Normal file
@@ -0,0 +1,448 @@
|
||||
/**
|
||||
* CLI Router for Onebox
|
||||
*/
|
||||
|
||||
import { logger } from './logging.ts';
|
||||
import { projectInfo } from './info.ts';
|
||||
import { Onebox } from './classes/onebox.ts';
|
||||
import { OneboxDaemon } from './classes/daemon.ts';
|
||||
|
||||
export async function runCli(): Promise<void> {
|
||||
const args = Deno.args;
|
||||
|
||||
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.includes('--version') || args.includes('-v')) {
|
||||
console.log(`${projectInfo.name} v${projectInfo.version}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const command = args[0];
|
||||
const subcommand = args[1];
|
||||
|
||||
try {
|
||||
// Server command has special handling (doesn't shut down)
|
||||
if (command === 'server') {
|
||||
const onebox = new Onebox();
|
||||
await onebox.init();
|
||||
await handleServerCommand(onebox, args.slice(1));
|
||||
// Server command runs forever (or until Ctrl+C), so this never returns
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Onebox
|
||||
const onebox = new Onebox();
|
||||
await onebox.init();
|
||||
|
||||
// Route commands
|
||||
switch (command) {
|
||||
case 'service':
|
||||
await handleServiceCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'registry':
|
||||
await handleRegistryCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'dns':
|
||||
await handleDnsCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'ssl':
|
||||
await handleSslCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'nginx':
|
||||
await handleNginxCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'daemon':
|
||||
await handleDaemonCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'config':
|
||||
await handleConfigCommand(onebox, subcommand, args.slice(2));
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
await handleStatusCommand(onebox);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.error(`Unknown command: ${command}`);
|
||||
printHelp();
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
await onebox.shutdown();
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
Deno.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Service commands
|
||||
async function handleServiceCommand(onebox: Onebox, subcommand: string, args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'add': {
|
||||
const name = args[0];
|
||||
const image = getArg(args, '--image');
|
||||
const domain = getArg(args, '--domain');
|
||||
const port = parseInt(getArg(args, '--port') || '80', 10);
|
||||
const envArgs = args.filter((a) => a.startsWith('--env=')).map((a) => a.slice(6));
|
||||
const envVars: Record<string, string> = {};
|
||||
for (const env of envArgs) {
|
||||
const [key, value] = env.split('=');
|
||||
envVars[key] = value;
|
||||
}
|
||||
|
||||
await onebox.services.deployService({ name, image, port, domain, envVars });
|
||||
break;
|
||||
}
|
||||
|
||||
case 'remove':
|
||||
await onebox.services.removeService(args[0]);
|
||||
break;
|
||||
|
||||
case 'start':
|
||||
await onebox.services.startService(args[0]);
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
await onebox.services.stopService(args[0]);
|
||||
break;
|
||||
|
||||
case 'restart':
|
||||
await onebox.services.restartService(args[0]);
|
||||
break;
|
||||
|
||||
case 'list': {
|
||||
const services = onebox.services.listServices();
|
||||
logger.table(
|
||||
['Name', 'Image', 'Status', 'Domain', 'Port'],
|
||||
services.map((s) => [s.name, s.image, s.status, s.domain || '-', s.port.toString()])
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'logs': {
|
||||
const logs = await onebox.services.getServiceLogs(args[0]);
|
||||
console.log(logs);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.error(`Unknown service subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Registry commands
|
||||
async function handleRegistryCommand(onebox: Onebox, subcommand: string, args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'add': {
|
||||
const url = getArg(args, '--url');
|
||||
const username = getArg(args, '--username');
|
||||
const password = getArg(args, '--password');
|
||||
await onebox.registries.addRegistry(url, username, password);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'remove':
|
||||
await onebox.registries.removeRegistry(getArg(args, '--url'));
|
||||
break;
|
||||
|
||||
case 'list': {
|
||||
const registries = onebox.registries.listRegistries();
|
||||
logger.table(
|
||||
['URL', 'Username'],
|
||||
registries.map((r) => [r.url, r.username])
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.error(`Unknown registry subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// DNS commands
|
||||
async function handleDnsCommand(onebox: Onebox, subcommand: string, args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'add':
|
||||
await onebox.dns.addDNSRecord(args[0]);
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
await onebox.dns.removeDNSRecord(args[0]);
|
||||
break;
|
||||
|
||||
case 'list': {
|
||||
const records = onebox.dns.listDNSRecords();
|
||||
logger.table(
|
||||
['Domain', 'Type', 'Value'],
|
||||
records.map((r) => [r.domain, r.type, r.value])
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'sync':
|
||||
await onebox.dns.syncFromCloudflare();
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.error(`Unknown dns subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// SSL commands
|
||||
async function handleSslCommand(onebox: Onebox, subcommand: string, args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'renew':
|
||||
if (args[0]) {
|
||||
await onebox.ssl.renewCertificate(args[0]);
|
||||
} else {
|
||||
await onebox.ssl.renewExpiring();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'list': {
|
||||
const certs = onebox.ssl.listCertificates();
|
||||
logger.table(
|
||||
['Domain', 'Expiry', 'Issuer'],
|
||||
certs.map((c) => [c.domain, new Date(c.expiryDate).toISOString(), c.issuer])
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'force-renew':
|
||||
await onebox.ssl.renewCertificate(args[0]);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.error(`Unknown ssl subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Nginx commands
|
||||
async function handleNginxCommand(onebox: Onebox, subcommand: string, _args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'reload':
|
||||
await onebox.nginx.reload();
|
||||
break;
|
||||
|
||||
case 'test':
|
||||
await onebox.nginx.test();
|
||||
break;
|
||||
|
||||
case 'status': {
|
||||
const status = await onebox.nginx.getStatus();
|
||||
logger.info(`Nginx status: ${status}`);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.error(`Unknown nginx subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Server command
|
||||
async function handleServerCommand(onebox: Onebox, args: string[]) {
|
||||
const ephemeral = args.includes('--ephemeral');
|
||||
const port = parseInt(getArg(args, '--port') || '3000', 10);
|
||||
const monitor = args.includes('--monitor') || ephemeral; // ephemeral includes monitoring
|
||||
|
||||
if (ephemeral) {
|
||||
// Ensure no daemon is running
|
||||
try {
|
||||
await OneboxDaemon.ensureNoDaemon();
|
||||
} catch (error) {
|
||||
logger.error('Cannot start in ephemeral mode: Daemon is already running');
|
||||
logger.info('Stop the daemon first: onebox daemon stop');
|
||||
logger.info('Or run without --ephemeral to use the existing daemon');
|
||||
Deno.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Starting Onebox server...');
|
||||
|
||||
// Start HTTP server
|
||||
await onebox.httpServer.start(port);
|
||||
|
||||
// Start monitoring if requested
|
||||
if (monitor) {
|
||||
logger.info('Starting monitoring loop...');
|
||||
onebox.daemon.startMonitoring();
|
||||
}
|
||||
|
||||
logger.success(`Onebox server running on http://localhost:${port}`);
|
||||
if (ephemeral) {
|
||||
logger.info('Running in ephemeral mode - Press Ctrl+C to stop');
|
||||
} else {
|
||||
logger.info('Press Ctrl+C to stop');
|
||||
}
|
||||
|
||||
// Setup signal handlers
|
||||
const shutdown = async () => {
|
||||
logger.info('Shutting down...');
|
||||
if (monitor) {
|
||||
onebox.daemon.stopMonitoring();
|
||||
}
|
||||
await onebox.httpServer.stop();
|
||||
await onebox.shutdown();
|
||||
Deno.exit(0);
|
||||
};
|
||||
|
||||
Deno.addSignalListener('SIGINT', shutdown);
|
||||
Deno.addSignalListener('SIGTERM', shutdown);
|
||||
|
||||
// Keep alive
|
||||
while (true) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
// Daemon commands
|
||||
async function handleDaemonCommand(onebox: Onebox, subcommand: string, _args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'install':
|
||||
await onebox.daemon.installService();
|
||||
break;
|
||||
|
||||
case 'start':
|
||||
await onebox.startDaemon();
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
await onebox.stopDaemon();
|
||||
break;
|
||||
|
||||
case 'logs': {
|
||||
const command = new Deno.Command('journalctl', {
|
||||
args: ['-u', 'smartdaemon_onebox', '-f'],
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
});
|
||||
await command.output();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'status': {
|
||||
const status = await onebox.daemon.getServiceStatus();
|
||||
logger.info(`Daemon status: ${status}`);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.error(`Unknown daemon subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Config commands
|
||||
async function handleConfigCommand(onebox: Onebox, subcommand: string, args: string[]) {
|
||||
switch (subcommand) {
|
||||
case 'show': {
|
||||
const settings = onebox.database.getAllSettings();
|
||||
logger.table(
|
||||
['Key', 'Value'],
|
||||
Object.entries(settings).map(([k, v]) => [k, v])
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set':
|
||||
onebox.database.setSetting(args[0], args[1]);
|
||||
logger.success(`Setting ${args[0]} updated`);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.error(`Unknown config subcommand: ${subcommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Status command
|
||||
async function handleStatusCommand(onebox: Onebox) {
|
||||
const status = await onebox.getSystemStatus();
|
||||
console.log(JSON.stringify(status, null, 2));
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function getArg(args: string[], flag: string): string {
|
||||
const arg = args.find((a) => a.startsWith(`${flag}=`));
|
||||
return arg ? arg.split('=')[1] : '';
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`
|
||||
Onebox v${projectInfo.version} - Self-hosted container platform
|
||||
|
||||
Usage: onebox <command> [options]
|
||||
|
||||
Commands:
|
||||
server [--ephemeral] [--port <port>] [--monitor]
|
||||
Start HTTP server
|
||||
--ephemeral: Run in foreground (development mode, checks no daemon running)
|
||||
--port: HTTP server port (default: 3000)
|
||||
--monitor: Enable monitoring loop (included with --ephemeral)
|
||||
|
||||
service add <name> --image <image> [--domain <domain>] [--port <port>] [--env KEY=VALUE]
|
||||
service remove <name>
|
||||
service start <name>
|
||||
service stop <name>
|
||||
service restart <name>
|
||||
service list
|
||||
service logs <name>
|
||||
|
||||
registry add --url <url> --username <user> --password <pass>
|
||||
registry remove --url <url>
|
||||
registry list
|
||||
|
||||
dns add <domain>
|
||||
dns remove <domain>
|
||||
dns list
|
||||
dns sync
|
||||
|
||||
ssl renew [domain]
|
||||
ssl list
|
||||
ssl force-renew <domain>
|
||||
|
||||
nginx reload
|
||||
nginx test
|
||||
nginx status
|
||||
|
||||
daemon install
|
||||
daemon start
|
||||
daemon stop
|
||||
daemon logs
|
||||
daemon status
|
||||
|
||||
config show
|
||||
config set <key> <value>
|
||||
|
||||
status
|
||||
|
||||
Options:
|
||||
--help, -h Show this help message
|
||||
--version, -v Show version
|
||||
--debug Enable debug logging
|
||||
|
||||
Development Workflow:
|
||||
deno task dev # Start ephemeral server with monitoring
|
||||
onebox service add ... # In another terminal
|
||||
|
||||
Production Workflow:
|
||||
onebox daemon install # Install systemd service
|
||||
onebox daemon start # Start daemon
|
||||
onebox service add ... # CLI uses daemon
|
||||
|
||||
Examples:
|
||||
onebox server --ephemeral # Start dev server
|
||||
onebox service add myapp --image nginx:latest --domain app.example.com --port 80
|
||||
onebox registry add --url registry.example.com --username user --password pass
|
||||
onebox daemon install
|
||||
onebox daemon start
|
||||
`);
|
||||
}
|
||||
Reference in New Issue
Block a user