From 2edb7c56ab4b705a58ac474b895db0d793efcaad Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Sun, 9 Feb 2025 23:32:13 -0600 Subject: [PATCH] mkRadarrInstance --- nixos/hosts/shadowfax/config/sops-secrets.nix | 48 ++- nixos/hosts/shadowfax/default.nix | 103 +++--- nixos/hosts/shadowfax/secrets.sops.yaml | 27 +- .../modules/nixos/services/radarr/default.nix | 314 ++++++++++-------- 4 files changed, 296 insertions(+), 196 deletions(-) diff --git a/nixos/hosts/shadowfax/config/sops-secrets.nix b/nixos/hosts/shadowfax/config/sops-secrets.nix index fecadb9..4c9ec16 100644 --- a/nixos/hosts/shadowfax/config/sops-secrets.nix +++ b/nixos/hosts/shadowfax/config/sops-secrets.nix @@ -91,37 +91,73 @@ restartUnits = [ "sonarr.service" ]; }; # Radarr - "arr/radarr/apiKey" = { + "arr/radarr/1080p/apiKey" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; restartUnits = [ "radarr.service" ]; }; - "arr/radarr/postgres/dbName" = { + "arr/radarr/1080p/postgres/dbName" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; restartUnits = [ "radarr.service" ]; }; - "arr/radarr/postgres/user" = { + "arr/radarr/1080p/postgres/user" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; restartUnits = [ "radarr.service" ]; }; - "arr/radarr/postgres/password" = { + "arr/radarr/1080p/postgres/password" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; restartUnits = [ "radarr.service" ]; }; - "arr/radarr/postgres/host" = { + "arr/radarr/1080p/postgres/host" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; restartUnits = [ "radarr.service" ]; }; - "arr/radarr/extraEnvVars" = { + "arr/radarr/1080p/extraEnvVars" = { + sopsFile = ../secrets.sops.yaml; + owner = "radarr"; + mode = "400"; + restartUnits = [ "radarr.service" ]; + }; + "arr/radarr/anime/apiKey" = { + sopsFile = ../secrets.sops.yaml; + owner = "radarr"; + mode = "400"; + restartUnits = [ "radarr.service" ]; + }; + "arr/radarr/anime/postgres/dbName" = { + sopsFile = ../secrets.sops.yaml; + owner = "radarr"; + mode = "400"; + restartUnits = [ "radarr.service" ]; + }; + "arr/radarr/anime/postgres/user" = { + sopsFile = ../secrets.sops.yaml; + owner = "radarr"; + mode = "400"; + restartUnits = [ "radarr.service" ]; + }; + "arr/radarr/anime/postgres/password" = { + sopsFile = ../secrets.sops.yaml; + owner = "radarr"; + mode = "400"; + restartUnits = [ "radarr.service" ]; + }; + "arr/radarr/anime/postgres/host" = { + sopsFile = ../secrets.sops.yaml; + owner = "radarr"; + mode = "400"; + restartUnits = [ "radarr.service" ]; + }; + "arr/radarr/anime/extraEnvVars" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; diff --git a/nixos/hosts/shadowfax/default.nix b/nixos/hosts/shadowfax/default.nix index 603e43e..fb23976 100644 --- a/nixos/hosts/shadowfax/default.nix +++ b/nixos/hosts/shadowfax/default.nix @@ -4,10 +4,11 @@ inputs, pkgs, ... -}: let - sanoidConfig = import ./config/sanoid.nix {}; +}: +let + sanoidConfig = import ./config/sanoid.nix { }; disks = import ./config/disks.nix; - smartdDevices = map (device: {inherit device;}) disks; + smartdDevices = map (device: { inherit device; }) disks; pushoverNotify = pkgs.writeShellApplication { name = "pushover-notify"; @@ -17,7 +18,7 @@ jq ]; - excludeShellChecks = ["SC2154"]; + excludeShellChecks = [ "SC2154" ]; text = '' ${builtins.readFile ./scripts/pushover-notify.sh} @@ -31,28 +32,29 @@ jq ]; - excludeShellChecks = ["SC2154"]; + excludeShellChecks = [ "SC2154" ]; text = '' ${builtins.readFile ./scripts/refresh-series.sh} ''; }; -in { +in +{ imports = [ inputs.disko.nixosModules.disko (import ../../profiles/disko-nixos.nix { - disks = ["/dev/sda|/dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_500GB_S58SNM0W406409E"]; + disks = [ "/dev/sda|/dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_500GB_S58SNM0W406409E" ]; }) inputs.nix-minecraft.nixosModules.minecraft-servers ]; boot = { initrd = { - kernelModules = ["nfs"]; - supportedFilesystems = ["nfs"]; + kernelModules = [ "nfs" ]; + supportedFilesystems = [ "nfs" ]; }; - binfmt.emulatedSystems = ["aarch64-linux"]; # Enabled for arm compilation + binfmt.emulatedSystems = [ "aarch64-linux" ]; # Enabled for arm compilation kernelModules = [ "vfio" @@ -60,11 +62,11 @@ in { "vfio_pci" "vfio_virqfd" ]; - extraModulePackages = []; - kernelParams = ["zfs.zfs_arc_max=107374182400"]; # 100GB + extraModulePackages = [ ]; + kernelParams = [ "zfs.zfs_arc_max=107374182400" ]; # 100GB }; - swapDevices = []; + swapDevices = [ ]; hardware = { cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; @@ -74,7 +76,7 @@ in { nvidia-container-toolkit.enable = true; }; - users.users.root.openssh.authorizedKeys.keys = []; + users.users.root.openssh.authorizedKeys.keys = [ ]; # Network settings networking = { hostName = "shadowfax"; @@ -167,7 +169,7 @@ in { # Minio minio = { enable = true; - dataDir = ["/eru/minio"]; + dataDir = [ "/eru/minio" ]; rootCredentialsFile = config.sops.secrets."minio".path; }; @@ -194,7 +196,7 @@ in { # Soft Serve - SSH git server soft-serve = { enable = true; - settings = import ./config/soft-serve.nix {}; + settings = import ./config/soft-serve.nix { }; }; sunshine = { @@ -214,7 +216,7 @@ in { # VSCode Compatibility Settings vscode-server.enable = true; - xserver.videoDrivers = ["nvidia"]; + xserver.videoDrivers = [ "nvidia" ]; greetd = { enable = true; vt = 3; @@ -228,7 +230,7 @@ in { }; # sops - sops = import ./config/sops-secrets.nix {}; + sops = import ./config/sops-secrets.nix { }; # System settings and services. mySystem = { @@ -267,22 +269,49 @@ in { # Radarr radarr = { enable = true; - package = pkgs.unstable.radarr; - dataDir = "/nahar/radarr"; - extraEnvVarFile = config.sops.secrets."arr/radarr/extraEnvVars".path; - moviesDir = "/moria/media/Movies"; - user = "radarr"; - group = "kah"; - port = 7878; - openFirewall = true; - hardening = true; - apiKeyFile = config.sops.secrets."arr/radarr/apiKey".path; - db = { - enable = true; - hostFile = config.sops.secrets."arr/radarr/postgres/host".path; - port = 5432; - userFile = config.sops.secrets."arr/radarr/postgres/user".path; - passwordFile = config.sops.secrets."arr/radarr/postgres/password".path; + instances = { + movies1080p = { + enable = true; + package = pkgs.unstable.radarr; + dataDir = "/nahar/radarr/1080p"; + extraEnvVarFile = config.sops.secrets."arr/radarr/1080p/extraEnvVars".path; + moviesDir = "/moria/media/Movies"; + user = "radarr"; + group = "kah"; + port = 7878; + openFirewall = true; + hardening = true; + apiKeyFile = config.sops.secrets."arr/radarr/1080p/apiKey".path; + db = { + enable = true; + hostFile = config.sops.secrets."arr/radarr/1080p/postgres/host".path; + port = 5432; + dbname = "radarr_main"; + userFile = config.sops.secrets."arr/radarr/1080p/postgres/user".path; + passwordFile = config.sops.secrets."arr/radarr/1080p/postgres/password".path; + }; + }; + moviesAnime = { + enable = false; + package = pkgs.unstable.radarr; + dataDir = "/nahar/radarr/anime"; + extraEnvVarFile = config.sops.secrets."arr/radarr/anime/extraEnvVars".path; + moviesDir = "/moria/media/Anime/Movies"; + user = "radarr"; + group = "kah"; + port = 7879; + openFirewall = true; + hardening = true; + apiKeyFile = config.sops.secrets."arr/radarr/anime/apiKey".path; + db = { + enable = true; + hostFile = config.sops.secrets."arr/radarr/anime/postgres/host".path; + port = 5432; + dbname = "radarr_anime"; + userFile = config.sops.secrets."arr/radarr/anime/postgres/user".path; + passwordFile = config.sops.secrets."arr/radarr/anime/postgres/password".path; + }; + }; }; }; # Sonarr @@ -354,7 +383,7 @@ in { # qBittorrent qbittorrent = { enable = true; - package = pkgs.unstable.qbittorrent.override {guiSupport = false;}; + package = pkgs.unstable.qbittorrent.override { guiSupport = false; }; user = "qbittorrent"; group = "kah"; dataDir = "/nahar/qbittorrent"; @@ -378,9 +407,9 @@ in { system = { incus = { enable = true; - preseed = import ./config/incus-preseed.nix {}; + preseed = import ./config/incus-preseed.nix { }; }; - motd.networkInterfaces = ["bond0"]; + motd.networkInterfaces = [ "bond0" ]; nfs.enable = true; zfs.enable = true; zfs.mountPoolsAtBoot = [ diff --git a/nixos/hosts/shadowfax/secrets.sops.yaml b/nixos/hosts/shadowfax/secrets.sops.yaml index e9f7c12..75deae8 100644 --- a/nixos/hosts/shadowfax/secrets.sops.yaml +++ b/nixos/hosts/shadowfax/secrets.sops.yaml @@ -28,13 +28,22 @@ arr: password: ENC[AES256_GCM,data:XOrycMom2utnefraGPoAq7xtP6yfSzTb8g==,iv:WQInK+bJuDNI9uN/GeQ2Fb1Mmlux6+lXwkGS1ZEh+kQ=,tag:DGqLerxomCVfVv15Gt3b8A==,type:str] extraEnvVars: ENC[AES256_GCM,data:KnZSJ2YbNLawSzrj7syx0cfAFseHbgjGvjpB7yWajXfCIy+CV800z9YU2SVO2kV6b+9OrmyKKFFbM5ac4cWnc5Pcx8TUxfiAuL5RSi6ZTmUrZUA7Zqx5UDTHwXgvhDI=,iv:TX8sFk7uc1TYG/gkuA9plGZlhP25WuczEXd+QKsPi4c=,tag:zhVeZxrTgcv+Y2OP8I+k5g==,type:str] radarr: - apiKey: ENC[AES256_GCM,data:Qcfzr12aftnS+b3pDHHnfOya1+vlyVaoNCPLzJ9xv5Pv,iv:9M33sfqZPzeghxmBtYk3LgsfbInC7sPSQGuYFJiydh4=,tag:lSmi6Do64sarG15q6+yuQw==,type:str] - postgres: - host: ENC[AES256_GCM,data:mYFhyUYIqp8=,iv:Cj03GOR58PWv3HWXRx0/D5tZ2oObejtIVuQcTcHGkuw=,tag:9K2asujaqi4/Qbozb18Ghg==,type:str] - dbName: ENC[AES256_GCM,data:zC4j0VJJpWWT0XY=,iv:ITupnWLgvI2wAPnkD826S77BMELDqRWZKax51SVkBgA=,tag:L7YXfoxAhi94ssBoE35Aug==,type:str] - user: ENC[AES256_GCM,data:jaYUWAzQ,iv:ayEutHFPyZ7CN3inTqmgPmintR8qE8HfatvzCx7VXnA=,tag:3Ou0JRzpcihL0AWcC0pC5w==,type:str] - password: ENC[AES256_GCM,data:XcS9H5L+ikA2KflepKrBHVlBjKwB0Vu8mw==,iv:lSpoEiCqOpP3p1T7bBH8F9YiSf2kwQQC+FQPuaKojnE=,tag:ScV/E6JeomQlfp35NIrh1g==,type:str] - extraEnvVars: ENC[AES256_GCM,data:hQlXQD4Zb3HnXfOZsX5PpxxyIzpYjqFletRiGRUWpICCEIcbsEK6WN8VtCaH+lpVqcVOpLUSSX1jILEwn4H483JyPk9EI04tIGBgq+svUOg7PZhc7zLllczMKN+BcdY=,iv:OyeAYXln8Dm1WvrrySkYXk+XC7kcwHFUSamIBdCTHRY=,tag:OGsR6IfBJF5Ki6VG15iyUw==,type:str] + 1080p: + apiKey: ENC[AES256_GCM,data:NVIa3nK4/QxZF1/peKei9Qsxn2AjRiBnBdr2N08QiXhi,iv:h/Rb38FCF8auMCTT+Cuj/i8YAeavntwbbJEA9LwDYUY=,tag:OwAQy76GjIrY9/126zExdA==,type:str] + postgres: + host: ENC[AES256_GCM,data:bycrytLkcdI=,iv:zfsSP+uoVpvG2HXydCXT+yfMc5GadjG8qo+4ld+rdjk=,tag:cXtYTFg27hS9Idn3UjUn1A==,type:str] + dbName: ENC[AES256_GCM,data:pjvSoLQ/TUdd6Y0=,iv:TsThUHuF9Mm1fuGYcEh/GanaDGuoSJxPt7LVRJHSFAo=,tag:tMIdTPSaNE4hOh97lck6DQ==,type:str] + user: ENC[AES256_GCM,data:0QFQsAfq,iv:+qtq/BhC26fdW9rXlbZFf6fQCOV+UsP1Y9cNMUtn5+w=,tag:WBg4ruYa7t+8d8iHjjPJ1g==,type:str] + password: ENC[AES256_GCM,data:TvGQsShraVE70JoHkk4PWkoeElu+dn882w==,iv:oQ15XrRCLUZ6XWSifB9hGF+wYActgDE9NEqly+LTTFA=,tag:kUbn5dKq9jU4Xl1+mRI2Zw==,type:str] + extraEnvVars: ENC[AES256_GCM,data:4E1NFQZOrH7sBGnJus7+VZLTWnzif1Asr+mCeaXorsfO8nJpA14HjQ9diAqPuabTlUsl4vMznFmKjdAFCvuRz9p7xfq96qmJKN3oTKuVaz6SlQbXKcfCeldn2E2EA2k=,iv:CVaiTGR0WpcCNvDpD202P4dHqEjX9xqJEVz0usEeANY=,tag:FRz7t5wV2TePakxmyBkb1Q==,type:str] + anime: + apiKey: ENC[AES256_GCM,data:7Z59sJCKBmayd1J71+C72IdoeNlU71jcJS885bUwc+g8,iv:0Uav84UAanmZvvZfJJNoDmvyKY068L8+ONnVGWmldSI=,tag:WYQvccwlm/Y8S2ynPkEPjA==,type:str] + postgres: + host: ENC[AES256_GCM,data:EL6Qzm8r/XQ=,iv:Gz099lg7zDRrIf7i2kOb+MSXGwHazTtcnBbCmciVCHE=,tag:l2Lqd7dDeRZYzflhK9/Kdg==,type:str] + dbName: ENC[AES256_GCM,data:9MJ6UcVlLPN+hI4=,iv:AwCdIJ1KwThbUVGM759m0GVWh3yf3anBHdsfdIjVWP4=,tag:7kUUSTNpt/rOtYHkDRlKxA==,type:str] + user: ENC[AES256_GCM,data:QE+Lr0L0ad3oufG4,iv:WCTkwhN9FjibXlyPqY7oDjSByXn6Exma/VDsE3ju/FE=,tag:orFBsxd/ETSaiFC1KNsO0A==,type:str] + password: ENC[AES256_GCM,data:3rYBq3cosu962m/FZIc/WZogr+BZxQ==,iv:dK/IDKYz3NKeAE7PV/f+qRtSu3veg20B6+6gZmaD/eg=,tag:9k0+zrcWC6i6YjwJkMYkzA==,type:str] + extraEnvVars: ENC[AES256_GCM,data:HAGHXI14CFx/CXbpHGn1MQQeRmMO9zzsnHEJrgTfho96XPtoKJQj+b3h7VNVkkHAifjuMzbKb0BM8qrtAIjssWuQUrWChxgHBg3OWodsJkstYplB0RYIun5RorzHaNY=,iv:3nW9806ciMdblFphEi9zK8kkIB8r01eF/167vYq177w=,tag:7aPJ63Wl+SGzWCBXuitgmA==,type:str] unpackerr: extraEnvVars: ENC[AES256_GCM,data:UT6HLaEKGEHggZx8Ict23OOLlRppErCnIDA16CleMFe33/IHyyp8El8aBNCq5A/1+NIGN5aZat2El562QAcZBVITa9ffmmtXqnDsheaRo+nDSPR5mGqh39uoKWIy2eSO5nv3rD0jN08MFYjxZNti/k0p0JIDMYN5shnVLiwO7krLs3Z6m+sZntg6euCWnHJMM2p3v38bffUzbZWrD1hEKaOUofOhTL0rDUiiysCawMZvJx6zXgfyO6DeUdwlYIxlg0mZ8SkmDRoFDPYrI40tIA+oSScJhJkDgoNPPJVEuGu6vVMPgqkAWUSucVGeiYVati2PsBJHn9wori2GWRTY0n7mjQKe8cz3nPIKQFWYLRWDZ9UBBvh05bYLa54+C4mGczFFPZra/DDoR2i9gzCXxjhRmt2Qs+g0RqefJTq5W2qQKRLj9PKFr02GuOY=,iv:pmOkkfFd4eGRCburRz6rgqqAFQJhPgjuUKn23ipLg3E=,tag:heZtewMOnnMxE5qSVEiI0Q==,type:str] sops: @@ -115,8 +124,8 @@ sops: aVlOSHhFb2I5UnYwVytyQzlWTXBDYUUKdQKilmfJ1F7UYKtQV9zV95FcRIK17p4M vGvu/pGJ32tH8xI7cNs9I5Hmg9c5wOam21W1FDk+VlJ/ClXqQzS0MA== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-02-04T04:28:14Z" - mac: ENC[AES256_GCM,data:7y38SzT51EOTx83lEJugW5XmsgGa00yemYIE5IRLasqwiSGSWHTt3iZy/2FLNXbFIqiz+O1K3xajstOF4ijvpYmP6Tu3rCh/4gxChqcokP6kxsI6b4nKp6ZkC/eZpoevFQmYHYqJU4b9iOQT9QZqdzwCIxOVRNZDAAc8u1SeBsg=,iv:7ni7qo6Xojs5MlOZBMLnnEKnDqdNDp9Ms/D9JMoRnm0=,tag:1/wqZh0igzhFyqdBxemntA==,type:str] + lastmodified: "2025-02-10T04:49:46Z" + mac: ENC[AES256_GCM,data:wT82lve5wNbxXUgcw3EZkOrsLFOmtriJtSNtpcfcH2KYkFEZTyzYCO10EBMrm1FjJxJFmhtde9f+CkSkT5ypjP6Ou4TGn9nP0jOlCyLxYyQkv7Lr31aEF8d2Q0NDjSHePW4YUiY88YrOX8shWYFVtNoZSonyCv8G6lwGVIsZzi4=,iv:MkHiwV5qRBE3LfTt0WHV56LWAeTEcRlux4xIf90R3AU=,tag:qUgCaPLa+W/pZ8uM1Gw7Xw==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.9.4 diff --git a/nixos/modules/nixos/services/radarr/default.nix b/nixos/modules/nixos/services/radarr/default.nix index c2c6a2d..dc9e0c5 100644 --- a/nixos/modules/nixos/services/radarr/default.nix +++ b/nixos/modules/nixos/services/radarr/default.nix @@ -5,7 +5,8 @@ utils, ... }: -with lib; let +with lib; +let cfg = config.mySystem.services.radarr; dbOptions = { options = { @@ -50,166 +51,77 @@ with lib; let }; }; }; -in { - options.mySystem.services.radarr = { - enable = mkEnableOption "Radarr"; - package = mkPackageOption pkgs "Radarr" {}; - - user = mkOption { - type = types.str; - default = "radarr"; - description = "User account under which radarr runs."; - }; - - group = mkOption { - type = types.str; - default = "radarr"; - description = "Group under which radarr runs."; - }; - - dataDir = mkOption { - type = types.path; - default = "/var/lib/radarr"; - description = "Storage directory for radarr data"; - }; - - moviesDir = mkOption { - type = types.path; - default = "/mnt/media/movies"; - description = "Directory where movies are stored"; - }; - - port = mkOption { - type = types.port; - default = 7878; - description = "Port for radarr web interface"; - }; - - openFirewall = mkOption { - type = types.bool; - default = false; - description = "Open firewall ports for radarr"; - }; - - hardening = mkOption { - type = types.bool; - default = true; - description = "Enable security hardening features"; - }; - - apiKey = mkOption { - type = types.str; - default = ""; - example = "abc123"; - description = "Direct API key for radarr (mutually exclusive with apiKeyFile)"; - }; - - apiKeyFile = mkOption { - type = types.path; - default = "/run/secrets/radarr_api_key"; - description = "API key for radarr from a file (mutually exclusive with apiKey)"; - }; - - db = mkOption { - type = types.submodule dbOptions; - example = { - enable = true; - host = "10.5.0.5"; # or use hostFile - port = "5432"; - user = "radarr"; # or userFile - passwordFile = "/run/secrets/radarr_db_password"; - dbname = "radarr_main"; - }; - description = "Database settings for radarr."; - }; - - extraEnvVars = mkOption { - type = types.attrs; - default = {}; - example = { - MY_VAR = "my value"; - }; - description = "Extra environment variables for radarr."; - }; - - extraEnvVarFile = mkOption { - type = lib.types.nullOr lib.types.path; - default = null; - example = "/run/secrets/radarr_extra_env"; - description = "Extra environment file for Radarr."; - }; - }; - - config = mkIf cfg.enable { + # Function to create instance-specific configuration + mkRadarrInstance = name: instanceCfg: { assertions = [ { - assertion = !(cfg.db.host != "" && cfg.db.hostFile != ""); + assertion = !(instanceCfg.db.host != "" && instanceCfg.db.hostFile != ""); message = "Specify either a direct database host via db.host or a file via db.hostFile (leave direct host empty)."; } { - assertion = !(cfg.db.user != "radarr" && cfg.db.userFile != ""); + assertion = !(instanceCfg.db.user != "radarr" && instanceCfg.db.userFile != ""); message = "Specify either a direct database user via db.user or a file via db.userFile."; } { - assertion = !(cfg.apiKey != "" && cfg.apiKeyFile != ""); + assertion = !(instanceCfg.apiKey != "" && instanceCfg.apiKeyFile != ""); message = "Specify either a direct API key via apiKey or a file via apiKeyFile (leave direct API key empty)."; } ]; systemd.tmpfiles.rules = [ - "d ${cfg.dataDir} 0775 ${cfg.user} ${cfg.group}" + "d ${instanceCfg.dataDir} 0775 ${instanceCfg.user} ${instanceCfg.group}" ]; - systemd.services.radarr = { - description = "Radarr"; + systemd.services."radarr-${name}" = { + description = "Radarr (${name})"; after = [ "network.target" "nss-lookup.target" ]; - wantedBy = ["multi-user.target"]; + wantedBy = [ "multi-user.target" ]; environment = lib.mkMerge [ { - RADARR__APP__INSTANCENAME = "Radarr"; + RADARR__APP__INSTANCENAME = name; RADARR__APP__THEME = "dark"; RADARR__AUTH__METHOD = "External"; RADARR__AUTH__REQUIRED = "DisabledForLocalAddresses"; RADARR__LOG__DBENABLED = "False"; RADARR__LOG__LEVEL = "info"; - RADARR__SERVER__PORT = toString cfg.port; + RADARR__SERVER__PORT = toString instanceCfg.port; RADARR__UPDATE__BRANCH = "develop"; } - (lib.mkIf cfg.db.enable { - RADARR__POSTGRES__PORT = toString cfg.db.port; - RADARR__POSTGRES__MAINDB = cfg.db.dbname; + (lib.mkIf instanceCfg.db.enable { + RADARR__POSTGRES__PORT = toString instanceCfg.db.port; + RADARR__POSTGRES__MAINDB = instanceCfg.db.dbname; }) - cfg.extraEnvVars + instanceCfg.extraEnvVars ]; serviceConfig = lib.mkMerge [ { + Name = "radarr-${name}"; Type = "simple"; - User = cfg.user; - Group = cfg.group; + User = instanceCfg.user; + Group = instanceCfg.group; ExecStart = utils.escapeSystemdExecArgs [ - (lib.getExe cfg.package) + (lib.getExe instanceCfg.package) "-nobrowser" - "-data=${cfg.dataDir}" - "-port=${toString cfg.port}" + "-data=${instanceCfg.dataDir}" + "-port=${toString instanceCfg.port}" ]; - WorkingDirectory = cfg.dataDir; - RuntimeDirectory = "radarr"; - LogsDirectory = "radarr"; + WorkingDirectory = instanceCfg.dataDir; + RuntimeDirectory = "radarr-${name}"; + LogsDirectory = "radarr-${name}"; RuntimeDirectoryMode = "0750"; Restart = "on-failure"; RestartSec = 5; } - (lib.mkIf cfg.hardening { - CapabilityBoundingSet = [""]; - DeviceAllow = [""]; + (lib.mkIf instanceCfg.hardening { + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ "" ]; DevicePolicy = "closed"; LockPersonality = true; - # Needs access to .Net CLR memory space. MemoryDenyWriteExecute = false; NoNewPrivileges = true; PrivateDevices = true; @@ -220,9 +132,9 @@ in { ProtectKernelTunables = true; ProtectSystem = "strict"; ReadWritePaths = [ - cfg.dataDir - cfg.moviesDir - "/var/log/radarr" + instanceCfg.dataDir + instanceCfg.moviesDir + "/var/log/radarr-${name}" "/eru/media" ]; RestrictAddressFamilies = [ @@ -242,61 +154,175 @@ in { SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" - #"~@privileged" - # .Net CLR requirement - #"~@resources" ]; }) - (lib.mkIf cfg.db.enable { - ExecStartPre = "+${pkgs.writeShellScript "radarr-pre-script" '' - mkdir -p /run/radarr - rm -f /run/radarr/secrets.env + (lib.mkIf instanceCfg.db.enable { + ExecStartPre = "+${pkgs.writeShellScript "radarr-${name}-pre-script" '' + mkdir -p /run/radarr-${name} + rm -f /run/radarr-${name}/secrets.env # Helper function to safely write variables write_var() { local var_name="$1" local value="$2" if [ -n "$value" ]; then - printf "%s=%s\n" "$var_name" "$value" >> /run/radarr/secrets.env + printf "%s=%s\n" "$var_name" "$value" >> /run/radarr-${name}/secrets.env fi } # API Key (direct value or file) - if [ -n "${cfg.apiKey}" ]; then - write_var "RADARR__AUTH__APIKEY" "${cfg.apiKey}" + if [ -n "${instanceCfg.apiKey}" ]; then + write_var "RADARR__AUTH__APIKEY" "${instanceCfg.apiKey}" else - write_var "RADARR__AUTH__APIKEY" "$(cat ${cfg.apiKeyFile})" + write_var "RADARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})" fi # Database Configuration - write_var "RADARR__POSTGRES__HOST" "$([ -n "${cfg.db.host}" ] && echo "${cfg.db.host}" || cat "${cfg.db.hostFile}")" - write_var "RADARR__POSTGRES__USER" "$([ -n "${cfg.db.user}" ] && echo "${cfg.db.user}" || cat "${cfg.db.userFile}")" - write_var "RADARR__POSTGRES__PASSWORD" "$(cat ${cfg.db.passwordFile})" + write_var "RADARR__POSTGRES__HOST" "$([ -n "${instanceCfg.db.host}" ] && echo "${instanceCfg.db.host}" || cat "${instanceCfg.db.hostFile}")" + write_var "RADARR__POSTGRES__USER" "$([ -n "${instanceCfg.db.user}" ] && echo "${instanceCfg.db.user}" || cat "${instanceCfg.db.userFile}")" + write_var "RADARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})" # Final permissions - chmod 600 /run/radarr/secrets.env - chown ${cfg.user}:${cfg.group} /run/radarr/secrets.env + chmod 600 /run/radarr-${name}/secrets.env + chown ${instanceCfg.user}:${instanceCfg.group} /run/radarr-${name}/secrets.env ''}"; EnvironmentFile = ( - ["-/run/radarr/secrets.env"] - ++ lib.optional (cfg.extraEnvVarFile != null && cfg.extraEnvVarFile != "") cfg.extraEnvVarFile + [ "-/run/radarr-${name}/secrets.env" ] + ++ lib.optional ( + instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != "" + ) instanceCfg.extraEnvVarFile ); }) ]; }; - networking.firewall = mkIf cfg.openFirewall { - allowedTCPPorts = [cfg.port]; + networking.firewall = mkIf instanceCfg.openFirewall { + allowedTCPPorts = [ instanceCfg.port ]; }; - users.groups.${cfg.group} = {}; - users.users = mkIf (cfg.user == "radarr") { + users.groups.${instanceCfg.group} = { }; + users.users = mkIf (instanceCfg.user == "radarr") { radarr = { - inherit (cfg) group; + inherit (instanceCfg) group; isSystemUser = true; - home = cfg.dataDir; + home = instanceCfg.dataDir; }; }; }; +in +{ + options.mySystem.services.radarr = { + enable = mkEnableOption "Radarr (global)"; + + instances = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + enable = mkEnableOption "Radarr (instance)"; + + package = mkPackageOption pkgs "Radarr" { }; + + user = mkOption { + type = types.str; + default = "radarr"; + description = "User account under which radarr runs."; + }; + + group = mkOption { + type = types.str; + default = "radarr"; + description = "Group under which radarr runs."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/radarr/${name}"; + description = "Storage directory for radarr data"; + }; + + moviesDir = mkOption { + type = types.path; + default = "/mnt/media/movies"; + description = "Directory where movies are stored"; + }; + + port = mkOption { + type = types.port; + default = 7878; + description = "Port for radarr web interface"; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open firewall ports for radarr"; + }; + + hardening = mkOption { + type = types.bool; + default = true; + description = "Enable security hardening features"; + }; + + apiKey = mkOption { + type = types.str; + default = ""; + example = "abc123"; + description = "Direct API key for radarr (mutually exclusive with apiKeyFile)"; + }; + + apiKeyFile = mkOption { + type = types.path; + default = "/run/secrets/radarr_api_key"; + description = "API key for radarr from a file (mutually exclusive with apiKey)"; + }; + + db = mkOption { + type = types.submodule dbOptions; + example = { + enable = true; + host = "10.5.0.5"; # or use hostFile + port = "5432"; + user = "radarr"; # or userFile + passwordFile = "/run/secrets/radarr_db_password"; + dbname = "radarr_main"; + }; + description = "Database settings for radarr."; + }; + + extraEnvVars = mkOption { + type = types.attrs; + default = { }; + example = { + MY_VAR = "my value"; + }; + description = "Extra environment variables for radarr."; + }; + + extraEnvVarFile = mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/secrets/radarr_extra_env"; + description = "Extra environment file for Radarr."; + }; + }; + } + ) + ); + default = { }; + description = "Radarr instance configurations."; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + # Create services for each enabled instance + (mkMerge ( + mapAttrsToList ( + name: instanceCfg: mkIf instanceCfg.enable (mkRadarrInstance name instanceCfg) + ) cfg.instances + )) + ]); }