diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/default.nix deleted file mode 100644 index cce385f..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/default.nix +++ /dev/null @@ -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 - ); - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/prowlarr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/prowlarr/default.nix deleted file mode 100644 index bce000d..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/prowlarr/default.nix +++ /dev/null @@ -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; - }; - } - ); - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/radarr/post-start.sh b/hosts/jupiter/users/storm/configs/console/podman/media/arr/radarr/post-start.sh deleted file mode 100644 index 0ba6c06..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/radarr/post-start.sh +++ /dev/null @@ -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 diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr-anime.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr-anime.nix deleted file mode 100644 index 067e170..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr-anime.nix +++ /dev/null @@ -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"; - }; - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr-uhd.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr-uhd.nix deleted file mode 100644 index 67d8af4..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr-uhd.nix +++ /dev/null @@ -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"; - }; - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr.nix deleted file mode 100644 index ffd4ec3..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/radarr.nix +++ /dev/null @@ -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"; - }; - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr-anime.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr-anime.nix deleted file mode 100644 index e6c2807..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr-anime.nix +++ /dev/null @@ -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"; - }; - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr-uhd.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr-uhd.nix deleted file mode 100644 index 92b6d0d..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr-uhd.nix +++ /dev/null @@ -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"; - }; - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr.nix deleted file mode 100644 index 672de69..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/apps/sonarr.nix +++ /dev/null @@ -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"; - }; - }; - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/default.nix deleted file mode 100644 index b2df853..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/recyclarr/default.nix +++ /dev/null @@ -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; - }; - } - ); - }; -} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/sonarr/post-start.sh b/hosts/jupiter/users/storm/configs/console/podman/media/arr/sonarr/post-start.sh deleted file mode 100644 index aefd529..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/sonarr/post-start.sh +++ /dev/null @@ -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" diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/default.nix index ed8bab9..5ad6416 100644 --- a/hosts/jupiter/users/storm/configs/console/podman/media/default.nix +++ b/hosts/jupiter/users/storm/configs/console/podman/media/default.nix @@ -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} = { diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/jellyfin/setup.sh b/hosts/jupiter/users/storm/configs/console/podman/media/jellyfin/setup.sh index 48cf451..ec0c249 100644 --- a/hosts/jupiter/users/storm/configs/console/podman/media/jellyfin/setup.sh +++ b/hosts/jupiter/users/storm/configs/console/podman/media/jellyfin/setup.sh @@ -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 diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/jellyseerr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/jellyseerr/default.nix new file mode 100644 index 0000000..1f1cb49 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/jellyseerr/default.nix @@ -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" + ]; + }; + }; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/jellyseerr/pre-start.sh b/hosts/jupiter/users/storm/configs/console/podman/media/jellyseerr/pre-start.sh new file mode 100644 index 0000000..65d8fe0 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/jellyseerr/pre-start.sh @@ -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 diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/default.nix new file mode 100644 index 0000000..67381a8 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/default.nix @@ -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; + }; + }; + }; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/prowlarr/post-start.sh b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh similarity index 55% rename from hosts/jupiter/users/storm/configs/console/podman/media/arr/prowlarr/post-start.sh rename to hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh index f667d01..d056d3b 100644 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/prowlarr/post-start.sh +++ b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh @@ -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" diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/common.nix b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/common.nix new file mode 100644 index 0000000..5fa4b97 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/common.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr-anime.nix b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr-anime.nix new file mode 100644 index 0000000..1e0ae73 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr-anime.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr-uhd.nix b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr-uhd.nix new file mode 100644 index 0000000..71d1c15 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr-uhd.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr.nix b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr.nix new file mode 100644 index 0000000..d665c89 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/apps/radarr.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/radarr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/default.nix new file mode 100644 index 0000000..138cfcd --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/default.nix @@ -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 + ); + }; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/radarr/post-start.sh b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/post-start.sh new file mode 100644 index 0000000..44a5b57 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/radarr/post-start.sh @@ -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 diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/default.nix new file mode 100644 index 0000000..3d32bfb --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/default.nix @@ -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; + }; + }; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/common.nix b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/common.nix new file mode 100644 index 0000000..54a1511 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/common.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr-anime.nix b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr-anime.nix new file mode 100644 index 0000000..395b249 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr-anime.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr-uhd.nix b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr-uhd.nix new file mode 100644 index 0000000..b7259bd --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr-uhd.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr.nix b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr.nix new file mode 100644 index 0000000..619a916 --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/apps/sonarr.nix @@ -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; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/default.nix b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/default.nix new file mode 100644 index 0000000..414075c --- /dev/null +++ b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/default.nix @@ -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 + ); + }; + }; +} diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/arr/common.sh b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/post-start.sh similarity index 69% rename from hosts/jupiter/users/storm/configs/console/podman/media/arr/common.sh rename to hosts/jupiter/users/storm/configs/console/podman/media/sonarr/post-start.sh index 39b0f2c..5f2f2d0 100644 --- a/hosts/jupiter/users/storm/configs/console/podman/media/arr/common.sh +++ b/hosts/jupiter/users/storm/configs/console/podman/media/sonarr/post-start.sh @@ -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" diff --git a/packages/default.nix b/packages/default.nix index 77e57d9..41443af 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -17,6 +17,7 @@ docker-grafana = import ./docker/grafana { inherit pkgs; }; docker-grafana-image-renderer = import ./docker/grafana-image-renderer { inherit pkgs; }; docker-jellyfin = import ./docker/jellyfin { inherit pkgs inputs system; }; + docker-jellyseerr = import ./docker/jellyseerr { inherit pkgs; }; docker-mariadb = import ./docker/mariadb { inherit pkgs; }; docker-nextcloud = import ./docker/nextcloud { inherit pkgs; }; docker-ntfy = import ./docker/ntfy { inherit pkgs; }; diff --git a/packages/docker/jellyfin/default.nix b/packages/docker/jellyfin/default.nix index 81861ac..1e47908 100644 --- a/packages/docker/jellyfin/default.nix +++ b/packages/docker/jellyfin/default.nix @@ -67,7 +67,7 @@ pkgs.dockerTools.buildImage { copyToRoot = pkgs.buildEnv { name = "root"; - paths = [ + paths = with pkgs; [ entrypoint jellyfin jellyfin-web @@ -78,9 +78,9 @@ pkgs.dockerTools.buildImage { jellyfin-plugin-sso jellyfin-plugin-subtitleextract jellyfin-plugin-tvdb - pkgs.jellyfin-ffmpeg - pkgs.curl - pkgs.jq + jellyfin-ffmpeg + curl + jq ]; pathsToLink = [ "/bin" diff --git a/packages/docker/jellyfin/entrypoint.sh b/packages/docker/jellyfin/entrypoint.sh index 3f0792b..8e16edd 100644 --- a/packages/docker/jellyfin/entrypoint.sh +++ b/packages/docker/jellyfin/entrypoint.sh @@ -17,11 +17,6 @@ start() { start "$@" -# shellcheck disable=SC2034 -JELLYFIN_HOST="http://localhost:8096" -# shellcheck disable=SC2034 -JELLYFIN_ADMIN_USERNAME="${JELLYFIN_ADMIN_USERNAME:-admin}" - if [ -f /etc/jellyfin/setup.sh ]; then # shellcheck disable=SC1091 . /etc/jellyfin/setup.sh diff --git a/packages/docker/jellyseerr/default.nix b/packages/docker/jellyseerr/default.nix new file mode 100644 index 0000000..fa550a0 --- /dev/null +++ b/packages/docker/jellyseerr/default.nix @@ -0,0 +1,55 @@ +{ pkgs, ... }: +let + entrypoint = pkgs.writeTextFile { + name = "entrypoint"; + executable = true; + destination = "/bin/entrypoint"; + text = builtins.readFile ./entrypoint.sh; + }; + + # FIXME: https://github.com/fallenbagel/jellyseerr/pull/1505 + jellyseerr = pkgs.jellyseerr.overrideAttrs (oldAttrs: rec { + src = pkgs.fetchFromGitHub { + owner = "Fallenbagel"; + repo = "jellyseerr"; + tag = "preview-OIDC"; + hash = "sha256-iBnO0WjNqvXfuJMoS6z/NmYgtW5FQ9Ptp9uV5rODIf8="; + }; + + pnpmDeps = (pkgs.pnpm_9.override { nodejs = pkgs.nodejs_22; }).fetchDeps { + inherit src; + inherit (oldAttrs) pname version; + hash = "sha256-lq/b2PqQHsZmnw91Ad4h1uxZXsPATSLqIdb/t2EsmMI="; + }; + }); +in +pkgs.dockerTools.buildImage { + name = "jellyseerr"; + fromImage = import ../base { inherit pkgs; }; + + copyToRoot = pkgs.buildEnv { + name = "root"; + paths = with pkgs; [ + entrypoint + jellyseerr + curl + jq + ]; + pathsToLink = [ + "/bin" + "/lib" + ]; + }; + + config = { + Entrypoint = [ "entrypoint" ]; + Env = [ "CONFIG_DIRECTORY=/var/lib/jellyseerr" ]; + WorkingDir = "/var/lib/jellyseerr"; + Volumes = { + "/var/lib/jellyseerr" = { }; + }; + ExposedPorts = { + "5055/tcp" = { }; + }; + }; +} diff --git a/packages/docker/jellyseerr/entrypoint.sh b/packages/docker/jellyseerr/entrypoint.sh new file mode 100644 index 0000000..c99273e --- /dev/null +++ b/packages/docker/jellyseerr/entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh + +set -o errexit +set -o nounset + +if [ -f /etc/jellyseerr/pre-start.sh ]; then + # shellcheck disable=SC1091 + . /etc/jellyseerr/pre-start.sh +fi + +exec jellyseerr "$@" diff --git a/packages/docker/nextcloud/default.nix b/packages/docker/nextcloud/default.nix index 71be528..72bb05c 100644 --- a/packages/docker/nextcloud/default.nix +++ b/packages/docker/nextcloud/default.nix @@ -136,7 +136,7 @@ pkgs.dockerTools.buildImage { copyToRoot = pkgs.buildEnv { name = "root"; - paths = [ + paths = with pkgs; [ apacheHttpd apacheHttpdConfig php @@ -144,8 +144,8 @@ pkgs.dockerTools.buildImage { occ entrypoint crontab - pkgs.cron - pkgs.ffmpeg + cron + ffmpeg ]; pathsToLink = [ "/bin" diff --git a/packages/docker/prometheus-fail2ban-exporter/default.nix b/packages/docker/prometheus-fail2ban-exporter/default.nix index e443001..ceeadba 100644 --- a/packages/docker/prometheus-fail2ban-exporter/default.nix +++ b/packages/docker/prometheus-fail2ban-exporter/default.nix @@ -20,9 +20,9 @@ pkgs.dockerTools.buildImage { copyToRoot = pkgs.buildEnv { name = "root"; - paths = [ + paths = with selfPkgs; [ entrypoint - selfPkgs.prometheus-fail2ban-exporter + prometheus-fail2ban-exporter ]; pathsToLink = [ "/bin" ]; }; diff --git a/packages/docker/shlink-web-client/default.nix b/packages/docker/shlink-web-client/default.nix index fe51d52..13ca8eb 100644 --- a/packages/docker/shlink-web-client/default.nix +++ b/packages/docker/shlink-web-client/default.nix @@ -63,11 +63,11 @@ pkgs.dockerTools.buildImage { copyToRoot = pkgs.buildEnv { name = "root"; - paths = [ + paths = with pkgs; [ entrypoint shlink-web-client nginxConfig - pkgs.nginx + nginx ]; pathsToLink = [ "/bin" diff --git a/packages/docker/shlink/default.nix b/packages/docker/shlink/default.nix index a7e8299..606d729 100644 --- a/packages/docker/shlink/default.nix +++ b/packages/docker/shlink/default.nix @@ -55,12 +55,12 @@ pkgs.dockerTools.buildImage { copyToRoot = pkgs.buildEnv { name = "root"; - paths = [ + paths = with pkgs; [ entrypoint shlink shlink-cli php - pkgs.roadrunner + roadrunner ]; pathsToLink = [ "/bin" diff --git a/packages/docker/transmission-protonvpn/default.nix b/packages/docker/transmission-protonvpn/default.nix index c5aab47..f0e676f 100644 --- a/packages/docker/transmission-protonvpn/default.nix +++ b/packages/docker/transmission-protonvpn/default.nix @@ -18,14 +18,14 @@ pkgs.dockerTools.buildImage { copyToRoot = pkgs.buildEnv { name = "root"; - paths = [ + paths = with pkgs; [ entrypoint flood-for-transmission - pkgs.transmission_4 - pkgs.wireguard-tools - pkgs.libnatpmp - pkgs.curl - pkgs.jq + transmission_4 + wireguard-tools + libnatpmp + curl + jq ]; pathsToLink = [ "/bin" diff --git a/packages/docker/transmission-protonvpn/entrypoint.sh b/packages/docker/transmission-protonvpn/entrypoint.sh index 4d9d2f1..b7bc5fe 100644 --- a/packages/docker/transmission-protonvpn/entrypoint.sh +++ b/packages/docker/transmission-protonvpn/entrypoint.sh @@ -50,7 +50,7 @@ jq '. + { }' "$TRANSMISSION_HOME/settings.json" > "$tmpfile" mv "$tmpfile" "$TRANSMISSION_HOME/settings.json" -if [ -f "$TRANSMISSION_HOME/settings.override".json ]; then +if [ -f "$TRANSMISSION_HOME/settings.override.json" ]; then tmpfile="$(mktemp)" jq -s \ '.[0] * .[1]' \