From 127764a06d3524ec149b4b50430ff3fe0fa5db04 Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Sat, 22 Mar 2025 23:07:58 +0100 Subject: [PATCH 1/6] chore: introduce central api key extraction service generator --- nixarr/lib/api-keys.nix | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 nixarr/lib/api-keys.nix diff --git a/nixarr/lib/api-keys.nix b/nixarr/lib/api-keys.nix new file mode 100644 index 0000000..62b1d7d --- /dev/null +++ b/nixarr/lib/api-keys.nix @@ -0,0 +1,79 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.nixarr; + + # Helper to create API key extraction for a service + mkApiKeyExtractor = serviceName: serviceConfig: { + description = "Extract ${serviceName} API key"; + after = ["${serviceName}.service"]; + requires = ["${serviceName}.service"]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + # Use DynamicUser if the parent service does + DynamicUser = serviceConfig.serviceConfig.DynamicUser or false; + # Only set User if not using DynamicUser + ${ + if !(serviceConfig.serviceConfig.DynamicUser or false) + then "User" + else null + } = + serviceConfig.user or null; + Group = "api-keys"; + UMask = "0027"; # Results in 0640 permissions + + ExecStartPre = [ + "${pkgs.coreutils}/bin/mkdir -p ${cfg.stateDir}/api-keys" + "${pkgs.coreutils}/bin/chown root:api-keys ${cfg.stateDir}/api-keys" + "${pkgs.coreutils}/bin/chmod 750 ${cfg.stateDir}/api-keys" + # Wait for config file to exist + "${pkgs.bash}/bin/bash -c 'while [ ! -f ${serviceConfig.stateDir}/config.xml ]; do sleep 1; done'" + ]; + + ExecStart = pkgs.writeShellScript "extract-${serviceName}-api-key" '' + ${pkgs.dasel}/bin/dasel -f "${serviceConfig.stateDir}/config.xml" \ + -s ".Config.ApiKey" | tr -d '\n\r' > "${cfg.stateDir}/api-keys/${serviceName}.key" + chown $USER:api-keys "${cfg.stateDir}/api-keys/${serviceName}.key" + ''; + }; + }; +in { + config = mkIf cfg.enable { + users.groups.api-keys = {}; + + # Ensure all services that need API keys are in the group + users.users = mkMerge [ + # Static users + (mkIf cfg.transmission.enable {torrenter.extraGroups = ["api-keys"];}) + (mkIf cfg.transmission.privateTrackers.cross-seed.enable {cross-seed.extraGroups = ["api-keys"];}) + ]; + + # Add api-keys group to services with DynamicUser + systemd.services = mkMerge [ + (mkIf cfg.sonarr.enable {sonarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + (mkIf cfg.radarr.enable {radarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + (mkIf cfg.lidarr.enable {lidarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + (mkIf cfg.readarr.enable {readarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + (mkIf cfg.prowlarr.enable {prowlarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + (mkIf cfg.recyclarr.enable {recyclarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + + # Create API key extractors for enabled services + (mkIf cfg.sonarr.enable {"sonarr-api-key" = mkApiKeyExtractor "sonarr" cfg.sonarr;}) + (mkIf cfg.radarr.enable {"radarr-api-key" = mkApiKeyExtractor "radarr" cfg.radarr;}) + (mkIf cfg.lidarr.enable {"lidarr-api-key" = mkApiKeyExtractor "lidarr" cfg.lidarr;}) + (mkIf cfg.readarr.enable {"readarr-api-key" = mkApiKeyExtractor "readarr" cfg.readarr;}) + (mkIf cfg.prowlarr.enable {"prowlarr-api-key" = mkApiKeyExtractor "prowlarr" cfg.prowlarr;}) + ]; + + # Create the api-keys directory + systemd.tmpfiles.rules = [ + "d ${cfg.stateDir}/api-keys 0750 root api-keys - -" + ]; + }; +} From 122741de1f02f367eca51a8fb67b7df541ae31dd Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Tue, 25 Mar 2025 07:22:49 +0100 Subject: [PATCH 2/6] chore: use one api key group per service --- nixarr/lib/api-keys.nix | 51 ++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/nixarr/lib/api-keys.nix b/nixarr/lib/api-keys.nix index 62b1d7d..4b6c9a9 100644 --- a/nixarr/lib/api-keys.nix +++ b/nixarr/lib/api-keys.nix @@ -25,12 +25,12 @@ with lib; let else null } = serviceConfig.user or null; - Group = "api-keys"; + Group = "${serviceName}-api"; UMask = "0027"; # Results in 0640 permissions ExecStartPre = [ "${pkgs.coreutils}/bin/mkdir -p ${cfg.stateDir}/api-keys" - "${pkgs.coreutils}/bin/chown root:api-keys ${cfg.stateDir}/api-keys" + "${pkgs.coreutils}/bin/chown root:${serviceName}-api ${cfg.stateDir}/api-keys" "${pkgs.coreutils}/bin/chmod 750 ${cfg.stateDir}/api-keys" # Wait for config file to exist "${pkgs.bash}/bin/bash -c 'while [ ! -f ${serviceConfig.stateDir}/config.xml ]; do sleep 1; done'" @@ -39,29 +39,44 @@ with lib; let ExecStart = pkgs.writeShellScript "extract-${serviceName}-api-key" '' ${pkgs.dasel}/bin/dasel -f "${serviceConfig.stateDir}/config.xml" \ -s ".Config.ApiKey" | tr -d '\n\r' > "${cfg.stateDir}/api-keys/${serviceName}.key" - chown $USER:api-keys "${cfg.stateDir}/api-keys/${serviceName}.key" + chown $USER:${serviceName}-api "${cfg.stateDir}/api-keys/${serviceName}.key" ''; }; }; in { config = mkIf cfg.enable { - users.groups.api-keys = {}; - - # Ensure all services that need API keys are in the group - users.users = mkMerge [ - # Static users - (mkIf cfg.transmission.enable {torrenter.extraGroups = ["api-keys"];}) - (mkIf cfg.transmission.privateTrackers.cross-seed.enable {cross-seed.extraGroups = ["api-keys"];}) + # Create per-service API key groups + users.groups = mkMerge [ + (mkIf cfg.sonarr.enable {sonarr-api = {};}) + (mkIf cfg.radarr.enable {radarr-api = {};}) + (mkIf cfg.lidarr.enable {lidarr-api = {};}) + (mkIf cfg.readarr.enable {readarr-api = {};}) + (mkIf cfg.prowlarr.enable {prowlarr-api = {};}) ]; - # Add api-keys group to services with DynamicUser + # Add services that need API keys to their respective groups + users.users = mkMerge [ + # Static users + (mkIf cfg.transmission.enable { + torrenter.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; + }) + (mkIf cfg.transmission.privateTrackers.cross-seed.enable { + cross-seed.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; + }) + ]; + + # Add api groups to services with DynamicUser systemd.services = mkMerge [ - (mkIf cfg.sonarr.enable {sonarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) - (mkIf cfg.radarr.enable {radarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) - (mkIf cfg.lidarr.enable {lidarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) - (mkIf cfg.readarr.enable {readarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) - (mkIf cfg.prowlarr.enable {prowlarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) - (mkIf cfg.recyclarr.enable {recyclarr.serviceConfig.SupplementaryGroups = ["api-keys"];}) + (mkIf cfg.sonarr.enable {sonarr.serviceConfig.SupplementaryGroups = ["sonarr-api"];}) + (mkIf cfg.radarr.enable {radarr.serviceConfig.SupplementaryGroups = ["radarr-api"];}) + (mkIf cfg.lidarr.enable {lidarr.serviceConfig.SupplementaryGroups = ["lidarr-api"];}) + (mkIf cfg.readarr.enable {readarr.serviceConfig.SupplementaryGroups = ["readarr-api"];}) + (mkIf cfg.prowlarr.enable {prowlarr.serviceConfig.SupplementaryGroups = ["prowlarr-api"];}) + (mkIf cfg.recyclarr.enable { + recyclarr.serviceConfig.SupplementaryGroups = + (optional cfg.sonarr.enable "sonarr-api") ++ + (optional cfg.radarr.enable "radarr-api"); + }) # Create API key extractors for enabled services (mkIf cfg.sonarr.enable {"sonarr-api-key" = mkApiKeyExtractor "sonarr" cfg.sonarr;}) @@ -73,7 +88,7 @@ in { # Create the api-keys directory systemd.tmpfiles.rules = [ - "d ${cfg.stateDir}/api-keys 0750 root api-keys - -" + "d ${cfg.stateDir}/api-keys 0750 root root - -" ]; }; } From efae009f94229127bf61bc546caba0e178ad517f Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Mon, 31 Mar 2025 22:19:41 +0200 Subject: [PATCH 3/6] chore: formatting --- nixarr/lib/api-keys.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nixarr/lib/api-keys.nix b/nixarr/lib/api-keys.nix index 4b6c9a9..d0194dd 100644 --- a/nixarr/lib/api-keys.nix +++ b/nixarr/lib/api-keys.nix @@ -73,9 +73,9 @@ in { (mkIf cfg.readarr.enable {readarr.serviceConfig.SupplementaryGroups = ["readarr-api"];}) (mkIf cfg.prowlarr.enable {prowlarr.serviceConfig.SupplementaryGroups = ["prowlarr-api"];}) (mkIf cfg.recyclarr.enable { - recyclarr.serviceConfig.SupplementaryGroups = - (optional cfg.sonarr.enable "sonarr-api") ++ - (optional cfg.radarr.enable "radarr-api"); + recyclarr.serviceConfig.SupplementaryGroups = + (optional cfg.sonarr.enable "sonarr-api") + ++ (optional cfg.radarr.enable "radarr-api"); }) # Create API key extractors for enabled services From 1b7dc69561dba8cafc8d35508c3287567cbc45c7 Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Tue, 1 Jul 2025 22:59:12 +0200 Subject: [PATCH 4/6] fix(monitoring): resolve rebase conflicts and improve wireguard exporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix API key service user reference (torrenter -> transmission) - Remove duplicate vpnNamespaces.wg definition - Add proper enable option to wireguard exporter for consistency - Make wireguard exporter port mappings conditional 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- nixarr/lib/api-keys.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixarr/lib/api-keys.nix b/nixarr/lib/api-keys.nix index d0194dd..1c95e04 100644 --- a/nixarr/lib/api-keys.nix +++ b/nixarr/lib/api-keys.nix @@ -58,7 +58,7 @@ in { users.users = mkMerge [ # Static users (mkIf cfg.transmission.enable { - torrenter.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; + transmission.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; }) (mkIf cfg.transmission.privateTrackers.cross-seed.enable { cross-seed.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; From 796787f6b047e6b8ec4b867ab6249d8c59e14eb2 Mon Sep 17 00:00:00 2001 From: Edward Pierzchalski Date: Sun, 19 Oct 2025 15:18:41 +1100 Subject: [PATCH 5/6] api-keys: cleanup - Remove handling of dynamic users - Split out "which file to wait for" and "how to read that file", per service - Rely on `systemd.tmpfiles` to make dirs with the right permissions - Remove per-service group membership changes; those will be easier to reason about in each service's *.nix file --- nixarr/default.nix | 1 + nixarr/lib/api-keys.nix | 127 ++++++++++++++++++++++------------------ 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/nixarr/default.nix b/nixarr/default.nix index 07e5ce3..2729286 100644 --- a/nixarr/default.nix +++ b/nixarr/default.nix @@ -15,6 +15,7 @@ in { ./ddns ./jellyfin ./jellyseerr + ./lib/api-keys.nix ./lidarr ./nixarr-command ./openssh diff --git a/nixarr/lib/api-keys.nix b/nixarr/lib/api-keys.nix index 1c95e04..96f0fab 100644 --- a/nixarr/lib/api-keys.nix +++ b/nixarr/lib/api-keys.nix @@ -7,8 +7,61 @@ with lib; let cfg = config.nixarr; + serviceCfgFile = { + bazarr = "${cfg.bazarr.stateDir}/config/config.yaml"; + jellyseerr = "${cfg.jellyseerr.stateDir}/settings.json"; + lidarr = "${cfg.lidarr.stateDir}/config.xml"; + prowlarr = "${cfg.prowlarr.stateDir}/config.xml"; + radarr = "${cfg.radarr.stateDir}/config.xml"; + readarr-audiobook = "${cfg.readarr-audiobook.stateDir}/config.xml"; + readarr = "${cfg.readarr.stateDir}/config.xml"; + sabnzbd = "${cfg.sabnzbd.stateDir}/sabnzbd.ini"; + sonarr = "${cfg.sonarr.stateDir}/config.xml"; + transmission = "${cfg.transmission.stateDir}/.config/transmission-daemon/settings.json"; + }; + + printServiceApiKey = let + yq = getExe' pkgs.yq "yq"; + xq = getExe' pkgs.yq "xq"; + grep = getExe pkgs.gnugrep; + sed = getExe pkgs.gnused; + in { + bazarr = pkgs.writeShellScript "print-bazarr-api-key" '' + ${yq} -r .auth.apiKey '${serviceCfgFile.bazarr}' + ''; + jellyseerr = pkgs.writeShellScript "print-jellyseerr-api-key" '' + ${yq} -r .main.apiKey '${serviceCfgFile.jellyseerr}' + ''; + lidarr = pkgs.writeShellScript "print-lidarr-api-key" '' + ${xq} -r .Config.ApiKey '${serviceCfgFile.lidarr}' + ''; + prowlarr = pkgs.writeShellScript "print-prowlarr-api-key" '' + ${xq} -r .Config.ApiKey '${serviceCfgFile.prowlarr}' + ''; + radarr = pkgs.writeShellScript "print-radarr-api-key" '' + ${xq} -r .Config.ApiKey '${serviceCfgFile.radarr}' + ''; + readarr-audiobook = pkgs.writeShellScript "print-readarr-audiobook-api-key" '' + ${xq} -r .Config.ApiKey '${serviceCfgFile.readarr-audiobook}' + ''; + readarr = pkgs.writeShellScript "print-readarr-api-key" '' + ${xq} -r .Config.ApiKey '${serviceCfgFile.readarr}' + ''; + sabnzbd = pkgs.writeShellScript "print-sabnzbd-api-key" '' + ${grep} api_key '${serviceCfgFile.sabnzbd}' | ${sed} 's/^api_key.*= *//g' + ''; + sonarr = pkgs.writeShellScript "print-sonarr-api-key" '' + ${xq} -r .Config.ApiKey '${serviceCfgFile.sonarr}' + ''; + transmission = pkgs.writeShellScript "print-transmission-api-key" '' + ${yq} -r .["rpc-password"] '${serviceCfgFile.transmission}' + ''; + }; + + servicesWithApiKeys = builtins.attrNames printServiceApiKey; + # Helper to create API key extraction for a service - mkApiKeyExtractor = serviceName: serviceConfig: { + mkApiKeyExtractor = serviceName: { description = "Extract ${serviceName} API key"; after = ["${serviceName}.service"]; requires = ["${serviceName}.service"]; @@ -16,79 +69,41 @@ with lib; let serviceConfig = { Type = "oneshot"; RemainAfterExit = true; - # Use DynamicUser if the parent service does - DynamicUser = serviceConfig.serviceConfig.DynamicUser or false; - # Only set User if not using DynamicUser - ${ - if !(serviceConfig.serviceConfig.DynamicUser or false) - then "User" - else null - } = - serviceConfig.user or null; Group = "${serviceName}-api"; UMask = "0027"; # Results in 0640 permissions ExecStartPre = [ - "${pkgs.coreutils}/bin/mkdir -p ${cfg.stateDir}/api-keys" - "${pkgs.coreutils}/bin/chown root:${serviceName}-api ${cfg.stateDir}/api-keys" - "${pkgs.coreutils}/bin/chmod 750 ${cfg.stateDir}/api-keys" - # Wait for config file to exist - "${pkgs.bash}/bin/bash -c 'while [ ! -f ${serviceConfig.stateDir}/config.xml ]; do sleep 1; done'" + (pkgs.writeShellScript "wait-for-${serviceName}-config" '' + while [ ! -f '${serviceCfgFile.${serviceName}}' ]; do sleep 1; done + '') ]; ExecStart = pkgs.writeShellScript "extract-${serviceName}-api-key" '' - ${pkgs.dasel}/bin/dasel -f "${serviceConfig.stateDir}/config.xml" \ - -s ".Config.ApiKey" | tr -d '\n\r' > "${cfg.stateDir}/api-keys/${serviceName}.key" - chown $USER:${serviceName}-api "${cfg.stateDir}/api-keys/${serviceName}.key" + ${printServiceApiKey.${serviceName}} > '${cfg.stateDir}/api-keys/${serviceName}.key' ''; }; }; in { config = mkIf cfg.enable { # Create per-service API key groups - users.groups = mkMerge [ - (mkIf cfg.sonarr.enable {sonarr-api = {};}) - (mkIf cfg.radarr.enable {radarr-api = {};}) - (mkIf cfg.lidarr.enable {lidarr-api = {};}) - (mkIf cfg.readarr.enable {readarr-api = {};}) - (mkIf cfg.prowlarr.enable {prowlarr-api = {};}) - ]; - - # Add services that need API keys to their respective groups - users.users = mkMerge [ - # Static users - (mkIf cfg.transmission.enable { - transmission.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; - }) - (mkIf cfg.transmission.privateTrackers.cross-seed.enable { - cross-seed.extraGroups = optional cfg.prowlarr.enable "prowlarr-api"; - }) - ]; - - # Add api groups to services with DynamicUser - systemd.services = mkMerge [ - (mkIf cfg.sonarr.enable {sonarr.serviceConfig.SupplementaryGroups = ["sonarr-api"];}) - (mkIf cfg.radarr.enable {radarr.serviceConfig.SupplementaryGroups = ["radarr-api"];}) - (mkIf cfg.lidarr.enable {lidarr.serviceConfig.SupplementaryGroups = ["lidarr-api"];}) - (mkIf cfg.readarr.enable {readarr.serviceConfig.SupplementaryGroups = ["readarr-api"];}) - (mkIf cfg.prowlarr.enable {prowlarr.serviceConfig.SupplementaryGroups = ["prowlarr-api"];}) - (mkIf cfg.recyclarr.enable { - recyclarr.serviceConfig.SupplementaryGroups = - (optional cfg.sonarr.enable "sonarr-api") - ++ (optional cfg.radarr.enable "radarr-api"); - }) + users.groups = mkMerge ( + builtins.map + (serviceName: mkIf cfg.${serviceName}.enable {"${serviceName}-api" = {};}) + servicesWithApiKeys + ); + systemd.services = mkMerge ( # Create API key extractors for enabled services - (mkIf cfg.sonarr.enable {"sonarr-api-key" = mkApiKeyExtractor "sonarr" cfg.sonarr;}) - (mkIf cfg.radarr.enable {"radarr-api-key" = mkApiKeyExtractor "radarr" cfg.radarr;}) - (mkIf cfg.lidarr.enable {"lidarr-api-key" = mkApiKeyExtractor "lidarr" cfg.lidarr;}) - (mkIf cfg.readarr.enable {"readarr-api-key" = mkApiKeyExtractor "readarr" cfg.readarr;}) - (mkIf cfg.prowlarr.enable {"prowlarr-api-key" = mkApiKeyExtractor "prowlarr" cfg.prowlarr;}) - ]; + builtins.map + (serviceName: mkIf cfg.${serviceName}.enable {"${serviceName}-api-key" = mkApiKeyExtractor serviceName;}) + servicesWithApiKeys + ); # Create the api-keys directory systemd.tmpfiles.rules = [ - "d ${cfg.stateDir}/api-keys 0750 root root - -" + # Needs to be world-executable for members of the `*-api` groups to access + # the files inside. + "d ${cfg.stateDir}/api-keys 0701 root root - -" ]; }; } From ff4980014d8d05130dca4058905878e642d3570a Mon Sep 17 00:00:00 2001 From: Edward Pierzchalski Date: Sun, 19 Oct 2025 20:46:44 +1100 Subject: [PATCH 6/6] recyclarr: use new api-keys services --- nixarr/recyclarr/default.nix | 61 +++++++++++++----------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/nixarr/recyclarr/default.nix b/nixarr/recyclarr/default.nix index 947d63c..d65da17 100644 --- a/nixarr/recyclarr/default.nix +++ b/nixarr/recyclarr/default.nix @@ -2,7 +2,6 @@ config, lib, pkgs, - inputs, ... }: with lib; let @@ -11,39 +10,6 @@ with lib; let 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; @@ -193,6 +159,9 @@ in { isSystemUser = true; group = globals.recyclarr.group; uid = globals.uids.${globals.recyclarr.user}; + extraGroups = + (optional nixarr.radarr.enable "radarr-api") + ++ (optional nixarr.sonarr.enable "sonarr-api"); }; }; @@ -207,16 +176,29 @@ in { requiredBy = ["recyclarr.service"]; before = ["recyclarr.service"]; requires = - (optionals nixarr.radarr.enable ["radarr.service"]) - ++ (optionals nixarr.sonarr.enable ["sonarr.service"]); + (optionals nixarr.radarr.enable ["radarr.service" "radarr-api-key.service"]) + ++ (optionals nixarr.sonarr.enable ["sonarr.service" "sonarr-api-key.service"]); after = - (optionals nixarr.radarr.enable ["radarr.service"]) - ++ (optionals nixarr.sonarr.enable ["sonarr.service"]); + (optionals nixarr.radarr.enable ["radarr.service" "radarr-api-key.service"]) + ++ (optionals nixarr.sonarr.enable ["sonarr.service" "sonarr-api-key.service"]); serviceConfig = { Type = "oneshot"; RemainAfterExit = true; - ExecStart = "${extractApiKeys}/bin/extract-recyclarr-api-keys"; + UMask = "0077"; # Results in 0600 permissions + User = config.services.recyclarr.user; + ExecStart = pkgs.writeShellScript "recyclar-setup" '' + set -euo pipefail + echo -n > '${cfg.stateDir}/env' + ${optionalString nixarr.radarr.enable '' + printf RADARR_API_KEY= >> '${cfg.stateDir}/env' + cat '${nixarr.stateDir}/api-keys/radarr.key' >> '${cfg.stateDir}/env' + ''} + ${optionalString nixarr.sonarr.enable '' + printf SONARR_API_KEY= >> '${cfg.stateDir}/env' + cat '${nixarr.stateDir}/api-keys/sonarr.key' >> '${cfg.stateDir}/env' + ''} + ''; }; }; @@ -232,6 +214,7 @@ in { systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' 0750 ${config.services.recyclarr.user} root - -" + "f '${cfg.stateDir}/env' 0600 ${config.services.recyclarr.user} ${config.services.recyclarr.group} - -" ]; }; }