From 22282691e71c6fe940f516682b5dde99112fedd7 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Thu, 23 Apr 2026 01:17:10 -0400 Subject: [PATCH] grafana: add minecraft server stats --- hosts/muffin/service-configs.nix | 4 + lib/overlays.nix | 15 +++ services/grafana/dashboard.nix | 182 +++++++++++++++++++++++++++++++ services/grafana/exporters.nix | 44 ++++++++ services/grafana/prometheus.nix | 6 + 5 files changed, 251 insertions(+) diff --git a/hosts/muffin/service-configs.nix b/hosts/muffin/service-configs.nix index e8caa00..037f3d7 100644 --- a/hosts/muffin/service-configs.nix +++ b/hosts/muffin/service-configs.nix @@ -196,6 +196,10 @@ rec { port = 9563; proto = "tcp"; }; + minecraft_exporter = { + port = 9567; + proto = "tcp"; + }; prometheus_zfs = { port = 9134; proto = "tcp"; diff --git a/lib/overlays.nix b/lib/overlays.nix index 5f8ee0e..e8820d0 100644 --- a/lib/overlays.nix +++ b/lib/overlays.nix @@ -75,4 +75,19 @@ final: prev: { ''; meta.mainProgram = "igpu-exporter"; }; + + mc-monitor = prev.buildGoModule rec { + pname = "mc-monitor"; + version = "0.16.1"; + src = prev.fetchFromGitHub { + owner = "itzg"; + repo = "mc-monitor"; + rev = version; + hash = "sha256-/94+Z9FTFOzQHynHiJuaGFiidkOxmM0g/FIpHn+xvJM="; + }; + vendorHash = "sha256-qq7rIpvGRi3AMnBbi8uAhiPcfSF4McIuqozdtxB5CeQ="; + # upstream tests probe live Minecraft servers + doCheck = false; + meta.mainProgram = "mc-monitor"; + }; } diff --git a/services/grafana/dashboard.nix b/services/grafana/dashboard.nix index 60f68dd..ca10e0e 100644 --- a/services/grafana/dashboard.nix +++ b/services/grafana/dashboard.nix @@ -687,6 +687,188 @@ let overrides = [ ]; }; } + + # -- Row 6: Minecraft -- + { + id = 14; + type = "stat"; + title = "Minecraft Players"; + gridPos = { + h = 8; + w = 6; + x = 0; + y = 40; + }; + datasource = promDs; + targets = [ + { + datasource = promDs; + expr = "sum(minecraft_status_players_online_count) or vector(0)"; + refId = "A"; + } + ]; + fieldConfig = { + defaults = { + thresholds = { + mode = "absolute"; + steps = [ + { + color = "green"; + value = null; + } + { + color = "yellow"; + value = 3; + } + { + color = "red"; + value = 6; + } + ]; + }; + }; + overrides = [ ]; + }; + options = { + reduceOptions = { + calcs = [ "lastNotNull" ]; + fields = ""; + values = false; + }; + colorMode = "value"; + graphMode = "area"; + }; + } + { + id = 15; + type = "stat"; + title = "Minecraft Server"; + gridPos = { + h = 8; + w = 6; + x = 6; + y = 40; + }; + datasource = promDs; + targets = [ + { + datasource = promDs; + expr = "max(minecraft_status_healthy) or vector(0)"; + refId = "A"; + } + ]; + fieldConfig = { + defaults = { + mappings = [ + { + type = "value"; + options = { + "0" = { + text = "Offline"; + color = "red"; + index = 0; + }; + "1" = { + text = "Online"; + color = "green"; + index = 1; + }; + }; + } + ]; + thresholds = { + mode = "absolute"; + steps = [ + { + color = "red"; + value = null; + } + { + color = "green"; + value = 1; + } + ]; + }; + }; + overrides = [ ]; + }; + options = { + reduceOptions = { + calcs = [ "lastNotNull" ]; + fields = ""; + values = false; + }; + colorMode = "value"; + graphMode = "none"; + }; + } + { + id = 16; + type = "timeseries"; + title = "Minecraft Player Activity"; + gridPos = { + h = 8; + w = 12; + x = 12; + y = 40; + }; + datasource = promDs; + targets = [ + { + datasource = promDs; + expr = "sum(minecraft_status_players_online_count) or vector(0)"; + legendFormat = "Online players"; + refId = "A"; + } + { + datasource = promDs; + expr = "max(minecraft_status_players_max_count) or vector(0)"; + legendFormat = "Max players"; + refId = "B"; + } + ]; + fieldConfig = { + defaults = { + unit = "short"; + min = 0; + decimals = 0; + color.mode = "palette-classic"; + custom = { + lineWidth = 2; + fillOpacity = 15; + spanNulls = true; + }; + }; + overrides = [ + { + matcher = { + id = "byFrameRefID"; + options = "B"; + }; + properties = [ + { + id = "custom.lineStyle"; + value = { + fill = "dash"; + dash = [ + 8 + 4 + ]; + }; + } + { + id = "custom.fillOpacity"; + value = 0; + } + { + id = "custom.lineWidth"; + value = 1; + } + ]; + } + ]; + }; + } ]; }; in diff --git a/services/grafana/exporters.nix b/services/grafana/exporters.nix index 96e4224..a93a0d1 100644 --- a/services/grafana/exporters.nix +++ b/services/grafana/exporters.nix @@ -10,6 +10,9 @@ let jellyfinExporterPort = service_configs.ports.private.jellyfin_exporter.port; qbitExporterPort = service_configs.ports.private.qbittorrent_exporter.port; igpuExporterPort = service_configs.ports.private.igpu_exporter.port; + minecraftExporterPort = service_configs.ports.private.minecraft_exporter.port; + minecraftServerName = service_configs.minecraft.server_name; + minecraftServerPort = service_configs.ports.public.minecraft.port; in { # -- Jellyfin Prometheus Exporter -- @@ -109,4 +112,45 @@ in REFRESH_PERIOD_MS = "30000"; }; }; + + # -- Minecraft Prometheus Exporter -- + # itzg/mc-monitor queries the local server via SLP on each scrape and exposes + # minecraft_status_{healthy,response_time_seconds,players_online_count,players_max_count}. + # mc-monitor binds to 0.0.0.0 (no listen-address flag); the firewall keeps + # 9567 internal and IPAddressAllow pins the socket to loopback as defense-in-depth. + systemd.services.minecraft-exporter = + lib.mkIf (config.services.grafana.enable && config.services.minecraft-servers.enable) + { + description = "Prometheus exporter for Minecraft (mc-monitor SLP)"; + after = [ + "network.target" + "minecraft-server-${minecraftServerName}.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${lib.getExe pkgs.mc-monitor} export-for-prometheus"; + Restart = "on-failure"; + RestartSec = "10s"; + DynamicUser = true; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + MemoryDenyWriteExecute = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + IPAddressAllow = [ + "127.0.0.0/8" + "::1/128" + ]; + IPAddressDeny = "any"; + }; + environment = { + EXPORT_SERVERS = "127.0.0.1:${toString minecraftServerPort}"; + EXPORT_PORT = toString minecraftExporterPort; + TIMEOUT = "5s"; + }; + }; } diff --git a/services/grafana/prometheus.nix b/services/grafana/prometheus.nix index 634de9b..acf5b87 100644 --- a/services/grafana/prometheus.nix +++ b/services/grafana/prometheus.nix @@ -95,6 +95,12 @@ in { targets = [ "127.0.0.1:${toString service_configs.ports.private.igpu_exporter.port}" ]; } ]; } + { + job_name = "minecraft"; + static_configs = [ + { targets = [ "127.0.0.1:${toString service_configs.ports.private.minecraft_exporter.port}" ]; } + ]; + } { job_name = "zfs"; static_configs = [