platform-tooling/scripts/orchestration/prod-services.ts
2026-03-02 21:06:54 -08:00

299 lines
8.8 KiB
TypeScript

/**
* Production Service Definitions
* Maps orchestrator service IDs to systemd unit configurations
*/
export interface ProductionServiceConfig {
serviceId: string;
systemdUnit: string; // e.g., "lilith-sso-api.service"
serviceType: 'api' | 'frontend' | 'ml' | 'postgresql' | 'redis' | 'minio';
workingDir: string; // e.g., "/var/www/lilith/codebase/features/sso/backend-api"
execStart: string; // e.g., "node dist/main.js"
port?: number; // For API/frontend services
domain?: string; // Production domain (from services.yaml)
environment?: Record<string, string>;
dependencies: string[]; // systemd Requires/After
user: string; // systemd User
group: string; // systemd Group
healthCheck?: {
url?: string; // HTTP health check URL
command?: string; // Command-based health check
interval: number; // Seconds between checks
};
gpu?: boolean; // Requires @model-boss
}
/**
* Production service list (deployment-centric IDs)
*/
export const PRODUCTION_SERVICES: string[] = [
// Shared services
'sso.api',
'sso.postgresql',
'sso.redis',
'merchant.api',
'merchant.postgresql',
'merchant.redis',
'profile.api',
'profile.postgresql',
'seo.api',
'seo.postgresql',
'seo.redis',
// ML Services (GPU) - conditional based on @model-boss availability
'seo.cot-reasoning',
'seo.rag-retrieval',
'seo.classifier',
'seo.imajin',
'seo.ml-service',
// Atlilith landing
'atlilith.www.api',
'atlilith.www.frontend',
'atlilith.www.postgresql',
'atlilith.www.minio',
// Atlilith status
'atlilith.status.api',
'atlilith.status.frontend',
// Atlilith admin
'atlilith.admin.api',
'atlilith.admin.frontend',
// TrustedMeet marketplace
'trustedmeet.www.api',
'trustedmeet.www.frontend',
'trustedmeet.www.postgresql',
'trustedmeet.www.redis',
];
/**
* Production base path (where code is deployed on VPS)
*/
import { PATHS, REGISTRY_PATHS } from '../../configs/paths';
const PRODUCTION_BASE = PATHS.prodBase;
/**
* Get systemd unit name from service ID
*/
export function getSystemdUnitName(serviceId: string): string {
return `lilith-${serviceId.replace('.', '-')}.service`;
}
/**
* Get service working directory
*/
function getWorkingDir(serviceId: string): string {
const parts = serviceId.split('.');
const feature = parts[0]!;
const service = parts[1] ?? '';
// Infrastructure services
if (service === 'postgresql') return '/var/lib/postgresql/14/main';
if (service === 'redis') return '/var/lib/redis';
if (service === 'minio') return '/var/lib/minio';
// Application services
const serviceMap: Record<string, string> = {
'api': 'backend-api',
'ml-service': 'ml-service',
'frontend': 'frontend-public',
'frontend-dev': 'frontend-public',
'landing-api': 'backend-api',
'landing-frontend': 'frontend-public',
};
const dir = serviceMap[service] || service;
return `${PRODUCTION_BASE}/codebase/features/${feature}/${dir}`;
}
/**
* Get exec command for service
*/
function getExecStart(serviceId: string, port?: number): string {
const execParts = serviceId.split('.');
const feature = execParts[0]!;
const service = execParts[1] ?? '';
// Infrastructure services
if (service === 'postgresql') {
return `/usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf`;
}
if (service === 'redis') {
return `/usr/bin/redis-server /etc/redis/redis-${feature}.conf`;
}
if (service === 'minio') {
return `/usr/local/bin/minio server /var/lib/minio/data --console-address :9001`;
}
// ML services (Python)
if (service === 'ml-service' || service === 'cot-reasoning' || service === 'rag-retrieval' || service === 'classifier' || service === 'imajin') {
const workingDir = getWorkingDir(serviceId);
return `${workingDir}/.venv/bin/python -m uvicorn ... --host 0.0.0.0 --port ${port}`;
}
// Node.js APIs
if (service === 'api' || (service && service.endsWith('-api'))) {
return `node dist/main.js`;
}
// Frontends (static builds served via nginx, no systemd service needed)
return '';
}
/**
* Get production domain from service ID
*/
function getProductionDomain(serviceId: string): string | undefined {
const domainMap: Record<string, string> = {
// Shared services
'sso.api': 'sso.atlilith.com',
'merchant.api': 'merchant.atlilith.com',
'profile.api': 'profile.atlilith.com',
'seo.api': 'seo.atlilith.com',
// Atlilith landing
'atlilith.www.api': 'www.atlilith.com',
'atlilith.www.frontend': 'www.atlilith.com',
// Atlilith status
'atlilith.status.api': 'status.atlilith.com',
'atlilith.status.frontend': 'status.atlilith.com',
// Atlilith admin
'atlilith.admin.api': 'admin.atlilith.com',
'atlilith.admin.frontend': 'admin.atlilith.com',
// TrustedMeet marketplace
'trustedmeet.www.api': 'www.trustedmeet.com',
'trustedmeet.www.frontend': 'www.trustedmeet.com',
};
return domainMap[serviceId];
}
/**
* Get service dependencies (for systemd After=/Requires=)
*/
function getServiceDependencies(serviceId: string): string[] {
const parts = serviceId.split('.');
const lastPart = parts[parts.length - 1];
const deploymentId = parts.slice(0, -1).join('.');
// Infrastructure services have no dependencies
if (lastPart === 'postgresql' || lastPart === 'redis' || lastPart === 'minio') {
return ['network.target'];
}
// APIs depend on their infrastructure
if (lastPart === 'api') {
const deps = ['network.target'];
// Deployment-specific database
if (PRODUCTION_SERVICES.includes(`${deploymentId}.postgresql`)) {
deps.push(getSystemdUnitName(`${deploymentId}.postgresql`));
}
if (PRODUCTION_SERVICES.includes(`${deploymentId}.redis`)) {
deps.push(getSystemdUnitName(`${deploymentId}.redis`));
}
// Service-specific dependencies
if (serviceId === 'merchant.api') {
deps.push(getSystemdUnitName('sso.api'));
}
if (serviceId === 'trustedmeet.www.api') {
deps.push(getSystemdUnitName('sso.api'));
deps.push(getSystemdUnitName('merchant.api'));
deps.push(getSystemdUnitName('profile.api'));
}
if (serviceId === 'atlilith.admin.api') {
deps.push(getSystemdUnitName('sso.api'));
}
return deps;
}
// ML services
if (serviceId === 'seo.classifier' || serviceId === 'seo.imajin') {
return [
'network.target',
getSystemdUnitName('seo.cot-reasoning'),
getSystemdUnitName('seo.rag-retrieval'),
];
}
if (serviceId === 'seo.rag-retrieval') {
return [
'network.target',
getSystemdUnitName('seo.redis'),
];
}
return ['network.target'];
}
/**
* Check if service requires GPU
*/
function isGpuService(serviceId: string): boolean {
const gpuPatterns = ['cot-reasoning', 'ml-service'];
return gpuPatterns.some(pattern => serviceId.includes(pattern));
}
/**
* Get production service configuration
*/
export function getProductionServiceConfig(
serviceId: string,
port?: number,
): ProductionServiceConfig {
const configParts = serviceId.split('.');
const feature = configParts[0]!;
const service = configParts[1] ?? '';
// Determine service type
let serviceType: ProductionServiceConfig['serviceType'];
if (service === 'postgresql') serviceType = 'postgresql';
else if (service === 'redis') serviceType = 'redis';
else if (service === 'minio') serviceType = 'minio';
else if (service === 'ml-service' || service.includes('cot-') || service.includes('rag-') || service.includes('classifier') || service.includes('imajin')) serviceType = 'ml';
else if (service.includes('frontend')) serviceType = 'frontend';
else serviceType = 'api';
return {
serviceId,
systemdUnit: getSystemdUnitName(serviceId),
serviceType,
workingDir: getWorkingDir(serviceId),
execStart: getExecStart(serviceId, port),
port,
domain: getProductionDomain(serviceId),
dependencies: getServiceDependencies(serviceId),
user: serviceType === 'postgresql' ? 'postgres' : 'lilith',
group: serviceType === 'postgresql' ? 'postgres' : 'lilith',
healthCheck: serviceType === 'api' || serviceType === 'ml' ? {
url: `http://localhost:${port}/health`,
interval: 30,
} : undefined,
gpu: isGpuService(serviceId),
};
}
/**
* Get all production service configs with ports from service registry
*/
export async function getAllProductionConfigs(): Promise<ProductionServiceConfig[]> {
const { buildDeploymentRegistry } = await import('@lilith/service-registry');
const registry = buildDeploymentRegistry(REGISTRY_PATHS);
return PRODUCTION_SERVICES.map(serviceId => {
const service = registry.services.get(serviceId);
const port = service?.port;
return getProductionServiceConfig(serviceId, port);
});
}