docker healthcheck api thingy

This commit is contained in:
legop3
2026-09-15 12:33:45 -04:00
parent bdb32d13ac
commit 5d4f7fe5cc
4 changed files with 75 additions and 1 deletions
+14
View File
@@ -16,6 +16,20 @@ app.use(morgan('dev'));
*/
app.use(PUBLIC_MEDIA_PREFIX, createMediaMtxProxy({ logger }));
app.use(express.json());
/*
Docker and the later lifecycle controller need one stable readiness result,
but they do not need administrator credentials or application details. Load
the health service only when the route is called so the global HTTP module
remains safe to initialize before the service graph during bootstrap.
*/
app.get('/health', async (_req, res) => {
const { getContainerHealth } = require('../services/healthService');
const health = await getContainerHealth();
res.status(health.healthy ? 200 : 503).json({
status: health.healthy ? 'healthy' : 'unhealthy',
checks: health.checks,
});
});
app.use(express.static(config.staticDir, { index: false }));
const httpServer = http.createServer(app);
+47 -1
View File
@@ -2,8 +2,9 @@
// Purpose: Defines the health Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fsp = require('fs/promises');
const fs = require('fs');
const path = require('path');
const { resolveRoverSnapshotDir } = require('../../helpers/dataPaths');
const { resolveDataDir, resolveRoverSnapshotDir } = require('../../helpers/dataPaths');
const roverManager = require('../roverManager');
const { getRoomCameras } = require('../roomCameraService');
const { getRoomCameraState } = require('../roomCameraService');
@@ -13,6 +14,8 @@ const ROVER_SNAPSHOT_DIR = resolveRoverSnapshotDir();
const HEALTH_INTERVAL_MS = 5000;
const ROOM_CAMERA_STALE_MS = 5000;
const ROVER_SNAPSHOT_STALE_MS = 5000;
const MEDIAMTX_HEALTH_URL = 'http://127.0.0.1:9998/metrics';
const MEDIAMTX_HEALTH_TIMEOUT_MS = 1500;
let latest = {
updatedAt: Date.now(),
@@ -88,6 +91,49 @@ function getHealthSnapshot() {
return latest;
}
async function getContainerHealth() {
let dataDirectory = false;
let mediaMtx = false;
try {
/*
The mounted data root is the container's only persistent storage. Check
the directory itself instead of creating a probe file on every request;
this proves that the running application user can reach the mount without
adding health-check writes to backups or administrative file listings.
*/
await fsp.access(resolveDataDir(), fs.constants.R_OK | fs.constants.W_OK);
dataDirectory = true;
} catch {
dataDirectory = false;
}
try {
/*
MediaMTX already exposes metrics only on loopback, so it is also the
smallest reliable readiness probe. Reading the response closes the body
before this request completes and avoids accumulating idle connections
across Docker's recurring health checks.
*/
const response = await fetch(MEDIAMTX_HEALTH_URL, {
signal: AbortSignal.timeout(MEDIAMTX_HEALTH_TIMEOUT_MS),
});
await response.text();
mediaMtx = response.ok;
} catch {
mediaMtx = false;
}
return {
healthy: dataDirectory && mediaMtx,
checks: {
dataDirectory,
mediaMtx,
},
};
}
module.exports = {
getContainerHealth,
getHealthSnapshot,
};