const express = require('express'); const { execSync } = require('child_process'); const app = express(); const PORT = process.env.PORT || 3100; const START_TIME = Date.now(); function exec(cmd) { try { return execSync(cmd, { timeout: 10000, encoding: 'utf-8' }).trim(); } catch (e) { return null; } } app.get('/health', (req, res) => { try { const containers = JSON.parse(exec('curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json?all=true') || '[]'); const running = containers.filter(c => c.State === 'running').length; const total = containers.length; const unhealthy = containers.filter(c => c.Status && c.Status.includes('unhealthy')).length; const status = unhealthy > 0 ? 'degraded' : (running === total ? 'healthy' : 'partial'); res.json({ status, platform: 'APEX OS', uptime_seconds: Math.floor((Date.now() - START_TIME) / 1000), containers: { running, total, unhealthy }, timestamp: new Date().toISOString() }); } catch (e) { res.status(500).json({ status: 'error', error: e.message }); } }); app.get('/containers', (req, res) => { try { const raw = exec('curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json?all=true'); const containers = JSON.parse(raw || '[]').map(c => ({ name: (c.Names || ['/unknown'])[0].replace('/', ''), image: c.Image, state: c.State, status: c.Status, created: new Date(c.Created * 1000).toISOString(), ports: (c.Ports || []).map(p => p.PublicPort ? `${p.PublicPort}:${p.PrivatePort}` : `${p.PrivatePort}`).filter(Boolean) })); res.json({ count: containers.length, containers }); } catch (e) { res.status(500).json({ error: e.message }); } }); app.get('/resources', (req, res) => { try { const memRaw = exec('free -m') || ''; const memLine = memRaw.split('\n').find(l => l.startsWith('Mem:')); const memParts = memLine ? memLine.split(/\s+/) : []; const diskRaw = exec('df -h /') || ''; const diskLine = diskRaw.split('\n')[1] || ''; const diskParts = diskLine.split(/\s+/); const loadRaw = exec('cat /proc/loadavg') || '0 0 0'; const loadParts = loadRaw.split(' '); res.json({ memory: { total_mb: parseInt(memParts[1]) || 0, used_mb: parseInt(memParts[2]) || 0, available_mb: parseInt(memParts[6]) || 0, usage_percent: memParts[1] ? Math.round((memParts[2] / memParts[1]) * 100) : 0 }, disk: { total: diskParts[1] || 'unknown', used: diskParts[2] || 'unknown', available: diskParts[3] || 'unknown', usage_percent: diskParts[4] || 'unknown' }, load: { avg_1m: parseFloat(loadParts[0]) || 0, avg_5m: parseFloat(loadParts[1]) || 0, avg_15m: parseFloat(loadParts[2]) || 0 }, timestamp: new Date().toISOString() }); } catch (e) { res.status(500).json({ error: e.message }); } }); app.get('/services', async (req, res) => { const services = [ { name: 'litellm', url: 'http://litellm:4000/health/readiness', critical: true }, { name: 'letta', url: 'http://letta:8283/v1/health/', critical: false }, { name: 'n8n', url: 'http://n8n:5678/healthz', critical: true }, { name: 'gitea', url: 'http://gitea:3000/api/v1/version', critical: true }, { name: 'grafana', url: 'http://grafana:3000/api/health', critical: false }, { name: 'ollama', url: 'http://ollama:11434/api/tags', critical: true } ]; const results = []; for (const svc of services) { const start = Date.now(); const response = exec('curl -sf -m 5 ' + svc.url); results.push({ name: svc.name, status: response ? 'healthy' : 'unreachable', response_ms: Date.now() - start, critical: svc.critical }); } const healthy = results.filter(r => r.status === 'healthy').length; res.json({ overall: healthy === results.length ? 'all_healthy' : 'degraded', healthy_count: healthy, total_count: results.length, services: results, timestamp: new Date().toISOString() }); }); app.listen(PORT, '0.0.0.0', () => { console.log('APEX Status API v1.1 - connectivity fix applied'); });