Files
modelgrid/ts/daemon.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

269 lines
6.6 KiB
TypeScript

/**
* ModelGrid Daemon
*
* Background process for managing containers and serving the API.
*/
import process from 'node:process';
import { logger } from './logger.ts';
import { TIMING } from './constants.ts';
import type { ModelGrid } from './modelgrid.ts';
import { ApiServer } from './api/server.ts';
import type { IModelGridConfig } from './interfaces/config.ts';
/**
* ModelGrid Daemon
*/
export class Daemon {
private modelgrid: ModelGrid;
private isRunning: boolean = false;
private apiServer?: ApiServer;
constructor(modelgrid: ModelGrid) {
this.modelgrid = modelgrid;
}
/**
* Start the daemon
*/
public async start(): Promise<void> {
if (this.isRunning) {
logger.warn('Daemon is already running');
return;
}
logger.log('Starting ModelGrid daemon...');
try {
// Initialize ModelGrid
await this.modelgrid.initialize();
const config = this.modelgrid.getConfig();
if (!config) {
throw new Error('Failed to load configuration');
}
this.logConfigLoaded(config);
// Start API server
await this.startApiServer(config);
// Start containers
await this.startContainers();
// Preload models if configured
await this.preloadModels(config);
// Setup signal handlers
this.setupSignalHandlers();
this.isRunning = true;
// Start monitoring loop
await this.monitor();
} catch (error) {
this.isRunning = false;
logger.error(`Daemon failed to start: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
}
/**
* Stop the daemon
*/
public async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
logger.log('Stopping ModelGrid daemon...');
this.isRunning = false;
// Stop API server
if (this.apiServer) {
await this.apiServer.stop();
}
// Shutdown ModelGrid (stops containers)
await this.modelgrid.shutdown();
logger.success('ModelGrid daemon stopped');
}
/**
* Start the API server
*/
private async startApiServer(config: IModelGridConfig): Promise<void> {
logger.info('Starting API server...');
this.apiServer = new ApiServer(
config.api,
this.modelgrid.getContainerManager(),
this.modelgrid.getModelRegistry(),
);
await this.apiServer.start();
}
/**
* Start configured containers
*/
private async startContainers(): Promise<void> {
logger.info('Starting containers...');
const containerManager = this.modelgrid.getContainerManager();
await containerManager.startAll();
// Wait for containers to be healthy
logger.dim('Waiting for containers to become healthy...');
await this.waitForContainersHealthy();
}
/**
* Wait for all containers to report healthy
*/
private async waitForContainersHealthy(timeout: number = 60000): Promise<void> {
const startTime = Date.now();
const containerManager = this.modelgrid.getContainerManager();
while (Date.now() - startTime < timeout) {
const allHealthy = await containerManager.checkAllHealth();
if (allHealthy) {
logger.success('All containers are healthy');
return;
}
await this.sleep(5000);
}
logger.warn('Timeout waiting for containers to become healthy');
}
/**
* Preload configured models
*/
private async preloadModels(config: IModelGridConfig): Promise<void> {
if (!config.models.autoLoad || config.models.autoLoad.length === 0) {
return;
}
logger.info(`Preloading ${config.models.autoLoad.length} model(s)...`);
const modelLoader = this.modelgrid.getModelLoader();
const results = await modelLoader.preloadModels(config.models.autoLoad);
let loaded = 0;
let failed = 0;
for (const [name, result] of results) {
if (result.success) {
loaded++;
logger.dim(`${name}`);
} else {
failed++;
logger.warn(`${name}: ${result.error}`);
}
}
if (failed > 0) {
logger.warn(`Preloaded ${loaded}/${config.models.autoLoad.length} models (${failed} failed)`);
} else {
logger.success(`Preloaded ${loaded} model(s)`);
}
}
/**
* Setup signal handlers for graceful shutdown
*/
private setupSignalHandlers(): void {
const shutdown = async () => {
logger.log('');
logger.log('Received shutdown signal');
await this.stop();
process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
}
/**
* Main monitoring loop
*/
private async monitor(): Promise<void> {
logger.log('Starting monitoring loop...');
const config = this.modelgrid.getConfig();
const checkInterval = config?.checkInterval || TIMING.CHECK_INTERVAL_MS;
while (this.isRunning) {
try {
// Check container health
await this.checkContainerHealth();
// Log periodic status
this.logPeriodicStatus();
await this.sleep(checkInterval);
} catch (error) {
logger.error(`Monitor error: ${error instanceof Error ? error.message : String(error)}`);
await this.sleep(checkInterval);
}
}
}
/**
* Check health of all containers
*/
private async checkContainerHealth(): Promise<void> {
const containerManager = this.modelgrid.getContainerManager();
const statuses = await containerManager.getAllStatus();
for (const [id, status] of statuses) {
if (status.running && status.health === 'unhealthy') {
logger.warn(`Container ${id} is unhealthy, attempting restart...`);
const container = containerManager.getContainer(id);
if (container) {
await container.restart();
}
}
}
}
/**
* Log periodic status
*/
private logPeriodicStatus(): void {
if (this.apiServer) {
const info = this.apiServer.getInfo();
if (info.running) {
logger.dim(`API server running on ${info.host}:${info.port} (uptime: ${info.uptime}s)`);
}
}
}
/**
* Log configuration loaded message
*/
private logConfigLoaded(config: IModelGridConfig): void {
logger.log('');
logger.logBoxTitle('Configuration Loaded', 60, 'success');
logger.logBoxLine(`API Port: ${config.api.port}`);
logger.logBoxLine(`Containers: ${config.containers.length}`);
logger.logBoxLine(`Auto-pull: ${config.models.autoPull ? 'Enabled' : 'Disabled'}`);
logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000}s`);
logger.logBoxEnd();
logger.log('');
}
/**
* Sleep for specified milliseconds
*/
private sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}