refactor(backend): use systemd for daemon management
Replace manual process management with systemctl --user commands. Uses ollama.service and llama-server.service units for proper lifecycle management, VRAM cleanup, and integration with system services.
This commit is contained in:
@@ -52,7 +52,6 @@ export class MinimalTui {
|
|||||||
private totalUsage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
|
private totalUsage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
|
||||||
private currentHint = '';
|
private currentHint = '';
|
||||||
private lastLine = '';
|
private lastLine = '';
|
||||||
private backendPids: Map<string, number> = new Map();
|
|
||||||
|
|
||||||
constructor(private config: MinimalTuiConfig) {}
|
constructor(private config: MinimalTuiConfig) {}
|
||||||
|
|
||||||
@@ -323,61 +322,62 @@ export class MinimalTui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async stopBackend(provider: string): Promise<void> {
|
private async stopBackend(provider: string): Promise<void> {
|
||||||
const pid = this.backendPids.get(provider);
|
|
||||||
if (!pid) {
|
|
||||||
return; // No tracked PID, process not started by us
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
process.kill(pid, 'SIGTERM');
|
const { exec } = await import('child_process');
|
||||||
// Wait up to 2 seconds for graceful shutdown
|
let serviceName: string;
|
||||||
await new Promise<void>((resolve) => {
|
switch (provider) {
|
||||||
const timeout = setTimeout(resolve, 2000);
|
case 'ollama':
|
||||||
const checkInterval = setInterval(() => {
|
serviceName = 'ollama.service';
|
||||||
try {
|
break;
|
||||||
process.kill(pid, 0); // Check if process exists
|
case 'llamacpp':
|
||||||
} catch {
|
serviceName = 'llama-server.service';
|
||||||
clearInterval(checkInterval);
|
break;
|
||||||
clearTimeout(timeout);
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
exec(`systemctl --user stop ${serviceName}`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
}, 100);
|
});
|
||||||
});
|
});
|
||||||
this.backendPids.delete(provider);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Process already dead or permission error
|
// Service might not exist or already stopped, ignore
|
||||||
this.backendPids.delete(provider);
|
console.log(`${colors.gray}Note: ${provider} service not managed by systemd${colors.reset}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startBackend(provider: string, config: ModelConfig): Promise<void> {
|
private async startBackend(provider: string, config: ModelConfig): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { spawn } = await import('child_process');
|
const { exec } = await import('child_process');
|
||||||
const args: string[] = [];
|
let serviceName: string;
|
||||||
|
|
||||||
let proc: ReturnType<typeof spawn> | undefined;
|
|
||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'ollama':
|
case 'ollama':
|
||||||
proc = spawn('ollama', ['serve'], { detached: true, stdio: 'ignore' });
|
serviceName = 'ollama.service';
|
||||||
break;
|
break;
|
||||||
case 'llamacpp':
|
case 'llamacpp':
|
||||||
args.push('--model', config.model);
|
serviceName = 'llama-server.service';
|
||||||
args.push('--port', new URL(config.endpoint ?? 'http://localhost:8080').port || '8080');
|
|
||||||
args.push('--host', '0.0.0.0');
|
|
||||||
proc = spawn('llama-server', args, { detached: true, stdio: 'ignore' });
|
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
exec(`systemctl --user start ${serviceName}`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (proc && proc.pid) {
|
// Wait briefly for daemon to start
|
||||||
proc.unref();
|
|
||||||
this.backendPids.set(provider, proc.pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait briefly for the daemon to start
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`${colors.gray}Warning: Failed to start ${provider}: ${error instanceof Error ? error.message : String(error)}${colors.reset}\n`);
|
console.log(`${colors.gray}Warning: Failed to start ${provider} via systemd: ${error instanceof Error ? error.message : String(error)}${colors.reset}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user