diff --git a/flake.nix b/flake.nix index 5a4def0..b5d095c 100644 --- a/flake.nix +++ b/flake.nix @@ -11,40 +11,41 @@ nixpkgs, vpnconfinement, ... - } @ inputs: - let - # Systems supported - supportedSystems = [ - "x86_64-linux" # 64-bit Intel/AMD Linux - "aarch64-linux" # 64-bit ARM Linux - "x86_64-darwin" # 64-bit Intel macOS - "aarch64-darwin" # 64-bit ARM macOS - ]; + } @ inputs: let + # Systems supported + supportedSystems = [ + "x86_64-linux" # 64-bit Intel/AMD Linux + "aarch64-linux" # 64-bit ARM Linux + "x86_64-darwin" # 64-bit Intel macOS + "aarch64-darwin" # 64-bit ARM macOS + ]; - # Helper to provide system-specific attributes - forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f { - pkgs = import nixpkgs { inherit system; }; - }); - in { - nixosModules = { - default = { - imports = [ ./nixarr vpnconfinement.nixosModules.default ]; - }; + # Helper to provide system-specific attributes + forAllSystems = f: + nixpkgs.lib.genAttrs supportedSystems (system: + f { + pkgs = import nixpkgs {inherit system;}; + }); + in { + nixosModules = { + default = { + imports = [./nixarr vpnconfinement.nixosModules.default]; }; + }; - devShells = forAllSystems ({ pkgs } : { - default = pkgs.mkShell { - packages = with pkgs; [ - alejandra - nixd - ]; - }; - }); + devShells = forAllSystems ({pkgs}: { + default = pkgs.mkShell { + packages = with pkgs; [ + alejandra + nixd + ]; + }; + }); - packages = forAllSystems ({ pkgs } : { - default = pkgs.callPackage ./mkDocs.nix {inherit inputs;}; - }); + packages = forAllSystems ({pkgs}: { + default = pkgs.callPackage ./mkDocs.nix {inherit inputs;}; + }); - formatter = forAllSystems ({ pkgs }: pkgs.alejandra); - }; + formatter = forAllSystems ({pkgs}: pkgs.alejandra); + }; } diff --git a/nixarr/bazarr/bazarr-module/default.nix b/nixarr/bazarr/bazarr-module/default.nix index 27441ef..319c62b 100644 --- a/nixarr/bazarr/bazarr-module/default.nix +++ b/nixarr/bazarr/bazarr-module/default.nix @@ -11,7 +11,7 @@ in { util-nixarr.services.bazarr = { enable = mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr"; - package = mkPackageOption pkgs "bazarr" { }; + package = mkPackageOption pkgs "bazarr" {}; openFirewall = mkOption { type = types.bool; diff --git a/nixarr/bazarr/default.nix b/nixarr/bazarr/default.nix index a84961d..89e22cc 100644 --- a/nixarr/bazarr/default.nix +++ b/nixarr/bazarr/default.nix @@ -24,7 +24,7 @@ in { ''; }; - package = mkPackageOption pkgs "bazarr" { }; + package = mkPackageOption pkgs "bazarr" {}; stateDir = mkOption { type = types.path; @@ -36,11 +36,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/bazarr > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/default.nix b/nixarr/default.nix index 8841ea3..7d1d20a 100644 --- a/nixarr/default.nix +++ b/nixarr/default.nix @@ -21,47 +21,58 @@ with lib; let fix-permissions = pkgs.writeShellApplication { name = "fix-permissions"; runtimeInputs = with pkgs; [util-linux]; - text = '' - if [ "$EUID" -ne 0 ]; then - echo "Please run as root" - exit - fi - - find "${cfg.mediaDir}" \( -type d -exec chmod 0775 {} + -true \) -o \( -exec chmod 0664 {} + \) - '' + strings.optionalString cfg.jellyfin.enable '' - chown -R streamer:media "${cfg.mediaDir}/library" - chown -R streamer:root "${cfg.jellyfin.stateDir}" - find "${cfg.jellyfin.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.transmission.enable '' - chown -R torrenter:media "${cfg.mediaDir}/torrents" - chown -R torrenter:cross-seed "${cfg.transmission.stateDir}" - find "${cfg.transmission.stateDir}" \( -type d -exec chmod 0750 {} + -true \) -o \( -exec chmod 0640 {} + \) - '' + strings.optionalString cfg.sabnzbd.enable '' - chown -R usenet:media "${cfg.mediaDir}/usenet" - chown -R usenet:root "${cfg.sabnzbd.stateDir}" - find "${cfg.sabnzbd.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.transmission.privateTrackers.cross-seed.enable '' - chown -R cross-seed:root "${cfg.transmission.privateTrackers.cross-seed.stateDir}" - find "${cfg.transmission.privateTrackers.cross-seed.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.prowlarr.enable '' - chown -R prowlarr:root "${cfg.prowlarr.stateDir}" - find "${cfg.prowlarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.sonarr.enable '' - chown -R sonarr:root "${cfg.sonarr.stateDir}" - find "${cfg.sonarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.radarr.enable '' - chown -R radarr:root "${cfg.radarr.stateDir}" - find "${cfg.radarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.lidarr.enable '' - chown -R lidarr:root "${cfg.lidarr.stateDir}" - find "${cfg.lidarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.bazarr.enable '' - chown -R bazarr:root "${cfg.bazarr.stateDir}" - find "${cfg.bazarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - '' + strings.optionalString cfg.readarr.enable '' - chown -R readarr:root "${cfg.readarr.stateDir}" - find "${cfg.readarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) - ''; + text = + '' + if [ "$EUID" -ne 0 ]; then + echo "Please run as root" + exit + fi + + find "${cfg.mediaDir}" \( -type d -exec chmod 0775 {} + -true \) -o \( -exec chmod 0664 {} + \) + '' + + strings.optionalString cfg.jellyfin.enable '' + chown -R streamer:media "${cfg.mediaDir}/library" + chown -R streamer:root "${cfg.jellyfin.stateDir}" + find "${cfg.jellyfin.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.transmission.enable '' + chown -R torrenter:media "${cfg.mediaDir}/torrents" + chown -R torrenter:cross-seed "${cfg.transmission.stateDir}" + find "${cfg.transmission.stateDir}" \( -type d -exec chmod 0750 {} + -true \) -o \( -exec chmod 0640 {} + \) + '' + + strings.optionalString cfg.sabnzbd.enable '' + chown -R usenet:media "${cfg.mediaDir}/usenet" + chown -R usenet:root "${cfg.sabnzbd.stateDir}" + find "${cfg.sabnzbd.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.transmission.privateTrackers.cross-seed.enable '' + chown -R cross-seed:root "${cfg.transmission.privateTrackers.cross-seed.stateDir}" + find "${cfg.transmission.privateTrackers.cross-seed.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.prowlarr.enable '' + chown -R prowlarr:root "${cfg.prowlarr.stateDir}" + find "${cfg.prowlarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.sonarr.enable '' + chown -R sonarr:root "${cfg.sonarr.stateDir}" + find "${cfg.sonarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.radarr.enable '' + chown -R radarr:root "${cfg.radarr.stateDir}" + find "${cfg.radarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.lidarr.enable '' + chown -R lidarr:root "${cfg.lidarr.stateDir}" + find "${cfg.lidarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.bazarr.enable '' + chown -R bazarr:root "${cfg.bazarr.stateDir}" + find "${cfg.bazarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + '' + + strings.optionalString cfg.readarr.enable '' + chown -R readarr:root "${cfg.readarr.stateDir}" + find "${cfg.readarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) + ''; }; in { imports = [ @@ -135,11 +146,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > mediaDir = /home/user/nixarr > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; @@ -153,11 +164,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/jellyfin/default.nix b/nixarr/jellyfin/default.nix index 0ad04bd..5325437 100644 --- a/nixarr/jellyfin/default.nix +++ b/nixarr/jellyfin/default.nix @@ -21,7 +21,7 @@ in ''; }; - package = mkPackageOption pkgs "jellyfin" { }; + package = mkPackageOption pkgs "jellyfin" {}; stateDir = mkOption { type = types.path; @@ -33,11 +33,11 @@ in > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/jellyfin > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/lidarr/default.nix b/nixarr/lidarr/default.nix index 17146e2..60817e4 100644 --- a/nixarr/lidarr/default.nix +++ b/nixarr/lidarr/default.nix @@ -21,7 +21,7 @@ in { ''; }; - package = mkPackageOption pkgs "lidarr" { }; + package = mkPackageOption pkgs "lidarr" {}; stateDir = mkOption { type = types.path; diff --git a/nixarr/prowlarr/default.nix b/nixarr/prowlarr/default.nix index 6a5c575..22e149b 100644 --- a/nixarr/prowlarr/default.nix +++ b/nixarr/prowlarr/default.nix @@ -25,7 +25,7 @@ in { ''; }; - package = mkPackageOption pkgs "prowlarr" { }; + package = mkPackageOption pkgs "prowlarr" {}; stateDir = mkOption { type = types.path; @@ -37,11 +37,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/prowlarr > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/radarr/default.nix b/nixarr/radarr/default.nix index 6d79a54..da00df4 100644 --- a/nixarr/radarr/default.nix +++ b/nixarr/radarr/default.nix @@ -21,7 +21,7 @@ in { ''; }; - package = mkPackageOption pkgs "radarr" { }; + package = mkPackageOption pkgs "radarr" {}; stateDir = mkOption { type = types.path; @@ -33,11 +33,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/radarr > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/readarr/default.nix b/nixarr/readarr/default.nix index c56b263..df2175f 100644 --- a/nixarr/readarr/default.nix +++ b/nixarr/readarr/default.nix @@ -21,7 +21,7 @@ in { ''; }; - package = mkPackageOption pkgs "readarr" { }; + package = mkPackageOption pkgs "readarr" {}; stateDir = mkOption { type = types.path; @@ -33,11 +33,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/readarr > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/sabnzbd/default.nix b/nixarr/sabnzbd/default.nix index 2727a1b..9370d2e 100644 --- a/nixarr/sabnzbd/default.nix +++ b/nixarr/sabnzbd/default.nix @@ -21,16 +21,16 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/sabnzbd > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; - package = mkPackageOption pkgs "sabnzbd" { }; + package = mkPackageOption pkgs "sabnzbd" {}; guiPort = mkOption { type = types.port; @@ -56,18 +56,18 @@ in { example = literalExpression ''[ "mediaserv" "media.example.com" ]''; description = '' A list that specifies what URLs that are allowed to represent your - SABnzbd instance. - + SABnzbd instance. + > **Note:** If you see an error message like this when trying to connect to > SABnzbd from another device: - > + > > ``` > Refused connection with hostname "your.hostname.com" > ``` - > + > > Then you should add your hostname ("`hostname.com`" above) to > this list. - > + > > SABnzbd only allows connections matching these URLs in order to prevent > DNS hijacking. See > for more info. @@ -97,7 +97,7 @@ in { }; }; - config = let + config = let ini-file-target = "${cfg.stateDir}/sabnzbd.ini"; concatStringsCommaIfExists = with lib.strings; stringList: ( @@ -154,9 +154,10 @@ in { user-configs ); - apply-user-configs-script = pkgs.writers.writePython3Bin "sabnzbd-set-user-values" { - libraries = [pkgs.python3Packages.configobj]; - } '' + apply-user-configs-script = + pkgs.writers.writePython3Bin "sabnzbd-set-user-values" { + libraries = [pkgs.python3Packages.configobj]; + } '' # flake8: noqa from pathlib import Path from configobj import ConfigObj @@ -170,86 +171,87 @@ in { ${lib.strings.concatStringsSep "\n" user-configs-to-python-list} sab_config_map.write() - ''; - in mkIf cfg.enable { - users = { - groups.usenet = {}; - users.usenet = { - isSystemUser = true; - group = "usenet"; + ''; + in + mkIf cfg.enable { + users = { + groups.usenet = {}; + users.usenet = { + isSystemUser = true; + group = "usenet"; + }; }; - }; - - systemd.tmpfiles.rules = [ - "d '${cfg.stateDir}' 0700 usenet root - -" - "C ${cfg.stateDir}/sabnzbd.ini - - - - ${ini-base-config-file}" - - # Media dirs - "d '${nixarr.mediaDir}/usenet' 0755 usenet media - -" - "d '${nixarr.mediaDir}/usenet/.incomplete' 0755 usenet media - -" - "d '${nixarr.mediaDir}/usenet/.watch' 0755 usenet media - -" - "d '${nixarr.mediaDir}/usenet/manual' 0775 usenet media - -" - "d '${nixarr.mediaDir}/usenet/liadarr' 0775 usenet media - -" - "d '${nixarr.mediaDir}/usenet/radarr' 0775 usenet media - -" - "d '${nixarr.mediaDir}/usenet/sonarr' 0775 usenet media - -" - "d '${nixarr.mediaDir}/usenet/readarr' 0775 usenet media - -" - ]; - - services.sabnzbd = { - enable = true; - package = cfg.package; - user = "usenet"; - group = "media"; - configFile = "${cfg.stateDir}/sabnzbd.ini"; - }; - networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.guiPort]; + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0700 usenet root - -" + "C ${cfg.stateDir}/sabnzbd.ini - - - - ${ini-base-config-file}" - systemd.services.sabnzbd.serviceConfig = { - ExecStartPre = lib.mkBefore [ - ("+" + fix-config-permissions-script + "/bin/sabnzbd-fix-config-permissions") - (apply-user-configs-script + "/bin/sabnzbd-set-user-values") + # Media dirs + "d '${nixarr.mediaDir}/usenet' 0755 usenet media - -" + "d '${nixarr.mediaDir}/usenet/.incomplete' 0755 usenet media - -" + "d '${nixarr.mediaDir}/usenet/.watch' 0755 usenet media - -" + "d '${nixarr.mediaDir}/usenet/manual' 0775 usenet media - -" + "d '${nixarr.mediaDir}/usenet/liadarr' 0775 usenet media - -" + "d '${nixarr.mediaDir}/usenet/radarr' 0775 usenet media - -" + "d '${nixarr.mediaDir}/usenet/sonarr' 0775 usenet media - -" + "d '${nixarr.mediaDir}/usenet/readarr' 0775 usenet media - -" ]; - Restart = "on-failure"; - StartLimitBurst = 5; - }; - # Enable and specify VPN namespace to confine service in. - systemd.services.sabnzbd.vpnConfinement = mkIf cfg.vpn.enable { - enable = true; - vpnNamespace = "wg"; - }; + services.sabnzbd = { + enable = true; + package = cfg.package; + user = "usenet"; + group = "media"; + configFile = "${cfg.stateDir}/sabnzbd.ini"; + }; - # Port mappings - vpnNamespaces.wg = mkIf cfg.vpn.enable { - portMappings = [ - { - from = cfg.guiPort; - to = cfg.guiPort; - } - ]; - }; + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.guiPort]; - services.nginx = mkIf cfg.vpn.enable { - enable = true; + systemd.services.sabnzbd.serviceConfig = { + ExecStartPre = lib.mkBefore [ + ("+" + fix-config-permissions-script + "/bin/sabnzbd-fix-config-permissions") + (apply-user-configs-script + "/bin/sabnzbd-set-user-values") + ]; + Restart = "on-failure"; + StartLimitBurst = 5; + }; - recommendedTlsSettings = true; - recommendedOptimisation = true; - recommendedGzipSettings = true; + # Enable and specify VPN namespace to confine service in. + systemd.services.sabnzbd.vpnConfinement = mkIf cfg.vpn.enable { + enable = true; + vpnNamespace = "wg"; + }; - virtualHosts."127.0.0.1:${builtins.toString cfg.guiPort}" = { - listen = [ + # Port mappings + vpnNamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [ { - addr = "0.0.0.0"; - port = cfg.guiPort; + from = cfg.guiPort; + to = cfg.guiPort; } ]; - locations."/" = { - recommendedProxySettings = true; - proxyWebsockets = true; - proxyPass = "http://192.168.15.1:${builtins.toString cfg.guiPort}"; + }; + + services.nginx = mkIf cfg.vpn.enable { + enable = true; + + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + + virtualHosts."127.0.0.1:${builtins.toString cfg.guiPort}" = { + listen = [ + { + addr = "0.0.0.0"; + port = cfg.guiPort; + } + ]; + locations."/" = { + recommendedProxySettings = true; + proxyWebsockets = true; + proxyPass = "http://192.168.15.1:${builtins.toString cfg.guiPort}"; + }; }; }; }; - }; } diff --git a/nixarr/sonarr/default.nix b/nixarr/sonarr/default.nix index c18233c..269ddfa 100644 --- a/nixarr/sonarr/default.nix +++ b/nixarr/sonarr/default.nix @@ -21,7 +21,7 @@ in { ''; }; - package = mkPackageOption pkgs "sonarr" { }; + package = mkPackageOption pkgs "sonarr" {}; stateDir = mkOption { type = types.path; @@ -33,11 +33,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/sonarr > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; diff --git a/nixarr/transmission/default.nix b/nixarr/transmission/default.nix index c09d9ba..b4d527c 100644 --- a/nixarr/transmission/default.nix +++ b/nixarr/transmission/default.nix @@ -76,7 +76,7 @@ in { ''; }; - package = mkPackageOption pkgs "transmission_4" { }; + package = mkPackageOption pkgs "transmission_4" {}; stateDir = mkOption { type = types.path; @@ -88,11 +88,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/transmission > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; @@ -108,7 +108,7 @@ in { extraAllowedIps = mkOption { type = with types; listOf str; default = []; - example = [ "10.19.5.10" ]; + example = ["10.19.5.10"]; description = '' Extra IP addresses allowed to access the Transmission RPC. By default `192.168.*` and `127.0.0.1` (localhost) are allowed, but if your @@ -167,11 +167,11 @@ in { > **Warning:** Setting this to any path, where the subpath is not > owned by root, will fail! For example: - > + > > ```nix > stateDir = /home/user/nixarr/.state/cross-seed > ``` - > + > > Is not supported, because `/home/user` is owned by `user`. ''; }; @@ -318,7 +318,7 @@ in { "d '${nixarr.mediaDir}/torrents/radarr' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/sonarr' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/readarr' 0755 torrenter media - -" - ]; + ]; util-nixarr.services.cross-seed = mkIf cfg-cross-seed.enable { enable = true; @@ -387,8 +387,9 @@ in { rpc-port = cfg.uiPort; rpc-whitelist-enabled = true; rpc-whitelist = strings.concatStringsSep "," ([ - "127.0.0.1,192.168.*,10.*" # Defaults - ] ++ cfg.extraAllowedIps); + "127.0.0.1,192.168.*,10.*" # Defaults + ] + ++ cfg.extraAllowedIps); rpc-authentication-required = false; blocklist-enabled = true;