diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix index 84f0437b941..01a5890a784 100644 --- a/nixos/modules/services/torrent/deluge.nix +++ b/nixos/modules/services/torrent/deluge.nix @@ -5,8 +5,33 @@ with lib; let cfg = config.services.deluge; cfg_web = config.services.deluge.web; - openFilesLimit = 4096; + 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 + ln -sf ${configFile} ${configDir}/core.conf + ln -sf ${cfg.authFile} ${configDir}/auth + else + # Declarative for the first time, backup stateful files + ln -sb --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 = { @@ -15,42 +40,151 @@ in { openFilesLimit = mkOption { default = openFilesLimit; - example = 8192; description = '' Number of files to allow deluged to open. ''; }; + + config = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + { + 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. + ''; + }; }; - deluge.web.enable = mkEnableOption "Deluge Web daemon"; + deluge.web = { + enable = mkEnableOption "Deluge Web daemon"; + port = mkOption { + type = types.port; + default = 8112; + description = '' + Deluge web UI port. + ''; + }; + }; }; }; config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ "d '${configDir}' 0770 deluge deluge" ] + ++ optional (cfg.config ? "download_location") + "d '${cfg.config.download_location}' 0770 deluge deluge" + ++ optional (cfg.config ? "torrentfiles_location") + "d '${cfg.config.torrentfiles_location}' 0770 deluge deluge" + ++ optional (cfg.config ? "move_completed_path") + "d '${cfg.config.move_completed_path}' 0770 deluge deluge"; + systemd.services.deluged = { after = [ "network.target" ]; description = "Deluge BitTorrent Daemon"; wantedBy = [ "multi-user.target" ]; path = [ pkgs.deluge ]; serviceConfig = { - ExecStart = "${pkgs.deluge}/bin/deluged -d"; - # To prevent "Quit & shutdown daemon" from working; we want systemd to manage it! + ExecStart = '' + ${pkgs.deluge}/bin/deluged \ + --do-not-daemonize \ + --config ${configDir} + ''; + # To prevent "Quit & shutdown daemon" from working; we want systemd to + # manage it! Restart = "on-success"; User = "deluge"; Group = "deluge"; + UMask = "0002"; LimitNOFILE = cfg.openFilesLimit; }; + preStart = preStart; }; systemd.services.delugeweb = mkIf cfg_web.enable { - after = [ "network.target" ]; + after = [ "network.target" "deluged.service"]; + requires = [ "deluged.service" ]; description = "Deluge BitTorrent WebUI"; wantedBy = [ "multi-user.target" ]; path = [ pkgs.deluge ]; - serviceConfig.ExecStart = "${pkgs.deluge}/bin/deluge --ui web"; - serviceConfig.User = "deluge"; - serviceConfig.Group = "deluge"; + serviceConfig = { + ExecStart = '' + ${pkgs.deluge}/bin/deluge-web \ + --config ${configDir} \ + --port ${toString cfg.web.port} + ''; + User = "deluge"; + Group = "deluge"; + }; + }; + + networking.firewall = 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)); }; environment.systemPackages = [ pkgs.deluge ]; @@ -58,7 +192,7 @@ in { users.users.deluge = { group = "deluge"; uid = config.ids.uids.deluge; - home = "/var/lib/deluge/"; + home = cfg.dataDir; createHome = true; description = "Deluge Daemon user"; }; diff --git a/nixos/tests/deluge.nix b/nixos/tests/deluge.nix index b4be5e465cc..22ad84e7bff 100644 --- a/nixos/tests/deluge.nix +++ b/nixos/tests/deluge.nix @@ -5,25 +5,56 @@ import ./make-test.nix ({ pkgs, ...} : { }; nodes = { - server = + simple = { + services.deluge = { + enable = true; + web.enable = true; + }; + networking.firewall.allowedTCPPorts = [ 8112 ]; + }; + + declarative = { ... }: - { services.deluge = { + { + services.deluge = { enable = true; - web.enable = true; + openFirewall = true; + declarative = true; + config = { + allow_remote = true; + download_location = "/var/lib/deluge/my-download"; + daemon_port = 58846; + listen_ports = [ 6881 6889 ]; + }; + web = { + enable = true; + port = 3142; + }; + authFile = pkgs.writeText "deluge-auth" '' + localclient:a7bef72a890:10 + andrew:password:10 + user3:anotherpass:5 + ''; }; - networking.firewall.allowedTCPPorts = [ 8112 ]; + environment.systemPackages = [ pkgs.deluge ]; }; - client = { }; }; testScript = '' startAll; - $server->waitForUnit("deluged"); - $server->waitForUnit("delugeweb"); - $client->waitForUnit("network.target"); - $client->waitUntilSucceeds("curl --fail http://server:8112"); + $simple->waitForUnit("deluged"); + $simple->waitForUnit("delugeweb"); + $simple->waitForOpenPort("8112"); + $declarative->waitForUnit("network.target"); + $declarative->waitUntilSucceeds("curl --fail http://simple:8112"); + + $declarative->waitForUnit("deluged"); + $declarative->waitForUnit("delugeweb"); + $declarative->waitUntilSucceeds("curl --fail http://declarative:3142"); + $declarative->succeed("deluge-console 'help' | grep -q 'rm - Remove a torrent'"); + $declarative->succeed("deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm - Remove a torrent'"); ''; })