{ config, pkgs, inputs, service_configs, lib, ... }: 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 -- # Replaces custom jellyfin-collector.nix textfile timer. # Exposes per-session metrics (jellyfin_now_playing_state) and library stats. systemd.services.jellyfin-exporter = lib.mkIf (config.services.grafana.enable && config.services.jellyfin.enable) { description = "Prometheus exporter for Jellyfin"; after = [ "network.target" "jellyfin.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = lib.getExe ( pkgs.writeShellApplication { name = "jellyfin-exporter-wrapper"; runtimeInputs = [ pkgs.jellyfin-exporter ]; text = '' exec jellyfin_exporter \ --jellyfin.address=http://127.0.0.1:${toString service_configs.ports.private.jellyfin.port} \ --jellyfin.token="$(cat "$CREDENTIALS_DIRECTORY/jellyfin-api-key")" \ --web.listen-address=127.0.0.1:${toString jellyfinExporterPort} ''; } ); Restart = "on-failure"; RestartSec = "10s"; DynamicUser = true; NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; MemoryDenyWriteExecute = true; LoadCredential = "jellyfin-api-key:${config.age.secrets.jellyfin-api-key.path}"; }; }; # -- qBittorrent Prometheus Exporter -- # Replaces custom qbittorrent-collector.nix textfile timer. # Exposes per-torrent metrics (qbit_dlspeed, qbit_upspeed) and aggregate stats. # qBittorrent runs in a VPN namespace; the exporter reaches it via namespace address. systemd.services.qbittorrent-exporter = lib.mkIf (config.services.grafana.enable && config.services.qbittorrent.enable) { description = "Prometheus exporter for qBittorrent"; after = [ "network.target" "qbittorrent.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = lib.getExe' inputs.qbittorrent-metrics-exporter.packages.${pkgs.system}.default "qbittorrent-metrics-exporter"; Restart = "on-failure"; RestartSec = "10s"; DynamicUser = true; NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; }; environment = { HOST = "127.0.0.1"; PORT = toString qbitExporterPort; SCRAPE_INTERVAL = "15"; BACKEND = "in-memory"; # qBittorrent has AuthSubnetWhitelist=0.0.0.0/0, so no real password needed. # The exporter still expects the env var to be set. QBITTORRENT_PASSWORD = "unused"; QBITTORRENT_USERNAME = "admin"; TORRENT_HOSTS = "qbit:main=http://${config.vpnNamespaces.wg.namespaceAddress}:${toString config.services.qbittorrent.webuiPort}|http://${config.vpnNamespaces.wg.namespaceAddress}:${toString config.services.qbittorrent.webuiPort}"; RUST_LOG = "warn"; }; }; # -- Intel GPU Prometheus Exporter -- # Replaces custom intel-gpu-collector.nix + intel-gpu-collector.py textfile timer. # Exposes engine busy%, frequency, and RC6 metrics via /metrics. # Requires privileged access to GPU debug interfaces (intel_gpu_top). systemd.services.igpu-exporter = lib.mkIf config.services.grafana.enable { description = "Prometheus exporter for Intel integrated GPU"; wantedBy = [ "multi-user.target" ]; path = [ pkgs.intel-gpu-tools ]; serviceConfig = { ExecStart = lib.getExe pkgs.igpu-exporter; Restart = "on-failure"; RestartSec = "10s"; # intel_gpu_top requires root-level access to GPU debug interfaces ProtectHome = true; PrivateTmp = true; }; environment = { PORT = toString igpuExporterPort; 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"; }; }; }