{ config, lib, pkgs, ... }: let cfg = config.homelab.apps.arr; networkName = "arrStack"; proxyNet = config.homelab.apps.traefik.sharedNetworkName; appNames = [ "bazarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ]; inUse = builtins.any (app: cfg.${app}.enable) appNames; PGID = toString config.users.groups.media.gid; UMASK = "002"; in { options.homelab.apps.arr = let mkAppOption = appName: { enable = lib.mkEnableOption "${appName} using Docker"; exposePorts = lib.mkOption { type = lib.types.bool; description = "Expose ${appName} port"; default = cfg.exposePorts; }; }; in { enable = lib.mkEnableOption "Arr Stack using Docker"; exposePorts = lib.mkOption { type = lib.types.bool; description = "Expose all app ports"; # Only expose ports by default if Traefik is not in use. default = ! config.homelab.apps.traefik.enable; }; bazarr = mkAppOption "Bazarr"; prowlarr = mkAppOption "Prowlarr"; qbittorrent = mkAppOption "qBittorrent"; radarr = mkAppOption "Radarr"; sonarr = mkAppOption "Sonarr"; }; config = { homelab = { users = lib.mkIf inUse { apps.enable = true; media.enable = true; }; # "Master switch": Enable all apps. apps.arr = lib.mkIf cfg.enable { bazarr.enable = true; prowlarr.enable = true; qbittorrent.enable = true; radarr.enable = true; sonarr.enable = true; }; fileSystems.media.video = { enable = true; permissions = [ "read" "write" ]; }; virtualisation.containers.enable = lib.mkIf inUse true; }; homelab.nfsMounts = let hugoBackup = "192.168.0.11:/mnt/BIG/BACKUP"; arrOptions = [ "auto" "rsize=1048576" "wsize=1048576" "hard" "timeo=600" "retrans=2" "_netdev" ]; mkMount = device: { inherit device; extraOptions = arrOptions; }; in lib.mkIf inUse { "/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable (mkMount "${hugoBackup}/BAZARR"); "/srv/prowlarr-backup" = lib.mkIf cfg.bazarr.enable (mkMount "${hugoBackup}/PROWLARR"); "/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable (mkMount "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT"); "/srv/radarr-backup" = lib.mkIf cfg.radarr.enable (mkMount "${hugoBackup}/RADARR"); "/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable (mkMount "${hugoBackup}/SONARR"); "/srv/torrent" = mkMount "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT"; }; # Make sure the Docker network exists. homelab.dockerNetworks."${networkName}" = lib.mkIf inUse { requiredBy = [ "docker-bazarr.service" "docker-prowlarr.service" "docker-qbittorrent.service" "docker-radarr.service" "docker-sonarr.service" ]; }; # Create a user for each app. homelab.appUsers = let mkUser = uid: { inherit uid; group = config.users.groups.media.name; }; in { bazarr = lib.mkIf cfg.bazarr.enable (mkUser 3003); prowlarr = lib.mkIf cfg.prowlarr.enable (mkUser 3004); qbittorrent = lib.mkIf cfg.qbittorrent.enable ((mkUser 3005) // { extraGroups = [ config.users.groups.apps.name ]; }); radarr = lib.mkIf cfg.radarr.enable (mkUser 3006); sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007); }; homelab.traefikRouters = { bazarr = lib.mkIf cfg.bazarr.enable { rule = "Host(`bazarr.depeuter.dev`)"; port = 6767; }; prowlarr = lib.mkIf cfg.prowlarr.enable { rule = "Host(`prowlarr.depeuter.dev`)"; port = 9696; }; qbittorrent = lib.mkIf cfg.qbittorrent.enable { rule = "Host(`qb.depeuter.dev`)"; port = 10095; }; radarr = lib.mkIf cfg.radarr.enable { rule = "Host(`radarr.depeuter.dev`)"; port = 7878; }; sonarr = lib.mkIf cfg.sonarr.enable { rule = "Host(`sonarr.depeuter.dev`)"; port = 8989; }; }; virtualisation.oci-containers.containers = let videoHostPath = config.homelab.fileSystems.media.video.hostPath; in { bazarr = let port = 6767; in lib.mkIf cfg.bazarr.enable { hostname = "bazarr"; image = "ghcr.io/hotio/bazarr:release-1.5.2"; autoStart = true; ports = lib.mkIf cfg.bazarr.exposePorts [ "${toString port}:${toString port}/tcp" "${toString port}:${toString port}/udp" ]; extraOptions = [ "--network=${networkName}" "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.bazarr.uid; inherit PGID UMASK; TZ = config.time.timeZone; WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp"; }; volumes = [ "bazarr-config:/config" "/srv/bazarr-backup:/config/backup" "${videoHostPath}/Films:/media/movies" "${videoHostPath}/Series:/media/series" ]; }; prowlarr = let port = 9696; in lib.mkIf cfg.prowlarr.enable { hostname = "prowlarr"; image = "ghcr.io/hotio/prowlarr:release-2.0.5.5160"; autoStart = true; ports = lib.mkIf cfg.prowlarr.exposePorts [ "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.prowlarr.uid; inherit PGID UMASK; TZ = config.time.timeZone; }; volumes = [ "prowlarr-config:/config" "/srv/prowlarr-backup:/config/Backups" ]; }; qbittorrent = let port = 10095; in lib.mkIf cfg.qbittorrent.enable { hostname = "qbittorrent"; image = "ghcr.io/hotio/qbittorrent:release-5.1.2"; autoStart = true; ports = lib.mkIf cfg.qbittorrent.exposePorts [ "${toString port}:${toString port}/tcp" "${toString port}:${toString port}/udp" ]; extraOptions = [ "--network=${networkName}" "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.qbittorrent.uid; inherit PGID UMASK; TZ = config.time.timeZone; WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp"; }; volumes = [ "/srv/qbittorrent:/config" "/srv/torrent:/media/cache" ]; }; radarr = let port = 7878; in lib.mkIf cfg.radarr.enable { hostname = "radarr"; image = "ghcr.io/hotio/radarr:testing-5.28.0.10205"; autoStart = true; ports = lib.mkIf cfg.radarr.exposePorts [ "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.radarr.uid; inherit PGID UMASK; TZ = config.time.timeZone; }; volumes = [ "radarr-config:/config" "/srv/radarr-backup:/config/Backups" "/srv/torrent:/media/cache" "${videoHostPath}/Films:/media/movies" ]; }; sonarr = let port = 8989; in lib.mkIf cfg.sonarr.enable { hostname = "sonarr"; image = "ghcr.io/hotio/sonarr:release-4.0.15.2941"; autoStart = true; ports = lib.mkIf cfg.sonarr.exposePorts [ "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.sonarr.uid; inherit PGID UMASK; TZ = config.time.timeZone; }; volumes = [ "sonarr-config:/config" "/srv/sonarr-backup:/config/Backups" "/srv/torrent:/media/cache" "${videoHostPath}/Series:/media/series" ]; }; }; }; }