diff --git a/flake.lock b/flake.lock index 59b63c9..aab114d 100644 --- a/flake.lock +++ b/flake.lock @@ -90,22 +90,6 @@ "type": "github" } }, - "nixpkgs_2": { - "locked": { - "lastModified": 1709218635, - "narHash": "sha256-nytX/MkfqeTD4z7bMq4QRXcHxO9B3vRo9tM6fMtPFA8=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "068d4db604958d05d0b46c47f79b507d84dbc069", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, "root": { "inputs": { "devshell": "devshell", @@ -153,7 +137,9 @@ }, "vpnconfinement": { "inputs": { - "nixpkgs": "nixpkgs_2" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1709159289, diff --git a/flake.nix b/flake.nix index 6cb5484..a78a9ed 100644 --- a/flake.nix +++ b/flake.nix @@ -45,10 +45,7 @@ flake = { nixosModules = rec { - #vpnconfinement = vpnconfinement.nixosModules.default; nixarr = (import ./nixarr vpnconfinement); - #imports = [ vpnconfinement.nixosModules.default ]; - #nixarr.imports = [ vpnconfinement ]; default = nixarr; }; }; @@ -56,8 +53,15 @@ perSystem = { config, pkgs, + lib, ... }: { + #flake = { + # nixosModules = rec { + # nixarr = import ./nixarr { inherit config pkgs lib vpnconfinement; }; + # default = nixarr; + # }; + #}; treefmt.config = { inherit (config.flake-root) projectRootFile; package = pkgs.treefmt; diff --git a/nixarr/default.nix b/nixarr/default.nix index 5cc263e..4a0e6f2 100644 --- a/nixarr/default.nix +++ b/nixarr/default.nix @@ -1,4 +1,5 @@ -vpnconfinement: { +{ + vpnconfinement, config, lib, pkgs, @@ -66,7 +67,7 @@ in { stateDir = mkOption { type = types.path; - default = "/data/.state"; + default = "/data/.state/nixarr"; description = '' The location of the state directory for the services. ''; @@ -223,7 +224,7 @@ in { ./dnsleaktest.sh '' + (if cfg.vpn.vpnTestService.port != null then '' echo "starting netcat on port ${builtins.toString cfg.vpn.vpnTestService.port}:" - nc -vnlp ${builtins.toString cfg.vpn.vpnTestService.port} + nc -vnlpu ${builtins.toString cfg.vpn.vpnTestService.port} '' else ""); }; in "${vpn-test}/bin/vpn-test"; diff --git a/nixarr/jellyfin/default.nix b/nixarr/jellyfin/default.nix index 69834b8..4d973d6 100644 --- a/nixarr/jellyfin/default.nix +++ b/nixarr/jellyfin/default.nix @@ -13,7 +13,7 @@ in with lib; { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/jellyfin"; + default = "${nixarr.stateDir}/jellyfin"; description = "The state directory for Jellyfin."; }; diff --git a/nixarr/lidarr/default.nix b/nixarr/lidarr/default.nix index 94d4b6e..39c1425 100644 --- a/nixarr/lidarr/default.nix +++ b/nixarr/lidarr/default.nix @@ -12,7 +12,7 @@ in { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/lidarr"; + default = "${nixarr.stateDir}/lidarr"; description = "The state directory for Lidarr"; }; diff --git a/nixarr/prowlarr/default.nix b/nixarr/prowlarr/default.nix index 7d9bd70..2040544 100644 --- a/nixarr/prowlarr/default.nix +++ b/nixarr/prowlarr/default.nix @@ -18,7 +18,7 @@ in { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/prowlarr"; + default = "${nixarr.stateDir}/prowlarr"; description = "The state directory for Prowlarr."; }; @@ -48,8 +48,6 @@ in { "d '${cfg.stateDir}' 0700 prowlarr root - -" ]; - users.groups.prowlarr = {}; - util-nixarr.services.prowlarr = { enable = true; dataDir = cfg.stateDir; diff --git a/nixarr/prowlarr/prowlarr-module/default.nix b/nixarr/prowlarr/prowlarr-module/default.nix index 7655ff4..48c919f 100644 --- a/nixarr/prowlarr/prowlarr-module/default.nix +++ b/nixarr/prowlarr/prowlarr-module/default.nix @@ -70,6 +70,8 @@ in { }; }; - users.groups = mkIf (cfg.group == "prowlarr") {}; + users.groups = mkIf (cfg.group == "prowlarr") { + prowlarr = { }; + }; }; } diff --git a/nixarr/radarr/default.nix b/nixarr/radarr/default.nix index a20176a..1957555 100644 --- a/nixarr/radarr/default.nix +++ b/nixarr/radarr/default.nix @@ -14,7 +14,7 @@ in { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/radarr"; + default = "${nixarr.stateDir}/radarr"; description = "The state directory for radarr."; }; diff --git a/nixarr/readarr/default.nix b/nixarr/readarr/default.nix index a79d4b6..9cd83e6 100644 --- a/nixarr/readarr/default.nix +++ b/nixarr/readarr/default.nix @@ -12,7 +12,7 @@ in { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/readarr"; + default = "${nixarr.stateDir}/readarr"; description = "The state directory for Readarr"; }; diff --git a/nixarr/transmission/cross-seed/default.nix b/nixarr/transmission/cross-seed/default.nix index b5278e0..5e32c4d 100644 --- a/nixarr/transmission/cross-seed/default.nix +++ b/nixarr/transmission/cross-seed/default.nix @@ -8,7 +8,37 @@ with lib; let cfg = config.util-nixarr.services.cross-seed; settingsFormat = pkgs.formats.json {}; settingsFile = settingsFormat.generate "settings.json" cfg.settings; - cross-seedPkg = import ../../../pkgs/cross-seed { inherit (pkgs) stdenv lib fetchFromGitHub; }; + cross-seedPkg = pkgs.callPackage ../../../pkgs/cross-seed {}; + configJs = pkgs.writeText "config.js" '' + // Loads a json.config + "use strict"; + const fs = require('fs'); + + const jsonPath = '${cfg.dataDir}/config.json' + + // Synchronously read the JSON-configuration file + const configFileContent = fs.readFileSync(jsonPath, { encoding: 'utf8' }); + + // Parse the JSON content into a JavaScript object + let config = JSON.parse(configFileContent); + + // Function to recursively replace null values with undefined + /* + function replaceNullWithUndefined(obj) { + Object.keys(obj).forEach(key => { + if (obj[key] === null) { + obj[key] = undefined; + } else if (typeof obj[key] === 'object') { + replaceNullWithUndefined(obj[key]); + } + }); + } + replaceNullWithUndefined(config); + */ + + // Export the configuration object + module.exports = config; + ''; in { options = { util-nixarr.services.cross-seed = { @@ -52,9 +82,23 @@ in { }; config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> cfg.settings.outputDir != null; + message = '' + The settings.outputDir must be set if cross-seed is enabled. + ''; + } + ]; + systemd.tmpfiles.rules = [ + "L+ '${cfg.dataDir}'/config.js - - - - ${configJs}" "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" - ]; + ] ++ ( + if cfg.settings.outputDir != null then + [ "d '${cfg.settings.outputDir}' 0755 ${cfg.user} ${cfg.group} - -" ] + else [] + ); systemd.services.cross-seed = { description = "cross-seed"; @@ -73,7 +117,7 @@ in { Type = "simple"; User = cfg.user; Group = cfg.group; - ExecStart = "${getExe cross-seedPkg} daemon"; + ExecStart = "${cross-seedPkg}/bin/cross-seed daemon"; Restart = "on-failure"; }; }; @@ -85,6 +129,8 @@ in { }; }; - users.groups = mkIf (cfg.group == "cross-seed") {}; + users.groups = mkIf (cfg.group == "cross-seed") { + cross-seed = { }; + }; }; } diff --git a/nixarr/transmission/default.nix b/nixarr/transmission/default.nix index 367bba9..f77b626 100644 --- a/nixarr/transmission/default.nix +++ b/nixarr/transmission/default.nix @@ -9,12 +9,25 @@ with lib; let nixarr = config.nixarr; cfg-cross-seed = config.nixarr.transmission.privateTrackers.cross-seed; transmissionCrossSeedScript = with builtins; pkgs.writeShellApplication { - name = "mk-cross-seed-credentials"; + name = "transmission-cross-seed-script"; runtimeInputs = with pkgs; [ curl ]; text = '' - curl -XPOST http://localhost:2468/api/webhook?apikey=YOUR_API_KEY --data-urlencode "infoHash=$TR_TORRENT_HASH" + PROWLARR_API_KEY=$(cat prowlarr-api-key) + curl -XPOST http://localhost:2468/api/webhook?apikey="$PROWLARR_API_KEY" --data-urlencode "infoHash=$TR_TORRENT_HASH" + ''; + }; + importProwlarrApi = with builtins; pkgs.writeShellApplication { + name = "import-prowlarr-api"; + + runtimeInputs = with pkgs; [ yq ]; + + text = '' + touch ${cfg.stateDir}/prowlarr-api-key + chmod 400 ${cfg.stateDir}/prowlarr-api-key + chown torrenter ${cfg.stateDir}/prowlarr-api-key + xq -r '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml" > "${cfg.stateDir}/prowlarr-api-key" ''; }; mkCrossSeedCredentials = with builtins; pkgs.writeShellApplication { @@ -24,14 +37,17 @@ with lib; let text = "INDEX_LINKS=(" - + strings.concatMapStringsSep " " toString cfg.privateTrackers.cross-seed.indexIds + + (strings.concatMapStringsSep " " toString cfg.privateTrackers.cross-seed.indexIds) + ")" - '' + + "\n" + + '' TMP_JSON=$(mktemp) CRED_FILE="/run/secrets/cross-seed/credentialsFile.json" - PROWLARR_API_KEY=$(xq '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml") - CRED_DIR=$(dirname "$filePath") + PROWLARR_API_KEY=$(xq -r '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml") + # shellcheck disable=SC2034 + CRED_DIR=$(dirname "$CRED_FILE") + mkdir -p "$CRED_DIR" echo '{}' > "$CRED_FILE" chmod 400 "$CRED_FILE" chown "${config.util-nixarr.services.cross-seed.user}" "$CRED_FILE" @@ -49,7 +65,7 @@ in { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/transmission"; + default = "${nixarr.stateDir}/transmission"; description = '' The state directory for Transmission. ''; @@ -103,7 +119,7 @@ in { stateDir = mkOption { type = types.path; - default = "${nixarr.stateDir}/nixarr/cross-seed"; + default = "${nixarr.stateDir}/cross-seed"; description = '' The state directory for Transmission. ''; @@ -212,8 +228,8 @@ in { #group = "media"; settings = { torrentDir = "${nixarr.mediaDir}/torrents"; - outputDir = "${nixarr.mediaDir}/torrents/cross-seed"; - transmissionRpcUrl = "http://transmission:${builtins.toString cfg.uiPort}/transmission/rpc"; + outputDir = "${nixarr.mediaDir}/torrents/.cross-seed"; + transmissionRpcUrl = "http://localhost:${builtins.toString cfg.uiPort}/transmission/rpc"; rssCadence = "20 minutes"; # Enable infrequent periodic searches @@ -224,11 +240,16 @@ in { }; # Run as root in case that the cfg.credentialsFile is not readable by cross-seed systemd.services.cross-seed.serviceConfig = mkIf cfg-cross-seed.enable { - ExecStartPre = [(mkBefore - ("+" + (getExe mkCrossSeedCredentials)) + ExecStartPre = mkBefore [( + "+" + "${mkCrossSeedCredentials}/bin/mk-cross-seed-credentials" )]; }; + systemd.services.transmission.serviceConfig = mkIf cfg-cross-seed.enable { + ExecStartPre = mkBefore [( + "+" + "${importProwlarrApi}/bin/import-prowlarr-api" + )]; + }; services.transmission = { enable = true; user = "torrenter"; @@ -270,7 +291,9 @@ in { anti-brute-force-threshold = 10; script-torrent-done-enabled = cfg-cross-seed.enable; - script-torrent-done-filename = if cfg-cross-seed.enable then transmissionCrossSeedScript else null; + script-torrent-done-filename = if cfg-cross-seed.enable then + "${transmissionCrossSeedScript}/bin/transmission-cross-seed-script" + else null; message-level = if cfg.messageLevel == "none" diff --git a/pkgs/cross-seed/default.nix b/pkgs/cross-seed/default.nix index b39f28a..2a69754 100644 --- a/pkgs/cross-seed/default.nix +++ b/pkgs/cross-seed/default.nix @@ -1,75 +1,22 @@ -{ - config, - pkgs, - lib, - ... -}: -with lib; let - cfg = config.util-nixarr.services.prowlarr; - settingsFormat = pkgs.formats.json {}; - settingsFile = settingsFormat.generate "settings.json" cfg.settings; - cross-seedPkg = import ../../../pkgs/cross-seed { inherit (pkgs) stdenv lib fetchFromGitHub; }; -in { - options = { - util-nixarr.services.prowlarr = { - enable = mkEnableOption "cross-seed"; +{ lib, buildNpmPackage, fetchFromGitHub }: - configFile = mkOption { - type = with types; nullOr path; - default = null; - example = "/var/lib/secrets/cross-seed/settings.json"; - description = ""; - }; +buildNpmPackage rec { + pname = "cross-seed"; + version = "5.9.2"; - dataDir = mkOption { - type = types.path; - default = "/var/lib/cross-seed"; - }; - - user = mkOption { - type = types.str; - default = "cross-seed"; - description = "User account under which cross-seed runs."; - }; - - group = mkOption { - type = types.str; - default = "cross-seed"; - description = "Group under which cross-seed runs."; - }; - }; + src = fetchFromGitHub { + owner = "cross-seed"; + repo = pname; + rev = "v${version}"; + hash = "sha256-E0AlsFV9RP01YVwjw6ZQ8Lf1IVyuudxrb5oJ61EfIyo="; }; - config = mkIf cfg.enable { - systemd.tmpfiles.rules = [ - "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" - ]; - - systemd.services.prowlarr = { - description = "cross-seed"; - after = ["network.target"]; - wantedBy = ["multi-user.target"]; - - environment.CONFIG_DIR = cfg.dataDir; - - serviceConfig = { - ExecStartPre = [("+" + pkgs.writeShellScript "transmission-prestart" '' - mv ${cfg.configFile} ${cfg.dataDir} - '')]; - Type = "simple"; - User = cfg.user; - Group = cfg.group; - ExecStart = "${getExe cross-seedPkg} daemon"; - Restart = "on-failure"; - }; - }; - - users.users = mkIf (cfg.user == "cross-seed") { - cross-seed = { - group = cfg.group; - }; - }; + npmDepsHash = "sha256-hZKLv+bzRFiMjNemydCUC1d7xul7Mm+vOPtCUD7p9XQ="; - users.groups = mkIf (cfg.group == "cross-seed") {}; + meta = with lib; { + description = "cross-seed is an app designed to help you download torrents that you can cross seed based on your existing torrents"; + homepage = "https://www.cross-seed.org"; + license = licenses.asl20; + maintainers = with maintainers; [ rasmus-kirk ]; }; }