container lifecycle management
This commit is contained in:
154
gateway/src/main.ts
Normal file
154
gateway/src/main.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import Fastify from 'fastify';
|
||||
import websocket from '@fastify/websocket';
|
||||
import cors from '@fastify/cors';
|
||||
import { UserService } from './db/user-service.js';
|
||||
import { Authenticator } from './auth/authenticator.js';
|
||||
import { WebSocketHandler } from './channels/websocket-handler.js';
|
||||
import { TelegramHandler } from './channels/telegram-handler.js';
|
||||
import { KubernetesClient } from './k8s/client.js';
|
||||
import { ContainerManager } from './k8s/container-manager.js';
|
||||
|
||||
const app = Fastify({
|
||||
logger: {
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Configuration from environment
|
||||
const config = {
|
||||
port: parseInt(process.env.PORT || '3000'),
|
||||
host: process.env.HOST || '0.0.0.0',
|
||||
databaseUrl: process.env.DATABASE_URL || 'postgresql://localhost/dexorder',
|
||||
|
||||
// LLM provider API keys
|
||||
providerConfig: {
|
||||
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
||||
openaiApiKey: process.env.OPENAI_API_KEY,
|
||||
googleApiKey: process.env.GOOGLE_API_KEY,
|
||||
openrouterApiKey: process.env.OPENROUTER_API_KEY,
|
||||
},
|
||||
|
||||
telegramBotToken: process.env.TELEGRAM_BOT_TOKEN || '',
|
||||
|
||||
// Kubernetes configuration
|
||||
kubernetes: {
|
||||
namespace: process.env.KUBERNETES_NAMESPACE || 'dexorder-agents',
|
||||
inCluster: process.env.KUBERNETES_IN_CLUSTER === 'true',
|
||||
context: process.env.KUBERNETES_CONTEXT,
|
||||
agentImage: process.env.AGENT_IMAGE || 'ghcr.io/dexorder/agent:latest',
|
||||
sidecarImage: process.env.SIDECAR_IMAGE || 'ghcr.io/dexorder/lifecycle-sidecar:latest',
|
||||
storageClass: process.env.AGENT_STORAGE_CLASS || 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
// Validate at least one LLM provider is configured
|
||||
const hasAnyProvider = Object.values(config.providerConfig).some(key => !!key);
|
||||
if (!hasAnyProvider) {
|
||||
app.log.error('At least one LLM provider API key is required (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, or OPENROUTER_API_KEY)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Register plugins
|
||||
await app.register(cors, {
|
||||
origin: process.env.CORS_ORIGIN || '*',
|
||||
});
|
||||
|
||||
await app.register(websocket, {
|
||||
options: {
|
||||
maxPayload: 1024 * 1024, // 1MB
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize services
|
||||
const userService = new UserService(config.databaseUrl);
|
||||
|
||||
// Initialize Kubernetes client and container manager
|
||||
const k8sClient = new KubernetesClient({
|
||||
namespace: config.kubernetes.namespace,
|
||||
inCluster: config.kubernetes.inCluster,
|
||||
context: config.kubernetes.context,
|
||||
logger: app.log,
|
||||
});
|
||||
|
||||
const containerManager = new ContainerManager({
|
||||
k8sClient,
|
||||
agentImage: config.kubernetes.agentImage,
|
||||
sidecarImage: config.kubernetes.sidecarImage,
|
||||
storageClass: config.kubernetes.storageClass,
|
||||
namespace: config.kubernetes.namespace,
|
||||
logger: app.log,
|
||||
});
|
||||
|
||||
const authenticator = new Authenticator({
|
||||
userService,
|
||||
containerManager,
|
||||
logger: app.log,
|
||||
});
|
||||
|
||||
// Initialize channel handlers
|
||||
const websocketHandler = new WebSocketHandler({
|
||||
authenticator,
|
||||
providerConfig: config.providerConfig,
|
||||
});
|
||||
|
||||
const telegramHandler = new TelegramHandler({
|
||||
authenticator,
|
||||
providerConfig: config.providerConfig,
|
||||
telegramBotToken: config.telegramBotToken,
|
||||
});
|
||||
|
||||
// Register routes
|
||||
websocketHandler.register(app);
|
||||
telegramHandler.register(app);
|
||||
|
||||
// Health check
|
||||
app.get('/health', async () => {
|
||||
return {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = async () => {
|
||||
app.log.info('Shutting down gracefully...');
|
||||
try {
|
||||
await userService.close();
|
||||
await app.close();
|
||||
app.log.info('Shutdown complete');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
app.log.error({ error }, 'Error during shutdown');
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
|
||||
// Start server
|
||||
try {
|
||||
await app.listen({
|
||||
port: config.port,
|
||||
host: config.host,
|
||||
});
|
||||
|
||||
app.log.info(
|
||||
{
|
||||
port: config.port,
|
||||
host: config.host,
|
||||
},
|
||||
'Gateway server started'
|
||||
);
|
||||
} catch (error) {
|
||||
app.log.error({ error }, 'Failed to start server');
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user