at master 6.9 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 utils, 6 ... 7}: 8let 9 cfg = config.services.qbittorrent; 10 inherit (builtins) concatStringsSep isAttrs isString; 11 inherit (lib) 12 literalExpression 13 getExe 14 mkEnableOption 15 mkOption 16 mkPackageOption 17 mkIf 18 maintainers 19 escape 20 collect 21 mapAttrsRecursive 22 optionals 23 ; 24 inherit (lib.types) 25 str 26 port 27 path 28 nullOr 29 listOf 30 attrsOf 31 anything 32 submodule 33 ; 34 inherit (lib.generators) toINI mkKeyValueDefault mkValueStringDefault; 35 gendeepINI = toINI { 36 mkKeyValue = 37 let 38 sep = "="; 39 in 40 k: v: 41 if isAttrs v then 42 concatStringsSep "\n" ( 43 collect isString ( 44 mapAttrsRecursive ( 45 path: value: 46 "${escape [ sep ] (concatStringsSep "\\" ([ k ] ++ path))}${sep}${mkValueStringDefault { } value}" 47 ) v 48 ) 49 ) 50 else 51 mkKeyValueDefault { } sep k v; 52 }; 53 configFile = pkgs.writeText "qBittorrent.conf" (gendeepINI cfg.serverConfig); 54in 55{ 56 options.services.qbittorrent = { 57 enable = mkEnableOption "qbittorrent, BitTorrent client"; 58 59 package = mkPackageOption pkgs "qbittorrent-nox" { }; 60 61 user = mkOption { 62 type = str; 63 default = "qbittorrent"; 64 description = "User account under which qbittorrent runs."; 65 }; 66 67 group = mkOption { 68 type = str; 69 default = "qbittorrent"; 70 description = "Group under which qbittorrent runs."; 71 }; 72 73 profileDir = mkOption { 74 type = path; 75 default = "/var/lib/qBittorrent/"; 76 description = "the path passed to qbittorrent via --profile."; 77 }; 78 79 openFirewall = mkEnableOption "opening both the webuiPort and torrentPort over TCP in the firewall"; 80 81 webuiPort = mkOption { 82 default = 8080; 83 type = nullOr port; 84 description = "the port passed to qbittorrent via `--webui-port`"; 85 }; 86 87 torrentingPort = mkOption { 88 default = null; 89 type = nullOr port; 90 description = "the port passed to qbittorrent via `--torrenting-port`"; 91 }; 92 93 serverConfig = mkOption { 94 default = { }; 95 type = submodule { 96 freeformType = attrsOf (attrsOf anything); 97 }; 98 description = '' 99 Free-form settings mapped to the `qBittorrent.conf` file in the profile. 100 Refer to [Explanation-of-Options-in-qBittorrent](https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent). 101 The Password_PBKDF2 format is oddly unique, you will likely want to use [this tool](https://codeberg.org/feathecutie/qbittorrent_password) to generate the format. 102 Alternatively you can run qBittorrent independently first and use its webUI to generate the format. 103 104 Optionally an alternative webUI can be easily set. VueTorrent for example: 105 ```nix 106 { 107 Preferences = { 108 WebUI = { 109 AlternativeUIEnabled = true; 110 RootFolder = "''${pkgs.vuetorrent}/share/vuetorrent"; 111 }; 112 }; 113 } 114 ]; 115 ``` 116 ''; 117 example = literalExpression '' 118 { 119 LegalNotice.Accepted = true; 120 Preferences = { 121 WebUI = { 122 Username = "user"; 123 Password_PBKDF2 = "generated ByteArray."; 124 }; 125 General.Locale = "en"; 126 }; 127 } 128 ''; 129 }; 130 131 extraArgs = mkOption { 132 type = listOf str; 133 default = [ ]; 134 description = '' 135 Extra arguments passed to qbittorrent. See `qbittorrent -h`, or the [source code](https://github.com/qbittorrent/qBittorrent/blob/master/src/app/cmdoptions.cpp), for the available arguments. 136 ''; 137 example = [ 138 "--confirm-legal-notice" 139 ]; 140 }; 141 }; 142 config = mkIf cfg.enable { 143 systemd = { 144 tmpfiles.settings = { 145 qbittorrent = { 146 "${cfg.profileDir}/qBittorrent/"."d" = { 147 mode = "755"; 148 inherit (cfg) user group; 149 }; 150 "${cfg.profileDir}/qBittorrent/config/"."d" = { 151 mode = "755"; 152 inherit (cfg) user group; 153 }; 154 "${cfg.profileDir}/qBittorrent/config/qBittorrent.conf"."L+" = mkIf (cfg.serverConfig != { }) { 155 mode = "1400"; 156 inherit (cfg) user group; 157 argument = "${configFile}"; 158 }; 159 }; 160 }; 161 services.qbittorrent = { 162 description = "qbittorrent BitTorrent client"; 163 wants = [ "network-online.target" ]; 164 after = [ 165 "local-fs.target" 166 "network-online.target" 167 "nss-lookup.target" 168 ]; 169 wantedBy = [ "multi-user.target" ]; 170 restartTriggers = optionals (cfg.serverConfig != { }) [ configFile ]; 171 172 serviceConfig = { 173 Type = "simple"; 174 User = cfg.user; 175 Group = cfg.group; 176 ExecStart = utils.escapeSystemdExecArgs ( 177 [ 178 (getExe cfg.package) 179 "--profile=${cfg.profileDir}" 180 ] 181 ++ optionals (cfg.webuiPort != null) [ "--webui-port=${toString cfg.webuiPort}" ] 182 ++ optionals (cfg.torrentingPort != null) [ "--torrenting-port=${toString cfg.torrentingPort}" ] 183 ++ cfg.extraArgs 184 ); 185 TimeoutStopSec = 1800; 186 187 # https://github.com/qbittorrent/qBittorrent/pull/6806#discussion_r121478661 188 PrivateTmp = false; 189 190 PrivateNetwork = false; 191 RemoveIPC = true; 192 NoNewPrivileges = true; 193 PrivateDevices = true; 194 PrivateUsers = true; 195 ProtectHome = "yes"; 196 ProtectProc = "invisible"; 197 ProcSubset = "pid"; 198 ProtectSystem = "full"; 199 ProtectClock = true; 200 ProtectHostname = true; 201 ProtectKernelLogs = true; 202 ProtectKernelModules = true; 203 ProtectKernelTunables = true; 204 ProtectControlGroups = true; 205 RestrictAddressFamilies = [ 206 "AF_INET" 207 "AF_INET6" 208 "AF_NETLINK" 209 ]; 210 RestrictNamespaces = true; 211 RestrictRealtime = true; 212 RestrictSUIDSGID = true; 213 LockPersonality = true; 214 MemoryDenyWriteExecute = true; 215 SystemCallArchitectures = "native"; 216 CapabilityBoundingSet = ""; 217 SystemCallFilter = [ "@system-service" ]; 218 }; 219 }; 220 }; 221 222 users = { 223 users = mkIf (cfg.user == "qbittorrent") { 224 qbittorrent = { 225 inherit (cfg) group; 226 isSystemUser = true; 227 }; 228 }; 229 groups = mkIf (cfg.group == "qbittorrent") { qbittorrent = { }; }; 230 }; 231 232 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall ( 233 optionals (cfg.webuiPort != null) [ cfg.webuiPort ] 234 ++ optionals (cfg.torrentingPort != null) [ cfg.torrentingPort ] 235 ); 236 }; 237 meta.maintainers = with maintainers; [ 238 fsnkty 239 undefined-landmark 240 ]; 241}