Compare commits

..

2 Commits

Author SHA1 Message Date
8ea96c8b8e llama-cpp: fix model hash
All checks were successful
Build and Deploy / deploy (push) Successful in 2m36s
2026-04-04 00:28:07 -04:00
3f62b9c88e grafana: replace custom metric collectors with community exporters
Replace three custom Prometheus textfile collector scripts with
dedicated community-maintained exporters:

- jellyfin-collector.nix (25 LoC shell) -> rebelcore/jellyfin_exporter
  Metric: jellyfin_active_streams -> count(jellyfin_now_playing_state)
  Bonus: per-session labels (user, title, device, codec info)

- qbittorrent-collector.nix (40 LoC shell) -> anriha/qbittorrent-metrics-exporter
  Metric: qbittorrent_{download,upload}_bytes_per_second -> qbit_{dl,up}speed
  Bonus: per-torrent metrics with category/tag aggregation

- intel-gpu-collector.nix + .py (130 LoC Python) -> mike1808/igpu-exporter
  Metric: intel_gpu_engine_busy_percent -> igpu_engines_busy_percent
  Bonus: persistent daemon vs oneshot timer, no streaming JSON parser

All three run as persistent daemons scraped by Prometheus, replacing
the textfile-collector pattern of systemd timers writing .prom files.
Dashboard PromQL queries updated to match new metric names.
2026-04-03 15:38:13 -04:00
3 changed files with 69 additions and 61 deletions

View File

@@ -9,4 +9,4 @@
./llama-cpp-annotations.nix ./llama-cpp-annotations.nix
./zfs-scrub-annotations.nix ./zfs-scrub-annotations.nix
]; ];
} }

View File

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

View File

@@ -16,7 +16,7 @@ in
model = toString ( model = toString (
pkgs.fetchurl { pkgs.fetchurl {
url = "https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-Q4_K_M.gguf"; url = "https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-Q4_K_M.gguf";
sha256 = "ced37f54b80068fe65e95c6dd79ac88cddc227e179fd1040b8f751b1e5bdf849"; sha256 = "sha256-4bxEJwn+eAqksuybIsFqf83/VC8X8B7Q4yAxFNKPnzQ=";
} }
); );
port = service_configs.ports.private.llama_cpp.port; port = service_configs.ports.private.llama_cpp.port;