Unify URL resolution, add health checker sync, and make modules optional
- Add url-resolver.js with single resolveServiceUrl() used by all 5 consumers (probes, health routes, health checker auto-config) - Health checker now does full sync (add/update/remove) instead of add-only, and re-syncs automatically after every services.json mutation - docker-maintenance and log-digest are now optional imports with try/catch, preventing container crashes when these files are absent - Add null guards in routes/logs.js for graceful 503 responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ const path = require('path');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const { exists, isAccessible } = require('./fs-helpers');
|
||||
const { resolveServiceUrl } = require('./url-resolver');
|
||||
|
||||
/**
|
||||
* Validate startup configuration and environment.
|
||||
@@ -146,17 +147,19 @@ async function validateStartupConfig({ log, CADDYFILE_PATH, SERVICES_FILE, CONFI
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-configure health checker from services.json + top-card services.
|
||||
* Full-sync health checker from services.json + top-card services.
|
||||
* Adds missing, updates changed URLs, removes deleted services.
|
||||
*
|
||||
* @param {Object} deps
|
||||
* @param {Function} deps.log - structured logger
|
||||
* @param {string} deps.SERVICES_FILE
|
||||
* @param {Object} deps.servicesStateManager - StateManager instance
|
||||
* @param {Object} deps.healthChecker - health checker module
|
||||
* @param {Function} deps.buildDomain - builds domain from subdomain
|
||||
* @param {Function} deps.buildServiceUrl - canonical URL builder
|
||||
* @param {Object} deps.siteConfig - site config (dnsServers, etc.)
|
||||
* @param {Object} deps.APP - app constants (USER_AGENTS)
|
||||
*/
|
||||
async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildDomain, APP }) {
|
||||
async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildServiceUrl, siteConfig, APP }) {
|
||||
try {
|
||||
const topCardServices = [
|
||||
{ id: 'dns1', name: 'DNS1' },
|
||||
@@ -164,6 +167,11 @@ async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateMana
|
||||
{ id: 'internet', name: 'Internet' },
|
||||
];
|
||||
|
||||
// Add dns3 if it exists in dnsServers config
|
||||
if (siteConfig?.dnsServers?.dns3) {
|
||||
topCardServices.push({ id: 'dns3', name: 'DNS3' });
|
||||
}
|
||||
|
||||
let appServices = [];
|
||||
if (await exists(SERVICES_FILE)) {
|
||||
const data = await servicesStateManager.read();
|
||||
@@ -171,38 +179,47 @@ async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateMana
|
||||
}
|
||||
|
||||
const allServices = [...topCardServices, ...appServices];
|
||||
const configuredIds = new Set(Object.keys(healthChecker.config.services || {}));
|
||||
let added = 0;
|
||||
const desiredIds = new Set();
|
||||
let added = 0, updated = 0, removed = 0;
|
||||
|
||||
for (const svc of allServices) {
|
||||
const id = svc.id || svc.name?.toLowerCase();
|
||||
if (!id || configuredIds.has(id)) continue;
|
||||
if (!id) continue;
|
||||
desiredIds.add(id);
|
||||
|
||||
let url;
|
||||
if (id === 'internet') {
|
||||
url = 'https://www.google.com';
|
||||
} else if (svc.isExternal && svc.externalUrl) {
|
||||
url = svc.externalUrl;
|
||||
} else {
|
||||
url = `https://${buildDomain(id)}`;
|
||||
const url = resolveServiceUrl(id, svc, siteConfig, buildServiceUrl);
|
||||
const existing = healthChecker.config.services?.[id];
|
||||
|
||||
if (!existing) {
|
||||
healthChecker.configureService(id, {
|
||||
name: svc.name || id,
|
||||
url,
|
||||
method: 'HEAD',
|
||||
timeout: 5000,
|
||||
expectedStatusCodes: [200, 201, 204, 301, 302, 303, 307, 308, 401, 403],
|
||||
headers: { 'User-Agent': APP.USER_AGENTS.HEALTH },
|
||||
});
|
||||
added++;
|
||||
} else if (existing.url !== url) {
|
||||
healthChecker.configureService(id, { ...existing, url });
|
||||
updated++;
|
||||
}
|
||||
|
||||
healthChecker.configureService(id, {
|
||||
name: svc.name || id,
|
||||
url,
|
||||
method: 'HEAD',
|
||||
timeout: 5000,
|
||||
expectedStatusCodes: [200, 201, 204, 301, 302, 303, 307, 308, 401, 403],
|
||||
headers: { 'User-Agent': APP.USER_AGENTS.HEALTH },
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added > 0) {
|
||||
log.info('health', 'Auto-configured services from services.json', { count: added });
|
||||
// Remove services no longer in the desired set
|
||||
const configuredIds = Object.keys(healthChecker.config.services || {});
|
||||
for (const id of configuredIds) {
|
||||
if (!desiredIds.has(id)) {
|
||||
healthChecker.removeService(id);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (added > 0 || updated > 0 || removed > 0) {
|
||||
log.info('health', 'Health checker synced', { added, updated, removed });
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('health', 'Error syncing services', { error: error.message });
|
||||
log.error('health', 'Error syncing health checker', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user