diff --git a/nixarr/default.nix b/nixarr/default.nix index b9b5664..6ac7198 100644 --- a/nixarr/default.nix +++ b/nixarr/default.nix @@ -75,6 +75,10 @@ with lib; let + strings.optionalString cfg.jellyseerr.enable '' chown -R jellyseerr:root "${cfg.jellyseerr.stateDir}" find "${cfg.jellyseerr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.recyclarr.enable '' + chown -R recyclarr:root "${cfg.recyclarr.stateDir}" + find "${cfg.recyclarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) ''; }; in { @@ -91,6 +95,7 @@ in { ./prowlarr ./transmission ./sabnzbd + ./recyclarr ../util ]; @@ -125,6 +130,7 @@ in { - [Prowlarr](#nixarr.prowlarr.enable) - [Radarr](#nixarr.radarr.enable) - [Readarr](#nixarr.readarr.enable) + - [Recyclarr](#nixarr.recyclarr.enable) - [Sonarr](#nixarr.sonarr.enable) - [Transmission](#nixarr.transmission.enable) - [SABnzbd](#nixarr.sabnzbd.enable) diff --git a/nixarr/recyclarr/default.nix b/nixarr/recyclarr/default.nix new file mode 100644 index 0000000..7af62de --- /dev/null +++ b/nixarr/recyclarr/default.nix @@ -0,0 +1,236 @@ +{ + config, + lib, + pkgs, + inputs, + ... +}: +with lib; let + cfg = config.nixarr.recyclarr; + nixarr = config.nixarr; + format = pkgs.formats.yaml {}; + + # Helper function to extract API keys + extractApiKeys = pkgs.writeShellApplication { + name = "extract-recyclarr-api-keys"; + runtimeInputs = with pkgs; [yq]; + text = '' + # Ensure state directory exists with proper permissions + mkdir -p "${cfg.stateDir}" + chown ${config.services.recyclarr.user}:${config.services.recyclarr.group} "${cfg.stateDir}" + chmod 755 "${cfg.stateDir}" + + ${optionalString nixarr.radarr.enable '' + # Extract Radarr API key + API_KEY_FILE="${cfg.stateDir}/radarr-api-key" + xq -r '.Config.ApiKey' "${nixarr.radarr.stateDir}/config.xml" > "$API_KEY_FILE" + chmod 400 "$API_KEY_FILE" + chown ${config.services.recyclarr.user}:${config.services.recyclarr.group} "$API_KEY_FILE" + echo "RADARR_API_KEY=$(tr -d '\n' < "$API_KEY_FILE")" >> "${cfg.stateDir}/env" + ''} + + ${optionalString nixarr.sonarr.enable '' + # Extract Sonarr API key + API_KEY_FILE="${cfg.stateDir}/sonarr-api-key" + xq -r '.Config.ApiKey' "${nixarr.sonarr.stateDir}/config.xml" > "$API_KEY_FILE" + chmod 400 "$API_KEY_FILE" + chown ${config.services.recyclarr.user}:${config.services.recyclarr.group} "$API_KEY_FILE" + echo "SONARR_API_KEY=$(tr -d '\n' < "$API_KEY_FILE")" >> "${cfg.stateDir}/env" + ''} + + chmod 400 "${cfg.stateDir}/env" + chown ${config.services.recyclarr.user}:${config.services.recyclarr.group} "${cfg.stateDir}/env" + ''; + }; + + # Generate configuration file from Nix attribute set if provided + generatedConfigFile = format.generate "recyclarr-config.yml" cfg.configuration; + + # Determine which config file to use + effectiveConfigFile = + if cfg.configFile != null + then cfg.configFile + else generatedConfigFile; +in { + options.nixarr.recyclarr = { + enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether or not to enable the Recyclarr service. This service does not need to be run behind a VPN. + + **Required options:** [`nixarr.enable`](#nixarr.enable) + ''; + }; + + package = mkPackageOption pkgs "recyclarr" {}; + + schedule = lib.mkOption { + type = lib.types.str; + default = "daily"; + description = "When to run recyclarr in systemd calendar format."; + }; + + stateDir = mkOption { + type = types.path; + default = "${nixarr.stateDir}/recyclarr"; + defaultText = literalExpression ''"''${nixarr.stateDir}/recyclarr"''; + example = "/nixarr/.state/recyclarr"; + description = "The location of the state directory for the Recyclarr service."; + }; + + configFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to the recyclarr YAML configuration file. See [Recyclarr's + documentation](https://recyclarr.dev/wiki/yaml/config-reference) + for more information. + + The API keys for Radarr and Sonarr can be referenced in the config + file using the `RADARR_API_KEY` and `SONARR_API_KEY` environment + variables (with macro `!env_var`). + + Note: You cannot set both `configFile` and `configuration` options. + ''; + example = "./recyclarr.yaml"; + }; + + configuration = mkOption { + type = types.nullOr format.type; + default = null; + example = literalExpression '' + { + sonarr = { + series = { + base_url = "http://localhost:8989"; + api_key = "!env_var SONARR_API_KEY"; + quality_definition = { + type = "series"; + }; + delete_old_custom_formats = true; + custom_formats = [ + { + trash_ids = [ + "85c61753df5da1fb2aab6f2a47426b09" # BR-DISK + "9c11cd3f07101cdba90a2d81cf0e56b4" # LQ + ]; + assign_scores_to = [ + { + name = "WEB-DL (1080p)"; + score = -10000; + } + ]; + } + ]; + }; + }; + radarr = { + movies = { + base_url = "http://localhost:7878"; + api_key = "!env_var RADARR_API_KEY"; + quality_definition = { + type = "movie"; + }; + delete_old_custom_formats = true; + custom_formats = [ + { + trash_ids = [ + "570bc9ebecd92723d2d21500f4be314c" # Remaster + "eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster + ]; + assign_scores_to = [ + { + name = "HD Bluray + WEB"; + score = 25; + } + ]; + } + ]; + }; + }; + } + ''; + description = '' + Recyclarr YAML configuration as a Nix attribute set. For detailed configuration options and examples, + see the [official configuration reference](https://recyclarr.dev/wiki/yaml/config-reference/). + + The API keys for Radarr and Sonarr can be referenced using the `RADARR_API_KEY` and `SONARR_API_KEY` + environment variables (with the string "!env_var RADARR_API_KEY"). + + Note: You cannot set both `configFile` and `configuration` options. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> nixarr.enable; + message = '' + The nixarr.recyclarr.enable option requires the nixarr.enable + option to be set, but it was not. + ''; + } + { + assertion = cfg.enable -> (nixarr.radarr.enable || nixarr.sonarr.enable); + message = '' + The nixarr.recyclarr.enable option requires at least one of nixarr.radarr.enable + or nixarr.sonarr.enable to be set, but neither was enabled. + ''; + } + { + assertion = !(cfg.configFile != null && cfg.configuration != null); + message = '' + You cannot set both nixarr.recyclarr.configFile and nixarr.recyclarr.configuration. + Please choose one method to configure Recyclarr. + ''; + } + { + assertion = cfg.configFile != null || cfg.configuration != null; + message = '' + You must set either nixarr.recyclarr.configFile or nixarr.recyclarr.configuration. + ''; + } + ]; + + services.recyclarr = { + enable = true; + package = cfg.package; + schedule = cfg.schedule; + }; + + systemd.services.recyclarr-setup = { + description = "Setup Recyclarr environment"; + requiredBy = ["recyclarr.service"]; + before = ["recyclarr.service"]; + requires = + (optionals nixarr.radarr.enable ["radarr.service"]) + ++ (optionals nixarr.sonarr.enable ["sonarr.service"]); + after = + (optionals nixarr.radarr.enable ["radarr.service"]) + ++ (optionals nixarr.sonarr.enable ["sonarr.service"]); + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${extractApiKeys}/bin/extract-recyclarr-api-keys"; + }; + }; + + systemd.services.recyclarr = { + requires = ["recyclarr-setup.service"]; + after = ["recyclarr-setup.service"]; + serviceConfig = { + ExecStart = lib.mkForce "${cfg.package}/bin/recyclarr sync --app-data ${cfg.stateDir} --config ${effectiveConfigFile}"; + EnvironmentFile = "${cfg.stateDir}/env"; + ReadWritePaths = [cfg.stateDir]; + }; + }; + + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${config.services.recyclarr.user} root - -" + ]; + }; +}