forked from mirrors/nixpkgs
transmission: apply RFC0042 and harden the service
This commit is contained in:
parent
f7f1f727c6
commit
2a49db6a89
|
@ -680,6 +680,37 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
|
|||
was removed, as udev gained native support to handle FIDO security tokens.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <literal>services.transmission</literal> module
|
||||
was enhanced with the new options:
|
||||
<xref linkend="opt-services.transmission.credentialsFile"/>,
|
||||
<xref linkend="opt-services.transmission.openFirewall"/>,
|
||||
and <xref linkend="opt-services.transmission.performanceNetParameters"/>.
|
||||
</para>
|
||||
<para>
|
||||
<literal>transmission-daemon</literal> is now started with additional systemd sandbox/hardening options for better security.
|
||||
Please <link xlink:href="https://github.com/NixOS/nixpkgs/issues">report</link>
|
||||
any use case where this is not working well.
|
||||
In particular, the <literal>RootDirectory</literal> option newly set
|
||||
forbids uploading or downloading a torrent outside of the default directory
|
||||
configured at <link linkend="opt-services.transmission.settings">settings.download-dir</link>.
|
||||
If you really need Transmission to access other directories,
|
||||
you must include those directories into the <literal>BindPaths</literal> of the service:
|
||||
<programlisting>
|
||||
systemd.services.transmission.serviceConfig.BindPaths = [ "/path/to/alternative/download-dir" ];
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Also, connection to the RPC (Remote Procedure Call) of <literal>transmission-daemon</literal>
|
||||
is now only available on the local network interface by default.
|
||||
Use:
|
||||
<programlisting>
|
||||
services.transmission.settings.rpc-bind-address = "0.0.0.0";
|
||||
</programlisting>
|
||||
to get the previous behavior of listening on all network interfaces.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
With this release <literal>systemd-networkd</literal> (when enabled through <xref linkend="opt-networking.useNetworkd"/>)
|
||||
|
|
|
@ -1,52 +1,51 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, options, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.transmission;
|
||||
inherit (config.environment) etc;
|
||||
apparmor = config.security.apparmor.enable;
|
||||
|
||||
homeDir = cfg.home;
|
||||
downloadDirPermissions = cfg.downloadDirPermissions;
|
||||
downloadDir = "${homeDir}/Downloads";
|
||||
incompleteDir = "${homeDir}/.incomplete";
|
||||
|
||||
settingsDir = "${homeDir}/config";
|
||||
settingsFile = pkgs.writeText "settings.json" (builtins.toJSON fullSettings);
|
||||
|
||||
# for users in group "transmission" to have access to torrents
|
||||
fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings;
|
||||
|
||||
preStart = pkgs.writeScript "transmission-pre-start" ''
|
||||
#!${pkgs.runtimeShell}
|
||||
set -ex
|
||||
cp -f ${settingsFile} ${settingsDir}/settings.json
|
||||
'';
|
||||
rootDir = "/run/transmission";
|
||||
homeDir = "/var/lib/transmission";
|
||||
settingsDir = ".config/transmission-daemon";
|
||||
downloadsDir = "Downloads";
|
||||
incompleteDir = ".incomplete";
|
||||
# TODO: switch to configGen.json once RFC0042 is implemented
|
||||
settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.transmission = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether or not to enable the headless Transmission BitTorrent daemon.
|
||||
enable = mkEnableOption ''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).
|
||||
Transmission daemon can be controlled via the RPC interface using
|
||||
transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
|
||||
or other clients like stig or tremc.
|
||||
|
||||
Torrents are downloaded to ${downloadDir} by default and are
|
||||
accessible to users in the "transmission" group.
|
||||
'';
|
||||
};
|
||||
Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
|
||||
accessible to users in the "transmission" group'';
|
||||
|
||||
settings = mkOption {
|
||||
settings = mkOption rec {
|
||||
# TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
|
||||
type = types.attrs;
|
||||
apply = recursiveUpdate default;
|
||||
default =
|
||||
{
|
||||
download-dir = downloadDir;
|
||||
incomplete-dir = incompleteDir;
|
||||
download-dir = "${cfg.home}/${downloadsDir}";
|
||||
incomplete-dir = "${cfg.home}/${incompleteDir}";
|
||||
incomplete-dir-enabled = true;
|
||||
message-level = 1;
|
||||
peer-port = 51413;
|
||||
peer-port-random-high = 65535;
|
||||
peer-port-random-low = 49152;
|
||||
peer-port-random-on-start = false;
|
||||
rpc-bind-address = "127.0.0.1";
|
||||
rpc-port = 9091;
|
||||
script-torrent-done-enabled = false;
|
||||
script-torrent-done-filename = "";
|
||||
umask = 2; # 0o002 in decimal as expected by Transmission
|
||||
utp-enabled = true;
|
||||
};
|
||||
example =
|
||||
{
|
||||
|
@ -56,11 +55,12 @@ in
|
|||
rpc-whitelist = "127.0.0.1,192.168.*.*";
|
||||
};
|
||||
description = ''
|
||||
Attribute set whos fields overwrites fields in settings.json (each
|
||||
time the service starts). String values must be quoted, integer and
|
||||
Attribute set whose fields overwrites fields in
|
||||
<literal>.config/transmission-daemon/settings.json</literal>
|
||||
(each time the service starts). String values must be quoted, integer and
|
||||
boolean values must not.
|
||||
|
||||
See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
|
||||
See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
|
||||
for documentation.
|
||||
'';
|
||||
};
|
||||
|
@ -70,22 +70,32 @@ in
|
|||
default = "770";
|
||||
example = "775";
|
||||
description = ''
|
||||
The permissions to set for download-dir and incomplete-dir.
|
||||
They will be applied on every service start.
|
||||
The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
|
||||
on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link>
|
||||
and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>.
|
||||
Note that you may also want to change
|
||||
<link linkend="opt-services.transmission.settings">settings.umask</link>.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 9091;
|
||||
description = "TCP port number to run the RPC/web interface.";
|
||||
type = types.port;
|
||||
description = ''
|
||||
TCP port number to run the RPC/web interface.
|
||||
|
||||
If instead you want to change the peer port,
|
||||
use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
|
||||
or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
|
||||
'';
|
||||
};
|
||||
|
||||
home = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/transmission";
|
||||
default = homeDir;
|
||||
description = ''
|
||||
The directory where transmission will create files.
|
||||
The directory where Transmission will create <literal>${settingsDir}</literal>.
|
||||
as well as <literal>${downloadsDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed,
|
||||
and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -100,32 +110,174 @@ in
|
|||
default = "transmission";
|
||||
description = "Group account under which Transmission runs.";
|
||||
};
|
||||
|
||||
credentialsFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to a JSON file to be merged with the settings.
|
||||
Useful to merge a file which is better kept out of the Nix store
|
||||
because it contains sensible data like <link linkend="opt-services.transmission.settings">settings.rpc-password</link>.
|
||||
'';
|
||||
default = "/dev/null";
|
||||
example = "/var/lib/secrets/transmission/settings.json";
|
||||
};
|
||||
|
||||
openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
|
||||
|
||||
performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
|
||||
to open many more connections at the same time.
|
||||
|
||||
Note that you may also want to increase
|
||||
<link linkend="opt-services.transmission.settings">settings.peer-limit-global</link>.
|
||||
And be aware that these settings are quite aggressive
|
||||
and might not suite your regular desktop use.
|
||||
For instance, SSH sessions may time out more easily'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${homeDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
|
||||
"d '${settingsDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
|
||||
"d '${fullSettings.download-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
|
||||
"d '${fullSettings.incomplete-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
|
||||
# Note that using systemd.tmpfiles would not work here
|
||||
# because it would fail when creating a directory
|
||||
# with a different owner than its parent directory, by saying:
|
||||
# Detected unsafe path transition /home/foo → /home/foo/Downloads during canonicalization of /home/foo/Downloads
|
||||
# when /home/foo is not owned by cfg.user.
|
||||
# Note also that using an ExecStartPre= wouldn't work either
|
||||
# because BindPaths= needs these directories before.
|
||||
system.activationScripts.transmission-daemon = ''
|
||||
install -d -m 700 '${cfg.home}/${settingsDir}'
|
||||
chown -R '${cfg.user}:${cfg.group}' ${cfg.home}/${settingsDir}
|
||||
install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
|
||||
'' + optionalString cfg.settings.incomplete-dir-enabled ''
|
||||
install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
|
||||
'';
|
||||
|
||||
assertions = [
|
||||
{ assertion = builtins.match "^/.*" cfg.home != null;
|
||||
message = "`services.transmission.home' must be an absolute path.";
|
||||
}
|
||||
{ assertion = types.path.check cfg.settings.download-dir;
|
||||
message = "`services.transmission.settings.download-dir' must be an absolute path.";
|
||||
}
|
||||
{ assertion = types.path.check cfg.settings.incomplete-dir;
|
||||
message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
|
||||
}
|
||||
{ assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
|
||||
message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
|
||||
}
|
||||
{ assertion = types.port.check cfg.settings.rpc-port;
|
||||
message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
|
||||
}
|
||||
# In case both port and settings.rpc-port are explicitely defined: they must be the same.
|
||||
{ assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
|
||||
message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
|
||||
}
|
||||
];
|
||||
|
||||
services.transmission.settings =
|
||||
optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
|
||||
|
||||
systemd.services.transmission = {
|
||||
description = "Transmission BitTorrent Service";
|
||||
after = [ "network.target" ] ++ optional apparmor "apparmor.service";
|
||||
requires = mkIf apparmor [ "apparmor.service" ];
|
||||
requires = optional apparmor "apparmor.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
|
||||
|
||||
# 1) Only the "transmission" user and group have access to torrents.
|
||||
# 2) Optionally update/force specific fields into the configuration file.
|
||||
serviceConfig.ExecStartPre = preStart;
|
||||
serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port} --config-dir ${settingsDir}";
|
||||
serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
serviceConfig.User = cfg.user;
|
||||
serviceConfig.Group = cfg.group;
|
||||
# NOTE: transmission has an internal umask that also must be set (in settings.json)
|
||||
serviceConfig.UMask = "0002";
|
||||
serviceConfig = {
|
||||
# Use "+" because credentialsFile may not be accessible to User= or Group=.
|
||||
ExecStartPre = [("+" + pkgs.writeShellScript "transmission-prestart" ''
|
||||
set -eu${lib.optionalString (cfg.settings.message-level >= 3) "x"}
|
||||
${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
|
||||
install -D -m 600 -o '${cfg.user}' -g '${cfg.group}' /dev/stdin \
|
||||
'${cfg.home}/${settingsDir}/settings.json'
|
||||
'')];
|
||||
ExecStart="${pkgs.transmission}/bin/transmission-daemon -f";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
# Create rootDir in the host's mount namespace.
|
||||
RuntimeDirectory = [(baseNameOf rootDir)];
|
||||
RuntimeDirectoryMode = "755";
|
||||
# Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace.
|
||||
InaccessiblePaths = ["-+${rootDir}"];
|
||||
# This is for BindPaths= and BindReadOnlyPaths=
|
||||
# to allow traversal of directories they create in RootDirectory=.
|
||||
UMask = "0066";
|
||||
# Using RootDirectory= makes it possible
|
||||
# to use the same paths download-dir/incomplete-dir
|
||||
# (which appear in user's interfaces) without requiring cfg.user
|
||||
# to have access to their parent directories,
|
||||
# by using BindPaths=/BindReadOnlyPaths=.
|
||||
# Note that TemporaryFileSystem= could have been used instead
|
||||
# but not without adding some BindPaths=/BindReadOnlyPaths=
|
||||
# that would only be needed for ExecStartPre=,
|
||||
# because RootDirectoryStartOnly=true would not help.
|
||||
RootDirectory = rootDir;
|
||||
RootDirectoryStartOnly = true;
|
||||
MountAPIVFS = true;
|
||||
BindPaths =
|
||||
[ "${cfg.home}/${settingsDir}"
|
||||
cfg.settings.download-dir
|
||||
] ++
|
||||
optional cfg.settings.incomplete-dir-enabled
|
||||
cfg.settings.incomplete-dir;
|
||||
BindReadOnlyPaths = [
|
||||
# No confinement done of /nix/store here like in systemd-confinement.nix,
|
||||
# an AppArmor profile is provided to get a confinement based upon paths and rights.
|
||||
builtins.storeDir
|
||||
"-/etc/hosts"
|
||||
"-/etc/ld-nix.so.preload"
|
||||
"-/etc/localtime"
|
||||
] ++
|
||||
optional (cfg.settings.script-torrent-done-enabled &&
|
||||
cfg.settings.script-torrent-done-filename != "")
|
||||
cfg.settings.script-torrent-done-filename;
|
||||
# The following options are only for optimizing:
|
||||
# systemd-analyze security transmission
|
||||
AmbientCapabilities = "";
|
||||
CapabilityBoundingSet = "";
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = mkDefault false;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
# ProtectHome=true would not allow BindPaths= to work accross /home,
|
||||
# and ProtectHome=tmpfs would break statfs(),
|
||||
# preventing transmission-daemon to report the available free space.
|
||||
# However, RootDirectory= is used, so this is not a security concern
|
||||
# since there would be nothing in /home but any BindPaths= wanted by the user.
|
||||
ProtectHome = "read-only";
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
# AF_UNIX may become usable one day:
|
||||
# https://github.com/transmission/transmission/issues/441
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
# Groups in @system-service which do not contain a syscall
|
||||
# listed by perf stat -e 'syscalls:sys_enter_*' transmission-daemon -f
|
||||
# in tests, and seem likely not necessary for transmission-daemon.
|
||||
"~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
|
||||
# In the @privileged group, but reached when querying infos through RPC (eg. with stig).
|
||||
"quotactl"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
};
|
||||
};
|
||||
|
||||
# It's useful to have transmission in path, e.g. for remote control
|
||||
|
@ -133,70 +285,153 @@ in
|
|||
|
||||
users.users = optionalAttrs (cfg.user == "transmission") ({
|
||||
transmission = {
|
||||
name = "transmission";
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.transmission;
|
||||
description = "Transmission BitTorrent user";
|
||||
home = homeDir;
|
||||
createHome = true;
|
||||
home = cfg.home;
|
||||
};
|
||||
});
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "transmission") ({
|
||||
transmission = {
|
||||
name = "transmission";
|
||||
gid = config.ids.gids.transmission;
|
||||
};
|
||||
});
|
||||
|
||||
# AppArmor profile
|
||||
networking.firewall = mkIf cfg.openFirewall (
|
||||
if cfg.settings.peer-port-random-on-start
|
||||
then
|
||||
{ allowedTCPPortRanges =
|
||||
[ { from = cfg.settings.peer-port-random-low;
|
||||
to = cfg.settings.peer-port-random-high;
|
||||
}
|
||||
];
|
||||
allowedUDPPortRanges =
|
||||
[ { from = cfg.settings.peer-port-random-low;
|
||||
to = cfg.settings.peer-port-random-high;
|
||||
}
|
||||
];
|
||||
}
|
||||
else
|
||||
{ allowedTCPPorts = [ cfg.settings.peer-port ];
|
||||
allowedUDPPorts = [ cfg.settings.peer-port ];
|
||||
}
|
||||
);
|
||||
|
||||
boot.kernel.sysctl = mkMerge [
|
||||
# Transmission uses a single UDP socket in order to implement multiple uTP sockets,
|
||||
# and thus expects large kernel buffers for the UDP socket,
|
||||
# https://trac.transmissionbt.com/browser/trunk/libtransmission/tr-udp.c?rev=11956.
|
||||
# at least up to the values hardcoded here:
|
||||
(mkIf cfg.settings.utp-enabled {
|
||||
"net.core.rmem_max" = mkDefault "4194304"; # 4MB
|
||||
"net.core.wmem_max" = mkDefault "1048576"; # 1MB
|
||||
})
|
||||
(mkIf cfg.performanceNetParameters {
|
||||
# Increase the number of available source (local) TCP and UDP ports to 49151.
|
||||
# Usual default is 32768 60999, ie. 28231 ports.
|
||||
# Find out your current usage with: ss -s
|
||||
"net.ipv4.ip_local_port_range" = "16384 65535";
|
||||
# Timeout faster generic TCP states.
|
||||
# Usual default is 600.
|
||||
# Find out your current usage with: watch -n 1 netstat -nptuo
|
||||
"net.netfilter.nf_conntrack_generic_timeout" = 60;
|
||||
# Timeout faster established but inactive connections.
|
||||
# Usual default is 432000.
|
||||
"net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
|
||||
# Clear immediately TCP states after timeout.
|
||||
# Usual default is 120.
|
||||
"net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
|
||||
# Increase the number of trackable connections.
|
||||
# Usual default is 262144.
|
||||
# Find out your current usage with: conntrack -C
|
||||
"net.netfilter.nf_conntrack_max" = 1048576;
|
||||
})
|
||||
];
|
||||
|
||||
security.apparmor.profiles = mkIf apparmor [
|
||||
(pkgs.writeText "apparmor-transmission-daemon" ''
|
||||
#include <tunables/global>
|
||||
include <tunables/global>
|
||||
|
||||
${pkgs.transmission}/bin/transmission-daemon {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
include <abstractions/base>
|
||||
include <abstractions/nameservice>
|
||||
|
||||
${getLib pkgs.glibc}/lib/*.so mr,
|
||||
${getLib pkgs.libevent}/lib/libevent*.so* mr,
|
||||
${getLib pkgs.curl}/lib/libcurl*.so* mr,
|
||||
${getLib pkgs.openssl}/lib/libssl*.so* mr,
|
||||
${getLib pkgs.openssl}/lib/libcrypto*.so* mr,
|
||||
${getLib pkgs.zlib}/lib/libz*.so* mr,
|
||||
${getLib pkgs.libssh2}/lib/libssh2*.so* mr,
|
||||
${getLib pkgs.systemd}/lib/libsystemd*.so* mr,
|
||||
${getLib pkgs.xz}/lib/liblzma*.so* mr,
|
||||
${getLib pkgs.libgcrypt}/lib/libgcrypt*.so* mr,
|
||||
${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
|
||||
${getLib pkgs.nghttp2}/lib/libnghttp2*.so* mr,
|
||||
${getLib pkgs.c-ares}/lib/libcares*.so* mr,
|
||||
${getLib pkgs.libcap}/lib/libcap*.so* mr,
|
||||
${getLib pkgs.attr}/lib/libattr*.so* mr,
|
||||
${getLib pkgs.lz4}/lib/liblz4*.so* mr,
|
||||
${getLib pkgs.libkrb5}/lib/lib*.so* mr,
|
||||
${getLib pkgs.keyutils}/lib/libkeyutils*.so* mr,
|
||||
${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
|
||||
${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
|
||||
${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
|
||||
${getLib pkgs.gcc.cc.lib}/lib/libstdc++.so.* mr,
|
||||
${getLib pkgs.gcc.cc.lib}/lib/libgcc_s.so.* mr,
|
||||
|
||||
@{PROC}/sys/kernel/random/uuid r,
|
||||
@{PROC}/sys/vm/overcommit_memory r,
|
||||
|
||||
${pkgs.openssl.out}/etc/** r,
|
||||
${pkgs.transmission}/share/transmission/** r,
|
||||
|
||||
owner ${settingsDir}/** rw,
|
||||
|
||||
${fullSettings.download-dir}/** rw,
|
||||
${optionalString fullSettings.incomplete-dir-enabled ''
|
||||
${fullSettings.incomplete-dir}/** rw,
|
||||
# NOTE: https://github.com/NixOS/nixpkgs/pull/93457
|
||||
# will remove the need for these by fixing <abstractions/base>
|
||||
r ${etc."hosts".source},
|
||||
r /etc/ld-nix.so.preload,
|
||||
${lib.optionalString (builtins.hasAttr "ld-nix.so.preload" etc) ''
|
||||
r ${etc."ld-nix.so.preload".source},
|
||||
${concatMapStrings (p: optionalString (p != "") ("mr ${p},\n"))
|
||||
(splitString "\n" config.environment.etc."ld-nix.so.preload".text)}
|
||||
''}
|
||||
r ${etc."ssl/certs/ca-certificates.crt".source},
|
||||
r ${pkgs.tzdata}/share/zoneinfo/**,
|
||||
r ${pkgs.stdenv.cc.libc}/share/i18n/**,
|
||||
r ${pkgs.stdenv.cc.libc}/share/locale/**,
|
||||
|
||||
mr ${getLib pkgs.stdenv.cc.cc}/lib/*.so*,
|
||||
mr ${getLib pkgs.stdenv.cc.libc}/lib/*.so*,
|
||||
mr ${getLib pkgs.attr}/lib/libattr*.so*,
|
||||
mr ${getLib pkgs.c-ares}/lib/libcares*.so*,
|
||||
mr ${getLib pkgs.curl}/lib/libcurl*.so*,
|
||||
mr ${getLib pkgs.keyutils}/lib/libkeyutils*.so*,
|
||||
mr ${getLib pkgs.libcap}/lib/libcap*.so*,
|
||||
mr ${getLib pkgs.libevent}/lib/libevent*.so*,
|
||||
mr ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*,
|
||||
mr ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so*,
|
||||
mr ${getLib pkgs.libkrb5}/lib/lib*.so*,
|
||||
mr ${getLib pkgs.libssh2}/lib/libssh2*.so*,
|
||||
mr ${getLib pkgs.lz4}/lib/liblz4*.so*,
|
||||
mr ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*,
|
||||
mr ${getLib pkgs.openssl}/lib/libcrypto*.so*,
|
||||
mr ${getLib pkgs.openssl}/lib/libssl*.so*,
|
||||
mr ${getLib pkgs.systemd}/lib/libsystemd*.so*,
|
||||
mr ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so*,
|
||||
mr ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so*,
|
||||
mr ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so*,
|
||||
mr ${getLib pkgs.xz}/lib/liblzma*.so*,
|
||||
mr ${getLib pkgs.zlib}/lib/libz*.so*,
|
||||
|
||||
r @{PROC}/sys/kernel/random/uuid,
|
||||
r @{PROC}/sys/vm/overcommit_memory,
|
||||
# @{pid} is not a kernel variable yet but a regexp
|
||||
#r @{PROC}/@{pid}/environ,
|
||||
r @{PROC}/@{pid}/mounts,
|
||||
rwk /tmp/tr_session_id_*,
|
||||
|
||||
r ${pkgs.openssl.out}/etc/**,
|
||||
r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
|
||||
r ${pkgs.transmission}/share/transmission/**,
|
||||
|
||||
owner rw ${cfg.home}/${settingsDir}/**,
|
||||
rw ${cfg.settings.download-dir}/**,
|
||||
${optionalString cfg.settings.incomplete-dir-enabled ''
|
||||
rw ${cfg.settings.incomplete-dir}/**,
|
||||
''}
|
||||
profile dirs {
|
||||
rw ${cfg.settings.download-dir}/**,
|
||||
${optionalString cfg.settings.incomplete-dir-enabled ''
|
||||
rw ${cfg.settings.incomplete-dir}/**,
|
||||
''}
|
||||
}
|
||||
|
||||
${optionalString (cfg.settings.script-torrent-done-enabled &&
|
||||
cfg.settings.script-torrent-done-filename != "") ''
|
||||
# Stack transmission_directories profile on top of
|
||||
# any existing profile for script-torrent-done-filename
|
||||
# FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
|
||||
# https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
|
||||
px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
|
||||
''}
|
||||
|
||||
# FIXME: enable customizing using https://github.com/NixOS/nixpkgs/pull/93457
|
||||
# include <local/transmission-daemon>
|
||||
}
|
||||
'')
|
||||
];
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ julm ];
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ let
|
|||
externalClient2Address = "80.100.100.2";
|
||||
externalTrackerAddress = "80.100.100.3";
|
||||
|
||||
download-dir = "/var/lib/transmission/Downloads";
|
||||
transmissionConfig = { ... }: {
|
||||
environment.systemPackages = [ pkgs.transmission ];
|
||||
services.transmission = {
|
||||
|
@ -26,6 +27,7 @@ let
|
|||
settings = {
|
||||
dht-enabled = false;
|
||||
message-level = 3;
|
||||
inherit download-dir;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -117,12 +119,12 @@ in
|
|||
router.wait_for_unit("miniupnpd")
|
||||
|
||||
# Create the torrent.
|
||||
tracker.succeed("mkdir /tmp/data")
|
||||
tracker.succeed("mkdir ${download-dir}/data")
|
||||
tracker.succeed(
|
||||
"cp ${file} /tmp/data/test.tar.bz2"
|
||||
"cp ${file} ${download-dir}/data/test.tar.bz2"
|
||||
)
|
||||
tracker.succeed(
|
||||
"transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
|
||||
"transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
|
||||
)
|
||||
tracker.succeed("chmod 644 /tmp/test.torrent")
|
||||
|
||||
|
@ -133,18 +135,16 @@ in
|
|||
|
||||
# Start the initial seeder.
|
||||
tracker.succeed(
|
||||
"transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data"
|
||||
"transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
|
||||
)
|
||||
|
||||
# Now we should be able to download from the client behind the NAT.
|
||||
tracker.wait_for_unit("httpd")
|
||||
client1.wait_for_unit("network-online.target")
|
||||
client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
|
||||
client1.wait_for_file("${download-dir}/test.tar.bz2")
|
||||
client1.succeed(
|
||||
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &"
|
||||
)
|
||||
client1.wait_for_file("/tmp/test.tar.bz2")
|
||||
client1.succeed(
|
||||
"cmp /tmp/test.tar.bz2 ${file}"
|
||||
"cmp ${download-dir}/test.tar.bz2 ${file}"
|
||||
)
|
||||
|
||||
# Bring down the initial seeder.
|
||||
|
@ -154,11 +154,11 @@ in
|
|||
# the first client created a NAT hole in the router.
|
||||
client2.wait_for_unit("network-online.target")
|
||||
client2.succeed(
|
||||
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &"
|
||||
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
|
||||
)
|
||||
client2.wait_for_file("/tmp/test.tar.bz2")
|
||||
client2.wait_for_file("${download-dir}/test.tar.bz2")
|
||||
client2.succeed(
|
||||
"cmp /tmp/test.tar.bz2 ${file}"
|
||||
"cmp ${download-dir}/test.tar.bz2 ${file}"
|
||||
)
|
||||
'';
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue