{ config, lib, pkgs, ... }: with lib; let cfg = config.services.deluge; cfg_web = config.services.deluge.web; isDeluge1 = versionOlder cfg.package.version "2.0.0"; openFilesLimit = 4096; listenPortsDefault = [ 6881 6889 ]; listToRange = x: { from = elemAt x 0; to = elemAt x 1; }; configDir = "${cfg.dataDir}/.config/deluge"; configFile = pkgs.writeText "core.conf" (builtins.toJSON cfg.config); declarativeLockFile = "${configDir}/.declarative"; preStart = if cfg.declarative then '' if [ -e ${declarativeLockFile} ]; then # Was declarative before, no need to back up anything ${if isDeluge1 then "ln -sf" else "cp"} ${configFile} ${configDir}/core.conf ln -sf ${cfg.authFile} ${configDir}/auth else # Declarative for the first time, backup stateful files ${if isDeluge1 then "ln -s" else "cp"} -b --suffix=.stateful ${configFile} ${configDir}/core.conf ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \ > ${declarativeLockFile} fi '' else '' if [ -e ${declarativeLockFile} ]; then rm ${declarativeLockFile} fi ''; in { options = { services = { deluge = { enable = mkEnableOption "Deluge daemon"; openFilesLimit = mkOption { default = openFilesLimit; type = types.either types.int types.str; description = '' Number of files to allow deluged to open. ''; }; config = mkOption { type = types.attrs; default = {}; example = literalExpression '' { download_location = "/srv/torrents/"; max_upload_speed = "1000.0"; share_ratio_limit = "2.0"; allow_remote = true; daemon_port = 58846; listen_ports = [ ${toString listenPortsDefault} ]; } ''; description = '' Deluge core configuration for the core.conf file. Only has an effect when is set to true. String values must be quoted, integer and boolean values must not. See for the availaible options. ''; }; declarative = mkOption { type = types.bool; default = false; description = '' Whether to use a declarative deluge configuration. Only if set to true, the options , and will be applied. ''; }; openFirewall = mkOption { default = false; type = types.bool; description = '' Whether to open the firewall for the ports in . It only takes effet if is set to true. It does NOT apply to the daemon port nor the web UI port. To access those ports secuerly check the documentation or use a VPN or configure certificates for deluge. ''; }; dataDir = mkOption { type = types.path; default = "/var/lib/deluge"; description = '' The directory where deluge will create files. ''; }; authFile = mkOption { type = types.path; example = "/run/keys/deluge-auth"; description = '' The file managing the authentication for deluge, the format of this file is straightforward, each line contains a username:password:level tuple in plaintext. It only has an effect when is set to true. See for more informations. ''; }; user = mkOption { type = types.str; default = "deluge"; description = '' User account under which deluge runs. ''; }; group = mkOption { type = types.str; default = "deluge"; description = '' Group under which deluge runs. ''; }; extraPackages = mkOption { type = types.listOf types.package; default = []; description = '' Extra packages available at runtime to enable Deluge's plugins. For example, extraction utilities are required for the built-in "Extractor" plugin. This always contains unzip, gnutar, xz and bzip2. ''; }; package = mkOption { type = types.package; example = literalExpression "pkgs.deluge-2_x"; description = '' Deluge package to use. ''; }; }; deluge.web = { enable = mkEnableOption "Deluge Web daemon"; port = mkOption { type = types.port; default = 8112; description = '' Deluge web UI port. ''; }; openFirewall = mkOption { type = types.bool; default = false; description = '' Open ports in the firewall for deluge web daemon ''; }; }; }; }; config = mkIf cfg.enable { services.deluge.package = mkDefault ( if versionAtLeast config.system.stateVersion "20.09" then pkgs.deluge-2_x else # deluge-1_x is no longer packaged and this will resolve to an error # thanks to the alias for this name. This is left here so that anyone # using NixOS older than 20.09 receives that error when they upgrade # and is forced to make an intentional choice to switch to deluge-2_x. # That might be slightly inconvenient but there is no path to # downgrade from 2.x to 1.x so NixOS should not automatically perform # this state migration. pkgs.deluge-1_x ); # Provide a default set of `extraPackages`. services.deluge.extraPackages = with pkgs; [ unzip gnutar xz bzip2 ]; systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}" "d '${cfg.dataDir}/.config' 0770 ${cfg.user} ${cfg.group}" "d '${cfg.dataDir}/.config/deluge' 0770 ${cfg.user} ${cfg.group}" ] ++ optional (cfg.config ? download_location) "d '${cfg.config.download_location}' 0770 ${cfg.user} ${cfg.group}" ++ optional (cfg.config ? torrentfiles_location) "d '${cfg.config.torrentfiles_location}' 0770 ${cfg.user} ${cfg.group}" ++ optional (cfg.config ? move_completed_path) "d '${cfg.config.move_completed_path}' 0770 ${cfg.user} ${cfg.group}"; systemd.services.deluged = { after = [ "network.target" ]; description = "Deluge BitTorrent Daemon"; wantedBy = [ "multi-user.target" ]; path = [ cfg.package ] ++ cfg.extraPackages; serviceConfig = { ExecStart = '' ${cfg.package}/bin/deluged \ --do-not-daemonize \ --config ${configDir} ''; # To prevent "Quit & shutdown daemon" from working; we want systemd to # manage it! Restart = "on-success"; User = cfg.user; Group = cfg.group; UMask = "0002"; LimitNOFILE = cfg.openFilesLimit; }; preStart = preStart; }; systemd.services.delugeweb = mkIf cfg_web.enable { after = [ "network.target" "deluged.service"]; requires = [ "deluged.service" ]; description = "Deluge BitTorrent WebUI"; wantedBy = [ "multi-user.target" ]; path = [ cfg.package ]; serviceConfig = { ExecStart = '' ${cfg.package}/bin/deluge-web \ ${optionalString (!isDeluge1) "--do-not-daemonize"} \ --config ${configDir} \ --port ${toString cfg.web.port} ''; User = cfg.user; Group = cfg.group; }; }; networking.firewall = mkMerge [ (mkIf (cfg.declarative && cfg.openFirewall && !(cfg.config.random_port or true)) { allowedTCPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault)); allowedUDPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault)); }) (mkIf (cfg.web.openFirewall) { allowedTCPPorts = [ cfg.web.port ]; }) ]; environment.systemPackages = [ cfg.package ]; users.users = mkIf (cfg.user == "deluge") { deluge = { group = cfg.group; uid = config.ids.uids.deluge; home = cfg.dataDir; description = "Deluge Daemon user"; }; }; users.groups = mkIf (cfg.group == "deluge") { deluge = { gid = config.ids.gids.deluge; }; }; }; }