initial
This commit is contained in:
252
ts/cli/service-handler.ts
Normal file
252
ts/cli/service-handler.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Service Handler
|
||||
*
|
||||
* CLI commands for systemd service management.
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { logger } from '../logger.ts';
|
||||
import { theme } from '../colors.ts';
|
||||
import { PATHS } from '../constants.ts';
|
||||
import type { ModelGrid } from '../modelgrid.ts';
|
||||
|
||||
/**
|
||||
* Handler for service-related CLI commands
|
||||
*/
|
||||
export class ServiceHandler {
|
||||
private readonly modelgrid: ModelGrid;
|
||||
|
||||
constructor(modelgrid: ModelGrid) {
|
||||
this.modelgrid = modelgrid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the service (requires root)
|
||||
*/
|
||||
public async enable(): Promise<void> {
|
||||
this.checkRootAccess('This command must be run as root.');
|
||||
await this.modelgrid.getSystemd().install();
|
||||
logger.log('ModelGrid service has been installed. Use "modelgrid service start" to start the service.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the daemon directly
|
||||
*/
|
||||
public async daemonStart(debugMode: boolean = false): Promise<void> {
|
||||
logger.log('Starting ModelGrid daemon...');
|
||||
try {
|
||||
if (debugMode) {
|
||||
logger.log('Debug mode enabled');
|
||||
}
|
||||
await this.modelgrid.getDaemon().start();
|
||||
} catch (error) {
|
||||
logger.error(`Daemon start failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show logs of the systemd service
|
||||
*/
|
||||
public async logs(): Promise<void> {
|
||||
try {
|
||||
const { spawn } = await import('child_process');
|
||||
logger.log('Tailing modelgrid service logs (Ctrl+C to exit)...\n');
|
||||
|
||||
const journalctl = spawn('journalctl', ['-u', 'modelgrid.service', '-n', '50', '-f'], {
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
journalctl.kill('SIGINT');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
journalctl.on('exit', () => resolve());
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to retrieve logs: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the systemd service
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
await this.modelgrid.getSystemd().stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the systemd service
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
try {
|
||||
await this.modelgrid.getSystemd().start();
|
||||
} catch (error) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show status of the systemd service
|
||||
*/
|
||||
public async status(): Promise<void> {
|
||||
await this.modelgrid.getSystemd().getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the service (requires root)
|
||||
*/
|
||||
public async disable(): Promise<void> {
|
||||
this.checkRootAccess('This command must be run as root.');
|
||||
await this.modelgrid.getSystemd().disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has root access
|
||||
*/
|
||||
private checkRootAccess(errorMessage: string): void {
|
||||
if (process.getuid && process.getuid() !== 0) {
|
||||
logger.error(errorMessage);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ModelGrid from repository
|
||||
*/
|
||||
public async update(): Promise<void> {
|
||||
try {
|
||||
this.checkRootAccess('This command must be run as root to update ModelGrid.');
|
||||
|
||||
console.log('');
|
||||
logger.info('Checking for updates...');
|
||||
|
||||
try {
|
||||
const currentVersion = this.modelgrid.getVersion();
|
||||
const apiUrl = 'https://code.foss.global/api/v1/repos/modelgrid.com/modelgrid/releases/latest';
|
||||
const response = execSync(`curl -sSL ${apiUrl}`).toString();
|
||||
const release = JSON.parse(response);
|
||||
const latestVersion = release.tag_name;
|
||||
|
||||
const normalizedCurrent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
|
||||
const normalizedLatest = latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}`;
|
||||
|
||||
logger.dim(`Current version: ${normalizedCurrent}`);
|
||||
logger.dim(`Latest version: ${normalizedLatest}`);
|
||||
console.log('');
|
||||
|
||||
if (normalizedCurrent === normalizedLatest) {
|
||||
logger.success('Already up to date!');
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`New version available: ${latestVersion}`);
|
||||
logger.dim('Downloading and installing...');
|
||||
console.log('');
|
||||
|
||||
const installUrl = 'https://code.foss.global/modelgrid.com/modelgrid/raw/branch/main/install.sh';
|
||||
|
||||
execSync(`curl -sSL ${installUrl} | bash`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
console.log('');
|
||||
logger.success(`Updated to ${latestVersion}`);
|
||||
console.log('');
|
||||
} catch (error) {
|
||||
console.log('');
|
||||
logger.error('Update failed');
|
||||
logger.dim(`${error instanceof Error ? error.message : String(error)}`);
|
||||
console.log('');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely uninstall ModelGrid from the system
|
||||
*/
|
||||
public async uninstall(): Promise<void> {
|
||||
this.checkRootAccess('This command must be run as root.');
|
||||
|
||||
try {
|
||||
const helpers = await import('../helpers/index.ts');
|
||||
const { prompt, close } = await helpers.createPrompt();
|
||||
|
||||
logger.log('');
|
||||
logger.highlight('ModelGrid Uninstaller');
|
||||
logger.dim('=====================');
|
||||
logger.log('This will completely remove ModelGrid from your system.');
|
||||
logger.log('');
|
||||
|
||||
const removeConfig = await prompt('Do you want to remove configuration files? (y/N): ');
|
||||
const removeContainers = await prompt('Do you want to remove Docker containers? (y/N): ');
|
||||
|
||||
close();
|
||||
|
||||
// Stop service first
|
||||
try {
|
||||
await this.modelgrid.getSystemd().stop();
|
||||
} catch {
|
||||
// Service might not be running
|
||||
}
|
||||
|
||||
// Disable service
|
||||
try {
|
||||
await this.modelgrid.getSystemd().disable();
|
||||
} catch {
|
||||
// Service might not be installed
|
||||
}
|
||||
|
||||
// Remove containers if requested
|
||||
if (removeContainers.toLowerCase() === 'y') {
|
||||
logger.info('Removing Docker containers...');
|
||||
try {
|
||||
execSync('docker rm -f $(docker ps -aq --filter "name=modelgrid")', { stdio: 'pipe' });
|
||||
} catch {
|
||||
// No containers to remove
|
||||
}
|
||||
}
|
||||
|
||||
// Remove configuration if requested
|
||||
if (removeConfig.toLowerCase() === 'y') {
|
||||
logger.info('Removing configuration...');
|
||||
try {
|
||||
const { rm } = await import('node:fs/promises');
|
||||
await rm(PATHS.CONFIG_DIR, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Config might not exist
|
||||
}
|
||||
}
|
||||
|
||||
// Run uninstall script
|
||||
const { dirname, join } = await import('path');
|
||||
const binPath = process.argv[1];
|
||||
const modulePath = dirname(dirname(binPath));
|
||||
const uninstallScriptPath = join(modulePath, 'uninstall.sh');
|
||||
|
||||
logger.log('');
|
||||
logger.log(`Running uninstaller from ${uninstallScriptPath}...`);
|
||||
|
||||
execSync(`sudo bash ${uninstallScriptPath}`, {
|
||||
env: {
|
||||
...process.env,
|
||||
REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no',
|
||||
MODELGRID_CLI_CALL: 'true',
|
||||
},
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user