Add jellyseerr
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -1,118 +0,0 @@
|
||||
{ user, home }:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||
|
||||
mkApp = type: name: shortName: urlBase: port: mediaFolderBase: {
|
||||
inherit
|
||||
type
|
||||
name
|
||||
shortName
|
||||
urlBase
|
||||
port
|
||||
mediaFolderBase
|
||||
;
|
||||
};
|
||||
|
||||
arrs = [
|
||||
(mkApp "radarr" "Radarr" "radarr" "/manage/films" 7878 "/films")
|
||||
(mkApp "radarr" "Radarr (UHD)" "radarr-uhd" "/manage/films/uhd" 7878 "/films")
|
||||
(mkApp "radarr" "Radarr (Anime)" "radarr-anime" "/manage/anime/films" 7878 "/anime/films")
|
||||
(mkApp "sonarr" "Sonarr" "sonarr" "/manage/shows" 8989 "/shows")
|
||||
(mkApp "sonarr" "Sonarr (UHD)" "sonarr-uhd" "/manage/shows/uhd" 8989 "/shows")
|
||||
(mkApp "sonarr" "Sonarr (Anime)" "sonarr-anime" "/manage/anime/shows" 8989 "/anime/shows")
|
||||
];
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(import ./prowlarr { inherit user home arrs; })
|
||||
(import ./recyclarr { inherit user home arrs; })
|
||||
];
|
||||
|
||||
home-manager.users.${user} = {
|
||||
sops = {
|
||||
secrets = builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "${arr.shortName}/apiKey";
|
||||
value.sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
}) arrs
|
||||
);
|
||||
|
||||
templates = builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "${arr.shortName}-env";
|
||||
value.content = ''
|
||||
API_KEY=${hmConfig.sops.placeholder."${arr.shortName}/apiKey"}
|
||||
'';
|
||||
}) arrs
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.quadlet = {
|
||||
volumes = builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = arr.shortName;
|
||||
value = { };
|
||||
}) arrs
|
||||
);
|
||||
|
||||
containers = builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = arr.shortName;
|
||||
value = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs."docker-${arr.type}"}";
|
||||
networks = [
|
||||
networks.media.ref
|
||||
networks.transmission.ref
|
||||
networks.traefik.ref
|
||||
];
|
||||
volumes =
|
||||
let
|
||||
postStart = pkgs.writeTextFile {
|
||||
name = "post-start.sh";
|
||||
executable = true;
|
||||
text = ''
|
||||
${builtins.readFile ./common.sh}
|
||||
${builtins.readFile ./${arr.type}/post-start.sh}
|
||||
'';
|
||||
};
|
||||
in
|
||||
[
|
||||
"${postStart}:/etc/${arr.type}/post-start.sh:ro"
|
||||
"${volumes.${arr.shortName}.ref}:/var/lib/${arr.type}"
|
||||
"/mnt/storage/private/storm/containers/storage/volumes/transmission-data/_data:/var/lib/transmission"
|
||||
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
|
||||
];
|
||||
environments = {
|
||||
INSTANCE_NAME = arr.name;
|
||||
URL_BASE = arr.urlBase;
|
||||
ROOT_FOLDER = "/var/lib/media${arr.mediaFolderBase}";
|
||||
DOWNLOAD_CATEGORY = arr.shortName;
|
||||
};
|
||||
environmentFiles = [ hmConfig.sops.templates."${arr.shortName}-env".path ];
|
||||
labels = [
|
||||
"traefik.enable=true"
|
||||
"traefik.http.routers.${arr.shortName}.rule=Host(`media.karaolidis.com`) && PathPrefix(`${arr.urlBase}`)"
|
||||
"traefik.http.routers.${arr.shortName}.middlewares=authelia@docker"
|
||||
];
|
||||
};
|
||||
|
||||
unitConfig.After = [
|
||||
"${containers.transmission._serviceName}.service"
|
||||
"sops-nix.service"
|
||||
];
|
||||
};
|
||||
}) arrs
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
arrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||
|
||||
arrMapping = {
|
||||
radarr = {
|
||||
implementation = "Radarr";
|
||||
configContract = "RadarrSettings";
|
||||
};
|
||||
|
||||
sonarr = {
|
||||
implementation = "Sonarr";
|
||||
configContract = "SonarrSettings";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops = {
|
||||
secrets."prowlarr/apiKey".sopsFile = ../../../../../../../../secrets/secrets.yaml;
|
||||
|
||||
templates =
|
||||
{
|
||||
prowlarr-env.content = ''
|
||||
API_KEY=${hmConfig.sops.placeholder."prowlarr/apiKey"}
|
||||
'';
|
||||
}
|
||||
// builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "prowlarr-${arr.shortName}";
|
||||
value.content = builtins.readFile (
|
||||
(pkgs.formats.json { }).generate "${arr.shortName}.json" {
|
||||
enable = true;
|
||||
name = arr.name;
|
||||
inherit (arrMapping.${arr.type}) implementation configContract;
|
||||
syncLevel = "fullSync";
|
||||
fields = [
|
||||
{
|
||||
name = "prowlarrUrl";
|
||||
value = "http://prowlarr:9696";
|
||||
}
|
||||
{
|
||||
name = "baseUrl";
|
||||
value = "http://${arr.shortName}:${builtins.toString arr.port}";
|
||||
}
|
||||
{
|
||||
name = "apiKey";
|
||||
value = hmConfig.sops.placeholder."${arr.shortName}/apiKey";
|
||||
}
|
||||
];
|
||||
}
|
||||
);
|
||||
}) arrs
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.quadlet = {
|
||||
networks.flaresolverr = { };
|
||||
|
||||
volumes.prowlarr = { };
|
||||
|
||||
containers = (
|
||||
let
|
||||
arrServices = builtins.map (arr: "${containers.${arr.shortName}._serviceName}.service") arrs;
|
||||
in
|
||||
{
|
||||
flaresolverr.containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-flaresolverr}";
|
||||
networks = [ networks.flaresolverr.ref ];
|
||||
};
|
||||
|
||||
prowlarr = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-prowlarr}";
|
||||
networks = [
|
||||
networks.media.ref
|
||||
networks.transmission.ref
|
||||
networks.flaresolverr.ref
|
||||
networks.traefik.ref
|
||||
];
|
||||
volumes =
|
||||
let
|
||||
postStart = pkgs.writeTextFile {
|
||||
name = "post-start.sh";
|
||||
executable = true;
|
||||
text = ''
|
||||
${builtins.readFile ../common.sh}
|
||||
${builtins.readFile ./post-start.sh}
|
||||
'';
|
||||
};
|
||||
in
|
||||
[
|
||||
"${postStart}:/etc/prowlarr/post-start.sh:ro"
|
||||
"${volumes.prowlarr.ref}:/var/lib/prowlarr"
|
||||
]
|
||||
++ builtins.map (
|
||||
arr:
|
||||
"${
|
||||
hmConfig.sops.templates."prowlarr-${arr.shortName}".path
|
||||
}:/etc/prowlarr/apps/${arr.shortName}.json:ro"
|
||||
) arrs;
|
||||
environments.URL_BASE = "/manage/indexers";
|
||||
environmentFiles = [ hmConfig.sops.templates.prowlarr-env.path ];
|
||||
labels = [
|
||||
"traefik.enable=true"
|
||||
"traefik.http.routers.prowlarr.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/indexers`)"
|
||||
"traefik.http.routers.prowlarr.middlewares=authelia@docker"
|
||||
];
|
||||
};
|
||||
|
||||
unitConfig.After = [
|
||||
"${containers.transmission._serviceName}.service"
|
||||
"${containers.flaresolverr._serviceName}.service"
|
||||
"sops-nix.service"
|
||||
] ++ arrServices;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
HOST="http://localhost:7878$URL_BASE"
|
||||
# shellcheck disable=SC2034
|
||||
API_SUBPATH="/api/v3"
|
||||
DOWNLOAD_CATEGORY="${DOWNLOAD_CATEGORY:-radarr}"
|
||||
|
||||
build_transmission_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"enable": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 1,
|
||||
"name": "Transmission",
|
||||
"fields": [
|
||||
{ "name": "host", "value": "transmission" },
|
||||
{ "name": "port", "value": 9091 },
|
||||
{ "name": "urlBase", "value": "" },
|
||||
{ "name": "movieCategory", "value": "$DOWNLOAD_CATEGORY" }
|
||||
],
|
||||
"implementation": "Transmission",
|
||||
"configContract": "TransmissionSettings"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
build_rootfolder_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"path": "$ROOT_FOLDER"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
wait_for_api
|
||||
|
||||
if [ -n "$ROOT_FOLDER" ]; then
|
||||
insert_or_skip_resource "rootfolder" "path" "$ROOT_FOLDER" "$(build_rootfolder_payload)"
|
||||
prune_resources "rootfolder" "path" "$ROOT_FOLDER"
|
||||
fi
|
||||
|
||||
try_forever upsert_resource "downloadclient" "name" "Transmission" "$(build_transmission_payload)"
|
||||
prune_resources "downloadclient" "name" "Transmission"
|
||||
|
||||
delete_resource "qualityprofile" "name" "SD"
|
||||
delete_resource "qualityprofile" "name" "HD-720p"
|
||||
delete_resource "qualityprofile" "name" "HD-1080p"
|
||||
delete_resource "qualityprofile" "name" "HD - 720p/1080p"
|
||||
delete_resource "qualityprofile" "name" "Ultra-HD"
|
||||
|
||||
try_forever sh -c "[ $(get_resources qualityprofile | jq length) -eq 2 ]"
|
||||
get_resources qualityprofile | jq -c '.[]' | while IFS= read -r profile; do
|
||||
id="$(printf '%s' "$profile" | jq -r '.id')"
|
||||
patched="$(printf '%s' "$profile" | jq '.language.id = -2 | .language.Name = "Original"')"
|
||||
echo "Patching qualityprofile id=$id with language = Original"
|
||||
call PUT "qualityprofile/$id?forceSave=true" "$patched"
|
||||
done
|
@@ -1,55 +0,0 @@
|
||||
{ base_url, api_key }:
|
||||
{
|
||||
radarr.radarr_anime = {
|
||||
inherit base_url api_key;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
include = [
|
||||
{ template = "radarr-quality-definition-anime"; }
|
||||
{ template = "radarr-quality-profile-anime"; }
|
||||
{ template = "radarr-custom-formats-anime"; }
|
||||
];
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"064af5f084a0a24458cc8ecd3220f93f" # Uncensored
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"a5d148168c4506b55cf53984107c396e" # 10bit
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"4a3b087eea2ce012fcc1ce319259a3be" # Anime Dual Audio
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
media_naming = {
|
||||
folder = "default";
|
||||
movie = {
|
||||
rename = true;
|
||||
standard = "anime";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
{ base_url, api_key }:
|
||||
{
|
||||
radarr.radarr_uhd = {
|
||||
inherit base_url api_key;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
include = [
|
||||
{ template = "radarr-quality-definition-movie"; }
|
||||
{ template = "radarr-quality-profile-uhd-bluray-web"; }
|
||||
{ template = "radarr-custom-formats-uhd-bluray-web"; }
|
||||
];
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"570bc9ebecd92723d2d21500f4be314c" # Remaster
|
||||
"eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster
|
||||
"e0c07d59beb37348e975a930d5e50319" # Criterion Collection
|
||||
"9d27d9d2181838f76dee150882bdc58c" # Masters of Cinema
|
||||
"db9b4c4b53d312a3ca5f1378f6440fc9" # Vinegar Syndrome
|
||||
"957d0f44b592285f26449575e8b1167e" # Special Edition
|
||||
"eecf3a857724171f968a66cb5719e152" # IMAX
|
||||
"9f6cbff8cfe4ebbc1bde14c7b7bec0de" # IMAX Enhanced
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"b6832f586342ef70d9c128d40c07b872" # Bad Dual Groups
|
||||
"cc444569854e9de0b084ab2b8b1532b2" # Black and White Editions
|
||||
"ae9b7c9ebde1f3bd336a8cbd1ec4c5e5" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"dc98083864ea246d05a42df0d05f81cc" # x265 (HD)
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "UHD Bluray + WEB";
|
||||
score = 0;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"839bea857ed2c0a8e084f3cbdbd65ecb" # x265 (no HDR/DV)
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"923b6abef9b17f937fab56cfcf89e1f1" # DV (WEBDL)
|
||||
"b17886cb4158d9fea189859409975758" # HDR10Plus Boost
|
||||
"55a5b50cb416dea5a50c4955896217ab" # DV HDR10+ Boost
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"9c38ebb7384dada637be8899efa68e6f" # SDR
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
];
|
||||
media_naming = {
|
||||
folder = "default";
|
||||
movie = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
{ base_url, api_key }:
|
||||
{
|
||||
radarr.radarr = {
|
||||
inherit base_url api_key;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
include = [
|
||||
{ template = "radarr-quality-definition-movie"; }
|
||||
{ template = "radarr-quality-profile-hd-bluray-web"; }
|
||||
{ template = "radarr-custom-formats-hd-bluray-web"; }
|
||||
];
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"570bc9ebecd92723d2d21500f4be314c" # Remaster
|
||||
"eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster
|
||||
"e0c07d59beb37348e975a930d5e50319" # Criterion Collection
|
||||
"9d27d9d2181838f76dee150882bdc58c" # Masters of Cinema
|
||||
"db9b4c4b53d312a3ca5f1378f6440fc9" # Vinegar Syndrome
|
||||
"957d0f44b592285f26449575e8b1167e" # Special Edition
|
||||
"eecf3a857724171f968a66cb5719e152" # IMAX
|
||||
"9f6cbff8cfe4ebbc1bde14c7b7bec0de" # IMAX Enhanced
|
||||
];
|
||||
assign_scores_to = [ { name = "HD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"b6832f586342ef70d9c128d40c07b872" # Bad Dual Groups
|
||||
"cc444569854e9de0b084ab2b8b1532b2" # Black and White Editions
|
||||
"ae9b7c9ebde1f3bd336a8cbd1ec4c5e5" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "HD Bluray + WEB"; } ];
|
||||
}
|
||||
];
|
||||
media_naming = {
|
||||
folder = "default";
|
||||
movie = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
{ base_url, api_key }:
|
||||
{
|
||||
sonarr.sonarr_anime = {
|
||||
inherit base_url api_key;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
include = [
|
||||
{ template = "sonarr-quality-definition-anime"; }
|
||||
{ template = "sonarr-v4-quality-profile-anime"; }
|
||||
{ template = "sonarr-v4-custom-formats-anime"; }
|
||||
];
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"026d5aadd1a6b4e550b134cb6c72b3ca" # Uncensored
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"b2550eb333d27b75833e25b8c2557b38" # 10bit
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"418f50b10f1907201b6cfdf881f467b7" # Anime Dual Audio
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
media_naming = {
|
||||
series = "default";
|
||||
season = "default";
|
||||
episodes = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
daily = "default";
|
||||
anime = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
{ base_url, api_key }:
|
||||
{
|
||||
sonarr.sonarr_uhd = {
|
||||
inherit base_url api_key;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
include = [
|
||||
{ template = "sonarr-quality-definition-series"; }
|
||||
{ template = "sonarr-v4-quality-profile-web-2160p"; }
|
||||
{ template = "sonarr-v4-custom-formats-web-2160p"; }
|
||||
];
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"9b27ab6498ec0f31a3353992e19434ca" # DV (WEBDL)
|
||||
"0dad0a507451acddd754fe6dc3a7f5e7" # HDR10+ Boost
|
||||
"385e9e8581d33133c3961bdcdeffb7b4" # DV HDR10+ Boost
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"32b367365729d530ca1c124a0b180c64" # Bad Dual Groups
|
||||
"82d40da2bc6923f41e14394075dd4b03" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"47435ece6b99a0b477caf360e79ba0bb" # x265 (HD)
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "WEB-2160p";
|
||||
score = 0;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"9b64dff695c2115facf1b6ea59c9bd07" # x265 (no HDR/DV)
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"2016d1676f5ee13a5b7257ff86ac9a93" # SDR
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
];
|
||||
media_naming = {
|
||||
series = "default";
|
||||
season = "default";
|
||||
episodes = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
daily = "default";
|
||||
anime = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
{ base_url, api_key }:
|
||||
{
|
||||
sonarr.sonarr = {
|
||||
inherit base_url api_key;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
include = [
|
||||
{ template = "sonarr-quality-definition-series"; }
|
||||
{ template = "sonarr-v4-quality-profile-web-1080p"; }
|
||||
{ template = "sonarr-v4-custom-formats-web-1080p"; }
|
||||
];
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"32b367365729d530ca1c124a0b180c64" # Bad Dual Groups
|
||||
"82d40da2bc6923f41e14394075dd4b03" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-1080p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"47435ece6b99a0b477caf360e79ba0bb" # x265 (HD)
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "WEB-1080p";
|
||||
score = 0;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"9b64dff695c2115facf1b6ea59c9bd07" # x265 (no HDR/DV)
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-1080p"; } ];
|
||||
}
|
||||
];
|
||||
media_naming = {
|
||||
series = "default";
|
||||
season = "default";
|
||||
episodes = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
daily = "default";
|
||||
anime = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
arrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers networks;
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops.templates = builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "recyclarr-${arr.shortName}";
|
||||
value.content = builtins.readFile (
|
||||
(pkgs.formats.yaml { }).generate "${arr.shortName}.yaml" (
|
||||
import ./apps/${arr.shortName}.nix {
|
||||
base_url = "http://${arr.shortName}:${builtins.toString arr.port}${arr.urlBase}/";
|
||||
api_key = hmConfig.sops.placeholder."${arr.shortName}/apiKey";
|
||||
}
|
||||
)
|
||||
);
|
||||
}) arrs
|
||||
);
|
||||
|
||||
virtualisation.quadlet.containers = (
|
||||
let
|
||||
arrServices = builtins.map (arr: "${containers.${arr.shortName}._serviceName}.service") arrs;
|
||||
in
|
||||
{
|
||||
# FIXME: https://recyclarr.dev/wiki/behavior/quality-profiles/#language
|
||||
recyclarr = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-recyclarr}";
|
||||
networks = [ networks.media.ref ];
|
||||
volumes = builtins.map (
|
||||
arr:
|
||||
"${
|
||||
hmConfig.sops.templates."recyclarr-${arr.shortName}".path
|
||||
}:/var/lib/recyclarr/configs/${arr.shortName}.yaml:ro"
|
||||
) arrs;
|
||||
};
|
||||
|
||||
unitConfig.After = [ "sops-nix.service" ] ++ arrServices;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
HOST="http://localhost:8989$URL_BASE"
|
||||
# shellcheck disable=SC2034
|
||||
API_SUBPATH="/api/v3"
|
||||
DOWNLOAD_CATEGORY="${DOWNLOAD_CATEGORY:-sonarr}"
|
||||
|
||||
build_transmission_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"enable": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 1,
|
||||
"name": "Transmission",
|
||||
"fields": [
|
||||
{ "name": "host", "value": "transmission" },
|
||||
{ "name": "port", "value": 9091 },
|
||||
{ "name": "urlBase", "value": "" },
|
||||
{ "name": "tvCategory", "value": "$DOWNLOAD_CATEGORY" }
|
||||
],
|
||||
"implementation": "Transmission",
|
||||
"configContract": "TransmissionSettings"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
build_rootfolder_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"path": "$ROOT_FOLDER"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
wait_for_api
|
||||
|
||||
if [ -n "$ROOT_FOLDER" ]; then
|
||||
insert_or_skip_resource "rootfolder" "path" "$ROOT_FOLDER" "$(build_rootfolder_payload)"
|
||||
prune_resources "rootfolder" "path" "$ROOT_FOLDER"
|
||||
fi
|
||||
|
||||
try_forever upsert_resource "downloadclient" "name" "Transmission" "$(build_transmission_payload)"
|
||||
prune_resources "downloadclient" "name" "Transmission"
|
||||
|
||||
delete_resource "qualityprofile" "name" "SD"
|
||||
delete_resource "qualityprofile" "name" "HD-720p"
|
||||
delete_resource "qualityprofile" "name" "HD-1080p"
|
||||
delete_resource "qualityprofile" "name" "HD - 720p/1080p"
|
||||
delete_resource "qualityprofile" "name" "Ultra-HD"
|
@@ -1,9 +1,54 @@
|
||||
{ user, home }:
|
||||
{ ... }:
|
||||
{ config, ... }:
|
||||
let
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
|
||||
radarrs = [
|
||||
(import ./radarr/apps/radarr.nix { inherit hmConfig; })
|
||||
(import ./radarr/apps/radarr-uhd.nix { inherit hmConfig; })
|
||||
(import ./radarr/apps/radarr-anime.nix { inherit hmConfig; })
|
||||
];
|
||||
|
||||
sonarrs = [
|
||||
(import ./sonarr/apps/sonarr.nix { inherit hmConfig; })
|
||||
(import ./sonarr/apps/sonarr-uhd.nix { inherit hmConfig; })
|
||||
(import ./sonarr/apps/sonarr-anime.nix { inherit hmConfig; })
|
||||
];
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(import ./jellyfin { inherit user home; })
|
||||
(import ./arr { inherit user home; })
|
||||
|
||||
(import ./jellyseerr {
|
||||
inherit
|
||||
user
|
||||
home
|
||||
radarrs
|
||||
sonarrs
|
||||
;
|
||||
})
|
||||
|
||||
(import ./prowlarr {
|
||||
inherit
|
||||
user
|
||||
home
|
||||
radarrs
|
||||
sonarrs
|
||||
;
|
||||
})
|
||||
|
||||
(import ./recyclarr {
|
||||
inherit
|
||||
user
|
||||
home
|
||||
radarrs
|
||||
sonarrs
|
||||
;
|
||||
})
|
||||
|
||||
(import ./radarr { inherit user home radarrs; })
|
||||
|
||||
(import ./sonarr { inherit user home sonarrs; })
|
||||
];
|
||||
|
||||
home-manager.users.${user} = {
|
||||
|
@@ -1,5 +1,8 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
JELLYFIN_HOST="${JELLYFIN_HOST:-http://localhost:8096}"
|
||||
JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}"
|
||||
|
||||
until response="$(curl -sf "$JELLYFIN_HOST/System/Info/Public")"; do
|
||||
echo "Waiting for Jellyfin to be ready..."
|
||||
sleep 1
|
||||
|
@@ -0,0 +1,204 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
radarrs,
|
||||
sonarrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
lib,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||
|
||||
arrs = radarrs ++ sonarrs;
|
||||
jellyseerrAutheliaClientId = "s8QyVqBdiEStH5WXeEYNSrEh8ls2xHif0qyTGbC7V8nHNcqHi5NhqHUapCHuVFT4kEtngqgLry2SKOKepQl3AiqCWlhTjlIxr7LI";
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops = {
|
||||
secrets = {
|
||||
"jellyseerr/smtp".sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
"jellyseerr/authelia/password".sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
"jellyseerr/authelia/digest".sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
};
|
||||
|
||||
templates =
|
||||
{
|
||||
jellyseerr-env.content = ''
|
||||
JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"}
|
||||
'';
|
||||
|
||||
jellyseerr.content = builtins.readFile (
|
||||
(pkgs.formats.json { }).generate "setings.json" {
|
||||
main = {
|
||||
applicationTitle = "Jellyseerr";
|
||||
applicationUrl = "https://request.karaolidis.com";
|
||||
cacheImages = true;
|
||||
# https://github.com/fallenbagel/jellyseerr/blob/d53ffca5db9b09bd1055936c2472d54a78f937b8/server/lib/permissions.ts#L7
|
||||
# REQUEST | CREATE_ISSUES | RECENT_VIEW
|
||||
# 32 | 4194304 | 67108864
|
||||
defaultPermissions = 71303200;
|
||||
localLogin = false;
|
||||
mediaServerLogin = false;
|
||||
oidcLogin = true;
|
||||
newPlexLogin = false;
|
||||
mediaServerType = 2;
|
||||
partialRequestsEnabled = true;
|
||||
enableSpecialEpisodes = true;
|
||||
};
|
||||
|
||||
jellyfin = {
|
||||
name = "jupiter";
|
||||
ip = "jellyfin";
|
||||
port = 8096;
|
||||
externalHostname = "https://media.karaolidis.com";
|
||||
jellyfinForgotPasswordUrl = "https://id.karaolidis.com/reset-password/step1";
|
||||
};
|
||||
|
||||
oidc.providers = [
|
||||
{
|
||||
slug = "authelia";
|
||||
name = "Authelia";
|
||||
issuerUrl = "https://id.karaolidis.com";
|
||||
clientId = jellyseerrAutheliaClientId;
|
||||
clientSecret = hmConfig.sops.placeholder."jellyseerr/authelia/password";
|
||||
scopes = lib.strings.concatStringsSep " " [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
"groups"
|
||||
];
|
||||
newUserLogin = true;
|
||||
}
|
||||
];
|
||||
|
||||
radarr = [ ];
|
||||
|
||||
sonarr = [ ];
|
||||
|
||||
public.initialized = true;
|
||||
|
||||
notifications.agents.email = {
|
||||
enabled = true;
|
||||
options = {
|
||||
emailFrom = "jupiter@karaolidis.com";
|
||||
smtpHost = "smtp.protonmail.ch";
|
||||
smtpPort = 587;
|
||||
authUser = "jupiter@karaolidis.com";
|
||||
authPass = hmConfig.sops.placeholder."jellyseerr/smtp";
|
||||
senderName = "Jellyseerr";
|
||||
};
|
||||
};
|
||||
|
||||
network.trustProxy = true;
|
||||
}
|
||||
);
|
||||
|
||||
authelia-jellyseerr.content = builtins.readFile (
|
||||
(pkgs.formats.yaml { }).generate "jellyseerr.yaml" {
|
||||
identity_providers.oidc = {
|
||||
authorization_policies.jellyseerr = {
|
||||
default_policy = "deny";
|
||||
rules = [
|
||||
{
|
||||
policy = "one_factor";
|
||||
subject = "group:jellyfin";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
clients = [
|
||||
{
|
||||
client_id = jellyseerrAutheliaClientId;
|
||||
client_name = "jellyseerr";
|
||||
client_secret = hmConfig.sops.placeholder."jellyseerr/authelia/digest";
|
||||
redirect_uris = [ "https://request.karaolidis.com/login?provider=authelia&callback=true" ];
|
||||
authorization_policy = "jellyseerr";
|
||||
scopes = [
|
||||
"openid"
|
||||
"email"
|
||||
"profile"
|
||||
"groups"
|
||||
];
|
||||
token_endpoint_auth_method = "client_secret_post";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
// builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "jellyseerr-${arr.hostName}";
|
||||
value.content = builtins.readFile (
|
||||
(pkgs.formats.json { }).generate "${arr.hostName}.json" arr.jellyseerrConfig
|
||||
);
|
||||
}) arrs
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.quadlet = {
|
||||
volumes.jellyseerr = { };
|
||||
|
||||
containers = {
|
||||
jellyseerr = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-jellyseerr}";
|
||||
networks = [
|
||||
networks.jellyfin.ref
|
||||
networks.media.ref
|
||||
networks.traefik.ref
|
||||
];
|
||||
volumes =
|
||||
let
|
||||
preStart = pkgs.writeTextFile {
|
||||
name = "pre-start.sh";
|
||||
executable = true;
|
||||
text = builtins.readFile ./pre-start.sh;
|
||||
};
|
||||
in
|
||||
[
|
||||
"${preStart}:/etc/jellyseerr/pre-start.sh:ro"
|
||||
"${hmConfig.sops.templates.jellyseerr.path}:/etc/jellyseerr/settings.default.json:ro"
|
||||
"${volumes.jellyseerr.ref}:/var/lib/jellyseerr"
|
||||
]
|
||||
++ builtins.map (
|
||||
radarr:
|
||||
"${
|
||||
hmConfig.sops.templates."jellyseerr-${radarr.hostName}".path
|
||||
}:/etc/jellyseerr/apps/radarr/${radarr.hostName}.json:ro"
|
||||
) radarrs
|
||||
++ builtins.map (
|
||||
sonarr:
|
||||
"${
|
||||
hmConfig.sops.templates."jellyseerr-${sonarr.hostName}".path
|
||||
}:/etc/jellyseerr/apps/sonarr/${sonarr.hostName}.json:ro"
|
||||
) sonarrs;
|
||||
environmentFiles = [ hmConfig.sops.templates.jellyseerr-env.path ];
|
||||
labels = [
|
||||
"traefik.enable=true"
|
||||
"traefik.http.routers.jellyseerr.rule=Host(`request.karaolidis.com`)"
|
||||
];
|
||||
};
|
||||
|
||||
unitConfig.After =
|
||||
let
|
||||
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
|
||||
in
|
||||
[ "sops-nix.service" ] ++ arrServices;
|
||||
};
|
||||
|
||||
authelia.containerConfig.volumes = [
|
||||
"${hmConfig.sops.templates.authelia-jellyseerr.path}:/etc/authelia/conf.d/jellyseerr.yaml:ro"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
JELLYFIN_HOST="${JELLYFIN_HOST:-http://jellyfin:8096}"
|
||||
JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}"
|
||||
|
||||
until public="$(curl -sf "$JELLYFIN_HOST/System/Info/Public")"; do
|
||||
echo "Waiting for Jellyfin to be ready..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
until [ "$(echo "$public" | jq -r '.StartupWizardCompleted')" = "true" ]; do
|
||||
echo "Waiting for Jellyfin setup wizard to finish..."
|
||||
sleep 1
|
||||
public="$(curl -sf "${JELLYFIN_HOST}/System/Info/Public")"
|
||||
done
|
||||
|
||||
token="$(curl -sf "$JELLYFIN_HOST/Users/AuthenticateByName" \
|
||||
-X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: MediaBrowser Client="jellyseerr-init", Device="sh", DeviceId="sh", Version="1.0"' \
|
||||
--data-raw '{"Username":"'"$JELLYFIN_ADMIN_USERNAME"'","Pw":"'"$JELLYFIN_ADMIN_PASSWORD"'"}' \
|
||||
| jq -r '.AccessToken')"
|
||||
|
||||
keys="$(curl -sf "$JELLYFIN_HOST/Auth/Keys" \
|
||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
||||
|
||||
jellyseerr_key="$(echo "$keys" | jq -r '.Items[] | select(.AppName=="Jellyseerr") | .AccessToken')"
|
||||
|
||||
if [ -z "$jellyseerr_key" ] || [ "$jellyseerr_key" = "null" ]; then
|
||||
curl -sf "$JELLYFIN_HOST/Auth/Keys?App=Jellyseerr" \
|
||||
-X POST \
|
||||
-H 'Authorization: MediaBrowser Token="'"$token"'"'
|
||||
|
||||
keys="$(curl -sf "$JELLYFIN_HOST/Auth/Keys" \
|
||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
||||
|
||||
jellyseerr_key="$(echo "$keys" | jq -r '.Items[] | select(.AppName=="Jellyseerr") | .AccessToken')"
|
||||
fi
|
||||
|
||||
serverId="$(echo "$public" | jq -r '.Id')"
|
||||
|
||||
libraries="$(curl -sf "$JELLYFIN_HOST/Library/VirtualFolders" \
|
||||
-H 'Authorization: MediaBrowser Token="'"$token"'"')"
|
||||
|
||||
libraries="$(
|
||||
echo "$libraries" | jq '[
|
||||
.[]
|
||||
| {
|
||||
id: .ItemId,
|
||||
name: .Name,
|
||||
enabled: .LibraryOptions.Enabled,
|
||||
type: ( .CollectionType | if . == "movies" then "movie" elif . == "tvshows" then "show" else . end )
|
||||
}
|
||||
]'
|
||||
)"
|
||||
|
||||
tmpfile="$(mktemp)"
|
||||
jq -s \
|
||||
--arg serverId "$serverId" \
|
||||
--arg apiKey "$jellyseerr_key" \
|
||||
--argjson libraries "$libraries" \
|
||||
'.[0] * .[1] # merge default + existing
|
||||
| .jellyfin.serverId = $serverId
|
||||
| .jellyfin.apiKey = $apiKey
|
||||
| .jellyfin.libraries = $libraries' \
|
||||
/var/lib/jellyseerr/settings.json \
|
||||
/etc/jellyseerr/settings.default.json \
|
||||
> "$tmpfile"
|
||||
mv "$tmpfile" /var/lib/jellyseerr/settings.json
|
||||
|
||||
try_forever() {
|
||||
until "$@" 2>&1; do
|
||||
echo "Try failed: $* - retrying in 1s"
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait_for_api() {
|
||||
try_forever curl -sf -H "X-Api-Key: $API_KEY" "$HOST$API_SUBPATH/health"
|
||||
echo "$HOST$API_SUBPATH is up!"
|
||||
}
|
||||
|
||||
call() {
|
||||
method="$1"
|
||||
path="$2"
|
||||
data="${3:-}"
|
||||
|
||||
if [ -n "$data" ]; then
|
||||
curl -sf \
|
||||
-X "$method" \
|
||||
-H "X-Api-Key: $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "$data" \
|
||||
"$HOST$API_SUBPATH/$path"
|
||||
else
|
||||
curl -sf \
|
||||
-X "$method" \
|
||||
-H "X-Api-Key: $API_KEY" \
|
||||
"$HOST$API_SUBPATH/$path"
|
||||
fi
|
||||
}
|
||||
|
||||
get_resources() {
|
||||
call GET "$1"
|
||||
}
|
||||
|
||||
get_resource_id() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
|
||||
get_resources "$endpoint" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field] == $name) | .id // empty'
|
||||
}
|
||||
|
||||
fetch_profile() {
|
||||
cfg="$(cat "$1")"
|
||||
|
||||
HOST="http://$(echo "$cfg" | jq -r '.hostname'):$(echo "$cfg" | jq -r '.port')$(echo "$cfg" | jq -r '.baseUrl')"
|
||||
API_SUBPATH="/api/v3"
|
||||
API_KEY="$(echo "$cfg" | jq -r '.apiKey')"
|
||||
|
||||
wait_for_api
|
||||
|
||||
profile_name="$(echo "$cfg" | jq -r '.activeProfileName')"
|
||||
profile_id="$(get_resource_id qualityProfile name "$profile_name")"
|
||||
}
|
||||
|
||||
radarr_json='[]'
|
||||
for f in /etc/jellyseerr/apps/radarr/*.json; do
|
||||
fetch_profile "$f"
|
||||
enriched="$(echo "$cfg" | jq --argjson profile_id "$profile_id" '. + { activeProfileId: $profile_id }')"
|
||||
radarr_json="$(echo "$radarr_json" "$enriched" | jq -s '.[0] + [.[1]]')"
|
||||
done
|
||||
|
||||
sonarr_json='[]'
|
||||
for f in /etc/jellyseerr/apps/sonarr/*.json; do
|
||||
fetch_profile "$f"
|
||||
enriched="$(echo "$cfg" | jq --argjson profile_id "$profile_id" '. + { activeProfileId: $profile_id, activeAnimeProfileId: $profile_id }')"
|
||||
sonarr_json="$(echo "$sonarr_json" "$enriched" | jq -s '.[0] + [.[1]]')"
|
||||
done
|
||||
|
||||
tmpfile=$(mktemp)
|
||||
jq -s \
|
||||
--argjson libs "$libraries" \
|
||||
--argjson radarr "$radarr_json" \
|
||||
--argjson sonarr "$sonarr_json" \
|
||||
--arg serverId "$serverId" \
|
||||
--arg apiKey "$jellyseerr_key" \
|
||||
'.[0] * .[1]
|
||||
| .jellyfin.serverId = $serverId
|
||||
| .jellyfin.apiKey = $apiKey
|
||||
| .jellyfin.libraries = $libs
|
||||
| .radarr = $radarr
|
||||
| .sonarr = $sonarr' \
|
||||
/var/lib/jellyseerr/settings.json \
|
||||
/etc/jellyseerr/settings.default.json \
|
||||
> "$tmpfile"
|
||||
mv "$tmpfile" /var/lib/jellyseerr/settings.json
|
@@ -0,0 +1,102 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
radarrs,
|
||||
sonarrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||
arrs = radarrs ++ sonarrs;
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops = {
|
||||
secrets."prowlarr/apiKey".sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
|
||||
templates =
|
||||
{
|
||||
prowlarr-env.content = ''
|
||||
API_KEY=${hmConfig.sops.placeholder."prowlarr/apiKey"}
|
||||
'';
|
||||
}
|
||||
// builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "prowlarr-${arr.hostName}";
|
||||
value.content = builtins.readFile (
|
||||
(pkgs.formats.json { }).generate "${arr.hostName}.json" arr.prowlarrConfig
|
||||
);
|
||||
}) arrs
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.quadlet = {
|
||||
networks.flaresolverr = { };
|
||||
|
||||
volumes.prowlarr = { };
|
||||
|
||||
containers = {
|
||||
flaresolverr.containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-flaresolverr}";
|
||||
networks = [ networks.flaresolverr.ref ];
|
||||
};
|
||||
|
||||
prowlarr = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-prowlarr}";
|
||||
networks = [
|
||||
networks.media.ref
|
||||
networks.transmission.ref
|
||||
networks.flaresolverr.ref
|
||||
networks.traefik.ref
|
||||
];
|
||||
volumes =
|
||||
let
|
||||
postStart = pkgs.writeTextFile {
|
||||
name = "post-start.sh";
|
||||
executable = true;
|
||||
text = builtins.readFile ./post-start.sh;
|
||||
};
|
||||
in
|
||||
[
|
||||
"${postStart}:/etc/prowlarr/post-start.sh:ro"
|
||||
"${volumes.prowlarr.ref}:/var/lib/prowlarr"
|
||||
]
|
||||
++ builtins.map (
|
||||
arr:
|
||||
"${
|
||||
hmConfig.sops.templates."prowlarr-${arr.hostName}".path
|
||||
}:/etc/prowlarr/apps/${arr.hostName}.json:ro"
|
||||
) arrs;
|
||||
environments.URL_BASE = "/manage/indexers";
|
||||
environmentFiles = [ hmConfig.sops.templates.prowlarr-env.path ];
|
||||
labels = [
|
||||
"traefik.enable=true"
|
||||
"traefik.http.routers.prowlarr.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/indexers`)"
|
||||
"traefik.http.routers.prowlarr.middlewares=authelia@docker"
|
||||
];
|
||||
};
|
||||
|
||||
unitConfig.After =
|
||||
let
|
||||
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
|
||||
in
|
||||
[
|
||||
"${containers.transmission._serviceName}.service"
|
||||
"${containers.flaresolverr._serviceName}.service"
|
||||
"sops-nix.service"
|
||||
]
|
||||
++ arrServices;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -5,6 +5,130 @@ HOST="http://localhost:9696$URL_BASE"
|
||||
# shellcheck disable=SC2034
|
||||
API_SUBPATH="/api/v1"
|
||||
|
||||
try_forever() {
|
||||
until "$@" 2>&1; do
|
||||
echo "Try failed: $* - retrying in 1s"
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
try() {
|
||||
attempts="$1"
|
||||
shift
|
||||
|
||||
delay=1
|
||||
count=1
|
||||
|
||||
while [ "$count" -le "$attempts" ]; do
|
||||
if "$@" 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$count" -lt "$attempts" ]; then
|
||||
echo "Attempt $count/$attempts failed, retrying in ${delay}s..."
|
||||
sleep "$delay"
|
||||
delay="$(( delay * 2 ))"
|
||||
fi
|
||||
|
||||
count="$(( count + 1 ))"
|
||||
done
|
||||
|
||||
echo "All $attempts attempts failed for: $*"
|
||||
}
|
||||
|
||||
wait_for_api() {
|
||||
try_forever curl -sf -H "X-Api-Key: $API_KEY" "$HOST$API_SUBPATH/health"
|
||||
echo "API is up!"
|
||||
}
|
||||
|
||||
call() {
|
||||
method="$1"
|
||||
path="$2"
|
||||
data="${3:-}"
|
||||
|
||||
if [ -n "$data" ]; then
|
||||
curl -sf \
|
||||
-X "$method" \
|
||||
-H "X-Api-Key: $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "$data" \
|
||||
"$HOST$API_SUBPATH/$path"
|
||||
else
|
||||
curl -sf \
|
||||
-X "$method" \
|
||||
-H "X-Api-Key: $API_KEY" \
|
||||
"$HOST$API_SUBPATH/$path"
|
||||
fi
|
||||
}
|
||||
|
||||
get_resources() {
|
||||
call GET "$1"
|
||||
}
|
||||
|
||||
get_resource_id() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
|
||||
get_resources "$endpoint" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field] == $name) | .id // empty'
|
||||
}
|
||||
|
||||
insert_or_skip_resource() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
json="$4"
|
||||
|
||||
all="$(get_resources "$endpoint")"
|
||||
id="$(printf '%s' "$all" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field]==$name) | .id')"
|
||||
|
||||
if [ -n "$id" ] && [ "$id" != "null" ]; then
|
||||
echo "Skipping existing $endpoint '$name' (id=$id)"
|
||||
else
|
||||
echo "Creating $endpoint '$name'"
|
||||
call POST "$endpoint?forceSave=true" "$json"
|
||||
fi
|
||||
}
|
||||
|
||||
upsert_resource() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
json="$4"
|
||||
|
||||
all="$(get_resources "$endpoint")"
|
||||
id="$(printf '%s' "$all" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field]==$name) | .id')"
|
||||
|
||||
if [ -n "$id" ] && [ "$id" != "null" ]; then
|
||||
echo "Updating $endpoint '$name' (id=$id)"
|
||||
call PUT "$endpoint/$id?forceSave=true" "$json"
|
||||
else
|
||||
echo "Creating $endpoint '$name'"
|
||||
call POST "$endpoint?forceSave=true" "$json"
|
||||
fi
|
||||
}
|
||||
|
||||
prune_resources() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
|
||||
shift 2
|
||||
|
||||
keep_list="$(printf '%s\n' "$@" | jq -R . | jq -s .)"
|
||||
resources="$(get_resources "$endpoint")"
|
||||
|
||||
printf '%s' "$resources" | jq -c '.[]' | while IFS= read -r client; do
|
||||
name="$(printf '%s' "$client" | jq -r --arg field "$ident_field" '.[$field]')"
|
||||
id="$(printf '%s' "$client" | jq -r '.id')"
|
||||
found="$(printf '%s' "$keep_list" | jq -r --arg name "$name" 'index($name)')"
|
||||
|
||||
if [ "$found" = "null" ]; then
|
||||
echo "Deleting extra $endpoint '$name' (id=$id)"
|
||||
call DELETE "$endpoint/$id" || echo "failed to delete $name, continuing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
build_transmission_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
@@ -62,8 +186,8 @@ build_cardigann_indexer_payload() {
|
||||
--argjson priority "$priority" \
|
||||
--arg definition_file "$definition_file" \
|
||||
--arg base_url "$base_url" \
|
||||
--argjson extra_fields "${extra_fields_json:-[]}" \
|
||||
--argjson tags "${tags_json:-[]}" '
|
||||
--argjson extra_fields "$extra_fields_json" \
|
||||
--argjson tags "$tags_json" '
|
||||
{
|
||||
enable: true,
|
||||
appProfileId: 1,
|
||||
@@ -104,18 +228,18 @@ done
|
||||
# shellcheck disable=SC2086
|
||||
eval "prune_resources \"applications\" \"name\" $app_names"
|
||||
|
||||
try 5 upsert_resource "indexer" "name" "1337x" "$(build_cardigann_indexer_payload "1337x" 25 "1337x" "https://1337x.to/" "" "[$flaresolverr_id]")"
|
||||
try 5 upsert_resource "indexer" "name" "Internet Archive" "$(build_cardigann_indexer_payload "Internet Archive" 25 "internetarchive" "https://archive.org/")"
|
||||
try 5 upsert_resource "indexer" "name" "kickasstorrents.to" "$(build_cardigann_indexer_payload "kickasstorrents.to" 25 "kickasstorrents-to" "https://kickass.torrentbay.st/" "" "[$flaresolverr_id]")"
|
||||
try 5 upsert_resource "indexer" "name" "kickasstorrents.ws" "$(build_cardigann_indexer_payload "kickasstorrents.ws" 25 "kickasstorrents-ws" "https://kickass.ws/")"
|
||||
try 5 upsert_resource "indexer" "name" "LimeTorrents" "$(build_cardigann_indexer_payload "LimeTorrents" 25 "limetorrents" "https://www.limetorrents.lol/")"
|
||||
try 5 upsert_resource "indexer" "name" "Nyaa.si" "$(build_cardigann_indexer_payload "Nyaa.si" 25 "nyaasi" "https://nyaa.si/" '[{"name":"sonarr_compatibility","value":true},{"name":"strip_s01","value":true},{"name":"radarr_compatibility","value":true}]')"
|
||||
try 5 upsert_resource "indexer" "name" "The Pirate Bay" "$(build_cardigann_indexer_payload "The Pirate Bay" 25 "thepiratebay" "https://thepiratebay.org/")"
|
||||
try 5 upsert_resource "indexer" "name" "TheRARBG" "$(build_cardigann_indexer_payload "TheRARBG" 25 "therarbg" "https://therarbg.to/")"
|
||||
try 5 upsert_resource "indexer" "name" "Torlock" "$(build_cardigann_indexer_payload "Torlock" 25 "torlock" "https://www.torlock.com/")"
|
||||
try 5 upsert_resource "indexer" "name" "TorrentDownload" "$(build_cardigann_indexer_payload "TorrentDownload" 25 "torrentdownload" "https://www.torrentdownload.info/")"
|
||||
try 5 upsert_resource "indexer" "name" "Torrent Downloads" "$(build_cardigann_indexer_payload "Torrent Downloads" 25 "torrentdownloads" "https://www.torrentdownloads.pro/")"
|
||||
try 5 upsert_resource "indexer" "name" "YourBittorrent" "$(build_cardigann_indexer_payload "YourBittorrent" 25 "yourbittorrent" "https://yourbittorrent.com/")"
|
||||
try 5 upsert_resource "indexer" "name" "1337x" "$(build_cardigann_indexer_payload "1337x" 25 "1337x" "https://1337x.to/" "[]" "[$flaresolverr_id]")"
|
||||
try 5 upsert_resource "indexer" "name" "Internet Archive" "$(build_cardigann_indexer_payload "Internet Archive" 25 "internetarchive" "https://archive.org/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "kickasstorrents.to" "$(build_cardigann_indexer_payload "kickasstorrents.to" 25 "kickasstorrents-to" "https://kickass.torrentbay.st/" "[]" "[$flaresolverr_id]")"
|
||||
try 5 upsert_resource "indexer" "name" "kickasstorrents.ws" "$(build_cardigann_indexer_payload "kickasstorrents.ws" 25 "kickasstorrents-ws" "https://kickass.ws/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "LimeTorrents" "$(build_cardigann_indexer_payload "LimeTorrents" 25 "limetorrents" "https://www.limetorrents.lol/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "Nyaa.si" "$(build_cardigann_indexer_payload "Nyaa.si" 25 "nyaasi" "https://nyaa.si/" '[{"name":"sonarr_compatibility","value":true},{"name":"strip_s01","value":true},{"name":"radarr_compatibility","value":true}]')" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "The Pirate Bay" "$(build_cardigann_indexer_payload "The Pirate Bay" 25 "thepiratebay" "https://thepiratebay.org/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "TheRARBG" "$(build_cardigann_indexer_payload "TheRARBG" 25 "therarbg" "https://therarbg.to/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "Torlock" "$(build_cardigann_indexer_payload "Torlock" 25 "torlock" "https://www.torlock.com/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "TorrentDownload" "$(build_cardigann_indexer_payload "TorrentDownload" 25 "torrentdownload" "https://www.torrentdownload.info/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "Torrent Downloads" "$(build_cardigann_indexer_payload "Torrent Downloads" 25 "torrentdownloads" "https://www.torrentdownloads.pro/")" "[]" "[]"
|
||||
try 5 upsert_resource "indexer" "name" "YourBittorrent" "$(build_cardigann_indexer_payload "YourBittorrent" 25 "yourbittorrent" "https://yourbittorrent.com/")" "[]" "[]"
|
||||
|
||||
prune_resources "indexer" "name" "1337x" "Internet Archive" "kickasstorrents.to" "kickasstorrents.ws" "LimeTorrents" "Nyaa.si" "The Pirate Bay" "TheRARBG" "Torlock" "TorrentDownload" "Torrent Downloads" "YourBittorrent"
|
||||
|
@@ -0,0 +1,88 @@
|
||||
rec {
|
||||
mkUrl =
|
||||
{
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
}:
|
||||
"http://${hostName}:${builtins.toString port}${urlBase}/";
|
||||
|
||||
mkProwlarrConfig =
|
||||
{
|
||||
name,
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
apiKey,
|
||||
}:
|
||||
{
|
||||
inherit name;
|
||||
enable = true;
|
||||
implementation = "Radarr";
|
||||
configContract = "RadarrSettings";
|
||||
syncLevel = "fullSync";
|
||||
fields = [
|
||||
{
|
||||
name = "prowlarrUrl";
|
||||
value = "http://prowlarr:9696";
|
||||
}
|
||||
{
|
||||
name = "baseUrl";
|
||||
value = mkUrl { inherit hostName port urlBase; };
|
||||
}
|
||||
{
|
||||
name = "apiKey";
|
||||
value = apiKey;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
mkRecyclarrConfig =
|
||||
{
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
apiKey,
|
||||
extraConfig,
|
||||
}:
|
||||
{
|
||||
radarr.${hostName} = {
|
||||
base_url = mkUrl { inherit hostName port urlBase; };
|
||||
api_key = apiKey;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
} // extraConfig;
|
||||
};
|
||||
|
||||
mkJellyseerrConfig =
|
||||
{
|
||||
name,
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
mediaFolderBase,
|
||||
apiKey,
|
||||
id,
|
||||
recyclarrProfile,
|
||||
is4k,
|
||||
isAnime,
|
||||
}:
|
||||
{
|
||||
inherit
|
||||
name
|
||||
port
|
||||
apiKey
|
||||
id
|
||||
is4k
|
||||
;
|
||||
|
||||
hostname = hostName;
|
||||
baseUrl = urlBase;
|
||||
activeProfileName = recyclarrProfile;
|
||||
activeDirectory = "/var/lib/media${mediaFolderBase}";
|
||||
minimumAvailability = "released";
|
||||
isDefault = !isAnime;
|
||||
externalUrl = "https://media.karaolidis.com${urlBase}";
|
||||
syncEnabled = true;
|
||||
};
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
{ hmConfig }:
|
||||
let
|
||||
inherit (import ./common.nix) mkProwlarrConfig mkRecyclarrConfig mkJellyseerrConfig;
|
||||
in
|
||||
rec {
|
||||
name = "Radarr (Anime)";
|
||||
hostName = "radarr-anime";
|
||||
urlBase = "/manage/anime/films";
|
||||
mediaFolderBase = "/anime/films";
|
||||
port = 7878;
|
||||
|
||||
prowlarrConfig = mkProwlarrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
};
|
||||
|
||||
recyclarrConfig = mkRecyclarrConfig {
|
||||
inherit hostName port urlBase;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
|
||||
extraConfig = {
|
||||
include = [
|
||||
{ template = "radarr-quality-definition-anime"; }
|
||||
{ template = "radarr-quality-profile-anime"; }
|
||||
{ template = "radarr-custom-formats-anime"; }
|
||||
];
|
||||
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"064af5f084a0a24458cc8ecd3220f93f" # Uncensored
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"a5d148168c4506b55cf53984107c396e" # 10bit
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"4a3b087eea2ce012fcc1ce319259a3be" # Anime Dual Audio
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
media_naming = {
|
||||
folder = "default";
|
||||
movie = {
|
||||
rename = true;
|
||||
standard = "anime";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jellyseerrConfig = mkJellyseerrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
mediaFolderBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
id = 2;
|
||||
recyclarrProfile = "Remux-1080p - Anime";
|
||||
is4k = false;
|
||||
isAnime = true;
|
||||
};
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
{ hmConfig }:
|
||||
let
|
||||
inherit (import ./common.nix) mkProwlarrConfig mkRecyclarrConfig mkJellyseerrConfig;
|
||||
in
|
||||
rec {
|
||||
name = "Radarr (UHD)";
|
||||
hostName = "radarr-uhd";
|
||||
urlBase = "/manage/films/uhd";
|
||||
mediaFolderBase = "/films";
|
||||
port = 7878;
|
||||
|
||||
prowlarrConfig = mkProwlarrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
};
|
||||
|
||||
recyclarrConfig = mkRecyclarrConfig {
|
||||
inherit hostName port urlBase;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
|
||||
extraConfig = {
|
||||
include = [
|
||||
{ template = "radarr-quality-definition-movie"; }
|
||||
{ template = "radarr-quality-profile-uhd-bluray-web"; }
|
||||
{ template = "radarr-custom-formats-uhd-bluray-web"; }
|
||||
];
|
||||
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"570bc9ebecd92723d2d21500f4be314c" # Remaster
|
||||
"eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster
|
||||
"e0c07d59beb37348e975a930d5e50319" # Criterion Collection
|
||||
"9d27d9d2181838f76dee150882bdc58c" # Masters of Cinema
|
||||
"db9b4c4b53d312a3ca5f1378f6440fc9" # Vinegar Syndrome
|
||||
"957d0f44b592285f26449575e8b1167e" # Special Edition
|
||||
"eecf3a857724171f968a66cb5719e152" # IMAX
|
||||
"9f6cbff8cfe4ebbc1bde14c7b7bec0de" # IMAX Enhanced
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"b6832f586342ef70d9c128d40c07b872" # Bad Dual Groups
|
||||
"cc444569854e9de0b084ab2b8b1532b2" # Black and White Editions
|
||||
"ae9b7c9ebde1f3bd336a8cbd1ec4c5e5" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"dc98083864ea246d05a42df0d05f81cc" # x265 (HD)
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "UHD Bluray + WEB";
|
||||
score = 0;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"839bea857ed2c0a8e084f3cbdbd65ecb" # x265 (no HDR/DV)
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"923b6abef9b17f937fab56cfcf89e1f1" # DV (WEBDL)
|
||||
"b17886cb4158d9fea189859409975758" # HDR10Plus Boost
|
||||
"55a5b50cb416dea5a50c4955896217ab" # DV HDR10+ Boost
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"9c38ebb7384dada637be8899efa68e6f" # SDR
|
||||
];
|
||||
assign_scores_to = [ { name = "UHD Bluray + WEB"; } ];
|
||||
}
|
||||
];
|
||||
|
||||
media_naming = {
|
||||
folder = "default";
|
||||
movie = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jellyseerrConfig = mkJellyseerrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
mediaFolderBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
id = 1;
|
||||
recyclarrProfile = "UHD Bluray + WEB";
|
||||
is4k = true;
|
||||
isAnime = false;
|
||||
};
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
{ hmConfig }:
|
||||
let
|
||||
inherit (import ./common.nix) mkProwlarrConfig mkRecyclarrConfig mkJellyseerrConfig;
|
||||
in
|
||||
rec {
|
||||
name = "Radarr";
|
||||
hostName = "radarr";
|
||||
urlBase = "/manage/films";
|
||||
mediaFolderBase = "/films";
|
||||
port = 7878;
|
||||
|
||||
prowlarrConfig = mkProwlarrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
};
|
||||
|
||||
recyclarrConfig = mkRecyclarrConfig {
|
||||
inherit hostName port urlBase;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
|
||||
extraConfig = {
|
||||
include = [
|
||||
{ template = "radarr-quality-definition-movie"; }
|
||||
{ template = "radarr-quality-profile-hd-bluray-web"; }
|
||||
{ template = "radarr-custom-formats-hd-bluray-web"; }
|
||||
];
|
||||
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"570bc9ebecd92723d2d21500f4be314c" # Remaster
|
||||
"eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster
|
||||
"e0c07d59beb37348e975a930d5e50319" # Criterion Collection
|
||||
"9d27d9d2181838f76dee150882bdc58c" # Masters of Cinema
|
||||
"db9b4c4b53d312a3ca5f1378f6440fc9" # Vinegar Syndrome
|
||||
"957d0f44b592285f26449575e8b1167e" # Special Edition
|
||||
"eecf3a857724171f968a66cb5719e152" # IMAX
|
||||
"9f6cbff8cfe4ebbc1bde14c7b7bec0de" # IMAX Enhanced
|
||||
];
|
||||
assign_scores_to = [ { name = "HD Bluray + WEB"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"b6832f586342ef70d9c128d40c07b872" # Bad Dual Groups
|
||||
"cc444569854e9de0b084ab2b8b1532b2" # Black and White Editions
|
||||
"ae9b7c9ebde1f3bd336a8cbd1ec4c5e5" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "HD Bluray + WEB"; } ];
|
||||
}
|
||||
];
|
||||
|
||||
media_naming = {
|
||||
folder = "default";
|
||||
movie = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jellyseerrConfig = mkJellyseerrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
mediaFolderBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
id = 0;
|
||||
recyclarrProfile = "HD Bluray + WEB";
|
||||
is4k = false;
|
||||
isAnime = false;
|
||||
};
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
radarrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops = {
|
||||
secrets = builtins.listToAttrs (
|
||||
builtins.map (radarr: {
|
||||
name = "${radarr.hostName}/apiKey";
|
||||
value.sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
}) radarrs
|
||||
);
|
||||
|
||||
templates = builtins.listToAttrs (
|
||||
builtins.map (radarr: {
|
||||
name = "${radarr.hostName}-env";
|
||||
value.content = ''
|
||||
API_KEY=${hmConfig.sops.placeholder."${radarr.hostName}/apiKey"}
|
||||
'';
|
||||
}) radarrs
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.quadlet = {
|
||||
networks.media = { };
|
||||
|
||||
volumes = builtins.listToAttrs (
|
||||
builtins.map (radarr: {
|
||||
name = radarr.hostName;
|
||||
value = { };
|
||||
}) radarrs
|
||||
);
|
||||
|
||||
containers = builtins.listToAttrs (
|
||||
builtins.map (radarr: {
|
||||
name = radarr.hostName;
|
||||
value = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-radarr}";
|
||||
networks = [
|
||||
networks.media.ref
|
||||
networks.transmission.ref
|
||||
networks.traefik.ref
|
||||
];
|
||||
volumes =
|
||||
let
|
||||
postStart = pkgs.writeTextFile {
|
||||
name = "post-start.sh";
|
||||
executable = true;
|
||||
text = builtins.readFile ./post-start.sh;
|
||||
};
|
||||
in
|
||||
[
|
||||
"${postStart}:/etc/radarr/post-start.sh:ro"
|
||||
"${volumes.${radarr.hostName}.ref}:/var/lib/radarr"
|
||||
"/mnt/storage/private/storm/containers/storage/volumes/transmission-data/_data:/var/lib/transmission"
|
||||
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
|
||||
];
|
||||
environments = {
|
||||
INSTANCE_NAME = radarr.name;
|
||||
URL_BASE = radarr.urlBase;
|
||||
ROOT_FOLDER = "/var/lib/media${radarr.mediaFolderBase}";
|
||||
DOWNLOAD_CATEGORY = radarr.hostName;
|
||||
};
|
||||
environmentFiles = [ hmConfig.sops.templates."${radarr.hostName}-env".path ];
|
||||
labels = [
|
||||
"traefik.enable=true"
|
||||
"traefik.http.routers.${radarr.hostName}.rule=Host(`media.karaolidis.com`) && PathPrefix(`${radarr.urlBase}`)"
|
||||
"traefik.http.routers.${radarr.hostName}.middlewares=authelia@docker"
|
||||
];
|
||||
};
|
||||
|
||||
unitConfig.After = [
|
||||
"${containers.transmission._serviceName}.service"
|
||||
"sops-nix.service"
|
||||
];
|
||||
};
|
||||
}) radarrs
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
HOST="http://localhost:7878$URL_BASE"
|
||||
API_SUBPATH="/api/v3"
|
||||
DOWNLOAD_CATEGORY="${DOWNLOAD_CATEGORY:-radarr}"
|
||||
|
||||
try_forever() {
|
||||
until "$@" 2>&1; do
|
||||
echo "Try failed: $* - retrying in 1s"
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait_for_api() {
|
||||
try_forever curl -sf -H "X-Api-Key: $API_KEY" "$HOST$API_SUBPATH/health"
|
||||
echo "API is up!"
|
||||
}
|
||||
|
||||
call() {
|
||||
method="$1"
|
||||
path="$2"
|
||||
data="${3:-}"
|
||||
|
||||
if [ -n "$data" ]; then
|
||||
curl -sf \
|
||||
-X "$method" \
|
||||
-H "X-Api-Key: $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "$data" \
|
||||
"$HOST$API_SUBPATH/$path"
|
||||
else
|
||||
curl -sf \
|
||||
-X "$method" \
|
||||
-H "X-Api-Key: $API_KEY" \
|
||||
"$HOST$API_SUBPATH/$path"
|
||||
fi
|
||||
}
|
||||
|
||||
get_resources() {
|
||||
call GET "$1"
|
||||
}
|
||||
|
||||
get_resource_id() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
|
||||
get_resources "$endpoint" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field] == $name) | .id // empty'
|
||||
}
|
||||
|
||||
insert_or_skip_resource() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
json="$4"
|
||||
|
||||
all="$(get_resources "$endpoint")"
|
||||
id="$(printf '%s' "$all" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field]==$name) | .id')"
|
||||
|
||||
if [ -n "$id" ] && [ "$id" != "null" ]; then
|
||||
echo "Skipping existing $endpoint '$name' (id=$id)"
|
||||
else
|
||||
echo "Creating $endpoint '$name'"
|
||||
call POST "$endpoint?forceSave=true" "$json"
|
||||
fi
|
||||
}
|
||||
|
||||
upsert_resource() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
json="$4"
|
||||
|
||||
all="$(get_resources "$endpoint")"
|
||||
id="$(printf '%s' "$all" | jq -r --arg field "$ident_field" --arg name "$name" '.[] | select(.[$field]==$name) | .id')"
|
||||
|
||||
if [ -n "$id" ] && [ "$id" != "null" ]; then
|
||||
echo "Updating $endpoint '$name' (id=$id)"
|
||||
call PUT "$endpoint/$id?forceSave=true" "$json"
|
||||
else
|
||||
echo "Creating $endpoint '$name'"
|
||||
call POST "$endpoint?forceSave=true" "$json"
|
||||
fi
|
||||
}
|
||||
|
||||
prune_resources() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
|
||||
shift 2
|
||||
|
||||
keep_list="$(printf '%s\n' "$@" | jq -R . | jq -s .)"
|
||||
resources="$(get_resources "$endpoint")"
|
||||
|
||||
printf '%s' "$resources" | jq -c '.[]' | while IFS= read -r client; do
|
||||
name="$(printf '%s' "$client" | jq -r --arg field "$ident_field" '.[$field]')"
|
||||
id="$(printf '%s' "$client" | jq -r '.id')"
|
||||
found="$(printf '%s' "$keep_list" | jq -r --arg name "$name" 'index($name)')"
|
||||
|
||||
if [ "$found" = "null" ]; then
|
||||
echo "Deleting extra $endpoint '$name' (id=$id)"
|
||||
call DELETE "$endpoint/$id" || echo "failed to delete $name, continuing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
delete_resource() {
|
||||
endpoint="$1"
|
||||
ident_field="$2"
|
||||
name="$3"
|
||||
|
||||
id="$(get_resource_id "$endpoint" "$ident_field" "$name")"
|
||||
|
||||
if [ -n "$id" ]; then
|
||||
echo "Deleting $endpoint '$name' (id=$id)"
|
||||
call DELETE "$endpoint/$id" || echo "Failed to delete $name, continuing"
|
||||
else
|
||||
echo "No $endpoint resource named '$name' found - skipping"
|
||||
fi
|
||||
}
|
||||
|
||||
build_transmission_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"enable": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 1,
|
||||
"name": "Transmission",
|
||||
"fields": [
|
||||
{ "name": "host", "value": "transmission" },
|
||||
{ "name": "port", "value": 9091 },
|
||||
{ "name": "urlBase", "value": "" },
|
||||
{ "name": "movieCategory", "value": "$DOWNLOAD_CATEGORY" }
|
||||
],
|
||||
"implementation": "Transmission",
|
||||
"configContract": "TransmissionSettings"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
build_rootfolder_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"path": "$ROOT_FOLDER"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
wait_for_api
|
||||
|
||||
if [ -n "$ROOT_FOLDER" ]; then
|
||||
insert_or_skip_resource "rootfolder" "path" "$ROOT_FOLDER" "$(build_rootfolder_payload)"
|
||||
prune_resources "rootfolder" "path" "$ROOT_FOLDER"
|
||||
fi
|
||||
|
||||
try_forever upsert_resource "downloadclient" "name" "Transmission" "$(build_transmission_payload)"
|
||||
prune_resources "downloadclient" "name" "Transmission"
|
||||
|
||||
delete_resource "qualityprofile" "name" "SD"
|
||||
delete_resource "qualityprofile" "name" "HD-720p"
|
||||
delete_resource "qualityprofile" "name" "HD-1080p"
|
||||
delete_resource "qualityprofile" "name" "HD - 720p/1080p"
|
||||
delete_resource "qualityprofile" "name" "Ultra-HD"
|
||||
|
||||
try_forever sh -c "[ $(get_resources qualityprofile | jq length) -eq 2 ]"
|
||||
get_resources qualityprofile | jq -c '.[]' | while IFS= read -r profile; do
|
||||
id="$(printf '%s' "$profile" | jq -r '.id')"
|
||||
patched="$(printf '%s' "$profile" | jq '.language.id = -2 | .language.Name = "Original"')"
|
||||
echo "Patching qualityprofile id=$id with language = Original"
|
||||
call PUT "qualityprofile/$id?forceSave=true" "$patched"
|
||||
done
|
@@ -0,0 +1,53 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
radarrs,
|
||||
sonarrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers networks;
|
||||
arrs = radarrs ++ sonarrs;
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops.templates = builtins.listToAttrs (
|
||||
builtins.map (arr: {
|
||||
name = "recyclarr-${arr.hostName}";
|
||||
value.content = builtins.readFile (
|
||||
(pkgs.formats.yaml { }).generate "${arr.hostName}.yaml" arr.recyclarrConfig
|
||||
);
|
||||
}) arrs
|
||||
);
|
||||
|
||||
virtualisation.quadlet.containers = {
|
||||
# FIXME: https://recyclarr.dev/wiki/behavior/quality-profiles/#language
|
||||
recyclarr = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-recyclarr}";
|
||||
networks = [ networks.media.ref ];
|
||||
volumes = builtins.map (
|
||||
arr:
|
||||
"${
|
||||
hmConfig.sops.templates."recyclarr-${arr.hostName}".path
|
||||
}:/var/lib/recyclarr/configs/${arr.hostName}.yaml:ro"
|
||||
) arrs;
|
||||
};
|
||||
|
||||
unitConfig.After =
|
||||
let
|
||||
arrServices = builtins.map (arr: "${containers.${arr.hostName}._serviceName}.service") arrs;
|
||||
in
|
||||
[ "sops-nix.service" ] ++ arrServices;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
rec {
|
||||
mkUrl =
|
||||
{
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
}:
|
||||
"http://${hostName}:${builtins.toString port}${urlBase}/";
|
||||
|
||||
mkProwlarrConfig =
|
||||
{
|
||||
name,
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
apiKey,
|
||||
}:
|
||||
{
|
||||
inherit name;
|
||||
enable = true;
|
||||
implementation = "Sonarr";
|
||||
configContract = "SonarrSettings";
|
||||
syncLevel = "fullSync";
|
||||
fields = [
|
||||
{
|
||||
name = "prowlarrUrl";
|
||||
value = "http://prowlarr:9696";
|
||||
}
|
||||
{
|
||||
name = "baseUrl";
|
||||
value = mkUrl { inherit hostName port urlBase; };
|
||||
}
|
||||
{
|
||||
name = "apiKey";
|
||||
value = apiKey;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
mkRecyclarrConfig =
|
||||
{
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
apiKey,
|
||||
extraConfig,
|
||||
}:
|
||||
{
|
||||
sonarr.${hostName} = {
|
||||
base_url = mkUrl { inherit hostName port urlBase; };
|
||||
api_key = apiKey;
|
||||
delete_old_custom_formats = true;
|
||||
replace_existing_custom_formats = true;
|
||||
} // extraConfig;
|
||||
};
|
||||
|
||||
mkJellyseerrConfig =
|
||||
{
|
||||
name,
|
||||
hostName,
|
||||
port,
|
||||
urlBase,
|
||||
mediaFolderBase,
|
||||
apiKey,
|
||||
id,
|
||||
recyclarrProfile,
|
||||
is4k,
|
||||
isAnime,
|
||||
}:
|
||||
{
|
||||
inherit
|
||||
name
|
||||
port
|
||||
apiKey
|
||||
id
|
||||
is4k
|
||||
;
|
||||
|
||||
hostname = hostName;
|
||||
baseUrl = urlBase;
|
||||
activeProfileName = recyclarrProfile;
|
||||
activeDirectory = "/var/lib/media${mediaFolderBase}";
|
||||
activeAnimeProfileName = recyclarrProfile;
|
||||
activeAnimeDirectory = "/var/lib/media${mediaFolderBase}";
|
||||
isDefault = !isAnime;
|
||||
enableSeasonFolders = true;
|
||||
externalUrl = "https://media.karaolidis.com${urlBase}";
|
||||
syncEnabled = true;
|
||||
};
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
{ hmConfig }:
|
||||
let
|
||||
inherit (import ./common.nix) mkProwlarrConfig mkRecyclarrConfig mkJellyseerrConfig;
|
||||
in
|
||||
rec {
|
||||
name = "Sonarr (Anime)";
|
||||
hostName = "sonarr-anime";
|
||||
urlBase = "/manage/anime/shows";
|
||||
mediaFolderBase = "/anime/shows";
|
||||
port = 8989;
|
||||
|
||||
prowlarrConfig = mkProwlarrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
};
|
||||
|
||||
recyclarrConfig = mkRecyclarrConfig {
|
||||
inherit hostName port urlBase;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
|
||||
extraConfig = {
|
||||
include = [
|
||||
{ template = "sonarr-quality-definition-anime"; }
|
||||
{ template = "sonarr-v4-quality-profile-anime"; }
|
||||
{ template = "sonarr-v4-custom-formats-anime"; }
|
||||
];
|
||||
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"026d5aadd1a6b4e550b134cb6c72b3ca" # Uncensored
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"b2550eb333d27b75833e25b8c2557b38" # 10bit
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"418f50b10f1907201b6cfdf881f467b7" # Anime Dual Audio
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "Remux-1080p - Anime";
|
||||
score = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
media_naming = {
|
||||
series = "default";
|
||||
season = "default";
|
||||
episodes = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
daily = "default";
|
||||
anime = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jellyseerrConfig = mkJellyseerrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
mediaFolderBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
id = 2;
|
||||
recyclarrProfile = "Remux-1080p - Anime";
|
||||
is4k = false;
|
||||
isAnime = true;
|
||||
};
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
{ hmConfig }:
|
||||
let
|
||||
inherit (import ./common.nix) mkProwlarrConfig mkRecyclarrConfig mkJellyseerrConfig;
|
||||
in
|
||||
rec {
|
||||
name = "Sonarr (UHD)";
|
||||
hostName = "sonarr-uhd";
|
||||
urlBase = "/manage/shows/uhd";
|
||||
mediaFolderBase = "/shows";
|
||||
port = 8989;
|
||||
|
||||
prowlarrConfig = mkProwlarrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
};
|
||||
|
||||
recyclarrConfig = mkRecyclarrConfig {
|
||||
inherit hostName port urlBase;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
|
||||
extraConfig = {
|
||||
include = [
|
||||
{ template = "sonarr-quality-definition-series"; }
|
||||
{ template = "sonarr-v4-quality-profile-web-2160p"; }
|
||||
{ template = "sonarr-v4-custom-formats-web-2160p"; }
|
||||
];
|
||||
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"9b27ab6498ec0f31a3353992e19434ca" # DV (WEBDL)
|
||||
"0dad0a507451acddd754fe6dc3a7f5e7" # HDR10+ Boost
|
||||
"385e9e8581d33133c3961bdcdeffb7b4" # DV HDR10+ Boost
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"32b367365729d530ca1c124a0b180c64" # Bad Dual Groups
|
||||
"82d40da2bc6923f41e14394075dd4b03" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"47435ece6b99a0b477caf360e79ba0bb" # x265 (HD)
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "WEB-2160p";
|
||||
score = 0;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"9b64dff695c2115facf1b6ea59c9bd07" # x265 (no HDR/DV)
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"2016d1676f5ee13a5b7257ff86ac9a93" # SDR
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-2160p"; } ];
|
||||
}
|
||||
];
|
||||
|
||||
media_naming = {
|
||||
series = "default";
|
||||
season = "default";
|
||||
episodes = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
daily = "default";
|
||||
anime = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jellyseerrConfig = mkJellyseerrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
mediaFolderBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
id = 1;
|
||||
recyclarrProfile = "WEB-2160p";
|
||||
is4k = true;
|
||||
isAnime = false;
|
||||
};
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
{ hmConfig }:
|
||||
let
|
||||
inherit (import ./common.nix) mkProwlarrConfig mkRecyclarrConfig mkJellyseerrConfig;
|
||||
in
|
||||
rec {
|
||||
name = "Sonarr";
|
||||
hostName = "sonarr";
|
||||
urlBase = "/manage/shows";
|
||||
mediaFolderBase = "/shows";
|
||||
port = 8989;
|
||||
|
||||
prowlarrConfig = mkProwlarrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
};
|
||||
|
||||
recyclarrConfig = mkRecyclarrConfig {
|
||||
inherit hostName port urlBase;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
|
||||
extraConfig = {
|
||||
include = [
|
||||
{ template = "sonarr-quality-definition-series"; }
|
||||
{ template = "sonarr-v4-quality-profile-web-1080p"; }
|
||||
{ template = "sonarr-v4-custom-formats-web-1080p"; }
|
||||
];
|
||||
|
||||
custom_formats = [
|
||||
{
|
||||
trash_ids = [
|
||||
"32b367365729d530ca1c124a0b180c64" # Bad Dual Groups
|
||||
"82d40da2bc6923f41e14394075dd4b03" # No-RlsGroup
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-1080p"; } ];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"47435ece6b99a0b477caf360e79ba0bb" # x265 (HD)
|
||||
];
|
||||
assign_scores_to = [
|
||||
{
|
||||
name = "WEB-1080p";
|
||||
score = 0;
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
trash_ids = [
|
||||
"9b64dff695c2115facf1b6ea59c9bd07" # x265 (no HDR/DV)
|
||||
];
|
||||
assign_scores_to = [ { name = "WEB-1080p"; } ];
|
||||
}
|
||||
];
|
||||
|
||||
media_naming = {
|
||||
series = "default";
|
||||
season = "default";
|
||||
episodes = {
|
||||
rename = true;
|
||||
standard = "default";
|
||||
daily = "default";
|
||||
anime = "default";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jellyseerrConfig = mkJellyseerrConfig {
|
||||
inherit
|
||||
name
|
||||
hostName
|
||||
port
|
||||
urlBase
|
||||
mediaFolderBase
|
||||
;
|
||||
|
||||
apiKey = hmConfig.sops.placeholder."${hostName}/apiKey";
|
||||
id = 0;
|
||||
recyclarrProfile = "WEB-1080p";
|
||||
is4k = false;
|
||||
isAnime = false;
|
||||
};
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
{
|
||||
user,
|
||||
home,
|
||||
sonarrs,
|
||||
}:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
selfPkgs = inputs.self.packages.${system};
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
inherit (hmConfig.virtualisation.quadlet) containers volumes networks;
|
||||
in
|
||||
{
|
||||
home-manager.users.${user} = {
|
||||
sops = {
|
||||
secrets = builtins.listToAttrs (
|
||||
builtins.map (sonarr: {
|
||||
name = "${sonarr.hostName}/apiKey";
|
||||
value.sopsFile = ../../../../../../../secrets/secrets.yaml;
|
||||
}) sonarrs
|
||||
);
|
||||
|
||||
templates = builtins.listToAttrs (
|
||||
builtins.map (sonarr: {
|
||||
name = "${sonarr.hostName}-env";
|
||||
value.content = ''
|
||||
API_KEY=${hmConfig.sops.placeholder."${sonarr.hostName}/apiKey"}
|
||||
'';
|
||||
}) sonarrs
|
||||
);
|
||||
};
|
||||
|
||||
virtualisation.quadlet = {
|
||||
networks.media = { };
|
||||
|
||||
volumes = builtins.listToAttrs (
|
||||
builtins.map (sonarr: {
|
||||
name = sonarr.hostName;
|
||||
value = { };
|
||||
}) sonarrs
|
||||
);
|
||||
|
||||
containers = builtins.listToAttrs (
|
||||
builtins.map (sonarr: {
|
||||
name = sonarr.hostName;
|
||||
value = {
|
||||
containerConfig = {
|
||||
image = "docker-archive:${selfPkgs.docker-sonarr}";
|
||||
networks = [
|
||||
networks.media.ref
|
||||
networks.transmission.ref
|
||||
networks.traefik.ref
|
||||
];
|
||||
volumes =
|
||||
let
|
||||
postStart = pkgs.writeTextFile {
|
||||
name = "post-start.sh";
|
||||
executable = true;
|
||||
text = builtins.readFile ./post-start.sh;
|
||||
};
|
||||
in
|
||||
[
|
||||
"${postStart}:/etc/sonarr/post-start.sh:ro"
|
||||
"${volumes.${sonarr.hostName}.ref}:/var/lib/sonarr"
|
||||
"/mnt/storage/private/storm/containers/storage/volumes/transmission-data/_data:/var/lib/transmission"
|
||||
"/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media"
|
||||
];
|
||||
environments = {
|
||||
INSTANCE_NAME = sonarr.name;
|
||||
URL_BASE = sonarr.urlBase;
|
||||
ROOT_FOLDER = "/var/lib/media${sonarr.mediaFolderBase}";
|
||||
DOWNLOAD_CATEGORY = sonarr.hostName;
|
||||
};
|
||||
environmentFiles = [ hmConfig.sops.templates."${sonarr.hostName}-env".path ];
|
||||
labels = [
|
||||
"traefik.enable=true"
|
||||
"traefik.http.routers.${sonarr.hostName}.rule=Host(`media.karaolidis.com`) && PathPrefix(`${sonarr.urlBase}`)"
|
||||
"traefik.http.routers.${sonarr.hostName}.middlewares=authelia@docker"
|
||||
];
|
||||
};
|
||||
|
||||
unitConfig.After = [
|
||||
"${containers.transmission._serviceName}.service"
|
||||
"sops-nix.service"
|
||||
];
|
||||
};
|
||||
}) sonarrs
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
HOST="http://localhost:8989$URL_BASE"
|
||||
API_SUBPATH="/api/v3"
|
||||
DOWNLOAD_CATEGORY="${DOWNLOAD_CATEGORY:-sonarr}"
|
||||
|
||||
try_forever() {
|
||||
until "$@" 2>&1; do
|
||||
echo "Try failed: $* - retrying in 1s"
|
||||
@@ -7,30 +11,6 @@ try_forever() {
|
||||
done
|
||||
}
|
||||
|
||||
try() {
|
||||
attempts="$1"
|
||||
shift
|
||||
|
||||
delay=1
|
||||
count=1
|
||||
|
||||
while [ "$count" -le "$attempts" ]; do
|
||||
if "$@" 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$count" -lt "$attempts" ]; then
|
||||
echo "Attempt $count/$attempts failed, retrying in ${delay}s..."
|
||||
sleep "$delay"
|
||||
delay="$(( delay * 2 ))"
|
||||
fi
|
||||
|
||||
count="$(( count + 1 ))"
|
||||
done
|
||||
|
||||
echo "All $attempts attempts failed for: $*"
|
||||
}
|
||||
|
||||
wait_for_api() {
|
||||
try_forever curl -sf -H "X-Api-Key: $API_KEY" "$HOST$API_SUBPATH/health"
|
||||
echo "API is up!"
|
||||
@@ -138,3 +118,46 @@ delete_resource() {
|
||||
echo "No $endpoint resource named '$name' found - skipping"
|
||||
fi
|
||||
}
|
||||
|
||||
build_transmission_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"enable": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 1,
|
||||
"name": "Transmission",
|
||||
"fields": [
|
||||
{ "name": "host", "value": "transmission" },
|
||||
{ "name": "port", "value": 9091 },
|
||||
{ "name": "urlBase", "value": "" },
|
||||
{ "name": "tvCategory", "value": "$DOWNLOAD_CATEGORY" }
|
||||
],
|
||||
"implementation": "Transmission",
|
||||
"configContract": "TransmissionSettings"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
build_rootfolder_payload() {
|
||||
cat <<-EOF
|
||||
{
|
||||
"path": "$ROOT_FOLDER"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
wait_for_api
|
||||
|
||||
if [ -n "$ROOT_FOLDER" ]; then
|
||||
insert_or_skip_resource "rootfolder" "path" "$ROOT_FOLDER" "$(build_rootfolder_payload)"
|
||||
prune_resources "rootfolder" "path" "$ROOT_FOLDER"
|
||||
fi
|
||||
|
||||
try_forever upsert_resource "downloadclient" "name" "Transmission" "$(build_transmission_payload)"
|
||||
prune_resources "downloadclient" "name" "Transmission"
|
||||
|
||||
delete_resource "qualityprofile" "name" "SD"
|
||||
delete_resource "qualityprofile" "name" "HD-720p"
|
||||
delete_resource "qualityprofile" "name" "HD-1080p"
|
||||
delete_resource "qualityprofile" "name" "HD - 720p/1080p"
|
||||
delete_resource "qualityprofile" "name" "Ultra-HD"
|
Reference in New Issue
Block a user