summaryrefslogtreecommitdiff
path: root/modules/nixos/qbittorrent/default.nix
blob: ad4b7e619e8df7f158f4814faafa73ee949cbcd6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# This module defines a service which runs a headless qBittorrent instance
{
  config,
  options,
  lib,
  pkgs,
  ...
}: let
  defaultUser = "qbittorrent";
  defaultGroup = "qbittorrent";

  cfg = config.services.qbittorrent;
in {
  options.services.qbittorrent = {
    enable = lib.mkEnableOption "headless qBittorrent instance";

    port = lib.mkOption {
      description = "The port on which to serve the WebUI.";
      type = lib.types.port;
      default = 8080;
    };

    openFirewall = lib.mkOption {
      description = ''
        Open holes in the firewall so clients on LAN can connect to the web
        interface. You must set up port forwarding if you want it accessable
        from the wider internet.
      '';
      type = lib.types.bool;
      default = false;
    };

    profile = lib.mkOption {
      description = ''
        The directory where qBittorrent should store it's data files. Note that
        these are for qBittorrent itself, not the files it downloads. Those are
        controlled through the configuration option `XXX`.
      '';
      type = lib.types.path;
      default = "/var/lib/qBittorrent";
    };

    user = lib.mkOption {
      description = ''
        The user to run the qBittorrent service as.

        The user is not automatically created if it is changed from the default value.
      '';
      type = lib.types.str;
      default = defaultUser;
    };

    group = lib.mkOption {
      description = ''
        The group to run the qBittorrent service as.

        The group is not automatically created if it is changed from the default value.
      '';
      type = lib.types.str;
      default = defaultGroup;
    };

    package = lib.mkOption {
      description = "qBittorrent package to use";
      type = lib.types.package;
      default = pkgs.qbittorrent-nox;
    };

    settings = lib.mkOption rec {
      description = ''
        An attribute set whose values overrides the ones specified in
        `qBittorrent.conf`.

        These values are applied on top of the existing configuration when the
        service starts. This is a necessary compromise between determinism and
        usability, as qBittorrent also saves all kinds of gunk in the
        configuration file.
      '';
      type = lib.types.attrs;
      default = {
        LegalNotice = {
          Accepted = false;
        };
      };
      apply = lib.recursiveUpdate default;
      example = {
        LegalNotice = {
          Accepted = true;
        };
        Preferences = {
          "Connection\\PortRangeMin" = 20082;
          "Downloads\\SavePath" = "/mnt";
          "WebUI\\UseUPnP" = false;
        };
      };
    };
  };

  config = lib.mkIf cfg.enable {
    # Create the user/group if required.
    users.users = lib.mkIf (cfg.user == defaultUser) {
      ${defaultUser} = {
        description = "Runs ${options.services.qbittorrent.enable.description}";
        group = cfg.group;
        isSystemUser = true;
      };
    };
    users.groups = lib.mkIf (cfg.group == defaultGroup) {
      ${defaultGroup} = {};
    };

    # Set up a service to run qBittorrent
    # See: https://github.com/qbittorrent/qBittorrent/blob/615b76f78c8ab92ad57bed42fc4266950c9f0251/dist/unix/systemd/qbittorrent-nox%40.service.in
    systemd.services.qbittorrent = {
      enable = true;

      unitConfig = {
        Wants = ["network-online.target"];
        After = ["local-fs.target" "network-online.target" "nss-lookup.target"];
      };

      serviceConfig = {
        Type = "simple";

        User = cfg.user;
        Group = cfg.group;
        PrivateTmp = false;

        ExecStartPre = let
          format = pkgs.formats.ini {};
          settingsFile = format.generate "qBittorrent.conf" cfg.settings;
          configPath = "${cfg.profile}/qBittorrent/config/qBittorrent.conf";

          start-pre-script = pkgs.writeShellScript "qbittorrent-start-pre" ''
             set -ue

             # Create data directory if it doesn't exist
             if ! test -d ${cfg.profile}; then
               echo "Creating initial qBittorrent data directory in: ${cfg.profile}"
               install -d -m 0755 -o ${cfg.user} -g ${cfg.group} ${cfg.profile}/qBittorrent/config/
             fi

            # Force-apply configuration.
            ${pkgs.crudini}/bin/crudini --ini-options=nospace --merge ${configPath} <${settingsFile}
          '';
        in
          # Requires full permissions to create data directory, hence the "!".
          "!${start-pre-script}";
        ExecStart = pkgs.writeShellScript "qbittorrent-start" ''
          exec ${cfg.package}/bin/qbittorrent-nox --webui-port=${toString cfg.port} --profile=${cfg.profile}
        '';
        TimeoutStopSec = 1800;

        # Set as low-priority
        IOSchedulingClass = "idle";
        IOSchedulingPriority = "7";
      };

      wantedBy = ["multi-user.target"];
    };

    networking.firewall = lib.mkIf cfg.openFirewall {
      allowedTCPPorts = [cfg.port];
    };
  };
}