From 2edb7c56ab4b705a58ac474b895db0d793efcaad Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Sun, 9 Feb 2025 23:32:13 -0600 Subject: [PATCH 1/6] 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 + )) + ]); } From 934af3c9b8c5e305c6d0eb2054fd45106fb34b1a Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Mon, 10 Feb 2025 10:34:41 -0600 Subject: [PATCH 2/6] update flake lock -- all --- flake.lock | 106 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/flake.lock b/flake.lock index 5c5db42..09f3047 100644 --- a/flake.lock +++ b/flake.lock @@ -57,11 +57,11 @@ ] }, "locked": { - "lastModified": 1738148035, - "narHash": "sha256-KYOATYEwaKysL3HdHdS5kbQMXvzS4iPJzJrML+3TKAo=", + "lastModified": 1738765162, + "narHash": "sha256-3Z40qHaFScWUCVQrGc4Y+RdoPsh1R/wIh+AN4cTXP0I=", "owner": "nix-community", "repo": "disko", - "rev": "18d0a984cc2bc82cf61df19523a34ad463aa7f54", + "rev": "ff3568858c54bd306e9e1f2886f0f781df307dff", "type": "github" }, "original": { @@ -437,11 +437,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1738546470, - "narHash": "sha256-pyMFj2IBeiRDnYoHD9XtbFiwBzvXJCavdFoVPnZ1YB0=", + "lastModified": 1739123889, + "narHash": "sha256-TinJJD88Y2EY0eoThanqN8Zr8I25JVhr1CeRS2LUeOc=", "owner": "hyprwm", "repo": "Hyprland", - "rev": "708d16636047c6a311c4e44424cf7d2090219a47", + "rev": "f261fb6fe028a1427cfd672eee6e7d5705cd696f", "type": "github" }, "original": { @@ -467,11 +467,11 @@ ] }, "locked": { - "lastModified": 1738500466, - "narHash": "sha256-GAO3bpA4nk7RETw0SFN3QhDPNcTYbxiCoRZO9NudKsY=", + "lastModified": 1738966025, + "narHash": "sha256-MzgrF0jKlTNnNS33D8PwI5z2MJ1Tzf5MQVf/oms2pdw=", "owner": "hyprwm", "repo": "hyprland-plugins", - "rev": "eefa87d099bac625234b9e89ed67624efea0d27a", + "rev": "4f48dbe12f3cbbeb4d31c91c67b21edbc5b4b451", "type": "github" }, "original": { @@ -802,11 +802,11 @@ ] }, "locked": { - "lastModified": 1738547119, - "narHash": "sha256-cc6AfR7W0AavgqA5nHUXRUus4Rr7oPWQNku5nhR4SYs=", + "lastModified": 1739152024, + "narHash": "sha256-9lPbbaZfltXnkATkqBETzonhW8T4zQKgCs+BqLWt1n4=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "5b93268c80c3300dbec0fbbb2b50f674f84a474a", + "rev": "a5890fd3546723e386f2e485bc37e1bd56c4f623", "type": "github" }, "original": { @@ -824,11 +824,11 @@ ] }, "locked": { - "lastModified": 1738547248, - "narHash": "sha256-ALPkA9L4G0j7piorEyeQ7zf6fW4vii4ULxRZBXmeKYM=", + "lastModified": 1739152168, + "narHash": "sha256-Zv6eGe+c5f9Chyw6O3ePJ4hbscetuzZxYShwjn3ACEs=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "bfacd5e2786caf61da0ad956728559dd6c1e8037", + "rev": "bd6b70f681b2561f53c9522be64330c7ff9d08d8", "type": "github" }, "original": { @@ -839,11 +839,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1738471961, - "narHash": "sha256-cgXDFrplNGs7bCVzXhRofjD8oJYqqXGcmUzXjHmip6Y=", + "lastModified": 1738816619, + "narHash": "sha256-5yRlg48XmpcX5b5HesdGMOte+YuCy9rzQkJz+imcu6I=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "537286c3c59b40311e5418a180b38034661d2536", + "rev": "2eccff41bab80839b1d25b303b53d339fbb07087", "type": "github" }, "original": { @@ -911,11 +911,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1738410390, - "narHash": "sha256-xvTo0Aw0+veek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc=", + "lastModified": 1739020877, + "narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "3a228057f5b619feb3186e986dbe76278d707b6e", + "rev": "a79cfe0ebd24952b580b1cf08cd906354996d547", "type": "github" }, "original": { @@ -927,11 +927,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1738435198, - "narHash": "sha256-5+Hmo4nbqw8FrW85FlNm4IIrRnZ7bn0cmXlScNsNRLo=", + "lastModified": 1739055578, + "narHash": "sha256-2MhC2Bgd06uI1A0vkdNUyDYsMD0SLNGKtD8600mZ69A=", "owner": "nixos", "repo": "nixpkgs", - "rev": "f6687779bf4c396250831aa5a32cbfeb85bb07a3", + "rev": "a45fa362d887f4d4a7157d95c28ca9ce2899b70e", "type": "github" }, "original": { @@ -943,11 +943,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1738142207, - "narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=", + "lastModified": 1739020877, + "narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9d3ae807ebd2981d593cddd0080856873139aa40", + "rev": "a79cfe0ebd24952b580b1cf08cd906354996d547", "type": "github" }, "original": { @@ -1032,11 +1032,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1738362438, - "narHash": "sha256-EO2dVkMVLThWqv4hobEZEZGWBEuH2Z9SYqQDrbLSclU=", + "lastModified": 1739155557, + "narHash": "sha256-LKagpHr2dxARgTSZDEXdpN6AX0moNzUhvjtmb+3+GrM=", "owner": "nix-community", "repo": "NUR", - "rev": "95ddad0ff0e67c90314c6ca46324dce5f9a910d2", + "rev": "38696e05e8c52a8e1db5e045fe857eb0f65cdbf7", "type": "github" }, "original": { @@ -1056,6 +1056,8 @@ "plugin-aerial-nvim": "plugin-aerial-nvim", "plugin-alpha-nvim": "plugin-alpha-nvim", "plugin-base16": "plugin-base16", + "plugin-blink-cmp": "plugin-blink-cmp", + "plugin-blink-compat": "plugin-blink-compat", "plugin-bufdelete-nvim": "plugin-bufdelete-nvim", "plugin-catppuccin": "plugin-catppuccin", "plugin-ccc": "plugin-ccc", @@ -1222,11 +1224,11 @@ "systems": "systems_6" }, "locked": { - "lastModified": 1738504946, - "narHash": "sha256-/btrLwD7UFgNWPxdewMdYYiI2GJxYipGnLuX7pRVAjc=", + "lastModified": 1739089639, + "narHash": "sha256-MCkgsVTAtoVUthorcCeit1oBuFyG7XktYdeMzyHL2uE=", "owner": "notashelf", "repo": "nvf", - "rev": "944327329712eda9eec8a86a972e0abd7ea368e6", + "rev": "a78026438cc8e280a696bcadb60f5c8f93b96a12", "type": "github" }, "original": { @@ -1326,6 +1328,38 @@ "type": "github" } }, + "plugin-blink-cmp": { + "flake": false, + "locked": { + "lastModified": 1736295934, + "narHash": "sha256-MfHI4efAdaoCU8si6YFdznZmSTprthDq3YKuF91z7ss=", + "owner": "saghen", + "repo": "blink.cmp", + "rev": "1cc3b1a908fbcfd15451c4772759549724f38524", + "type": "github" + }, + "original": { + "owner": "saghen", + "repo": "blink.cmp", + "type": "github" + } + }, + "plugin-blink-compat": { + "flake": false, + "locked": { + "lastModified": 1734896240, + "narHash": "sha256-Rrrh+O3FbBnaAnCHwPuQyfhH+XueSkQp6ipEkn6esGY=", + "owner": "saghen", + "repo": "blink.compat", + "rev": "74b251a1e9478c4fa6d7c6bc2921d7124e6f6cbb", + "type": "github" + }, + "original": { + "owner": "saghen", + "repo": "blink.compat", + "type": "github" + } + }, "plugin-bufdelete-nvim": { "flake": false, "locked": { @@ -4223,11 +4257,11 @@ ] }, "locked": { - "lastModified": 1738397340, - "narHash": "sha256-Vt3y544m/w04ty8v+W/kfG3p34j+nys9rL13sNoqlcU=", + "lastModified": 1739084700, + "narHash": "sha256-8WzaXHu81bcAgFNC/VnnlDFAchH7B8UNlust1oyOZqA=", "owner": "budimanjojo", "repo": "talhelper", - "rev": "5886ae9f388613dc0f3b9f17b5228c0d406b77ed", + "rev": "3802fa0b656a1eb585280b88895b1f7339336694", "type": "github" }, "original": { From 5161eba75c46407a1d4d70ee5f0b043c100e3323 Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Mon, 10 Feb 2025 11:20:38 -0600 Subject: [PATCH 3/6] reconfig - no mkfunction --- .../modules/nixos/services/radarr/default.nix | 337 ++++++++++-------- 1 file changed, 194 insertions(+), 143 deletions(-) diff --git a/nixos/modules/nixos/services/radarr/default.nix b/nixos/modules/nixos/services/radarr/default.nix index dc9e0c5..adf5179 100644 --- a/nixos/modules/nixos/services/radarr/default.nix +++ b/nixos/modules/nixos/services/radarr/default.nix @@ -54,147 +54,10 @@ let # Function to create instance-specific configuration mkRadarrInstance = name: instanceCfg: { - assertions = [ - { - 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 = !(instanceCfg.db.user != "radarr" && instanceCfg.db.userFile != ""); - message = "Specify either a direct database user via db.user or a file via db.userFile."; - } - { - 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 ${instanceCfg.dataDir} 0775 ${instanceCfg.user} ${instanceCfg.group}" - ]; systemd.services."radarr-${name}" = { description = "Radarr (${name})"; - after = [ - "network.target" - "nss-lookup.target" - ]; - wantedBy = [ "multi-user.target" ]; - environment = lib.mkMerge [ - { - 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 instanceCfg.port; - RADARR__UPDATE__BRANCH = "develop"; - } - (lib.mkIf instanceCfg.db.enable { - RADARR__POSTGRES__PORT = toString instanceCfg.db.port; - RADARR__POSTGRES__MAINDB = instanceCfg.db.dbname; - }) - instanceCfg.extraEnvVars - ]; - serviceConfig = lib.mkMerge [ - { - Name = "radarr-${name}"; - Type = "simple"; - User = instanceCfg.user; - Group = instanceCfg.group; - ExecStart = utils.escapeSystemdExecArgs [ - (lib.getExe instanceCfg.package) - "-nobrowser" - "-data=${instanceCfg.dataDir}" - "-port=${toString instanceCfg.port}" - ]; - WorkingDirectory = instanceCfg.dataDir; - RuntimeDirectory = "radarr-${name}"; - LogsDirectory = "radarr-${name}"; - RuntimeDirectoryMode = "0750"; - Restart = "on-failure"; - RestartSec = 5; - } - (lib.mkIf instanceCfg.hardening { - CapabilityBoundingSet = [ "" ]; - DeviceAllow = [ "" ]; - DevicePolicy = "closed"; - LockPersonality = true; - MemoryDenyWriteExecute = false; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateTmp = true; - ProtectControlGroups = true; - ProtectHome = "read-only"; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectSystem = "strict"; - ReadWritePaths = [ - instanceCfg.dataDir - instanceCfg.moviesDir - "/var/log/radarr-${name}" - "/eru/media" - ]; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - "AF_NETLINK" - ]; - RestrictNamespaces = [ - "uts" - "ipc" - "pid" - "user" - "cgroup" - "net" - ]; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - ]; - }) - (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-${name}/secrets.env - fi - } - - # API Key (direct value or file) - if [ -n "${instanceCfg.apiKey}" ]; then - write_var "RADARR__AUTH__APIKEY" "${instanceCfg.apiKey}" - else - write_var "RADARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})" - fi - - # Database Configuration - 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-${name}/secrets.env - chown ${instanceCfg.user}:${instanceCfg.group} /run/radarr-${name}/secrets.env - ''}"; - - EnvironmentFile = ( - [ "-/run/radarr-${name}/secrets.env" ] - ++ lib.optional ( - instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != "" - ) instanceCfg.extraEnvVarFile - ); - }) - ]; }; networking.firewall = mkIf instanceCfg.openFirewall { @@ -317,12 +180,200 @@ in }; }; - config = mkIf cfg.enable (mkMerge [ - # Create services for each enabled instance - (mkMerge ( + config = mkIf cfg.enable { + # Add assertions for all instances + assertions = flatten ( mapAttrsToList ( - name: instanceCfg: mkIf instanceCfg.enable (mkRadarrInstance name instanceCfg) + name: instanceCfg: + if instanceCfg.enable then + [ + { + 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 = !(instanceCfg.db.user != "radarr" && instanceCfg.db.userFile != ""); + message = "Specify either a direct database user via db.user or a file via db.userFile."; + } + { + assertion = !(instanceCfg.apiKey != "" && instanceCfg.apiKeyFile != ""); + message = "Specify either a direct API key via apiKey or a file via apiKeyFile (leave direct API key empty)."; + } + ] + else + [ ] ) cfg.instances - )) - ]); + ); + + # Create systemd tmpfiles rules for each enabled instance + systemd.tmpfiles.rules = flatten ( + mapAttrsToList ( + name: instanceCfg: + if instanceCfg.enable then + [ + "d ${instanceCfg.dataDir} 0775 ${instanceCfg.user} ${instanceCfg.group}" + ] + else + [ ] + ) cfg.instances + ); + + # Create services for each enabled instance + systemd.services = mapAttrs' ( + name: instanceCfg: + nameValuePair "radarr-${name}" ( + mkIf instanceCfg.enable { + description = "Radarr (${name})"; + after = [ + "network.target" + "nss-lookup.target" + ]; + wantedBy = [ "multi-user.target" ]; + environment = lib.mkMerge [ + { + 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 instanceCfg.port; + RADARR__UPDATE__BRANCH = "develop"; + } + (lib.mkIf instanceCfg.db.enable { + RADARR__POSTGRES__PORT = toString instanceCfg.db.port; + RADARR__POSTGRES__MAINDB = instanceCfg.db.dbname; + }) + instanceCfg.extraEnvVars + ]; + + serviceConfig = lib.mkMerge [ + { + Type = "simple"; + User = instanceCfg.user; + Group = instanceCfg.group; + ExecStart = utils.escapeSystemdExecArgs [ + (lib.getExe instanceCfg.package) + "-nobrowser" + "-data=${instanceCfg.dataDir}" + "-port=${toString instanceCfg.port}" + ]; + WorkingDirectory = instanceCfg.dataDir; + RuntimeDirectory = "radarr-${name}"; + LogsDirectory = "radarr-${name}"; + RuntimeDirectoryMode = "0750"; + Restart = "on-failure"; + RestartSec = 5; + } + (lib.mkIf instanceCfg.hardening { + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ "" ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = false; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProtectControlGroups = true; + ProtectHome = "read-only"; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + ReadWritePaths = [ + instanceCfg.dataDir + instanceCfg.moviesDir + "/var/log/radarr-${name}" + "/eru/media" + ]; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = [ + "uts" + "ipc" + "pid" + "user" + "cgroup" + "net" + ]; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + ]; + }) + (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-${name}/secrets.env + fi + } + + # API Key (direct value or file) + if [ -n "${instanceCfg.apiKey}" ]; then + write_var "RADARR__AUTH__APIKEY" "${instanceCfg.apiKey}" + else + write_var "RADARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})" + fi + + # Database Configuration + 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-${name}/secrets.env + chown ${instanceCfg.user}:${instanceCfg.group} /run/radarr-${name}/secrets.env + ''}"; + + EnvironmentFile = ( + [ "-/run/radarr-${name}/secrets.env" ] + ++ lib.optional ( + instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != "" + ) instanceCfg.extraEnvVarFile + ); + }) + ]; + } + ) + ) cfg.instances; + + # Firewall configurations + networking.firewall = mkMerge ( + mapAttrsToList ( + name: instanceCfg: + mkIf (instanceCfg.enable && instanceCfg.openFirewall) { + allowedTCPPorts = [ instanceCfg.port ]; + } + ) cfg.instances + ); + + # Users and groups + users = mkMerge ( + mapAttrsToList ( + name: instanceCfg: + mkIf instanceCfg.enable { + groups.${instanceCfg.group} = { }; + users = mkIf (instanceCfg.user == "radarr") { + radarr = { + inherit (instanceCfg) group; + isSystemUser = true; + # home = instanceCfg.dataDir; + home = "/nahar/radarr"; + }; + }; + } + ) cfg.instances + ); + }; + } From 5fa10d703878f83b18e80f57bc4427b90dcc5a2e Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Mon, 10 Feb 2025 11:20:44 -0600 Subject: [PATCH 4/6] update service names --- nixos/hosts/shadowfax/config/sops-secrets.nix | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/nixos/hosts/shadowfax/config/sops-secrets.nix b/nixos/hosts/shadowfax/config/sops-secrets.nix index 4c9ec16..c99120e 100644 --- a/nixos/hosts/shadowfax/config/sops-secrets.nix +++ b/nixos/hosts/shadowfax/config/sops-secrets.nix @@ -95,73 +95,73 @@ sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-movies1080p.service" ]; }; "arr/radarr/1080p/postgres/dbName" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-movies1080p.service" ]; }; "arr/radarr/1080p/postgres/user" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-movies1080p.service" ]; }; "arr/radarr/1080p/postgres/password" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-movies1080p.service" ]; }; "arr/radarr/1080p/postgres/host" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-movies1080p.service" ]; }; "arr/radarr/1080p/extraEnvVars" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-movies1080p.service" ]; }; "arr/radarr/anime/apiKey" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-anime.service" ]; }; "arr/radarr/anime/postgres/dbName" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-anime.service" ]; }; "arr/radarr/anime/postgres/user" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-anime.service" ]; }; "arr/radarr/anime/postgres/password" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-anime.service" ]; }; "arr/radarr/anime/postgres/host" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-anime.service" ]; }; "arr/radarr/anime/extraEnvVars" = { sopsFile = ../secrets.sops.yaml; owner = "radarr"; mode = "400"; - restartUnits = [ "radarr.service" ]; + restartUnits = [ "radarr-anime.service" ]; }; # Unpackerr "arr/unpackerr/extraEnvVars" = { From b19ab0375bb80324f4b2a12fc4741240c0b8d342 Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Mon, 10 Feb 2025 11:47:36 -0600 Subject: [PATCH 5/6] reverse logic --- nixos/modules/nixos/services/radarr/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/nixos/services/radarr/default.nix b/nixos/modules/nixos/services/radarr/default.nix index adf5179..0f15084 100644 --- a/nixos/modules/nixos/services/radarr/default.nix +++ b/nixos/modules/nixos/services/radarr/default.nix @@ -327,7 +327,7 @@ in # Database Configuration 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__USER" "$([ -n "${instanceCfg.db.userFile}" ] && cat "${instanceCfg.db.userFile}" || echo "${instanceCfg.db.user}")" write_var "RADARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})" # Final permissions From 81ae076bafb73e43a9b821738345fc379780d3f3 Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Mon, 10 Feb 2025 11:47:44 -0600 Subject: [PATCH 6/6] enable radarr anime --- nixos/hosts/shadowfax/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/shadowfax/default.nix b/nixos/hosts/shadowfax/default.nix index fb23976..fa06c95 100644 --- a/nixos/hosts/shadowfax/default.nix +++ b/nixos/hosts/shadowfax/default.nix @@ -292,7 +292,7 @@ in }; }; moviesAnime = { - enable = false; + enable = true; package = pkgs.unstable.radarr; dataDir = "/nahar/radarr/anime"; extraEnvVarFile = config.sops.secrets."arr/radarr/anime/extraEnvVars".path;