253 lines
7.2 KiB
TypeScript
253 lines
7.2 KiB
TypeScript
|
|
/**
|
||
|
|
* 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|