From 3272063a43221fe95fc247bfd97d07e9768a69be Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Thu, 10 Jul 2025 19:38:19 +0100 Subject: [PATCH] Add recyclarr Signed-off-by: Nikolaos Karaolidis --- .../configs/console/podman/media/default.nix | 1013 ++++++----------- .../podman/media/prowlarr/post-start.sh | 9 - .../console/podman/media/prowlarr/setup.sh | 24 +- .../console/podman/media/radarr/setup.sh | 18 +- .../podman/media/recyclarr/radarr-anime.nix | 55 + .../podman/media/recyclarr/radarr-uhd.nix | 74 ++ .../console/podman/media/recyclarr/radarr.nix | 43 + .../podman/media/recyclarr/sonarr-anime.nix | 58 + .../podman/media/recyclarr/sonarr-uhd.nix | 63 + .../console/podman/media/recyclarr/sonarr.nix | 49 + .../console/podman/media/sonarr/setup.sh | 18 +- packages/default.nix | 1 + packages/docker/prowlarr/entrypoint.sh | 58 +- packages/docker/radarr/entrypoint.sh | 55 +- packages/docker/recyclarr/default.nix | 46 + packages/docker/recyclarr/entrypoint.sh | 9 + packages/docker/sonarr/entrypoint.sh | 55 +- 17 files changed, 878 insertions(+), 770 deletions(-) delete mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh create mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/radarr-anime.nix create mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/radarr-uhd.nix create mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/radarr.nix create mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/sonarr-anime.nix create mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/sonarr-uhd.nix create mode 100644 hosts/jupiter/users/storm/configs/console/podman/media/recyclarr/sonarr.nix create mode 100644 packages/docker/recyclarr/default.nix create mode 100644 packages/docker/recyclarr/entrypoint.sh 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 a98a907..7541386 100644 --- a/hosts/jupiter/users/storm/configs/console/podman/media/default.nix +++ b/hosts/jupiter/users/storm/configs/console/podman/media/default.nix @@ -15,6 +15,43 @@ let inherit (hmConfig.virtualisation.quadlet) containers volumes networks; jellyfinAutheliaClientId = "59TRpNutxEeRRCAZbDsK7rsnrA5NC69HAdAO45CEfc740xl4hgIacDy2u03oiFc89Exb67udBQvmfwxgeAQtJPiNAJxA5OzGmdQf"; + + mkApp = type: name: shortName: urlBase: mediaFolderBase: { + inherit + type + name + shortName + urlBase + mediaFolderBase + ; + }; + + arrs = [ + (mkApp "radarr" "Radarr" "radarr" "/manage/films" "/films") + (mkApp "radarr" "Radarr (UHD)" "radarr-uhd" "/manage/films/uhd" "/films") + (mkApp "radarr" "Radarr (Anime)" "radarr-anime" "/manage/anime/films" "/anime/films") + (mkApp "sonarr" "Sonarr" "sonarr" "/manage/shows" "/shows") + (mkApp "sonarr" "Sonarr (UHD)" "sonarr-uhd" "/manage/shows/uhd" "/shows") + (mkApp "sonarr" "Sonarr (Anime)" "sonarr-anime" "/manage/anime/shows" "/anime/shows") + ]; + + arrMapping = { + radarr = { + port = 7878; + prowlarr = { + implementation = "Radarr"; + configContract = "RadarrSettings"; + }; + }; + + sonarr = { + port = 8989; + prowlarr = { + implementation = "Sonarr"; + configContract = "SonarrSettings"; + }; + }; + }; in { home-manager.users.${user} = { @@ -27,233 +64,116 @@ in ]; sops = { - secrets = { - "jellyfin/admin".sopsFile = ../../../../../../secrets/secrets.yaml; - "jellyfin/authelia/password".sopsFile = ../../../../../../secrets/secrets.yaml; - "jellyfin/authelia/digest".sopsFile = ../../../../../../secrets/secrets.yaml; + secrets = + { + "jellyfin/admin".sopsFile = ../../../../../../secrets/secrets.yaml; + "jellyfin/authelia/password".sopsFile = ../../../../../../secrets/secrets.yaml; + "jellyfin/authelia/digest".sopsFile = ../../../../../../secrets/secrets.yaml; - "prowlarr/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; + "prowlarr/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; + } + // builtins.listToAttrs ( + builtins.map (arr: { + name = "${arr.shortName}/apiKey"; + value.sopsFile = ../../../../../../secrets/secrets.yaml; + }) arrs + ); - "radarr/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; - "radarr-uhd/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; - "radarr-anime/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; - "sonarr/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; - "sonarr-uhd/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; - "sonarr-anime/apiKey".sopsFile = ../../../../../../secrets/secrets.yaml; - }; + templates = + { + jellyfin-env.content = '' + JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"} + JELLYFIN_OIDC_SECRET=${hmConfig.sops.placeholder."jellyfin/authelia/password"} + ''; - templates = { - jellyfin-env.content = '' - JELLYFIN_ADMIN_PASSWORD=${hmConfig.sops.placeholder."jellyfin/admin"} - JELLYFIN_OIDC_SECRET=${hmConfig.sops.placeholder."jellyfin/authelia/password"} - ''; + authelia-jellyfin.content = builtins.readFile ( + (pkgs.formats.yaml { }).generate "jellyfin.yaml" { + identity_providers.oidc = { + authorization_policies.jellyfin = { + default_policy = "deny"; + rules = [ + { + policy = "one_factor"; + subject = "group:jellyfin"; + } + ]; + }; - authelia-jellyfin.content = builtins.readFile ( - (pkgs.formats.yaml { }).generate "jellyfin.yaml" { - identity_providers.oidc = { - authorization_policies.jellyfin = { - default_policy = "deny"; - rules = [ + clients = [ { - policy = "one_factor"; - subject = "group:jellyfin"; + client_id = jellyfinAutheliaClientId; + client_name = "Jellyfin"; + client_secret = hmConfig.sops.placeholder."jellyfin/authelia/digest"; + redirect_uris = [ "https://media.karaolidis.com/sso/OID/redirect/authelia" ]; + authorization_policy = "jellyfin"; + require_pkce = true; + pkce_challenge_method = "S256"; + scopes = [ + "openid" + "profile" + "groups" + ]; + token_endpoint_auth_method = "client_secret_post"; } ]; }; + } + ); - clients = [ - { - client_id = jellyfinAutheliaClientId; - client_name = "Jellyfin"; - client_secret = hmConfig.sops.placeholder."jellyfin/authelia/digest"; - redirect_uris = [ "https://media.karaolidis.com/sso/OID/redirect/authelia" ]; - authorization_policy = "jellyfin"; - require_pkce = true; - pkce_challenge_method = "S256"; - scopes = [ - "openid" - "profile" - "groups" - ]; - token_endpoint_auth_method = "client_secret_post"; + prowlarr-env.content = '' + API_KEY=${hmConfig.sops.placeholder."prowlarr/apiKey"} + ''; + } + // builtins.listToAttrs ( + builtins.map (arr: { + name = "${arr.shortName}-env"; + value.content = '' + API_KEY=${hmConfig.sops.placeholder."${arr.shortName}/apiKey"} + ''; + }) arrs + ) + // 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}.prowlarr) implementation configContract; + syncLevel = "fullSync"; + fields = [ + { + name = "prowlarrUrl"; + value = "http://prowlarr:9696"; + } + { + name = "baseUrl"; + value = "http://${arr.shortName}:${builtins.toString arrMapping.${arr.type}.port}"; + } + { + name = "apiKey"; + value = hmConfig.sops.placeholder."${arr.shortName}/apiKey"; + } + ]; + } + ); + }) arrs + ) + // builtins.listToAttrs ( + builtins.map (arr: { + name = "recyclarr-${arr.shortName}"; + value.content = builtins.readFile ( + (pkgs.formats.yaml { }).generate "${arr.shortName}.yaml" ( + import ./recyclarr/${arr.shortName}.nix { + base_url = "http://${arr.shortName}:${ + builtins.toString arrMapping.${arr.type}.port + }${arr.urlBase}/"; + api_key = hmConfig.sops.placeholder."${arr.shortName}/apiKey"; } - ]; - }; - } + ) + ); + }) arrs ); - - prowlarr-env.content = '' - PROWLARR_API_KEY=${hmConfig.sops.placeholder."prowlarr/apiKey"} - ''; - - prowlarr-radarr.content = builtins.readFile ( - (pkgs.formats.json { }).generate "radarr.json" { - enable = true; - name = "Radarr"; - implementation = "Radarr"; - configContract = "RadarrSettings"; - syncLevel = "fullSync"; - fields = [ - { - name = "prowlarrUrl"; - value = "http://prowlarr:9696"; - } - { - name = "baseUrl"; - value = "http://radarr:7878"; - } - { - name = "apiKey"; - value = hmConfig.sops.placeholder."radarr/apiKey"; - } - ]; - } - ); - - prowlarr-radarr-uhd.content = builtins.readFile ( - (pkgs.formats.json { }).generate "radarr-uhd.json" { - enable = true; - name = "Radarr (UHD)"; - implementation = "Radarr"; - configContract = "RadarrSettings"; - syncLevel = "fullSync"; - fields = [ - { - name = "prowlarrUrl"; - value = "http://prowlarr:9696"; - } - { - name = "baseUrl"; - value = "http://radarr-uhd:7878"; - } - { - name = "apiKey"; - value = hmConfig.sops.placeholder."radarr-uhd/apiKey"; - } - ]; - } - ); - - prowlarr-radarr-anime.content = builtins.readFile ( - (pkgs.formats.json { }).generate "radarr-anime.json" { - enable = true; - name = "Radarr (Anime)"; - implementation = "Radarr"; - configContract = "RadarrSettings"; - syncLevel = "fullSync"; - fields = [ - { - name = "prowlarrUrl"; - value = "http://prowlarr:9696"; - } - { - name = "baseUrl"; - value = "http://radarr-anime:7878"; - } - { - name = "apiKey"; - value = hmConfig.sops.placeholder."radarr-anime/apiKey"; - } - ]; - } - ); - - prowlarr-sonarr.content = builtins.readFile ( - (pkgs.formats.json { }).generate "sonarr.json" { - enable = true; - name = "Sonarr"; - implementation = "Sonarr"; - configContract = "SonarrSettings"; - syncLevel = "fullSync"; - fields = [ - { - name = "prowlarrUrl"; - value = "http://prowlarr:9696"; - } - { - name = "baseUrl"; - value = "http://sonarr:8989"; - } - { - name = "apiKey"; - value = hmConfig.sops.placeholder."sonarr/apiKey"; - } - ]; - } - ); - - prowlarr-sonarr-uhd.content = builtins.readFile ( - (pkgs.formats.json { }).generate "sonarr-uhd.json" { - enable = true; - name = "Sonarr (UHD)"; - implementation = "Sonarr"; - configContract = "SonarrSettings"; - syncLevel = "fullSync"; - fields = [ - { - name = "prowlarrUrl"; - value = "http://prowlarr:9696"; - } - { - name = "baseUrl"; - value = "http://sonarr-uhd:8989"; - } - { - name = "apiKey"; - value = hmConfig.sops.placeholder."sonarr-uhd/apiKey"; - } - ]; - } - ); - - prowlarr-sonarr-anime.content = builtins.readFile ( - (pkgs.formats.json { }).generate "sonarr-anime.json" { - enable = true; - name = "Sonarr (Anime)"; - implementation = "Sonarr"; - configContract = "SonarrSettings"; - syncLevel = "fullSync"; - fields = [ - { - name = "prowlarrUrl"; - value = "http://prowlarr:9696"; - } - { - name = "baseUrl"; - value = "http://sonarr-anime:8989"; - } - { - name = "apiKey"; - value = hmConfig.sops.placeholder."sonarr-anime/apiKey"; - } - ]; - } - ); - - radarr-env.content = '' - RADARR_API_KEY=${hmConfig.sops.placeholder."radarr/apiKey"} - ''; - - radarr-uhd-env.content = '' - RADARR_API_KEY=${hmConfig.sops.placeholder."radarr-uhd/apiKey"} - ''; - - radarr-anime-env.content = '' - RADARR_API_KEY=${hmConfig.sops.placeholder."radarr-anime/apiKey"} - ''; - - sonarr-env.content = '' - SONARR_API_KEY=${hmConfig.sops.placeholder."sonarr/apiKey"} - ''; - - sonarr-uhd-env.content = '' - SONARR_API_KEY=${hmConfig.sops.placeholder."sonarr-uhd/apiKey"} - ''; - - sonarr-anime-env.content = '' - SONARR_API_KEY=${hmConfig.sops.placeholder."sonarr-anime/apiKey"} - ''; - }; }; virtualisation.quadlet = { @@ -263,457 +183,232 @@ in flaresolverr = { }; }; - volumes = { - jellyfin-config = { }; - jellyfin-data = { }; - jellyfin-log = { }; - jellyfin-cache = { }; + volumes = + { + jellyfin-config = { }; + jellyfin-data = { }; + jellyfin-log = { }; + jellyfin-cache = { }; - prowlarr = { }; + prowlarr = { }; + } + // builtins.listToAttrs ( + builtins.map (arr: { + name = arr.shortName; + value = { }; + }) arrs + ); - radarr = { }; - radarr-uhd = { }; - radarr-anime = { }; - sonarr = { }; - sonarr-uhd = { }; - sonarr-anime = { }; - }; + containers = + { + jellyfin = { + containerConfig = { + image = "docker-archive:${selfPkgs.docker-jellyfin}"; + networks = [ + networks.jellyfin.ref + networks.traefik.ref + ]; + volumes = + let + setup = pkgs.writeTextFile { + name = "setup.sh"; + executable = true; + text = builtins.readFile ./jellyfin/setup.sh; + }; + in + [ + "/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media" + "${setup}:/etc/jellyfin/setup.sh:ro" + "${./jellyfin/libraries}:/etc/jellyfin/libraries:ro" + "${volumes.jellyfin-config.ref}:/etc/jellyfin" + "${volumes.jellyfin-data.ref}:/var/lib/jellyfin" + "${volumes.jellyfin-log.ref}:/var/log/jellyfin" + "${volumes.jellyfin-cache.ref}:/tmp/jellyfin" + ]; + environments.JELLYFIN_OIDC_CLIENT_ID = jellyfinAutheliaClientId; + environmentFiles = [ hmConfig.sops.templates.jellyfin-env.path ]; + labels = [ + "traefik.enable=true" + "traefik.http.routers.jellyfin.rule=Host(`media.karaolidis.com`)" + ]; + podmanArgs = [ "--cdi-spec-dir=/run/cdi" ]; + devices = [ "nvidia.com/gpu=all" ]; + }; - containers = { - jellyfin = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-jellyfin}"; - networks = [ - networks.jellyfin.ref - networks.traefik.ref - ]; - volumes = + unitConfig = let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./jellyfin/setup.sh; - }; + dependencies = [ "sops-nix.service" ]; in - [ - "/mnt/storage/private/storm/containers/storage/volumes/media/_data:/var/lib/media" - "${setup}:/etc/jellyfin/setup.sh:ro" - "${./jellyfin/libraries}:/etc/jellyfin/libraries:ro" - "${volumes.jellyfin-config.ref}:/etc/jellyfin" - "${volumes.jellyfin-data.ref}:/var/lib/jellyfin" - "${volumes.jellyfin-log.ref}:/var/log/jellyfin" - "${volumes.jellyfin-cache.ref}:/tmp/jellyfin" - ]; - environments.JELLYFIN_OIDC_CLIENT_ID = jellyfinAutheliaClientId; - environmentFiles = [ hmConfig.sops.templates.jellyfin-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.jellyfin.rule=Host(`media.karaolidis.com`)" - ]; - podmanArgs = [ "--cdi-spec-dir=/run/cdi" ]; - devices = [ "nvidia.com/gpu=all" ]; + { + After = dependencies; + Requires = dependencies; + }; }; - - unitConfig = - let - dependencies = [ "sops-nix.service" ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - 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 - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./prowlarr/setup.sh; - }; - - postStart = pkgs.writeTextFile { - name = "post-start.sh"; - executable = true; - text = builtins.readFile ./prowlarr/post-start.sh; - }; - in - [ - "${setup}:/etc/prowlarr/setup.sh:ro" - "${postStart}:/etc/prowlarr/post-start.sh:ro" - "${./prowlarr/indexers}:/etc/prowlarr/indexers:ro" - "${hmConfig.sops.templates.prowlarr-radarr.path}:/etc/prowlarr/apps/radarr.json:ro" - "${hmConfig.sops.templates.prowlarr-radarr-uhd.path}:/etc/prowlarr/apps/radarr-uhd.json:ro" - "${hmConfig.sops.templates.prowlarr-radarr-anime.path}:/etc/prowlarr/apps/radarr-anime.json:ro" - "${hmConfig.sops.templates.prowlarr-sonarr.path}:/etc/prowlarr/apps/sonarr.json:ro" - "${hmConfig.sops.templates.prowlarr-sonarr-uhd.path}:/etc/prowlarr/apps/sonarr-uhd.json:ro" - "${hmConfig.sops.templates.prowlarr-sonarr-anime.path}:/etc/prowlarr/apps/sonarr-anime.json:ro" - "${volumes.prowlarr.ref}:/var/lib/prowlarr" - ]; - environments.PROWLARR_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 = - let - dependencies = [ - "sops-nix.service" - "${containers.transmission._serviceName}.service" - "${containers.flaresolverr._serviceName}.service" - "${containers.radarr._serviceName}.service" - "${containers.radarr-uhd._serviceName}.service" - "${containers.radarr-anime._serviceName}.service" - "${containers.sonarr._serviceName}.service" - "${containers.sonarr-uhd._serviceName}.service" - "${containers.sonarr-anime._serviceName}.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - radarr = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-radarr}"; - networks = [ - networks.media.ref - networks.transmission.ref - networks.traefik.ref - ]; - volumes = - let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./radarr/setup.sh; - }; - in - [ - "${setup}:/etc/radarr/setup.sh:ro" - "${volumes.radarr.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 = { - RADARR_INSTANCE_NAME = "Radarr"; - RADARR_URL_BASE = "/manage/films"; - RADARR_ROOT_FOLDER = "/var/lib/media/films"; - RADARR_DOWNLOAD_CATEGORY = "radarr"; - }; - environmentFiles = [ hmConfig.sops.templates.radarr-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.radarr.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/films`)" - "traefik.http.routers.radarr.middlewares=authelia@docker" - ]; - }; - - unitConfig = - let - dependencies = [ - "${containers.transmission._serviceName}.service" - "sops-nix.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - radarr-uhd = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-radarr}"; - networks = [ - networks.media.ref - networks.transmission.ref - networks.traefik.ref - ]; - volumes = - let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./radarr/setup.sh; - }; - in - [ - "${setup}:/etc/radarr/setup.sh:ro" - "${volumes.radarr-uhd.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 = { - RADARR_INSTANCE_NAME = "Radarr (UHD)"; - RADARR_URL_BASE = "/manage/films/uhd"; - RADARR_ROOT_FOLDER = "/var/lib/media/films"; - RADARR_DOWNLOAD_CATEGORY = "radarr-uhd"; - }; - environmentFiles = [ hmConfig.sops.templates.radarr-uhd-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.radarr-uhd.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/films/uhd`)" - "traefik.http.routers.radarr-uhd.middlewares=authelia@docker" - ]; - }; - - unitConfig = - let - dependencies = [ - "${containers.transmission._serviceName}.service" - "sops-nix.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - radarr-anime = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-radarr}"; - networks = [ - networks.media.ref - networks.transmission.ref - networks.traefik.ref - ]; - volumes = - let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./radarr/setup.sh; - }; - in - [ - "${setup}:/etc/radarr/setup.sh:ro" - "${volumes.radarr-anime.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 = { - RADARR_INSTANCE_NAME = "Radarr (Anime)"; - RADARR_URL_BASE = "/manage/anime/films"; - RADARR_ROOT_FOLDER = "/var/lib/media/anime/films"; - RADARR_DOWNLOAD_CATEGORY = "radarr-anime"; - }; - environmentFiles = [ hmConfig.sops.templates.radarr-anime-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.radarr-anime.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/anime/films`)" - "traefik.http.routers.radarr-anime.middlewares=authelia@docker" - ]; - }; - - unitConfig = - let - dependencies = [ - "${containers.transmission._serviceName}.service" - "sops-nix.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - sonarr = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-sonarr}"; - networks = [ - networks.media.ref - networks.transmission.ref - networks.traefik.ref - ]; - volumes = - let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./sonarr/setup.sh; - }; - in - [ - "${setup}:/etc/sonarr/setup.sh:ro" - "${volumes.sonarr.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 = { - SONARR_INSTANCE_NAME = "Sonarr"; - SONARR_URL_BASE = "/manage/shows"; - SONARR_ROOT_FOLDER = "/var/lib/media/shows"; - SONARR_DOWNLOAD_CATEGORY = "sonarr"; - }; - environmentFiles = [ hmConfig.sops.templates.sonarr-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.sonarr.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/shows`)" - "traefik.http.routers.sonarr.middlewares=authelia@docker" - ]; - }; - - unitConfig = - let - dependencies = [ - "${containers.transmission._serviceName}.service" - "sops-nix.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - sonarr-uhd = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-sonarr}"; - networks = [ - networks.media.ref - networks.transmission.ref - networks.traefik.ref - ]; - volumes = - let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./sonarr/setup.sh; - }; - in - [ - "${setup}:/etc/sonarr/setup.sh:ro" - "${volumes.sonarr-uhd.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 = { - SONARR_INSTANCE_NAME = "Sonarr (UHD)"; - SONARR_URL_BASE = "/manage/shows/uhd"; - SONARR_ROOT_FOLDER = "/var/lib/media/shows"; - SONARR_DOWNLOAD_CATEGORY = "sonarr-uhd"; - }; - environmentFiles = [ hmConfig.sops.templates.sonarr-uhd-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.sonarr-uhd.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/shows/uhd`)" - "traefik.http.routers.sonarr-uhd.middlewares=authelia@docker" - ]; - }; - - unitConfig = - let - dependencies = [ - "${containers.transmission._serviceName}.service" - "sops-nix.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - sonarr-anime = { - containerConfig = { - image = "docker-archive:${selfPkgs.docker-sonarr}"; - networks = [ - networks.media.ref - networks.transmission.ref - networks.traefik.ref - ]; - volumes = - let - setup = pkgs.writeTextFile { - name = "setup.sh"; - executable = true; - text = builtins.readFile ./sonarr/setup.sh; - }; - in - [ - "${setup}:/etc/sonarr/setup.sh:ro" - "${volumes.sonarr-anime.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 = { - SONARR_INSTANCE_NAME = "Sonarr (Anime)"; - SONARR_URL_BASE = "/manage/anime/shows"; - SONARR_ROOT_FOLDER = "/var/lib/media/anime/shows"; - SONARR_DOWNLOAD_CATEGORY = "sonarr-anime"; - }; - environmentFiles = [ hmConfig.sops.templates.sonarr-anime-env.path ]; - labels = [ - "traefik.enable=true" - "traefik.http.routers.sonarr-anime.rule=Host(`media.karaolidis.com`) && PathPrefix(`/manage/anime/shows`)" - "traefik.http.routers.sonarr-anime.middlewares=authelia@docker" - ]; - }; - - unitConfig = - let - dependencies = [ - "${containers.transmission._serviceName}.service" - "sops-nix.service" - ]; - in - { - After = dependencies; - Requires = dependencies; - }; - }; - - authelia.containerConfig.volumes = + } + // ( let - mediaConfig = (pkgs.formats.yaml { }).generate "media.yaml" { - access_control.rules = [ - { - domain = "media.karaolidis.com"; - policy = "one_factor"; - resources = [ "^/manage([/?].*)?$" ]; - subject = [ "group:media" ]; - } - { - domain = "media.karaolidis.com"; - policy = "deny"; - resources = [ "^/manage([/?].*)?$" ]; - } - { - domain = "media.karaolidis.com"; - policy = "bypass"; - } - ]; - }; + arrServices = builtins.map (arr: "${containers.${arr.shortName}._serviceName}.service") arrs; in - [ - "${mediaConfig}:/etc/authelia/conf.d/media.yaml:ro" - "${hmConfig.sops.templates.authelia-jellyfin.path}:/etc/authelia/conf.d/jellyfin.yaml:ro" - ]; - }; + { + 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 + setup = pkgs.writeTextFile { + name = "setup.sh"; + executable = true; + text = builtins.readFile ./prowlarr/setup.sh; + }; + in + [ + "${setup}:/etc/prowlarr/setup.sh:ro" + "${./prowlarr/indexers}:/etc/prowlarr/indexers: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 = + let + dependencies = [ "sops-nix.service" ]; + in + { + After = + dependencies + ++ [ + "${containers.transmission._serviceName}.service" + "${containers.flaresolverr._serviceName}.service" + ] + ++ arrServices; + Requires = dependencies; + }; + }; + + 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 = + let + dependencies = [ "sops-nix.service" ]; + in + { + After = dependencies ++ arrServices; + Requires = dependencies; + }; + }; + } + ) + // 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 + setup = pkgs.writeTextFile { + name = "setup.sh"; + executable = true; + text = builtins.readFile ./${arr.type}/setup.sh; + }; + in + [ + "${setup}:/etc/${arr.type}/setup.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 = + let + dependencies = [ "sops-nix.service" ]; + in + { + After = dependencies ++ [ "${containers.transmission._serviceName}.service" ]; + Requires = dependencies; + }; + }; + }) arrs + ) + // { + authelia.containerConfig.volumes = + let + mediaConfig = (pkgs.formats.yaml { }).generate "media.yaml" { + access_control.rules = [ + { + domain = "media.karaolidis.com"; + policy = "one_factor"; + resources = [ "^/manage([/?].*)?$" ]; + subject = [ "group:media" ]; + } + { + domain = "media.karaolidis.com"; + policy = "deny"; + resources = [ "^/manage([/?].*)?$" ]; + } + { + domain = "media.karaolidis.com"; + policy = "bypass"; + } + ]; + }; + in + [ + "${mediaConfig}:/etc/authelia/conf.d/media.yaml:ro" + "${hmConfig.sops.templates.authelia-jellyfin.path}:/etc/authelia/conf.d/jellyfin.yaml:ro" + ]; + }; }; }; } diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh deleted file mode 100644 index 4c6b75c..0000000 --- a/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/post-start.sh +++ /dev/null @@ -1,9 +0,0 @@ -# shellcheck shell=sh - -for filepath in /etc/prowlarr/indexers/*.json; do - curl -sf --retry 10 "$PROWLARR_HOST/api/v1/indexer?forceSave=true" \ - -X POST \ - -H "Content-Type: application/json" \ - -H "X-Api-Key: $PROWLARR_API_KEY" \ - --data-binary @"$filepath" || true -done diff --git a/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/setup.sh b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/setup.sh index c31d561..da4fae2 100644 --- a/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/setup.sh +++ b/hosts/jupiter/users/storm/configs/console/podman/media/prowlarr/setup.sh @@ -1,17 +1,17 @@ # shellcheck shell=sh # Tag ID: 1 -curl -sf "$PROWLARR_HOST/api/v1/tag" \ +curl -sf "$HOST/api/v1/tag" \ -X POST \ -H 'Content-Type: application/json' \ - -H "X-Api-Key: $PROWLARR_API_KEY" \ + -H "X-Api-Key: $API_KEY" \ --data-raw '{"label": "flaresolverr"}' || true { - curl -sf --retry 5 "$PROWLARR_HOST/api/v1/indexerProxy?forceSave=true" \ + curl -sf --retry 5 "$HOST/api/v1/indexerProxy?forceSave=true" \ -X POST \ -H 'Content-Type: application/json' \ - -H "X-Api-Key: $PROWLARR_API_KEY" \ + -H "X-Api-Key: $API_KEY" \ --data-binary @- <' > /var/lib/prowlarr/config.xml - - xmlstarlet ed -L \ - -s /Config -t elem -n LaunchBrowser -v "False" \ - -s /Config -t elem -n ApiKey -v "$PROWLARR_API_KEY" \ - -s /Config -t elem -n AuthenticationMethod -v "External" \ - -s /Config -t elem -n AuthenticationRequired -v "DisabledForLocalAddresses" \ - -s /Config -t elem -n LogLevel -v "info" \ - -s /Config -t elem -n UrlBase -v "$PROWLARR_URL_BASE" \ - -s /Config -t elem -n InstanceName -v "${PROWLARR_INSTANCE_NAME:-prowlarr}" \ - -s /Config -t elem -n AnalyticsEnabled -v "False" \ - /var/lib/prowlarr/config.xml fi +set_config_value() { + name="$1" + value="$2" + + CONFIG_FILE="/var/lib/prowlarr/config.xml" + + if xmlstarlet sel -t -v "/Config/$name" "$CONFIG_FILE" >/dev/null 2>&1; then + xmlstarlet ed -L -u "/Config/$name" -v "$value" "$CONFIG_FILE" + else + xmlstarlet ed -L -s "/Config" -t elem -n "$name" -v "$value" "$CONFIG_FILE" + fi +} + +set_config_value "LaunchBrowser" "False" +set_config_value "ApiKey" "$API_KEY" +set_config_value "AuthenticationMethod" "External" +set_config_value "AuthenticationRequired" "DisabledForLocalAddresses" +set_config_value "LogLevel" "info" +set_config_value "UrlBase" "$URL_BASE" +set_config_value "InstanceName" "${INSTANCE_NAME:-Prowlarr}" +set_config_value "AnalyticsEnabled" "False" + Prowlarr -data=/var/lib/prowlarr -nobrowser "$@" & PID=$! -PROWLARR_HOST="http://localhost:9696$PROWLARR_URL_BASE" +HOST="http://localhost:9696$URL_BASE" -if [ ! -f /var/lib/prowlarr/init ]; then - curl -sf --retry 10 --retry-connrefused \ - -H "X-Api-Key: $PROWLARR_API_KEY" \ - "$PROWLARR_HOST/api/v1/health" +curl -sf --retry 10 --retry-connrefused \ + -H "X-Api-Key: $API_KEY" \ + "$HOST/api/v1/health" - if [ -f /etc/prowlarr/setup.sh ]; then - # shellcheck disable=SC1091 - . /etc/prowlarr/setup.sh - fi - - touch /var/lib/prowlarr/init -fi - -if [ -f /etc/prowlarr/post-start.sh ]; then +if [ -f /etc/prowlarr/setup.sh ]; then # shellcheck disable=SC1091 - . /etc/prowlarr/post-start.sh + . /etc/prowlarr/setup.sh fi trap 'kill -INT "$PID"' INT TERM diff --git a/packages/docker/radarr/entrypoint.sh b/packages/docker/radarr/entrypoint.sh index 8148fbc..f93491f 100644 --- a/packages/docker/radarr/entrypoint.sh +++ b/packages/docker/radarr/entrypoint.sh @@ -3,39 +3,46 @@ set -o errexit set -o nounset -RADARR_URL_BASE="${RADARR_URL_BASE:-}" +URL_BASE="${URL_BASE:-}" -if [ ! -f /var/lib/radarr/init ]; then +if [ ! -f /var/lib/radarr/config.xml ]; then echo '' > /var/lib/radarr/config.xml - - xmlstarlet ed -L \ - -s /Config -t elem -n LaunchBrowser -v "False" \ - -s /Config -t elem -n ApiKey -v "$RADARR_API_KEY" \ - -s /Config -t elem -n AuthenticationMethod -v "External" \ - -s /Config -t elem -n AuthenticationRequired -v "DisabledForLocalAddresses" \ - -s /Config -t elem -n LogLevel -v "info" \ - -s /Config -t elem -n UrlBase -v "$RADARR_URL_BASE" \ - -s /Config -t elem -n InstanceName -v "${RADARR_INSTANCE_NAME:-Radarr}" \ - -s /Config -t elem -n AnalyticsEnabled -v "False" \ - /var/lib/radarr/config.xml fi +set_config_value() { + name="$1" + value="$2" + + CONFIG_FILE="/var/lib/radarr/config.xml" + + if xmlstarlet sel -t -v "/Config/$name" "$CONFIG_FILE" >/dev/null 2>&1; then + xmlstarlet ed -L -u "/Config/$name" -v "$value" "$CONFIG_FILE" + else + xmlstarlet ed -L -s "/Config" -t elem -n "$name" -v "$value" "$CONFIG_FILE" + fi +} + +set_config_value "LaunchBrowser" "False" +set_config_value "ApiKey" "$API_KEY" +set_config_value "AuthenticationMethod" "External" +set_config_value "AuthenticationRequired" "DisabledForLocalAddresses" +set_config_value "LogLevel" "info" +set_config_value "UrlBase" "$URL_BASE" +set_config_value "InstanceName" "${INSTANCE_NAME:-Radarr}" +set_config_value "AnalyticsEnabled" "False" + Radarr -data=/var/lib/radarr -nobrowser "$@" & PID=$! -RADARR_HOST="http://localhost:7878$RADARR_URL_BASE" +HOST="http://localhost:7878$URL_BASE" -if [ ! -f /var/lib/radarr/init ]; then - curl -sf --retry 10 --retry-connrefused \ - -H "X-Api-Key: $RADARR_API_KEY" \ - "$RADARR_HOST/api/v1/health" +curl -sf --retry 10 --retry-connrefused \ + -H "X-Api-Key: $API_KEY" \ + "$HOST/api/v1/health" - if [ -f /etc/radarr/setup.sh ]; then - # shellcheck disable=SC1091 - . /etc/radarr/setup.sh - fi - - touch /var/lib/radarr/init +if [ -f /etc/radarr/setup.sh ]; then + # shellcheck disable=SC1091 + . /etc/radarr/setup.sh fi trap 'kill -INT "$PID"' INT TERM diff --git a/packages/docker/recyclarr/default.nix b/packages/docker/recyclarr/default.nix new file mode 100644 index 0000000..abb9124 --- /dev/null +++ b/packages/docker/recyclarr/default.nix @@ -0,0 +1,46 @@ +{ pkgs, ... }: +let + entrypoint = pkgs.writeTextFile { + name = "entrypoint"; + executable = true; + destination = "/bin/entrypoint"; + text = builtins.readFile ./entrypoint.sh; + }; + + crontab = pkgs.writeTextDir "/var/cron/tabs/root" '' + */0 * * * * recyclarr sync + ''; +in +pkgs.dockerTools.buildImage { + name = "recyclarr"; + fromImage = import ../base { inherit pkgs; }; + + copyToRoot = pkgs.buildEnv { + name = "root"; + paths = with pkgs; [ + entrypoint + crontab + recyclarr + cron + ]; + pathsToLink = [ + "/bin" + "/lib" + "/var" + ]; + }; + + runAsRoot = '' + ${pkgs.dockerTools.shadowSetup} + mkdir -p /var/run + ''; + + config = { + Entrypoint = [ "entrypoint" ]; + Env = [ "RECYCLARR_APP_DATA=/var/lib/recyclarr" ]; + WorkingDir = "/var/lib/recyclarr"; + Volumes = { + "/var/lib/recyclarr" = { }; + }; + }; +} diff --git a/packages/docker/recyclarr/entrypoint.sh b/packages/docker/recyclarr/entrypoint.sh new file mode 100644 index 0000000..1205f16 --- /dev/null +++ b/packages/docker/recyclarr/entrypoint.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +set -o errexit +set -o nounset + +recyclarr sync + +cron +trap : TERM INT; sleep infinity & wait diff --git a/packages/docker/sonarr/entrypoint.sh b/packages/docker/sonarr/entrypoint.sh index b74ea41..b9b1fab 100644 --- a/packages/docker/sonarr/entrypoint.sh +++ b/packages/docker/sonarr/entrypoint.sh @@ -3,39 +3,46 @@ set -o errexit set -o nounset -SONARR_URL_BASE="${SONARR_URL_BASE:-}" +URL_BASE="${URL_BASE:-}" -if [ ! -f /var/lib/sonarr/init ]; then +if [ ! -f /var/lib/sonarr/config.xml ]; then echo '' > /var/lib/sonarr/config.xml - - xmlstarlet ed -L \ - -s /Config -t elem -n LaunchBrowser -v "False" \ - -s /Config -t elem -n ApiKey -v "$SONARR_API_KEY" \ - -s /Config -t elem -n AuthenticationMethod -v "External" \ - -s /Config -t elem -n AuthenticationRequired -v "DisabledForLocalAddresses" \ - -s /Config -t elem -n LogLevel -v "info" \ - -s /Config -t elem -n UrlBase -v "$SONARR_URL_BASE" \ - -s /Config -t elem -n InstanceName -v "${SONARR_INSTANCE_NAME:-Sonarr}" \ - -s /Config -t elem -n AnalyticsEnabled -v "False" \ - /var/lib/sonarr/config.xml fi +set_config_value() { + name="$1" + value="$2" + + CONFIG_FILE="/var/lib/sonarr/config.xml" + + if xmlstarlet sel -t -v "/Config/$name" "$CONFIG_FILE" >/dev/null 2>&1; then + xmlstarlet ed -L -u "/Config/$name" -v "$value" "$CONFIG_FILE" + else + xmlstarlet ed -L -s "/Config" -t elem -n "$name" -v "$value" "$CONFIG_FILE" + fi +} + +set_config_value "LaunchBrowser" "False" +set_config_value "ApiKey" "$API_KEY" +set_config_value "AuthenticationMethod" "External" +set_config_value "AuthenticationRequired" "DisabledForLocalAddresses" +set_config_value "LogLevel" "info" +set_config_value "UrlBase" "$URL_BASE" +set_config_value "InstanceName" "${INSTANCE_NAME:-Sonarr}" +set_config_value "AnalyticsEnabled" "False" + Sonarr -data=/var/lib/sonarr -nobrowser "$@" & PID=$! -SONARR_HOST="http://localhost:8989$SONARR_URL_BASE" +HOST="http://localhost:8989$URL_BASE" -if [ ! -f /var/lib/sonarr/init ]; then - curl -sf --retry 10 --retry-connrefused \ - -H "X-Api-Key: $SONARR_API_KEY" \ - "$SONARR_HOST/api/v1/health" +curl -sf --retry 10 --retry-connrefused \ + -H "X-Api-Key: $API_KEY" \ + "$HOST/api/v1/health" - if [ -f /etc/sonarr/setup.sh ]; then - # shellcheck disable=SC1091 - . /etc/sonarr/setup.sh - fi - - touch /var/lib/sonarr/init +if [ -f /etc/sonarr/setup.sh ]; then + # shellcheck disable=SC1091 + . /etc/sonarr/setup.sh fi trap 'kill -INT "$PID"' INT TERM