From 5548b9e280a5eabe5e8bf9c68a5cafbbc7ae89cc Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Wed, 18 Feb 2026 20:45:32 -0500 Subject: [PATCH] add recyclarr service for automated TRaSH Guides sync --- configuration.nix | 1 + flake.nix | 4 + services/qbittorrent.nix | 5 ++ services/recyclarr.nix | 158 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 services/recyclarr.nix diff --git a/configuration.nix b/configuration.nix index 80b212e..ef218c9 100644 --- a/configuration.nix +++ b/configuration.nix @@ -37,6 +37,7 @@ ./services/radarr.nix ./services/bazarr.nix ./services/jellyseerr.nix + ./services/recyclarr.nix ./services/soulseek.nix diff --git a/flake.nix b/flake.nix index c71e3fe..1ab9a59 100644 --- a/flake.nix +++ b/flake.nix @@ -219,6 +219,10 @@ configDir = services_dir + "/jellyseerr"; }; + recyclarr = { + dataDir = services_dir + "/recyclarr"; + }; + media = { moviesDir = torrents_path + "/media/movies"; tvDir = torrents_path + "/media/tv"; diff --git a/services/qbittorrent.nix b/services/qbittorrent.nix index c366222..b9d3e9b 100644 --- a/services/qbittorrent.nix +++ b/services/qbittorrent.nix @@ -86,6 +86,11 @@ # how many connections per sec ConnectionSpeed = 300; + # Automatic Torrent Management: use category save paths for new torrents + DisableAutoTMMByDefault = false; + DisableAutoTMMTriggers.CategorySavePathChanged = false; + DisableAutoTMMTriggers.DefaultSavePathChanged = false; + ChokingAlgorithm = "RateBased"; PieceExtentAffinity = true; SuggestMode = true; diff --git a/services/recyclarr.nix b/services/recyclarr.nix new file mode 100644 index 0000000..23412b3 --- /dev/null +++ b/services/recyclarr.nix @@ -0,0 +1,158 @@ +{ + pkgs, + config, + service_configs, + lib, + ... +}: +let + configTemplate = (pkgs.formats.yaml {}).generate "recyclarr.yml" { + radarr.movies = { + base_url = "http://localhost:${service_configs.ports.radarr}"; + api_key = "RADARR_API_KEY_PLACEHOLDER"; + + include = [ + { template = "radarr-quality-definition-movie"; } + { template = "radarr-quality-profile-remux-web-2160p"; } + { template = "radarr-custom-formats-remux-web-2160p"; } + ]; + + quality_profiles = [{ + name = "Remux + WEB 2160p"; + upgrade = { + allowed = true; + until_quality = "Remux-2160p"; + }; + qualities = [ + { name = "Remux-2160p"; } + { + name = "WEB 2160p"; + qualities = [ "WEBDL-2160p" "WEBRip-2160p" ]; + } + { name = "Remux-1080p"; } + { name = "Bluray-1080p"; } + { + name = "WEB 1080p"; + qualities = [ "WEBDL-1080p" "WEBRip-1080p" ]; + } + { name = "HDTV-1080p"; } + ]; + }]; + + custom_formats = [ + # Upscaled + { + trash_ids = [ "bfd8eb01832d646a0a89c4deb46f8564" ]; + assign_scores_to = [{ name = "Remux + WEB 2160p"; score = -10000; }]; + } + # x265 (HD) - override template -10000 penalty + { + trash_ids = [ "dc98083864ea246d05a42df0d05f81cc" ]; + assign_scores_to = [{ name = "Remux + WEB 2160p"; score = 0; }]; + } + # x265 (no HDR/DV) - override template -10000 penalty + { + trash_ids = [ "839bea857ed2c0a8e084f3cbdbd65ecb" ]; + assign_scores_to = [{ name = "Remux + WEB 2160p"; score = 0; }]; + } + ]; + }; + + sonarr.series = { + base_url = "http://localhost:${service_configs.ports.sonarr}"; + api_key = "SONARR_API_KEY_PLACEHOLDER"; + + include = [ + { template = "sonarr-quality-definition-series"; } + { template = "sonarr-v4-quality-profile-web-2160p"; } + { template = "sonarr-v4-custom-formats-web-2160p"; } + ]; + + quality_profiles = [{ + name = "WEB-2160p"; + upgrade = { + allowed = true; + until_quality = "WEB 2160p"; + }; + qualities = [ + { + name = "WEB 2160p"; + qualities = [ "WEBDL-2160p" "WEBRip-2160p" ]; + } + { name = "Bluray-1080p Remux"; } + { name = "Bluray-1080p"; } + { + name = "WEB 1080p"; + qualities = [ "WEBDL-1080p" "WEBRip-1080p" ]; + } + { name = "HDTV-1080p"; } + ]; + }]; + + custom_formats = [ + # Upscaled + { + trash_ids = [ "23297a736ca77c0fc8e70f8edd7ee56c" ]; + assign_scores_to = [{ name = "WEB-2160p"; score = -10000; }]; + } + # x265 (HD) - override template -10000 penalty + { + trash_ids = [ "47435ece6b99a0b477caf360e79ba0bb" ]; + assign_scores_to = [{ name = "WEB-2160p"; score = 0; }]; + } + # x265 (no HDR/DV) - override template -10000 penalty + { + trash_ids = [ "9b64dff695c2115facf1b6ea59c9bd07" ]; + assign_scores_to = [{ name = "WEB-2160p"; score = 0; }]; + } + ]; + }; + }; + + dataDir = service_configs.recyclarr.dataDir; + radarrConfig = "${service_configs.radarr.dataDir}/config.xml"; + sonarrConfig = "${service_configs.sonarr.dataDir}/config.xml"; + + # Script to inject API keys from sonarr/radarr config.xml at runtime + generateConfig = pkgs.writeShellScript "recyclarr-generate-config" '' + RADARR_KEY=$(${pkgs.gnugrep}/bin/grep -oP '(?<=)[^<]+' ${radarrConfig}) + SONARR_KEY=$(${pkgs.gnugrep}/bin/grep -oP '(?<=)[^<]+' ${sonarrConfig}) + ${pkgs.gnused}/bin/sed \ + -e "s/RADARR_API_KEY_PLACEHOLDER/$RADARR_KEY/" \ + -e "s/SONARR_API_KEY_PLACEHOLDER/$SONARR_KEY/" \ + ${configTemplate} > ${dataDir}/recyclarr.yml + ''; +in +{ + imports = [ + (lib.serviceMountWithZpool "recyclarr" service_configs.zpool_ssds [ + dataDir + ]) + ]; + + systemd.tmpfiles.rules = [ + "d ${dataDir} 0755 root root -" + "d ${dataDir}/data 0755 root root -" + ]; + + systemd.services.recyclarr = { + description = "Recyclarr TRaSH Guides Sync"; + after = [ "network-online.target" "radarr.service" "sonarr.service" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStartPre = "${generateConfig}"; + ExecStart = "${pkgs.recyclarr}/bin/recyclarr sync --config ${dataDir}/recyclarr.yml --app-data ${dataDir}/data"; + }; + }; + + systemd.timers.recyclarr = { + description = "Run Recyclarr daily"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + RandomizedDelaySec = "1h"; + }; + }; +}