]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/torrent/transmission.nix
transmission: improve the service module
[sourcephile-nix.git] / nixos / modules / services / torrent / transmission.nix
1 { config, lib, pkgs, ... }:
2
3 with lib;
4
5 let
6 cfg = config.services.transmission;
7 apparmor = config.security.apparmor.enable;
8 # TODO: switch to configGen.json once RFC42 is implemented
9 settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
10 stateDir = "/var/lib/transmission";
11 settingsDir = ".config/transmission-daemon";
12 in
13 {
14 imports = [
15 (mkRemovedOptionModule [ "services" "transmission" "port" ]
16 "Instead, use the option `services.transmission.settings.rpc-port'.")
17 (mkRemovedOptionModule [ "services" "transmission" "home" ]
18 "Instead, use systemd's StateDirectory: `${stateDir}'.")
19 (mkRemovedOptionModule [ "services" "transmission" "downloadDirPermissions" ] ''
20 Instead, use the option `services.transmission.settings.umask'
21 (which currently is: ${toString cfg.settings.umask}) (in decimal, as expected by Transmission)
22 and `systemd.services.transmission.serviceConfig.StateDirectoryMode'
23 (which currently is: ${config.systemd.services.transmission.serviceConfig.StateDirectoryMode}).
24 '')
25 ];
26 options = {
27 services.transmission = {
28 enable = mkEnableOption ''
29 Whether or not to enable the headless Transmission BitTorrent daemon.
30
31 Transmission daemon can be controlled via the RPC interface using
32 transmission-remote, the WebUI (http://${cfg.settings.rpc-bind-address}:${toString cfg.settings.rpc-port}/ by default),
33 or other clients like stig or tremc.
34
35 Torrents are downloaded to ${stateDir}/${cfg.settings.download-dir} by default and are
36 accessible to users in the "transmission" group.
37 '';
38
39 settings = mkOption rec {
40 # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
41 type = types.attrs;
42 apply = recursiveUpdate default;
43 default =
44 {
45 download-dir = "Downloads";
46 incomplete-dir = ".incomplete";
47 incomplete-dir-enabled = true;
48 rpc-port = 9091;
49 umask = 63; # echo $((8#077))
50 };
51 example =
52 {
53 download-dir = "Torrents";
54 incomplete-dir = ".Torrents";
55 incomplete-dir-enabled = true;
56 rpc-whitelist = "127.0.0.1,192.168.*.*";
57 };
58 description = ''
59 Attribute set whose fields overwrites fields in settings.json (each
60 time the service starts). String values must be quoted, integer and
61 boolean values must not.
62
63 See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
64 for documentation.
65 '';
66 };
67
68 user = mkOption {
69 type = types.str;
70 default = "transmission";
71 description = "User account under which Transmission runs.";
72 };
73
74 group = mkOption {
75 type = types.str;
76 default = "transmission";
77 description = "Group account under which Transmission runs.";
78 };
79 };
80 };
81
82 config = mkIf cfg.enable {
83 assertions = [
84 { assertion = builtins.match "^/.*" cfg.settings.download-dir == null;
85 message = "`services.transmission.settings.download-dir' " +
86 "can no longer be an absolute path, it must be relative to `${stateDir}'"; }
87 { assertion = builtins.match "^/.*" cfg.settings.incomplete-dir == null;
88 message = "`services.transmission.settings.incomplete-dir' " +
89 "can no longer be an absolute path, it must be relative to `${stateDir}'"; }
90 ];
91
92 systemd.services.transmission = {
93 description = "Transmission BitTorrent Service";
94 after = [ "network.target" ] ++ optional apparmor "apparmor.service";
95 requires = mkIf apparmor [ "apparmor.service" ];
96 wantedBy = [ "multi-user.target" ];
97 preStart = ''
98 chmod 755 '${stateDir}'
99 chmod 0700 '${stateDir}/${settingsDir}'
100 cp -f ${settingsFile} ${settingsDir}/settings.json
101 '';
102
103 serviceConfig = {
104 WorkingDirectory = stateDir;
105 StateDirectory = concatMapStringsSep " " (p: "transmission/${p}")
106 ([ settingsDir cfg.settings.download-dir ]
107 ++ optional cfg.settings.incomplete-dir-enabled cfg.settings.incomplete-dir);
108 StateDirectoryMode = mkDefault "770";
109 ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --config-dir ${settingsDir}";
110 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
111 User = cfg.user;
112 Group = cfg.group;
113 UMask = "0007";
114
115 # Hardening options
116 #DevicePolicy = "closed";
117 #LockPersonality = true;
118 #MemoryDenyWriteExecute = true;
119 #NoNewPrivileges = true;
120 #PrivateDevices = true;
121 #PrivateTmp = true;
122 #ProtectControlGroups = true;
123 #ProtectHome = true;
124 #ProtectKernelModules = true;
125 #ProtectKernelTunables = true;
126 #ProtectSystem = "strict";
127 #RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
128 #RestrictNamespaces = true;
129 #RestrictRealtime = true;
130 #RestrictSUIDSGID = true;
131 };
132 };
133
134 # It's useful to have transmission in path, e.g. for remote control
135 environment.systemPackages = [ pkgs.transmission ];
136
137 users.users = optionalAttrs (cfg.user == "transmission") ({
138 transmission = {
139 group = cfg.group;
140 uid = config.ids.uids.transmission;
141 description = "Transmission BitTorrent user";
142 home = stateDir;
143 createHome = true;
144 };
145 });
146
147 users.groups = optionalAttrs (cfg.group == "transmission") ({
148 transmission = {
149 gid = config.ids.gids.transmission;
150 };
151 });
152
153 # AppArmor profile
154 security.apparmor.profiles = mkIf apparmor [
155 (pkgs.writeText "apparmor-transmission-daemon" ''
156 #include <tunables/global>
157
158 ${pkgs.transmission}/bin/transmission-daemon {
159 #include <abstractions/base>
160 #include <abstractions/nameservice>
161
162 ${getLib pkgs.gcc-unwrapped}/lib/*.so* mr,
163 ${getLib pkgs.glibc}/lib/*.so* mr,
164 ${getLib pkgs.libevent}/lib/libevent*.so* mr,
165 ${getLib pkgs.curl}/lib/libcurl*.so* mr,
166 ${getLib pkgs.openssl}/lib/libssl*.so* mr,
167 ${getLib pkgs.openssl}/lib/libcrypto*.so* mr,
168 ${getLib pkgs.zlib}/lib/libz*.so* mr,
169 ${getLib pkgs.libssh2}/lib/libssh2*.so* mr,
170 ${getLib pkgs.systemd}/lib/libsystemd*.so* mr,
171 ${getLib pkgs.xz}/lib/liblzma*.so* mr,
172 ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so* mr,
173 ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
174 ${getLib pkgs.nghttp2}/lib/libnghttp2*.so* mr,
175 ${getLib pkgs.c-ares}/lib/libcares*.so* mr,
176 ${getLib pkgs.libcap}/lib/libcap*.so* mr,
177 ${getLib pkgs.attr}/lib/libattr*.so* mr,
178 ${getLib pkgs.lz4}/lib/liblz4*.so* mr,
179 ${getLib pkgs.libkrb5}/lib/lib*.so* mr,
180 ${getLib pkgs.keyutils}/lib/libkeyutils*.so* mr,
181 ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
182 ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
183 ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
184
185 @{PROC}/sys/kernel/random/uuid r,
186 @{PROC}/sys/vm/overcommit_memory r,
187
188 ${pkgs.openssl.out}/etc/** r,
189 ${pkgs.transmission}/share/transmission/** r,
190
191 owner ${stateDir}/${settingsDir}/** rw,
192
193 ${stateDir}/${cfg.settings.download-dir}/** rw,
194 ${optionalString cfg.settings.incomplete-dir-enabled ''
195 ${stateDir}/${cfg.settings.incomplete-dir}/** rw,
196 ''}
197 }
198 '')
199 ];
200 };
201
202 }