Files
caddy-proxy-manager/src/instrumentation.ts
T
fuomag9 8be69d2774 feat: add analytics dashboard with traffic monitoring
- Parse Caddy access logs every 30s into traffic_events SQLite table
- GeoIP country lookup via maxmind (GeoLite2-Country.mmdb)
- 90-day retention with automatic purge
- Analytics page with interval (24h/7d/30d) and per-host filtering:
  - Stats cards: total requests, unique IPs, blocked count, block rate
  - Requests-over-time area chart (ApexCharts)
  - SVG world choropleth map (d3-geo + topojson-client, React 19 compatible)
  - Top countries table with flag emojis
  - HTTP protocol donut chart
  - Top user agents horizontal bar chart
  - Recent blocked requests table with pagination
- Traffic (24h) summary card on Overview page linking to analytics
- 7 authenticated API routes under /api/analytics/
- Share caddy-logs volume with web container (read-only)
- group_add caddy GID to web container for log file read access

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 20:43:23 +01:00

96 lines
3.5 KiB
TypeScript

/**
* Next.js instrumentation hook - runs once when the server starts
* https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
*/
export async function register() {
// Only run on the server side
if (process.env.NEXT_RUNTIME === "nodejs") {
// Validate production configuration early to catch misconfigurations
const { validateProductionConfig } = await import("./lib/config");
try {
validateProductionConfig();
} catch (error) {
console.error("Configuration validation failed:", error);
if (process.env.NODE_ENV === "production") {
// Fail fast in production with bad config
throw error;
}
}
const { ensureAdminUser } = await import("./lib/init-db");
try {
await ensureAdminUser();
console.log("Database initialization complete");
} catch (error) {
console.error("Failed to initialize database:", error);
// Don't throw - let the app start anyway, errors will surface when users try to use features
}
// Apply Caddy configuration from database on startup
const { applyCaddyConfig } = await import("./lib/caddy");
try {
console.log("Applying Caddy configuration from database...");
await applyCaddyConfig();
console.log("Caddy configuration applied successfully");
} catch (error) {
console.error("Failed to apply Caddy configuration on startup:", error);
// Don't throw - Caddy might not be ready yet, or config might be applied later
// This ensures proxy hosts work after container restart
}
// Start Caddy health monitoring to detect restarts and auto-reapply config
const { startCaddyMonitoring } = await import("./lib/caddy-monitor");
try {
startCaddyMonitoring();
console.log("Caddy health monitoring started");
} catch (error) {
console.error("Failed to start Caddy health monitoring:", error);
// Don't throw - monitoring is a nice-to-have feature
}
// Start log parser for analytics
const { initLogParser, parseNewLogEntries, stopLogParser } = await import("./lib/log-parser");
try {
await initLogParser();
const logParserInterval = setInterval(async () => {
try {
await parseNewLogEntries();
} catch (err) {
console.error("Log parser interval error:", err);
}
}, 30_000);
process.on("SIGTERM", () => {
stopLogParser();
clearInterval(logParserInterval);
});
console.log("Log parser started");
} catch (error) {
console.error("Failed to start log parser:", error);
}
// Start periodic instance sync if configured (master mode only)
const { getInstanceMode, getSyncIntervalMs, syncInstances } = await import("./lib/instance-sync");
try {
const mode = await getInstanceMode();
const intervalMs = getSyncIntervalMs();
if (mode === "master" && intervalMs > 0) {
console.log(`Starting periodic instance sync (every ${intervalMs / 1000}s)`);
setInterval(async () => {
try {
const result = await syncInstances();
if (result.total > 0) {
console.log(`Periodic sync completed: ${result.success}/${result.total} succeeded`);
}
} catch (error) {
console.error("Periodic sync failed:", error);
}
}, intervalMs);
}
} catch (error) {
console.error("Failed to start periodic instance sync:", error);
// Don't throw - periodic sync is optional
}
}
}