{ config, pkgs, lib, ... }: let arrConfig = { radarr = { exportarrPort = "9707"; url = "localhost:7878"; apiKey = config.secrets.radarrApiKey.dest; }; sonarr = { exportarrPort = "9708"; url = "localhost:8989"; apiKey = config.secrets.sonarrApiKey.dest; }; prowlarr = { exportarrPort = "9709"; url = "localhost:9696"; apiKey = config.secrets.prowlarrApiKey.dest; }; sabnzbd = { exportarrPort = "9710"; url = "localhost:8085"; apiKey = config.secrets.sabnzbdApiKey.dest; }; }; in { options = { arrs.enable = lib.mkEnableOption "Arr services"; }; config = lib.mkIf config.arrs.enable { services = { bazarr = { enable = true; group = "media"; }; jellyseerr.enable = true; prowlarr.enable = true; sabnzbd = { enable = true; group = "media"; configFile = "/data/downloads/sabnzbd/sabnzbd.ini"; }; sonarr = { enable = true; group = "media"; }; radarr = { enable = true; group = "media"; }; }; users.groups.media = { }; users.users.${config.user}.extraGroups = [ "media" ]; users.users.sabnzbd.homeMode = "0770"; unfreePackages = [ "unrar" ]; # Required for sabnzbd # Requires updating the base_url config value in each service # If you try to rewrite the URL, the service won't redirect properly caddy.routes = [ { group = "download"; match = [{ host = [ config.hostnames.download ]; path = [ "/sonarr*" ]; }]; handle = [{ handler = "reverse_proxy"; upstreams = [{ dial = arrConfig.sonarr.url; }]; }]; } { group = "download"; match = [{ host = [ config.hostnames.download ]; path = [ "/radarr*" ]; }]; handle = [{ handler = "reverse_proxy"; upstreams = [{ dial = arrConfig.radarr.url; }]; }]; } { group = "download"; match = [{ host = [ config.hostnames.download ]; path = [ "/prowlarr*" ]; }]; handle = [{ handler = "reverse_proxy"; upstreams = [{ dial = "localhost:9696"; }]; }]; } { group = "download"; match = [{ host = [ config.hostnames.download ]; path = [ "/bazarr*" ]; }]; handle = [{ handler = "reverse_proxy"; upstreams = [{ dial = "localhost:${ builtins.toString config.services.bazarr.listenPort }"; }]; }]; } { group = "download"; match = [{ host = [ config.hostnames.download ]; path = [ "/sabnzbd*" ]; }]; handle = [{ handler = "reverse_proxy"; upstreams = [{ dial = arrConfig.sabnzbd.url; }]; }]; } { group = "download"; match = [{ host = [ config.hostnames.download ]; }]; handle = [{ handler = "reverse_proxy"; upstreams = [{ dial = "localhost:${builtins.toString config.services.jellyseerr.port}"; }]; }]; } ]; # Enable Prometheus exporters systemd.services = lib.mapAttrs' (name: attrs: { name = "prometheus-${name}-exporter"; value = { description = "Export Prometheus metrics for ${name}"; after = [ "network.target" ]; wantedBy = [ "${name}.service" ]; serviceConfig = { Type = "simple"; DynamicUser = true; ExecStart = let url = if name != "sabnzbd" then "http://${attrs.url}/${name}" else "http://${attrs.url}"; in '' ${pkgs.exportarr}/bin/exportarr ${name} \ --url ${url} \ --port ${attrs.exportarrPort}''; EnvironmentFile = lib.mkIf (builtins.hasAttr "apiKey" attrs) attrs.apiKey; Restart = "on-failure"; ProtectHome = true; ProtectSystem = "strict"; PrivateTmp = true; PrivateDevices = true; ProtectHostname = true; ProtectClock = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; NoNewPrivileges = true; RestrictRealtime = true; RestrictSUIDSGID = true; RemoveIPC = true; PrivateMounts = true; }; }; }) arrConfig; # Secrets for Prometheus exporters secrets.radarrApiKey = { source = ../../../private/radarr-api-key.age; dest = "/var/private/radarr-api"; prefix = "API_KEY="; }; secrets.sonarrApiKey = { source = ../../../private/sonarr-api-key.age; dest = "/var/private/sonarr-api"; prefix = "API_KEY="; }; secrets.prowlarrApiKey = { source = ../../../private/prowlarr-api-key.age; dest = "/var/private/prowlarr-api"; prefix = "API_KEY="; }; secrets.sabnzbdApiKey = { source = ../../../private/sabnzbd-api-key.age; dest = "/var/private/sabnzbd-api"; prefix = "API_KEY="; }; # Prometheus scrape targets prometheus.scrapeTargets = map (key: "127.0.0.1:${ lib.attrsets.getAttrFromPath [ key "exportarrPort" ] arrConfig }") (builtins.attrNames arrConfig); }; }