Files
modelgrid/ts/cli/service-handler.ts
Juergen Kunz daaf6559e3
Some checks failed
CI / Type Check & Lint (push) Failing after 5s
CI / Build Test (Current Platform) (push) Failing after 5s
CI / Build All Platforms (push) Successful in 49s
initial
2026-01-30 03:16:57 +00:00

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);
}
}
}