diff --git a/nixos/hosts/shadowfax/default.nix b/nixos/hosts/shadowfax/default.nix index fa1c569..ba4665e 100644 --- a/nixos/hosts/shadowfax/default.nix +++ b/nixos/hosts/shadowfax/default.nix @@ -101,6 +101,7 @@ in { # System packages environment.systemPackages = with pkgs; [ + # Hyprland libva-utils # to view graphics capabilities greetd.tuigreet rofi-wayland @@ -114,6 +115,8 @@ in { wl-clipboard wlogout wlr-randr + # dev + uv # fun fastfetch # Scripts @@ -169,6 +172,8 @@ in { 9001 # api interface # Beszel-agent 45876 + # scrypted + 45005 ]; }; @@ -265,13 +270,13 @@ in { openFirewall = true; hardening = true; apiKeyFile = config.sops.secrets."arr/prowlarr/apiKey".path; - db = { - enable = true; - hostFile = config.sops.secrets."arr/prowlarr/postgres/host".path; - port = 5432; - userFile = config.sops.secrets."arr/prowlarr/postgres/user".path; - passwordFile = config.sops.secrets."arr/prowlarr/postgres/password".path; - }; + # db = { + # enable = true; + # hostFile = config.sops.secrets."arr/prowlarr/postgres/host".path; + # port = 5432; + # userFile = config.sops.secrets."arr/prowlarr/postgres/user".path; + # passwordFile = config.sops.secrets."arr/prowlarr/postgres/password".path; + # }; }; # Radarr radarr = { @@ -289,14 +294,14 @@ in { 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; - }; + # 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 = true; @@ -310,14 +315,14 @@ in { 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; - }; + # 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; + # }; }; }; }; @@ -337,14 +342,14 @@ in { openFirewall = true; hardening = true; apiKeyFile = config.sops.secrets."arr/sonarr/1080p/apiKey".path; - db = { - enable = true; - hostFile = config.sops.secrets."arr/sonarr/1080p/postgres/host".path; - port = 5432; - dbname = "sonarr_main"; - userFile = config.sops.secrets."arr/sonarr/1080p/postgres/user".path; - passwordFile = config.sops.secrets."arr/sonarr/1080p/postgres/password".path; - }; + # db = { + # enable = true; + # hostFile = config.sops.secrets."arr/sonarr/1080p/postgres/host".path; + # port = 5432; + # dbname = "sonarr_main"; + # userFile = config.sops.secrets."arr/sonarr/1080p/postgres/user".path; + # passwordFile = config.sops.secrets."arr/sonarr/1080p/postgres/password".path; + # }; }; anime = { enable = true; @@ -358,14 +363,14 @@ in { openFirewall = true; hardening = true; apiKeyFile = config.sops.secrets."arr/sonarr/anime/apiKey".path; - db = { - enable = true; - hostFile = config.sops.secrets."arr/sonarr/anime/postgres/host".path; - port = 5432; - dbname = "sonarr_anime"; - userFile = config.sops.secrets."arr/sonarr/anime/postgres/user".path; - passwordFile = config.sops.secrets."arr/sonarr/anime/postgres/password".path; - }; + # db = { + # enable = true; + # hostFile = config.sops.secrets."arr/sonarr/anime/postgres/host".path; + # port = 5432; + # dbname = "sonarr_anime"; + # userFile = config.sops.secrets."arr/sonarr/anime/postgres/user".path; + # passwordFile = config.sops.secrets."arr/sonarr/anime/postgres/password".path; + # }; }; }; }; diff --git a/nixos/modules/nixos/services/prowlarr/default.nix b/nixos/modules/nixos/services/prowlarr/default.nix index cd164cf..fd8fa88 100644 --- a/nixos/modules/nixos/services/prowlarr/default.nix +++ b/nixos/modules/nixos/services/prowlarr/default.nix @@ -5,8 +5,7 @@ utils, ... }: -with lib; -let +with lib; let cfg = config.mySystem.services.prowlarr; dbOptions = { options = { @@ -51,12 +50,11 @@ let }; }; }; -in -{ +in { options.mySystem.services.prowlarr = { enable = mkEnableOption "Prowlarr"; - package = mkPackageOption pkgs "prowlarr" { }; + package = mkPackageOption pkgs "prowlarr" {}; user = mkOption { type = types.str; @@ -109,7 +107,7 @@ in extraEnvVars = mkOption { type = types.attrs; - default = { }; + default = {}; example = { MY_VAR = "my value"; }; @@ -125,6 +123,14 @@ in db = mkOption { type = types.submodule dbOptions; + default = { + enable = false; + host = ""; + port = "5432"; + user = ""; + passwordFile = ""; + dbname = ""; + }; example = { enable = true; host = "10.5.0.5"; # or use hostFile @@ -140,11 +146,11 @@ in config = mkIf cfg.enable { assertions = [ { - assertion = !(cfg.db.host != "" && cfg.db.hostFile != ""); + assertion = !(cfg.db.enable && (cfg.db.host != "" && cfg.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 != "prowlarr" && cfg.db.userFile != ""); + assertion = !(cfg.db.enable && (cfg.db.user != "prowlarr" && cfg.db.userFile != "")); message = "Specify either a direct database user via db.user or a file via db.userFile."; } { @@ -159,7 +165,7 @@ in "network.target" "nss-lookup.target" ]; - wantedBy = [ "multi-user.target" ]; + wantedBy = ["multi-user.target"]; environment = lib.mkMerge [ { PROWLARR__APP__INSTANCENAME = "Prowlarr"; @@ -171,7 +177,7 @@ in PROWLARR__SERVER__PORT = toString cfg.port; PROWLARR__UPDATE__BRANCH = "develop"; } - (lib.mkIf cfg.db.enable { + (lib.optionalAttrs cfg.db.enable { PROWLARR__POSTGRES__PORT = toString cfg.db.port; PROWLARR__POSTGRES__MAINDB = cfg.db.dbname; }) @@ -197,8 +203,8 @@ in RestartSec = 5; } (lib.mkIf cfg.hardening { - CapabilityBoundingSet = [ "" ]; - DeviceAllow = [ "" ]; + CapabilityBoundingSet = [""]; + DeviceAllow = [""]; DevicePolicy = "closed"; LockPersonality = true; # Needs access to .Net CLR memory space. @@ -237,7 +243,7 @@ in #"~@resources" ]; }) - (lib.mkIf cfg.db.enable { + { ExecStartPre = "+${pkgs.writeShellScript "prowlarr-pre-script" '' mkdir -p /run/prowlarr rm -f /run/prowlarr/secrets.env @@ -258,10 +264,12 @@ in write_var "PROWLARR__AUTH__APIKEY" "$(cat ${cfg.apiKeyFile})" fi - # Database Configuration - write_var "PROWLARR__POSTGRES__HOST" "$([ -n "${cfg.db.host}" ] && echo "${cfg.db.host}" || cat "${cfg.db.hostFile}")" - write_var "PROWLARR__POSTGRES__USER" "$([ -n "${cfg.db.user}" ] && echo "${cfg.db.user}" || cat "${cfg.db.userFile}")" - write_var "PROWLARR__POSTGRES__PASSWORD" "$(cat ${cfg.db.passwordFile})" + ${lib.optionalString cfg.db.enable '' + # Database Configuration + write_var "PROWLARR__POSTGRES__HOST" "$([ -n "${cfg.db.host}" ] && echo "${cfg.db.host}" || cat "${cfg.db.hostFile}")" + write_var "PROWLARR__POSTGRES__USER" "$([ -n "${cfg.db.user}" ] && echo "${cfg.db.user}" || cat "${cfg.db.userFile}")" + write_var "PROWLARR__POSTGRES__PASSWORD" "$(cat ${cfg.db.passwordFile})" + ''} # Final permissions chmod 600 /run/prowlarr/secrets.env @@ -269,18 +277,18 @@ in ''}"; EnvironmentFile = ( - [ "-/run/prowlarr/secrets.env" ] + ["-/run/prowlarr/secrets.env"] ++ lib.optional (cfg.extraEnvVarFile != null && cfg.extraEnvVarFile != "") cfg.extraEnvVarFile ); - }) + } ]; }; networking.firewall = mkIf cfg.openFirewall { - allowedTCPPorts = [ cfg.port ]; + allowedTCPPorts = [cfg.port]; }; - users.groups.${cfg.group} = { }; + users.groups.${cfg.group} = {}; users.users = mkIf (cfg.user == "prowlarr") { prowlarr = { inherit (cfg) group; diff --git a/nixos/modules/nixos/services/radarr/default.nix b/nixos/modules/nixos/services/radarr/default.nix index e3821c5..6b31164 100644 --- a/nixos/modules/nixos/services/radarr/default.nix +++ b/nixos/modules/nixos/services/radarr/default.nix @@ -5,8 +5,7 @@ utils, ... }: -with lib; -let +with lib; let cfg = config.mySystem.services.radarr; dbOptions = { options = { @@ -51,20 +50,18 @@ let }; }; }; -in -{ +in { options.mySystem.services.radarr = { enable = mkEnableOption "Radarr (global)"; instances = mkOption { type = types.attrsOf ( types.submodule ( - { name, ... }: - { + {name, ...}: { options = { enable = mkEnableOption "Radarr (instance)"; - package = mkPackageOption pkgs "Radarr" { }; + package = mkPackageOption pkgs "Radarr" {}; user = mkOption { type = types.str; @@ -131,12 +128,20 @@ in passwordFile = "/run/secrets/radarr_db_password"; dbname = "radarr_main"; }; + default = { + enable = false; + host = ""; + port = "5432"; + user = ""; + passwordFile = ""; + dbname = ""; + }; description = "Database settings for radarr."; }; extraEnvVars = mkOption { type = types.attrs; - default = { }; + default = {}; example = { MY_VAR = "my value"; }; @@ -153,7 +158,7 @@ in } ) ); - default = { }; + default = {}; description = "Radarr instance configurations."; }; }; @@ -163,8 +168,8 @@ in assertions = flatten ( mapAttrsToList ( name: instanceCfg: - if instanceCfg.enable then - [ + 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)."; @@ -178,180 +183,186 @@ in message = "Specify either a direct API key via apiKey or a file via apiKeyFile (leave direct API key empty)."; } ] - else - [ ] - ) cfg.instances + else [] + ) + cfg.instances ); # Create systemd tmpfiles rules for each enabled instance systemd.tmpfiles.rules = flatten ( mapAttrsToList ( name: instanceCfg: - if instanceCfg.enable then - [ + if instanceCfg.enable + then [ "d ${instanceCfg.dataDir} 0775 ${instanceCfg.user} ${instanceCfg.group}" ] - else - [ ] - ) cfg.instances + 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}" + systemd.services = + mapAttrs' ( + name: instanceCfg: + nameValuePair "radarr-${name}" ( + mkIf instanceCfg.enable { + description = "Radarr (${name})"; + after = [ + "network.target" + "nss-lookup.target" ]; - 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 + 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 + ]; - # 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 + 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" + ]; + }) + { + ExecStartPre = "+${pkgs.writeShellScript "radarr-${name}-pre-script" '' + mkdir -p /run/radarr-${name} + rm -f /run/radarr-${name}/secrets.env - # 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.userFile}" ] && cat "${instanceCfg.db.userFile}" || echo "${instanceCfg.db.user}")" - write_var "RADARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})" + # 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 + } - # Final permissions - chmod 600 /run/radarr-${name}/secrets.env - chown ${instanceCfg.user}:${instanceCfg.group} /run/radarr-${name}/secrets.env - ''}"; + # 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 - EnvironmentFile = ( - [ "-/run/radarr-${name}/secrets.env" ] - ++ lib.optional ( - instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != "" - ) instanceCfg.extraEnvVarFile - ); - }) - ]; - } + ${lib.optionalString instanceCfg.db.enable '' + # 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.userFile}" ] && cat "${instanceCfg.db.userFile}" || echo "${instanceCfg.db.user}")" + 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; + cfg.instances; # Firewall configurations networking.firewall = mkMerge ( mapAttrsToList ( name: instanceCfg: - mkIf (instanceCfg.enable && instanceCfg.openFirewall) { - allowedTCPPorts = [ instanceCfg.port ]; - } - ) cfg.instances + 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"; + 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 + } + ) + cfg.instances ); }; - } diff --git a/nixos/modules/nixos/services/sonarr/default.nix b/nixos/modules/nixos/services/sonarr/default.nix index 4ee670a..602111c 100644 --- a/nixos/modules/nixos/services/sonarr/default.nix +++ b/nixos/modules/nixos/services/sonarr/default.nix @@ -5,8 +5,7 @@ utils, ... }: -with lib; -let +with lib; let cfg = config.mySystem.services.sonarr; dbOptions = { options = { @@ -51,20 +50,18 @@ let }; }; }; -in -{ +in { options.mySystem.services.sonarr = { enable = mkEnableOption "Sonarr (global)"; instances = mkOption { type = types.attrsOf ( types.submodule ( - { name, ... }: - { + {name, ...}: { options = { enable = mkEnableOption "Sonarr (instance)"; - package = mkPackageOption pkgs "Sonarr" { }; + package = mkPackageOption pkgs "Sonarr" {}; user = mkOption { type = types.str; @@ -131,12 +128,20 @@ in passwordFile = "/run/secrets/sonarr_db_password"; dbname = "sonarr_main"; }; + default = { + enable = false; + host = ""; + port = "5432"; + user = ""; + passwordFile = ""; + dbname = ""; + }; description = "Database settings for sonarr."; }; extraEnvVars = mkOption { type = types.attrs; - default = { }; + default = {}; example = { MY_VAR = "my value"; }; @@ -153,7 +158,7 @@ in } ) ); - default = { }; + default = {}; description = "Sonarr instance configurations."; }; }; @@ -163,14 +168,14 @@ in assertions = flatten ( mapAttrsToList ( name: instanceCfg: - if instanceCfg.enable then - [ + if instanceCfg.enable + then [ { - assertion = !(instanceCfg.db.host != "" && instanceCfg.db.hostFile != ""); + assertion = !(instanceCfg.db.enable && (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 != "sonarr" && instanceCfg.db.userFile != ""); + assertion = !(instanceCfg.db.enable && (instanceCfg.db.user != "sonarr" && instanceCfg.db.userFile != "")); message = "Specify either a direct database user via db.user or a file via db.userFile."; } { @@ -178,180 +183,187 @@ in message = "Specify either a direct API key via apiKey or a file via apiKeyFile (leave direct API key empty)."; } ] - else - [ ] - ) cfg.instances + else [] + ) + cfg.instances ); # Create systemd tmpfiles rules for each enabled instance systemd.tmpfiles.rules = flatten ( mapAttrsToList ( name: instanceCfg: - if instanceCfg.enable then - [ + if instanceCfg.enable + then [ "d ${instanceCfg.dataDir} 0775 ${instanceCfg.user} ${instanceCfg.group}" ] - else - [ ] - ) cfg.instances + else [] + ) + cfg.instances ); # Create services for each enabled instance - systemd.services = mapAttrs' ( - name: instanceCfg: - nameValuePair "sonarr-${name}" ( - mkIf instanceCfg.enable { - description = "Sonarr (${name})"; - after = [ - "network.target" - "nss-lookup.target" - ]; - wantedBy = [ "multi-user.target" ]; - environment = lib.mkMerge [ - { - SONARR__APP__INSTANCENAME = name; - SONARR__APP__THEME = "dark"; - SONARR__AUTH__METHOD = "External"; - SONARR__AUTH__REQUIRED = "DisabledForLocalAddresses"; - SONARR__LOG__DBENABLED = "False"; - SONARR__LOG__LEVEL = "info"; - SONARR__SERVER__PORT = toString instanceCfg.port; - SONARR__UPDATE__BRANCH = "develop"; - } - (lib.mkIf instanceCfg.db.enable { - SONARR__POSTGRES__PORT = toString instanceCfg.db.port; - SONARR__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}" + systemd.services = + mapAttrs' ( + name: instanceCfg: + nameValuePair "sonarr-${name}" ( + mkIf instanceCfg.enable { + description = "Sonarr (${name})"; + after = [ + "network.target" + "nss-lookup.target" ]; - WorkingDirectory = instanceCfg.dataDir; - RuntimeDirectory = "sonarr-${name}"; - LogsDirectory = "sonarr-${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.tvDir - "/var/log/sonarr-${name}" - "/eru/media" - ]; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - "AF_NETLINK" - ]; - RestrictNamespaces = [ - "uts" - "ipc" - "pid" - "user" - "cgroup" - "net" - "mnt" - ]; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - ]; - }) - (lib.mkIf instanceCfg.db.enable { - ExecStartPre = "+${pkgs.writeShellScript "sonarr-${name}-pre-script" '' - mkdir -p /run/sonarr-${name} - rm -f /run/sonarr-${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/sonarr-${name}/secrets.env - fi + wantedBy = ["multi-user.target"]; + environment = lib.mkMerge [ + { + SONARR__APP__INSTANCENAME = name; + SONARR__APP__THEME = "dark"; + SONARR__AUTH__METHOD = "External"; + SONARR__AUTH__REQUIRED = "DisabledForLocalAddresses"; + SONARR__LOG__DBENABLED = "False"; + SONARR__LOG__LEVEL = "info"; + SONARR__SERVER__PORT = toString instanceCfg.port; + SONARR__UPDATE__BRANCH = "develop"; } + (lib.mkIf instanceCfg.db.enable { + SONARR__POSTGRES__PORT = toString instanceCfg.db.port; + SONARR__POSTGRES__MAINDB = instanceCfg.db.dbname; + }) + instanceCfg.extraEnvVars + ]; - # API Key (direct value or file) - if [ -n "${instanceCfg.apiKey}" ]; then - write_var "SONARR__AUTH__APIKEY" "${instanceCfg.apiKey}" - else - write_var "SONARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})" - fi + 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 = "sonarr-${name}"; + LogsDirectory = "sonarr-${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.tvDir + "/var/log/sonarr-${name}" + "/eru/media" + ]; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = [ + "uts" + "ipc" + "pid" + "user" + "cgroup" + "net" + "mnt" + ]; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + ]; + }) + { + ExecStartPre = "+${pkgs.writeShellScript "sonarr-${name}-pre-script" '' + mkdir -p /run/sonarr-${name} + rm -f /run/sonarr-${name}/secrets.env - # Database Configuration - write_var "SONARR__POSTGRES__HOST" "$([ -n "${instanceCfg.db.host}" ] && echo "${instanceCfg.db.host}" || cat "${instanceCfg.db.hostFile}")" - write_var "SONARR__POSTGRES__USER" "$([ -n "${instanceCfg.db.userFile}" ] && cat "${instanceCfg.db.userFile}" || echo "${instanceCfg.db.user}")" - write_var "SONARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})" + # 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/sonarr-${name}/secrets.env + fi + } - # Final permissions - chmod 600 /run/sonarr-${name}/secrets.env - chown ${instanceCfg.user}:${instanceCfg.group} /run/sonarr-${name}/secrets.env - ''}"; + # API Key (direct value or file) + if [ -n "${instanceCfg.apiKey}" ]; then + write_var "SONARR__AUTH__APIKEY" "${instanceCfg.apiKey}" + else + write_var "SONARR__AUTH__APIKEY" "$(cat ${instanceCfg.apiKeyFile})" + fi - EnvironmentFile = ( - [ "-/run/sonarr-${name}/secrets.env" ] - ++ lib.optional ( - instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != "" - ) instanceCfg.extraEnvVarFile - ); - }) - ]; - } + ${lib.optionalString instanceCfg.db.enable '' + # Database Configuration + write_var "SONARR__POSTGRES__HOST" "$([ -n "${instanceCfg.db.host}" ] && echo "${instanceCfg.db.host}" || cat "${instanceCfg.db.hostFile}")" + write_var "SONARR__POSTGRES__USER" "$([ -n "${instanceCfg.db.userFile}" ] && cat "${instanceCfg.db.userFile}" || echo "${instanceCfg.db.user}")" + write_var "SONARR__POSTGRES__PASSWORD" "$(cat ${instanceCfg.db.passwordFile})" + ''} + + # Final permissions + chmod 600 /run/sonarr-${name}/secrets.env + chown ${instanceCfg.user}:${instanceCfg.group} /run/sonarr-${name}/secrets.env + ''}"; + + EnvironmentFile = ( + ["-/run/sonarr-${name}/secrets.env"] + ++ lib.optional ( + instanceCfg.extraEnvVarFile != null && instanceCfg.extraEnvVarFile != "" + ) + instanceCfg.extraEnvVarFile + ); + } + ]; + } + ) ) - ) cfg.instances; + cfg.instances; # Firewall configurations networking.firewall = mkMerge ( mapAttrsToList ( name: instanceCfg: - mkIf (instanceCfg.enable && instanceCfg.openFirewall) { - allowedTCPPorts = [ instanceCfg.port ]; - } - ) cfg.instances + 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 == "sonarr") { - sonarr = { - inherit (instanceCfg) group; - isSystemUser = true; - # home = instanceCfg.dataDir; - home = "/nahar/sonarr"; + mkIf instanceCfg.enable { + groups.${instanceCfg.group} = {}; + users = mkIf (instanceCfg.user == "sonarr") { + sonarr = { + inherit (instanceCfg) group; + isSystemUser = true; + # home = instanceCfg.dataDir; + home = "/nahar/sonarr"; + }; }; - }; - } - ) cfg.instances + } + ) + cfg.instances ); }; }