Merge pull request #94 from pierzchalski/eap/api-key-extractor
Add systemd services for extracting *arr API keys
This commit is contained in:
@@ -15,6 +15,7 @@ in {
|
|||||||
./ddns
|
./ddns
|
||||||
./jellyfin
|
./jellyfin
|
||||||
./jellyseerr
|
./jellyseerr
|
||||||
|
./lib/api-keys.nix
|
||||||
./komga
|
./komga
|
||||||
./lidarr
|
./lidarr
|
||||||
./nixarr-command
|
./nixarr-command
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
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: {
|
||||||
|
description = "Extract ${serviceName} API key";
|
||||||
|
after = ["${serviceName}.service"];
|
||||||
|
requires = ["${serviceName}.service"];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
Group = "${serviceName}-api";
|
||||||
|
UMask = "0027"; # Results in 0640 permissions
|
||||||
|
|
||||||
|
ExecStartPre = [
|
||||||
|
(pkgs.writeShellScript "wait-for-${serviceName}-config" ''
|
||||||
|
while [ ! -f '${serviceCfgFile.${serviceName}}' ]; do sleep 1; done
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
|
||||||
|
ExecStart = pkgs.writeShellScript "extract-${serviceName}-api-key" ''
|
||||||
|
${printServiceApiKey.${serviceName}} > '${cfg.stateDir}/api-keys/${serviceName}.key'
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Create per-service API key groups
|
||||||
|
users.groups = mkMerge (
|
||||||
|
builtins.map
|
||||||
|
(serviceName: mkIf cfg.${serviceName}.enable {"${serviceName}-api" = {};})
|
||||||
|
servicesWithApiKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
systemd.services = mkMerge (
|
||||||
|
# Create API key extractors for enabled services
|
||||||
|
builtins.map
|
||||||
|
(serviceName: mkIf cfg.${serviceName}.enable {"${serviceName}-api-key" = mkApiKeyExtractor serviceName;})
|
||||||
|
servicesWithApiKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
# Create the api-keys directory
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
# Needs to be world-executable for members of the `*-api` groups to access
|
||||||
|
# the files inside.
|
||||||
|
"d ${cfg.stateDir}/api-keys 0701 root root - -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
with lib; let
|
with lib; let
|
||||||
@@ -11,39 +10,6 @@ with lib; let
|
|||||||
nixarr = config.nixarr;
|
nixarr = config.nixarr;
|
||||||
format = pkgs.formats.yaml {};
|
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
|
# Generate configuration file from Nix attribute set if provided
|
||||||
generatedConfigFile = format.generate "recyclarr-config.yml" cfg.configuration;
|
generatedConfigFile = format.generate "recyclarr-config.yml" cfg.configuration;
|
||||||
|
|
||||||
@@ -193,6 +159,9 @@ in {
|
|||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = globals.recyclarr.group;
|
group = globals.recyclarr.group;
|
||||||
uid = globals.uids.${globals.recyclarr.user};
|
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"];
|
requiredBy = ["recyclarr.service"];
|
||||||
before = ["recyclarr.service"];
|
before = ["recyclarr.service"];
|
||||||
requires =
|
requires =
|
||||||
(optionals nixarr.radarr.enable ["radarr.service"])
|
(optionals nixarr.radarr.enable ["radarr.service" "radarr-api-key.service"])
|
||||||
++ (optionals nixarr.sonarr.enable ["sonarr.service"]);
|
++ (optionals nixarr.sonarr.enable ["sonarr.service" "sonarr-api-key.service"]);
|
||||||
after =
|
after =
|
||||||
(optionals nixarr.radarr.enable ["radarr.service"])
|
(optionals nixarr.radarr.enable ["radarr.service" "radarr-api-key.service"])
|
||||||
++ (optionals nixarr.sonarr.enable ["sonarr.service"]);
|
++ (optionals nixarr.sonarr.enable ["sonarr.service" "sonarr-api-key.service"]);
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
RemainAfterExit = true;
|
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 = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '${cfg.stateDir}' 0750 ${config.services.recyclarr.user} root - -"
|
"d '${cfg.stateDir}' 0750 ${config.services.recyclarr.user} root - -"
|
||||||
|
"f '${cfg.stateDir}/env' 0600 ${config.services.recyclarr.user} ${config.services.recyclarr.group} - -"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user