29027fd1e1
Using pkgs.lib on the spine of module evaluation is problematic because the pkgs argument depends on the result of module evaluation. To prevent an infinite recursion, pkgs and some of the modules are evaluated twice, which is inefficient. Using ‘with lib’ prevents this problem.
174 lines
5.9 KiB
Nix
174 lines
5.9 KiB
Nix
# NixOS module for Transmission BitTorrent daemon
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.services.transmission;
|
|
homeDir = "/var/lib/transmission";
|
|
settingsDir = "${homeDir}/.config/transmission-daemon";
|
|
settingsFile = "${settingsDir}/settings.json";
|
|
|
|
# Strings must be quoted, ints and bools must not (for settings.json).
|
|
toOption = x:
|
|
if x == true then "true"
|
|
else if x == false then "false"
|
|
else if isInt x then toString x
|
|
else toString ''\"${x}\"'';
|
|
|
|
# All lines in settings.json end with a ',' (comma), except for the last
|
|
# line. This is standard JSON. But a comma can also appear *inside* some
|
|
# fields, notably the "rpc-whitelist" field. This is difficult to handle in
|
|
# sed so we simply ignore it and say that if you want to change the option at
|
|
# the last line of settings.json, you have to do it manually. At this time of
|
|
# writing, the last option is "utp-enable":true.
|
|
attrsToSedArgs = as:
|
|
concatStrings (concatLists (mapAttrsToList (name: value:
|
|
#map (x: '' -e 's=\(\"${name}\":\)[^,]*\(.*\)=\1 ${toOption x}\2=' '') # breaks if comma inside value field
|
|
map (x: '' -e 's=\(\"${name}\":\).*=\1 ${toOption x},=' '') # always append ',' (breaks last line in settings.json)
|
|
(if isList value then value else [value]))
|
|
as));
|
|
|
|
in
|
|
|
|
{
|
|
|
|
### configuration
|
|
|
|
options = {
|
|
|
|
services.transmission = {
|
|
|
|
enable = mkOption {
|
|
type = types.uniq types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether or not to enable the headless Transmission BitTorrent daemon.
|
|
|
|
Transmission daemon can be controlled via the RPC interface using
|
|
transmission-remote or the WebUI (http://localhost:9091/ by default).
|
|
|
|
Torrents are downloaded to ${homeDir}/Downloads/ by default and are
|
|
accessible to users in the "transmission" group.
|
|
'';
|
|
};
|
|
|
|
settings = mkOption {
|
|
type = types.attrs;
|
|
default =
|
|
{
|
|
# for users in group "transmission" to have access to torrents
|
|
umask = 2;
|
|
}
|
|
;
|
|
example =
|
|
{
|
|
download-dir = "/srv/torrents/";
|
|
incomplete-dir = "/srv/torrents/.incomplete/";
|
|
incomplete-dir-enabled = true;
|
|
rpc-whitelist = "127.0.0.1,192.168.*.*";
|
|
# for users in group "transmission" to have access to torrents
|
|
umask = 2;
|
|
}
|
|
;
|
|
description = ''
|
|
Attribute set whos fields overwrites fields in settings.json (each
|
|
time the service starts). String values must be quoted, integer and
|
|
boolean values must not.
|
|
|
|
See https://trac.transmissionbt.com/wiki/EditConfigFiles for documentation
|
|
and/or look at ${settingsFile}."
|
|
'';
|
|
};
|
|
|
|
rpc_port = mkOption {
|
|
type = types.uniq types.int;
|
|
default = 9091;
|
|
description = "TCP port number to run the RPC/web interface.";
|
|
};
|
|
|
|
apparmor = mkOption {
|
|
type = types.uniq types.bool;
|
|
default = true;
|
|
description = "Generate apparmor profile for transmission-daemon.";
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
### implementation
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
systemd.services.transmission = {
|
|
description = "Transmission BitTorrent Daemon";
|
|
after = [ "network.target" ] ++ optional (config.security.apparmor.enable && cfg.apparmor) "apparmor.service";
|
|
requires = mkIf (config.security.apparmor.enable && cfg.apparmor) [ "apparmor.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
# 1) Only the "transmission" user and group have access to torrents.
|
|
# 2) Optionally update/force specific fields into the configuration file.
|
|
serviceConfig.ExecStartPre =
|
|
if cfg.settings != {} then ''
|
|
${pkgs.stdenv.shell} -c "chmod 770 ${homeDir} && mkdir -p ${settingsDir} && ${pkgs.transmission}/bin/transmission-daemon -d |& sed ${attrsToSedArgs cfg.settings} > ${settingsFile}.tmp && mv ${settingsFile}.tmp ${settingsFile}"
|
|
''
|
|
else ''
|
|
${pkgs.stdenv.shell} -c "chmod 770 ${homeDir}"
|
|
'';
|
|
serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.rpc_port}";
|
|
serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
|
serviceConfig.User = "transmission";
|
|
# NOTE: transmission has an internal umask that also must be set (in settings.json)
|
|
serviceConfig.UMask = "0002";
|
|
};
|
|
|
|
# It's useful to have transmission in path, e.g. for remote control
|
|
environment.systemPackages = [ pkgs.transmission ];
|
|
|
|
users.extraUsers.transmission = {
|
|
group = "transmission";
|
|
uid = config.ids.uids.transmission;
|
|
description = "Transmission BitTorrent user";
|
|
home = homeDir;
|
|
createHome = true;
|
|
};
|
|
|
|
users.extraGroups.transmission.gid = config.ids.gids.transmission;
|
|
|
|
# AppArmor profile
|
|
security.apparmor.profiles = mkIf (config.security.apparmor.enable && cfg.apparmor) [
|
|
(pkgs.writeText "apparmor-transmission-daemon" ''
|
|
#include <tunables/global>
|
|
|
|
${pkgs.transmission}/bin/transmission-daemon {
|
|
#include <abstractions/base>
|
|
#include <abstractions/nameservice>
|
|
|
|
${pkgs.glibc}/lib/*.so mr,
|
|
${pkgs.libevent}/lib/libevent*.so* mr,
|
|
${pkgs.curl}/lib/libcurl*.so* mr,
|
|
${pkgs.openssl}/lib/libssl*.so* mr,
|
|
${pkgs.openssl}/lib/libcrypto*.so* mr,
|
|
${pkgs.zlib}/lib/libz*.so* mr,
|
|
${pkgs.libssh2}/lib/libssh2*.so* mr,
|
|
|
|
@{PROC}/sys/kernel/random/uuid r,
|
|
@{PROC}/sys/vm/overcommit_memory r,
|
|
|
|
${pkgs.transmission}/share/transmission/** r,
|
|
|
|
owner ${settingsDir}/** rw,
|
|
|
|
${cfg.settings.download-dir}/** rw,
|
|
${optionalString cfg.settings.incomplete-dir-enabled ''
|
|
${cfg.settings.incomplete-dir}/** rw,
|
|
''}
|
|
}
|
|
'')
|
|
];
|
|
};
|
|
|
|
}
|