/** * Kubernetes API client utilities */ import * as k8s from '@kubernetes/client-node' let k8sClient: k8s.CoreV1Api | null = null let k8sConfig: k8s.KubeConfig | null = null /** * Initialize Kubernetes client */ export function initK8sClient() { if (k8sClient) return k8sClient k8sConfig = new k8s.KubeConfig() // Check if running in cluster const inCluster = process.env.K8S_IN_CLUSTER === 'true' if (inCluster) { k8sConfig.loadFromCluster() } else { // Load from kubeconfig file const configPath = process.env.K8S_CONFIG_PATH || process.env.KUBECONFIG || '~/.kube/config' k8sConfig.loadFromFile(configPath) } k8sClient = k8sConfig.makeApiClient(k8s.CoreV1Api) return k8sClient } /** * Get Kubernetes client */ export function getK8sClient(): k8s.CoreV1Api { if (!k8sClient) { return initK8sClient() } return k8sClient } /** * Create pod spec for agent */ export function createAgentPodSpec(podName: string, userId: string): k8s.V1Pod { return { metadata: { name: podName, labels: { app: 'claude-agent', userId: userId, 'aiworker.io/agent': 'true', }, }, spec: { serviceAccountName: 'agent-sa', imagePullSecrets: [ { name: 'gitea-registry', }, ], containers: [ { name: 'agent', image: 'git.fuq.tv/admin/aiworker-agent:latest', imagePullPolicy: 'Always', ports: [ { containerPort: 7681, name: 'terminal', }, ], env: [ { name: 'BACKEND_URL', value: 'https://api.fuq.tv', }, { name: 'MCP_ENDPOINT', value: 'https://api.fuq.tv/api/mcp', }, { name: 'GITEA_URL', value: 'https://git.fuq.tv', }, { name: 'GITEA_TOKEN', valueFrom: { secretKeyRef: { name: 'agent-secrets', key: 'gitea-token', }, }, }, { name: 'POD_NAME', valueFrom: { fieldRef: { fieldPath: 'metadata.name', }, }, }, { name: 'NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace', }, }, }, { name: 'USER_ID', value: userId, }, ], resources: { requests: { cpu: '500m', memory: '1Gi', }, limits: { cpu: '2000m', memory: '4Gi', }, }, volumeMounts: [ { name: 'workspace', mountPath: '/workspace', }, ], }, ], volumes: [ { name: 'workspace', emptyDir: {}, }, ], }, } } /** * Create agent pod in Kubernetes */ export async function createAgentPod(podName: string, userId: string): Promise { const client = getK8sClient() const podSpec = createAgentPodSpec(podName, userId) try { await client.createNamespacedPod({ namespace: 'agents', body: podSpec }) console.log(`✅ Pod ${podName} created successfully`) } catch (error: any) { console.error(`❌ Failed to create pod ${podName}:`, error.message) throw error } } /** * Delete agent pod from Kubernetes */ export async function deleteAgentPod(podName: string): Promise { const client = getK8sClient() try { await client.deleteNamespacedPod({ name: podName, namespace: 'agents' }) console.log(`✅ Pod ${podName} deleted successfully`) } catch (error: any) { // Ignore 404 errors (pod already deleted) if (error.statusCode === 404 || error.response?.statusCode === 404) { console.log(`⚠️ Pod ${podName} not found (already deleted)`) return } console.error(`❌ Failed to delete pod ${podName}:`, error.message) throw error } } /** * Get pod status */ export async function getPodStatus(podName: string): Promise { const client = getK8sClient() try { const response = await client.readNamespacedPod({ name: podName, namespace: 'agents' }) return response.body.status?.phase || null } catch (error: any) { if (error.statusCode === 404 || error.response?.statusCode === 404) { return null } throw error } }