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:
@@ -17,6 +17,7 @@ const credentialManager = require('./credential-manager');
|
||||
const { CACHE_CONFIGS, createCache } = require('./cache-config');
|
||||
const { AppError } = require('./errors');
|
||||
const { validateConfig } = require('./config-schema');
|
||||
const { resolveServiceUrl } = require('./url-resolver');
|
||||
const {
|
||||
APP, APP_PORTS, ARR_SERVICES, TIMEOUTS, RETRIES,
|
||||
SESSION_TTL, RATE_LIMITS, CADDY, PLEX, LIMITS,
|
||||
@@ -50,8 +51,10 @@ const backupManager = require('./backup-manager');
|
||||
const healthChecker = require('./health-checker');
|
||||
const updateManager = require('./update-manager');
|
||||
const selfUpdater = require('./self-updater');
|
||||
const dockerMaintenance = require('./docker-maintenance');
|
||||
const logDigest = require('./log-digest');
|
||||
let dockerMaintenance;
|
||||
try { dockerMaintenance = require('./docker-maintenance'); } catch (_) { console.warn('[WARN] docker-maintenance module not found, skipped'); }
|
||||
let logDigest;
|
||||
try { logDigest = require('./log-digest'); } catch (_) { console.warn('[WARN] log-digest module not found, skipped'); }
|
||||
const StateManager = require('./state-manager');
|
||||
const auditLogger = require('./audit-logger');
|
||||
const portLockManager = require('./port-lock-manager');
|
||||
@@ -1173,6 +1176,7 @@ Object.assign(ctx, {
|
||||
loadDnsCredentials: () => {},
|
||||
SERVICES_FILE, CONFIG_FILE, TOTP_CONFIG_FILE, TAILSCALE_CONFIG_FILE,
|
||||
NOTIFICATIONS_FILE, ERROR_LOG_FILE,
|
||||
resyncHealthChecker: () => syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildServiceUrl, siteConfig, APP }),
|
||||
});
|
||||
|
||||
// Build versioned API router — all route modules attach here
|
||||
@@ -1225,30 +1229,16 @@ app.get('/probe/:id', asyncHandler(async (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
try {
|
||||
let url;
|
||||
|
||||
if (id === 'internet') {
|
||||
// Internet connectivity check
|
||||
url = 'https://www.google.com';
|
||||
} else {
|
||||
// Look up service in services.json for custom URLs
|
||||
let service = null;
|
||||
if (await exists(SERVICES_FILE)) {
|
||||
const data = await servicesStateManager.read();
|
||||
const services = Array.isArray(data) ? data : data.services || [];
|
||||
service = services.find(s => s.id === id);
|
||||
}
|
||||
|
||||
if (service?.isExternal && service?.externalUrl) {
|
||||
url = service.externalUrl;
|
||||
} else if (service?.url) {
|
||||
url = service.url.startsWith('http') ? service.url : `https://${service.url}`;
|
||||
} else {
|
||||
// Build URL from configured routing mode (subdomain or subdirectory)
|
||||
url = buildServiceUrl(id);
|
||||
}
|
||||
// Look up service in services.json
|
||||
let service = null;
|
||||
if (id !== 'internet' && await exists(SERVICES_FILE)) {
|
||||
const data = await servicesStateManager.read();
|
||||
const services = Array.isArray(data) ? data : data.services || [];
|
||||
service = services.find(s => s.id === id);
|
||||
}
|
||||
|
||||
const url = resolveServiceUrl(id, service, siteConfig, buildServiceUrl);
|
||||
|
||||
const parsed = new URL(url);
|
||||
const isHttps = parsed.protocol === 'https:';
|
||||
const lib = isHttps ? https : http;
|
||||
@@ -1734,6 +1724,8 @@ async function addServiceToConfig(service) {
|
||||
return services;
|
||||
});
|
||||
log.info('deploy', 'Service added to config', { serviceId: service.id });
|
||||
// Sync health checker with updated services list
|
||||
ctx.resyncHealthChecker?.().catch(() => {});
|
||||
} catch (error) {
|
||||
log.error('deploy', 'Failed to add service to config', { serviceId: service.id, error: error.message });
|
||||
throw error;
|
||||
@@ -1852,7 +1844,7 @@ const server = app.listen(PORT, '0.0.0.0', () => {
|
||||
(async () => {
|
||||
try {
|
||||
// Auto-configure health checker from services.json
|
||||
await syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildDomain, APP });
|
||||
await syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildServiceUrl, siteConfig, APP });
|
||||
healthChecker.start();
|
||||
log.info('server', 'Health checker started');
|
||||
} catch (error) {
|
||||
@@ -1887,37 +1879,41 @@ const server = app.listen(PORT, '0.0.0.0', () => {
|
||||
log.error('server', 'Self-updater failed to start', { error: error.message });
|
||||
}
|
||||
|
||||
try {
|
||||
dockerMaintenance.start();
|
||||
log.info('server', 'Docker maintenance started');
|
||||
dockerMaintenance.on('maintenance-complete', (result) => {
|
||||
const saved = Math.round(result.spaceReclaimed.total / 1024 / 1024);
|
||||
if (saved > 0 || result.warnings.length > 0) {
|
||||
log.info('maintenance', 'Docker maintenance completed', {
|
||||
spaceReclaimedMB: saved,
|
||||
pruned: result.pruned,
|
||||
warnings: result.warnings.length
|
||||
});
|
||||
}
|
||||
if (result.warnings.length > 0) {
|
||||
for (const w of result.warnings) log.warn('maintenance', w);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error('server', 'Docker maintenance failed to start', { error: error.message });
|
||||
if (dockerMaintenance) {
|
||||
try {
|
||||
dockerMaintenance.start();
|
||||
log.info('server', 'Docker maintenance started');
|
||||
dockerMaintenance.on('maintenance-complete', (result) => {
|
||||
const saved = Math.round(result.spaceReclaimed.total / 1024 / 1024);
|
||||
if (saved > 0 || result.warnings.length > 0) {
|
||||
log.info('maintenance', 'Docker maintenance completed', {
|
||||
spaceReclaimedMB: saved,
|
||||
pruned: result.pruned,
|
||||
warnings: result.warnings.length
|
||||
});
|
||||
}
|
||||
if (result.warnings.length > 0) {
|
||||
for (const w of result.warnings) log.warn('maintenance', w);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error('server', 'Docker maintenance failed to start', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
logDigest.start(platformPaths.digestDir);
|
||||
log.info('server', 'Log digest started', { digestDir: platformPaths.digestDir });
|
||||
logDigest.on('digest-generated', ({ date }) => {
|
||||
log.info('digest', `Daily digest generated for ${date}`);
|
||||
if (typeof ctx.notification?.send === 'function') {
|
||||
ctx.notification.send('system.digest', 'Daily Log Digest', `Log digest for ${date} is ready. View it in the DashCaddy dashboard.`, 'info');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error('server', 'Log digest failed to start', { error: error.message });
|
||||
if (logDigest) {
|
||||
try {
|
||||
logDigest.start(platformPaths.digestDir);
|
||||
log.info('server', 'Log digest started', { digestDir: platformPaths.digestDir });
|
||||
logDigest.on('digest-generated', ({ date }) => {
|
||||
log.info('digest', `Daily digest generated for ${date}`);
|
||||
if (typeof ctx.notification?.send === 'function') {
|
||||
ctx.notification.send('system.digest', 'Daily Log Digest', `Log digest for ${date} is ready. View it in the DashCaddy dashboard.`, 'info');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error('server', 'Log digest failed to start', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Tailscale API sync (if OAuth configured)
|
||||
@@ -1935,8 +1931,8 @@ function shutdown(signal) {
|
||||
log.info('shutdown', `${signal} received, draining connections...`);
|
||||
resourceMonitor.stop();
|
||||
backupManager.stop();
|
||||
dockerMaintenance.stop();
|
||||
logDigest.stop();
|
||||
if (dockerMaintenance) dockerMaintenance.stop();
|
||||
if (logDigest) logDigest.stop();
|
||||
healthChecker.stop();
|
||||
updateManager.stop();
|
||||
selfUpdater.stop();
|
||||
|
||||
Reference in New Issue
Block a user