Fix: Use correct health check endpoints for LiteLLM and Letta

Root cause: Status API was using wrong health endpoints:
- LiteLLM /health requires API key auth (returns 401) -> changed to /health/readiness (no auth)
- Letta /api/health returns 404 -> changed to /v1/health/ (correct endpoint)
This commit is contained in:
2026-07-02 05:21:08 +00:00
commit cc9c903d6d
3 changed files with 140 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
FROM node:20-alpine
RUN apk add --no-cache curl
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY server.js ./
EXPOSE 3100
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -sf http://localhost:3100/health || exit 1
CMD ["node", "server.js"]
+13
View File
@@ -0,0 +1,13 @@
{
"name": "apex-status-api",
"version": "1.0.0",
"description": "APEX OS Platform Health Monitor",
"main": "server.js",
"scripts": {
"start": "node server.js",
"test": "node test.js"
},
"dependencies": {
"express": "^4.18.0"
}
}
+118
View File
@@ -0,0 +1,118 @@
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');
});