Files
nixconfig/modules/server/containers/apps/servarr.nix
2026-05-20 20:43:06 +02:00

210 lines
12 KiB
Nix

{ config, containerCfg, pkgs, lib, builder, name, ... }:
let
serverCfg = config.syscfg.server;
defaultModules = ["prowlarr" "sonarr" "radarr" "lidarr" "flaresolverr" ];
mkServarrImage = appName: appPkg: binaryPath: pkgs.dockerTools.streamLayeredImage {
name = appPkg.name;
tag = appPkg.version;
contents = with pkgs; [ cacert openssl ];
config = {
Cmd = [ "${appPkg}/${binaryPath}" "-nobrowser" "-data=/config" ];
Env = [ "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=0" "HOME=/tmp" ];
};
};
images = {
prowlarr = mkServarrImage "prowlarr" pkgs.prowlarr "bin/Prowlarr";
radarr = mkServarrImage "radarr" pkgs.radarr "bin/Radarr";
sonarr = mkServarrImage "sonarr" pkgs.sonarr "bin/Sonarr";
bazarr = mkServarrImage "bazarr" pkgs.bazarr "bin/bazarr";
lidarr = mkServarrImage "lidarr" pkgs.lidarr "bin/Lidarr";
readarr = mkServarrImage "readarr" pkgs.readarr "bin/Readarr";
};
sharedVolumes = [
"${serverCfg.dataPath}/media:/media" # Fast hardlinking requires a single shared root
"${serverCfg.configPath}/servarr:/config-root"
];
in
assert containerCfg.subpath == null || throw "Error: Servarr does not support subpath.";
{
sops = true;
# db = [ "prowlarr" "sonarr" "radarr" ]; -> one db for each
paths = [
{ path = "${serverCfg.dataPath}/media/"; mode = "0755"; }
{ path = "${serverCfg.configPath}/servarr/prowlarr"; mode = "0755"; }
{ path = "${serverCfg.configPath}/servarr/radarr"; mode = "0755"; }
{ path = "${serverCfg.configPath}/servarr/sonarr"; mode = "0755"; }
];
containers = {
}// lib.optionalAttrs (builtins.elem "prowlarr" (containerCfg.extra.modules or defaultModules)) {
prowlarr = builder.mkContainer {
subdomain = containerCfg.subdomain;
subpath = "prowlarr";
imageStream = images.prowlarr;
port = 8989;
secret = name;
extraEnv = {
PROWLARR__APP__INSTANCENAME = "Prowlarr";
PROWLARR__AUTH__METHOD = "External";
PROWLARR__SERVER__PORT = "8989";
PROWLARR__SERVER__URLBASE = "/prowlarr";
PROWLARR__LOG__ANALYTICSENABLED = "False";
};
extraOptions = [
"--user=0:0"
"--tmpfs=/tmp:rw,noexec,nosuid,size=512m"
"--passwd-entry=root:x:0:0:root:/root:/bin/sh"
];
overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/prowlarr:/config" ];
};
}// lib.optionalAttrs (builtins.elem "radarr" (containerCfg.extra.modules or defaultModules)) {
radarr = builder.mkContainer {
subdomain = containerCfg.subdomain;
subpath = "radarr";
imageStream = images.radarr;
port = 8989;
secret = name;
extraEnv = {
RADARR__APP__INSTANCENAME = "Radarr";
RADARR__AUTH__METHOD = "External";
RADARR__SERVER__PORT = "8989";
RADARR__SERVER__URLBASE = "/radarr";
RADARR__LOG__ANALYTICSENABLED = "False";
};
extraOptions = [
"--user=0:0"
"--tmpfs=/tmp:rw,noexec,nosuid,size=512m"
"--passwd-entry=root:x:0:0:root:/root:/bin/sh"
];
overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/radarr:/config" ];
};
}// lib.optionalAttrs (builtins.elem "sonarr" (containerCfg.extra.modules or defaultModules)) {
sonarr = builder.mkContainer {
subdomain = containerCfg.subdomain;
subpath = "sonarr";
imageStream = images.sonarr;
port = 8989;
secret = name;
extraEnv = {
SONARR__APP__INSTANCENAME = "Sonarr";
SONARR__AUTH__METHOD = "External";
SONARR__SERVER__PORT = "8989";
SONARR__SERVER__URLBASE = "/sonarr";
SONARR__LOG__ANALYTICSENABLED = "False";
};
extraOptions = [
"--user=0:0"
"--tmpfs=/tmp:rw,noexec,nosuid,size=512m"
"--passwd-entry=root:x:0:0:root:/root:/bin/sh"
];
overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/sonarr:/config" ];
};
}// lib.optionalAttrs (builtins.elem "readarr" (containerCfg.extra.modules or defaultModules)) {
readarr = throw "Not Implemented";
}// lib.optionalAttrs (builtins.elem "mylarr" (containerCfg.extra.modules or defaultModules)) {
mylarr = throw "Not Implemented";
}// lib.optionalAttrs (builtins.elem "bazarr" (containerCfg.extra.modules or defaultModules)) {
bazarr = throw "Not Implemented";
}// lib.optionalAttrs (builtins.elem "seerr" (containerCfg.extra.modules or defaultModules)) {
seerr = throw "Not Implemented";
}// lib.optionalAttrs (builtins.elem "flaresolverr" (containerCfg.extra.modules or defaultModules)) {
flaresolverr = builder.mkContainer {
image = "ghcr.io/flaresolverr/flaresolverr:latest";
port = 8191;
extraEnv = {
CAPTCHA_SOLVER = "none";
};
};
};
setup = {
trigger = "prowlarr"; # Triggers atomic environment verification on main controller
envFile = config.sops.secrets."SERVARR".path;
script = pkgs.writeShellScript "setup-servarr" ''
${lib.optionalString (builtins.elem "prowlarr" (containerCfg.extra.modules or defaultModules)) ''
echo "Prowlarr Setup - START"
PROWLARR_URL="https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr"
${lib.optionalString (builtins.elem "flaresolverr" (containerCfg.extra.modules or defaultModules)) ''
PROWL_PROXY=$(${pkgs.curl}/bin/curl -s -X GET "$PROWLARR_URL/api/v1/indexerProxy" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json')
if ! echo "$PROWL_PROXY" | ${pkgs.jq}/bin/jq -e 'any(.[]? ; .name == "FlareSolverr")' > /dev/null; then
echo "Prowlarr Setup - FlareSolverr"
${pkgs.curl}/bin/curl -s -X POST "$PROWLARR_URL/api/v1/indexerProxy" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"onHealthIssue":false,"supportsOnHealthIssue":false,"includeHealthWarnings":false,"name":"FlareSolverr","fields":[{"name":"host","value":"http://servarr-flaresolverr:8191/"},{"name":"requestTimeout","value":60}],"implementationName":"FlareSolverr","implementation":"FlareSolverr","configContract":"FlareSolverrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#flaresolverr","tags":[]}'
fi
''}
PROWL_APPS=$(${pkgs.curl}/bin/curl -s -X GET "$PROWLARR_URL/api/v1/applications" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json')
${lib.optionalString (builtins.elem "sonarr" (containerCfg.extra.modules or defaultModules)) ''
if ! echo "$PROWL_APPS" | ${pkgs.jq}/bin/jq -e 'any(.[]? ; .name == "Sonarr")' > /dev/null; then
echo "Prowlarr Setup - Sonarr"
${pkgs.curl}/bin/curl -s -X POST "$PROWLARR_URL/api/v1/applications" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"syncLevel":"fullSync","enable":true,"fields":[{"name":"prowlarrUrl","value":"http://servarr-prowlarr:8989/prowlarr"},{"name":"baseUrl","value":"http://servarr-sonarr:8989/sonarr"},{"name":"apiKey","value":"'"$SONARR__AUTH__APIKEY"'"},{"name":"syncCategories","value":[5000,5010,5020,5030,5040,5045,5050,5090]},{"name":"animeSyncCategories","value":[5070]},{"name":"syncAnimeStandardFormatSearch","value":true},{"name":"syncRejectBlocklistedTorrentHashesWhileGrabbing","value":false}],"implementationName":"Sonarr","implementation":"Sonarr","configContract":"SonarrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#sonarr","tags":[],"name":"Sonarr"}'
fi
''}
${lib.optionalString (builtins.elem "lidarr" (containerCfg.extra.modules or defaultModules)) ''
if ! echo "$PROWL_APPS" | ${pkgs.jq}/bin/jq -e 'any(.[]? ; .name == "Lidarr")' > /dev/null; then
echo "Prowlarr Setup - Lidarr"
${pkgs.curl}/bin/curl -s -X POST "$PROWLARR_URL/api/v1/applications" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"syncLevel":"fullSync","enable":true,"fields":[{"name":"prowlarrUrl","value":"http://servarr-prowlarr:8989/prowlarr"},{"name":"baseUrl","value":"http://servarr-lidarr:8989/lidarr"},{"name":"apiKey","value":"'"$LIDARR__AUTH__APIKEY"'"},{"name":"syncCategories","value":[5000,5010,5020,5030,5040,5045,5050,5090]},{"name":"animeSyncCategories","value":[5070]},{"name":"syncAnimeStandardFormatSearch","value":true},{"name":"syncRejectBlocklistedTorrentHashesWhileGrabbing","value":false}],"implementationName":"Lidarr","implementation":"Lidarr","configContract":"LidarrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#lidarr","tags":[],"name":"Lidarr"}'
fi
''}
${lib.optionalString (builtins.elem "radarr" (containerCfg.extra.modules or defaultModules)) ''
if ! echo "$PROWL_APPS" | ${pkgs.jq}/bin/jq -e 'any(.[]? ; .name == "Radarr")' > /dev/null; then
echo "Prowlarr Setup - Radarr"
${pkgs.curl}/bin/curl -s -X POST "$PROWLARR_URL/api/v1/applications" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"syncLevel":"fullSync","enable":true,"fields":[{"name":"prowlarrUrl","value":"http://servarr-prowlarr:8989/prowlarr"},{"name":"baseUrl","value":"http://servarr-radarr:8989/radarr"},{"name":"apiKey","value":"'"$RADARR__AUTH__APIKEY"'"},{"name":"syncCategories","value":[5000,5010,5020,5030,5040,5045,5050,5090]},{"name":"animeSyncCategories","value":[5070]},{"name":"syncAnimeStandardFormatSearch","value":true},{"name":"syncRejectBlocklistedTorrentHashesWhileGrabbing","value":false}],"implementationName":"Radarr","implementation":"Radarr","configContract":"RadarrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#lidarr","tags":[],"name":"Radarr"}'
fi
''}
${lib.optionalString (serverCfg.containers?transmission ) ''
PROWL_DL=$(${pkgs.curl}/bin/curl -s -X GET "$PROWLARR_URL/api/v1/downloadclient" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json')
if ! echo "$PROWL_DL" | ${pkgs.jq}/bin/jq -e 'any(.[]? ; .name == "Transmission")' > /dev/null; then
echo "Prowlarr Setup - Transmission"
${pkgs.curl}/bin/curl -s -X POST "$PROWLARR_URL/api/v1/downloadclient" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"enable":true,"protocol":"torrent","priority":1,"categories":[],"supportsCategories":true,"name":"Transmission","fields":[{"name":"host","value":"transmission-server"},{"name":"port","value":9091},{"name":"useSsl","value":false},{"name":"urlBase","value":"/transmission/"},{"name":"username"},{"name":"password"},{"name":"category"},{"name":"directory"},{"name":"priority","value":0},{"name":"addPaused","value":false}],"implementationName":"Transmission","implementation":"Transmission","configContract":"TransmissionSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#transmission","tags":[]}'
fi
''}
echo "Prowlarr Setup - Indexers"
PROWL_IDX=$(${pkgs.curl}/bin/curl -s -X GET "$PROWLARR_URL/api/v1/indexer" \
-H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \
-H 'Accept: application/json' -H 'Content-Type: application/json')
echo $PROWL_IDX
#... For extra.indexer -> ...
''}
'';
};
}