homelab/modules/homelab/services/gatus.nix
plasmagoat a955528e44
Some checks failed
Test / tests (push) Failing after 1m51s
/ OpenTofu (push) Successful in 13s
another refactor partly done
2025-07-29 02:18:19 +02:00

267 lines
6.9 KiB
Nix

{
config,
lib,
...
}:
with lib; let
serviceName = "gatus";
cfg = config.homelab.services.${serviceName};
homelabCfg = config.homelab;
# Convert homelab health checks to Gatus format
formatHealthCheck = check: let
# Build the URL based on the health check configuration
url = check._url or "http://${check.host}:${toString (check.port or 80)}${check.path}";
# Convert conditions to Gatus format (they should already be compatible)
conditions = check.conditions or ["[STATUS] == 200"];
# Convert alerts to Gatus format
alerts = map (alert: {
inherit (alert) type enabled;
failure-threshold = alert.failure-threshold or 3;
success-threshold = alert.success-threshold or 2;
description = "Health check alert for ${check.name}";
}) (check.alerts or []);
in {
name = check.name;
group = check.group or "default";
url = url;
interval = check.interval or "30s";
# Add method and headers for HTTP/HTTPS checks
method =
if (check.protocol == "http" || check.protocol == "https")
then check.method or "GET"
else null;
conditions = conditions;
# Add timeout
client = {
timeout = check.timeout or "10s";
};
# Add alerts if configured
alerts =
if alerts != []
then alerts
else [];
# Add labels for UI organization
ui = {
hide-hostname = false;
hide-url = false;
description = "Health check for ${check.name} on ${check.host or check._actualHost or "unknown"}";
};
};
# Generate Gatus configuration from aggregated health checks
gatusConfig =
recursiveUpdate {
# Global Gatus settings
alerting = mkIf (cfg.alerting != {}) cfg.alerting;
web = {
address = cfg.web.address;
port = cfg.port;
};
# Enable metrics
metrics = cfg.monitoring.enable;
ui = {
title = cfg.ui.title;
header = cfg.ui.header;
link = cfg.ui.link;
buttons = cfg.ui.buttons;
};
storage = cfg.storage;
# Convert all enabled health checks from the fleet to Gatus endpoints
endpoints = let
# Get all health checks - try global first, fallback to local
allHealthChecks = homelabCfg.monitoring.global.allHealthChecks
or homelabCfg.monitoring.allHealthChecks
or [];
# Filter only enabled health checks
enabledHealthChecks = filter (check: check.enabled or true) allHealthChecks;
# Convert to Gatus format
gatusEndpoints = map formatHealthCheck enabledHealthChecks;
in
gatusEndpoints;
}
cfg.extraConfig;
in {
imports = [
(import ../lib/features/monitoring.nix serviceName)
(import ../lib/features/logging.nix serviceName)
(import ../lib/features/proxy.nix serviceName)
];
# Core service options
options.homelab.services.${serviceName} = {
enable = mkEnableOption "Gatus Status Page";
port = mkOption {
type = types.port;
default = 8080;
};
description = mkOption {
type = types.str;
default = "Gatus Status Page";
};
# Gatus-specific options
ui = {
title = mkOption {
type = types.str;
default = "Homelab Status";
description = "Title for the Gatus web interface";
};
header = mkOption {
type = types.str;
default = "Homelab Services Status";
description = "Header text for the Gatus interface";
};
link = mkOption {
type = types.str;
default = "https://status.${homelabCfg.externalDomain}";
description = "Link in the Gatus header";
};
buttons = mkOption {
type = types.listOf (types.submodule {
options = {
name = mkOption {type = types.str;};
link = mkOption {type = types.str;};
};
});
default = [
{
name = "Grafana";
link = "https://grafana.${homelabCfg.externalDomain}";
}
{
name = "Prometheus";
link = "https://prometheus.${homelabCfg.externalDomain}";
}
];
description = "Navigation buttons in the Gatus interface";
};
};
alerting = mkOption {
type = types.attrs;
default = {};
description = "Gatus alerting configuration";
example = literalExpression ''
{
discord = {
webhook-url = "https://discord.com/api/webhooks/...";
default-alert = {
enabled = true;
description = "Health check failed";
failure-threshold = 3;
success-threshold = 2;
};
};
}
'';
};
storage = mkOption {
type = types.attrs;
default = {
type = "memory";
};
description = "Gatus storage configuration";
example = literalExpression ''
{
type = "postgres";
path = "postgres://user:password@localhost/gatus?sslmode=disable";
}
'';
};
web = {
address = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Web interface bind address";
};
};
extraConfig = mkOption {
type = types.attrs;
default = {};
description = "Additional Gatus configuration options";
};
};
# Service configuration with smart defaults
config = mkIf cfg.enable (mkMerge [
# Core Gatus service
{
services.gatus = {
enable = true;
settings = gatusConfig;
};
networking.firewall.allowedTCPPorts = [cfg.port];
homelab.services.${serviceName}.monitoring.enable = mkDefault true;
}
# Smart defaults for Gatus
(mkIf cfg.monitoring.enable {
homelab.services.${serviceName}.monitoring = mkDefault {
metrics = {
path = "/metrics";
extraEndpoints = [];
};
healthCheck = {
path = "/health";
conditions = [
"[STATUS] == 200"
"[BODY].status == UP"
"[RESPONSE_TIME] < 1000"
];
extraChecks = [];
};
extraLabels = {
component = "status-monitoring";
tier = "monitoring";
};
};
})
(mkIf cfg.logging.enable {
homelab.services.${serviceName}.logging = mkDefault {
files = ["/var/log/gatus/gatus.log"];
parsing = {
# Gatus log format: 2024-01-01T12:00:00Z [INFO] message
regex = "^(?P<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z) \\[(?P<level>\\w+)\\] (?P<message>.*)";
extractFields = ["level"];
};
extraLabels = {
component = "status-monitoring";
application = "gatus";
};
};
})
(mkIf cfg.proxy.enable {
homelab.services.${serviceName}.proxy = mkDefault {
subdomain = "status";
enableAuth = false; # Status page should be public
};
})
]);
}