summary refs log tree commit diff
diff options
context:
space:
mode:
authorSimon South <simon@simonsouth.net>2020-12-05 10:27:55 -0500
committer宋文武 <iyzsong@member.fsf.org>2021-02-12 15:11:36 +0800
commitdb6b9d2f4bc59511904e8c1412d0257675c46095 (patch)
treebb14d07667f3ae3065cb48b30a64ce8c5059ed44
parent8458d8db70885b350285e8b1490734349135e51e (diff)
downloadguix-db6b9d2f4bc59511904e8c1412d0257675c46095.tar.gz
services: Add transmission-daemon service.
* gnu/services/file-sharing.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* po/packages/POTFILES.in: Add it.
* tests/services/file-sharing.scm: New file.
* Makefile.am (SCM_TESTS): Add it.
* doc/guix.texi (File-Sharing Services): New section.

Signed-off-by: 宋文武 <iyzsong@member.fsf.org>
-rw-r--r--Makefile.am1
-rw-r--r--doc/guix.texi799
-rw-r--r--gnu/local.mk1
-rw-r--r--gnu/services/file-sharing.scm804
-rw-r--r--po/packages/POTFILES.in1
-rw-r--r--tests/services/file-sharing.scm59
6 files changed, 1665 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 798808bde6..52537fb53d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -475,6 +475,7 @@ SCM_TESTS =					\
   tests/scripts.scm				\
   tests/search-paths.scm			\
   tests/services.scm				\
+  tests/services/file-sharing.scm		\
   tests/services/linux.scm			\
   tests/sets.scm				\
   tests/size.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index 8944f5129d..aba8a6b575 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -14716,6 +14716,7 @@ declaration.
 * Mail Services::               IMAP, POP3, SMTP, and all that.
 * Messaging Services::          Messaging services.
 * Telephony Services::          Telephony services.
+* File-Sharing Services::       File-sharing services.
 * Monitoring Services::         Monitoring services.
 * Kerberos Services::           Kerberos services.
 * LDAP Services::               LDAP services.
@@ -22287,6 +22288,804 @@ If it is set your server will be linked by this host name instead.
 
 
 
+@node File-Sharing Services
+@subsection File-Sharing Services
+
+The @code{(gnu services file-sharing)} module provides services that
+assist with transferring files over peer-to-peer file-sharing networks.
+
+@subsubheading Transmission Daemon Service
+
+@uref{https://transmissionbt.com/, Transmission} is a flexible
+BitTorrent client that offers a variety of graphical and command-line
+interfaces.  A @code{transmission-daemon-service-type} service provides
+Transmission's headless variant, @command{transmission-daemon}, as a
+system service, allowing users to share files via BitTorrent even when
+they are not logged in.
+
+@deffn {Scheme Variable} transmission-daemon-service-type
+The service type for the Transmission Daemon BitTorrent client. Its
+value must be a @code{transmission-daemon-configuration} object as in
+this example:
+
+@lisp
+(service transmission-daemon-service-type
+         (transmission-daemon-configuration
+          ;; Restrict access to the RPC ("control") interface
+          (rpc-authentication-required? #t)
+          (rpc-username "transmission")
+          (rpc-password
+           (transmission-password-hash
+            "transmission" ; desired password
+            "uKd1uMs9"))   ; arbitrary salt value
+
+          ;; Accept requests from this and other hosts on the
+          ;; local network
+          (rpc-whitelist-enabled? #t)
+          (rpc-whitelist '("::1" "127.0.0.1" "192.168.0.*"))
+
+          ;; Limit bandwidth use during work hours
+          (alt-speed-down (* 1024 2)) ;   2 MB/s
+          (alt-speed-up 512)          ; 512 kB/s
+
+          (alt-speed-time-enabled? #t)
+          (alt-speed-time-day 'weekdays)
+          (alt-speed-time-begin
+           (+ (* 60 8) 30))           ; 8:30 am
+          (alt-speed-time-end
+           (+ (* 60 (+ 12 5)) 30))))  ; 5:30 pm
+@end lisp
+@end deffn
+
+Once the service is started, users can interact with the daemon through
+its Web interface (at @code{http://localhost:9091/}) or by using the
+@command{transmission-remote} command-line tool, available in the
+@code{transmission} package.  (Emacs users may want to also consider the
+@code{emacs-transmission} package.)  Both communicate with the daemon
+through its remote procedure call (RPC) interface, which by default is
+available to all users on the system; you may wish to change this by
+assigning values to the @code{rpc-authentication-required?},
+@code{rpc-username} and @code{rpc-password} settings, as shown in the
+example above and documented further below.
+
+The value for @code{rpc-password} must be a password hash of the type
+generated and used by Transmission clients.  This can be copied verbatim
+from an existing @file{settings.json} file, if another Transmission
+client is already being used.  Otherwise, the
+@code{transmission-password-hash} and @code{transmission-random-salt}
+procedures provided by this module can be used to obtain a suitable hash
+value.
+
+@deffn {Scheme Procedure} transmission-password-hash @var{password} @var{salt}
+Returns a string containing the result of hashing @var{password}
+together with @var{salt}, in the format recognized by Transmission
+clients for their @code{rpc-password} configuration setting.
+
+@var{salt} must be an eight-character string.  The
+@code{transmission-random-salt} procedure can be used to generate a
+suitable salt value at random.
+@end deffn
+
+@deffn {Scheme Procedure} transmission-random-salt
+Returns a string containing a random, eight-character salt value of the
+type generated and used by Transmission clients, suitable for passing to
+the @code{transmission-password-hash} procedure.
+@end deffn
+
+These procedures are accessible from within a Guile REPL started with
+the @command{guix repl} command (@pxref {Invoking guix repl}).  This is
+useful for obtaining a random salt value to provide as the second
+parameter to `transmission-password-hash`, as in this example session:
+
+@example
+$ guix repl
+scheme@@(guix-user)> ,use (gnu services file-sharing)
+scheme@@(guix-user)> (transmission-random-salt)
+$1 = "uKd1uMs9"
+@end example
+
+Alternatively, a complete password hash can generated in a single step:
+
+@example
+scheme@@(guix-user)> (transmission-password-hash "transmission"
+(transmission-random-salt))
+$2 = "@{c8bbc6d1740cd8dc819a6e25563b67812c1c19c9VtFPfdsX"
+@end example
+
+The resulting string can be used as-is for the value of
+@code{rpc-password}, allowing the password to be kept hidden even in the
+operating-system configuration.
+
+Torrent files downloaded by the daemon are directly accessible only to
+users in the ``transmission'' user group, who receive read-only access
+to the directory specified by the @code{download-dir} configuration
+setting (and also the directory specified by @code{incomplete-dir}, if
+@code{incomplete-dir-enabled?} is @code{#t}).  Downloaded files can be
+moved to another directory or deleted altogether using
+@command{transmission-remote} with its @code{--move} and
+@code{--remove-and-delete} options.
+
+If the @code{watch-dir-enabled?} setting is set to @code{#t}, users in
+the ``transmission'' group are able also to place @file{.torrent} files
+in the directory specified by @code{watch-dir} to have the corresponding
+torrents added by the daemon.  (The @code{trash-original-torrent-files?}
+setting controls whether the daemon deletes these files after processing
+them.)
+
+Some of the daemon's configuration settings can be changed temporarily
+by @command{transmission-remote} and similar tools. To undo these
+changes, use the service's @code{reload} action to have the daemon
+reload its settings from disk:
+
+@example
+# herd reload transmission-daemon
+@end example
+
+The full set of available configuration settings is defined by the
+@code{transmission-daemon-configuration} data type.
+
+@deftp {Data Type} transmission-daemon-configuration
+The data type representing configuration settings for Transmission
+Daemon.  These correspond directly to the settings recognized by
+Transmission clients in their @file{settings.json} file.
+@end deftp
+
+@c The following documentation was initially generated by
+@c (generate-transmission-daemon-documentation) in (gnu services
+@c file-sharing).  Manually maintained documentation is better, so we
+@c shouldn't hesitate to edit below as needed.  However if the change
+@c you want to make to this documentation can be done in an automated
+@c way, it's probably easier to change (generate-documentation) than to
+@c make it below and have to deal with the churn as Transmission Daemon
+@c updates.
+
+@c %start of fragment
+
+Available @code{transmission-daemon-configuration} fields are:
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} package transmission
+The Transmission package to use.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer stop-wait-period
+The period, in seconds, to wait when stopping the service for
+@command{transmission-daemon} to exit before killing its process.  This
+allows the daemon time to complete its housekeeping and send a final
+update to trackers as it shuts down.  On slow hosts, or hosts with a
+slow network connection, this value may need to be increased.
+
+Defaults to @samp{10}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string download-dir
+The directory to which torrent files are downloaded.
+
+Defaults to @samp{"/var/lib/transmission-daemon/downloads"}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean incomplete-dir-enabled?
+If @code{#t}, files will be held in @code{incomplete-dir} while their
+torrent is being downloaded, then moved to @code{download-dir} once the
+torrent is complete.  Otherwise, files for all torrents (including those
+still being downloaded) will be placed in @code{download-dir}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-string incomplete-dir
+The directory in which files from incompletely downloaded torrents will
+be held when @code{incomplete-dir-enabled?} is @code{#t}.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} umask umask
+The file mode creation mask used for downloaded files.  (See the
+@command{umask} man page for more information.)
+
+Defaults to @samp{18}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean rename-partial-files?
+When @code{#t}, ``.part'' is appended to the name of partially
+downloaded files.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} preallocation-mode preallocation
+The mode by which space should be preallocated for downloaded files, one
+of @code{none}, @code{fast} (or @code{sparse}) and @code{full}.
+Specifying @code{full} will minimize disk fragmentation at a cost to
+file-creation speed.
+
+Defaults to @samp{fast}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean watch-dir-enabled?
+If @code{#t}, the directory specified by @code{watch-dir} will be
+watched for new @file{.torrent} files and the torrents they describe
+added automatically (and the original files removed, if
+@code{trash-original-torrent-files?} is @code{#t}).
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-string watch-dir
+The directory to be watched for @file{.torrent} files indicating new
+torrents to be added, when @code{watch-dir-enabled} is @code{#t}.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean trash-original-torrent-files?
+When @code{#t}, @file{.torrent} files will be deleted from the watch
+directory once their torrent has been added (see
+@code{watch-directory-enabled?}).
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean speed-limit-down-enabled?
+When @code{#t}, the daemon's download speed will be limited to the rate
+specified by @code{speed-limit-down}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer speed-limit-down
+The default global-maximum download speed, in kilobytes per second.
+
+Defaults to @samp{100}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean speed-limit-up-enabled?
+When @code{#t}, the daemon's upload speed will be limited to the rate
+specified by @code{speed-limit-up}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer speed-limit-up
+The default global-maximum upload speed, in kilobytes per second.
+
+Defaults to @samp{100}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean alt-speed-enabled?
+When @code{#t}, the alternate speed limits @code{alt-speed-down} and
+@code{alt-speed-up} are used (in place of @code{speed-limit-down} and
+@code{speed-limit-up}, if they are enabled) to constrain the daemon's
+bandwidth usage.  This can be scheduled to occur automatically at
+certain times during the week; see @code{alt-speed-time-enabled?}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer alt-speed-down
+The alternate global-maximum download speed, in kilobytes per second.
+
+Defaults to @samp{50}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer alt-speed-up
+The alternate global-maximum upload speed, in kilobytes per second.
+
+Defaults to @samp{50}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean alt-speed-time-enabled?
+When @code{#t}, the alternate speed limits @code{alt-speed-down} and
+@code{alt-speed-up} will be enabled automatically during the periods
+specified by @code{alt-speed-time-day}, @code{alt-speed-time-begin} and
+@code{alt-time-speed-end}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} day-list alt-speed-time-day
+The days of the week on which the alternate-speed schedule should be
+used, specified either as a list of days (@code{sunday}, @code{monday},
+and so on) or using one of the symbols @code{weekdays}, @code{weekends}
+or @code{all}.
+
+Defaults to @samp{all}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer alt-speed-time-begin
+The time of day at which to enable the alternate speed limits, expressed
+as a number of minutes since midnight.
+
+Defaults to @samp{540}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer alt-speed-time-end
+The time of day at which to disable the alternate speed limits,
+expressed as a number of minutes since midnight.
+
+Defaults to @samp{1020}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string bind-address-ipv4
+The IP address at which to listen for peer connections, or ``0.0.0.0''
+to listen at all available IP addresses.
+
+Defaults to @samp{"0.0.0.0"}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string bind-address-ipv6
+The IPv6 address at which to listen for peer connections, or ``::'' to
+listen at all available IPv6 addresses.
+
+Defaults to @samp{"::"}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean peer-port-random-on-start?
+If @code{#t}, when the daemon starts it will select a port at random on
+which to listen for peer connections, from the range specified
+(inclusively) by @code{peer-port-random-low} and
+@code{peer-port-random-high}.  Otherwise, it listens on the port
+specified by @code{peer-port}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} port-number peer-port-random-low
+The lowest selectable port number when @code{peer-port-random-on-start?}
+is @code{#t}.
+
+Defaults to @samp{49152}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} port-number peer-port-random-high
+The highest selectable port number when @code{peer-port-random-on-start}
+is @code{#t}.
+
+Defaults to @samp{65535}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} port-number peer-port
+The port on which to listen for peer connections when
+@code{peer-port-random-on-start?} is @code{#f}.
+
+Defaults to @samp{51413}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean port-forwarding-enabled?
+If @code{#t}, the daemon will attempt to configure port-forwarding on an
+upstream gateway automatically using @acronym{UPnP} and
+@acronym{NAT-PMP}.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} encryption-mode encryption
+The encryption preference for peer connections, one of
+@code{prefer-unencrypted-connections},
+@code{prefer-encrypted-connections} or
+@code{require-encrypted-connections}.
+
+Defaults to @samp{prefer-encrypted-connections}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-string peer-congestion-algorithm
+The TCP congestion-control algorithm to use for peer connections,
+specified using a string recognized by the operating system in calls to
+@code{setsockopt} (or set to @code{disabled}, in which case the
+operating-system default is used).
+
+Note that on GNU/Linux systems, the kernel must be configured to allow
+processes to use a congestion-control algorithm not in the default set;
+otherwise, it will deny these requests with ``Operation not permitted''.
+To see which algorithms are available on your system and which are
+currently permitted for use, look at the contents of the files
+@file{tcp_available_congestion_control} and
+@file{tcp_allowed_congestion_control} in the @file{/proc/sys/net/ipv4}
+directory.
+
+As an example, to have Transmission Daemon use
+@uref{http://www-ece.rice.edu/networks/TCP-LP/,the TCP Low Priority
+congestion-control algorithm}, you'll need to modify your kernel
+configuration to build in support for the algorithm, then update your
+operating-system configuration to allow its use by adding a
+@code{sysctl-service-type} service (or updating the existing one's
+configuration) with lines like the following:
+
+@lisp
+(service sysctl-service-type
+         (sysctl-configuration
+          (settings
+           ("net.ipv4.tcp_allowed_congestion_control" .
+            "reno cubic lp"))))
+@end lisp
+
+The Transmission Daemon configuration can then be updated with
+
+@lisp
+(peer-congestion-algorithm "lp")
+@end lisp
+
+and the system reconfigured to have the changes take effect.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} tcp-type-of-service peer-socket-tos
+The type of service to request in outgoing @acronym{TCP} packets, one of
+@code{default}, @code{low-cost}, @code{throughput}, @code{low-delay} and
+@code{reliability}.
+
+Defaults to @samp{default}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer peer-limit-global
+The global limit on the number of connected peers.
+
+Defaults to @samp{200}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer peer-limit-per-torrent
+The per-torrent limit on the number of connected peers.
+
+Defaults to @samp{50}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer upload-slots-per-torrent
+The maximum number of peers to which the daemon will upload data
+simultaneously for each torrent.
+
+Defaults to @samp{14}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer peer-id-ttl-hours
+The maximum lifespan, in hours, of the peer ID associated with each
+public torrent before it is regenerated.
+
+Defaults to @samp{6}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean blocklist-enabled?
+When @code{#t}, the daemon will ignore peers mentioned in the blocklist
+it has most recently downloaded from @code{blocklist-url}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-string blocklist-url
+The URL of a peer blocklist (in @acronym{P2P}-plaintext or eMule
+@file{.dat} format) to be periodically downloaded and applied when
+@code{blocklist-enabled?} is @code{#t}.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean download-queue-enabled?
+If @code{#t}, the daemon will be limited to downloading at most
+@code{download-queue-size} non-stalled torrents simultaneously.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer download-queue-size
+The size of the daemon's download queue, which limits the number of
+non-stalled torrents it will download at any one time when
+@code{download-queue-enabled?} is @code{#t}.
+
+Defaults to @samp{5}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean seed-queue-enabled?
+If @code{#t}, the daemon will be limited to seeding at most
+@code{seed-queue-size} non-stalled torrents simultaneously.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer seed-queue-size
+The size of the daemon's seed queue, which limits the number of
+non-stalled torrents it will seed at any one time when
+@code{seed-queue-enabled?} is @code{#t}.
+
+Defaults to @samp{10}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean queue-stalled-enabled?
+When @code{#t}, the daemon will consider torrents for which it has not
+shared data in the past @code{queue-stalled-minutes} minutes to be
+stalled and not count them against its @code{download-queue-size} and
+@code{seed-queue-size} limits.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer queue-stalled-minutes
+The maximum period, in minutes, a torrent may be idle before it is
+considered to be stalled, when @code{queue-stalled-enabled?} is
+@code{#t}.
+
+Defaults to @samp{30}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean ratio-limit-enabled?
+When @code{#t}, a torrent being seeded will automatically be paused once
+it reaches the ratio specified by @code{ratio-limit}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-rational ratio-limit
+The ratio at which a torrent being seeded will be paused, when
+@code{ratio-limit-enabled?} is @code{#t}.
+
+Defaults to @samp{2.0}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean idle-seeding-limit-enabled?
+When @code{#t}, a torrent being seeded will automatically be paused once
+it has been idle for @code{idle-seeding-limit} minutes.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer idle-seeding-limit
+The maximum period, in minutes, a torrent being seeded may be idle
+before it is paused, when @code{idle-seeding-limit-enabled?} is
+@code{#t}.
+
+Defaults to @samp{30}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean dht-enabled?
+Enable @uref{http://bittorrent.org/beps/bep_0005.html,the distributed
+hash table (@acronym{DHT}) protocol}, which supports the use of
+trackerless torrents.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean lpd-enabled?
+Enable @uref{https://en.wikipedia.org/wiki/Local_Peer_Discovery,local
+peer discovery} (@acronym{LPD}), which allows the discovery of peers on
+the local network and may reduce the amount of data sent over the public
+Internet.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean pex-enabled?
+Enable @uref{https://en.wikipedia.org/wiki/Peer_exchange,peer exchange}
+(@acronym{PEX}), which reduces the daemon's reliance on external
+trackers and may improve its performance.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean utp-enabled?
+Enable @uref{http://bittorrent.org/beps/bep_0029.html,the micro
+transport protocol} (@acronym{uTP}), which aims to reduce the impact of
+BitTorrent traffic on other users of the local network while maintaining
+full utilization of the available bandwidth.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean rpc-enabled?
+If @code{#t}, enable the remote procedure call (@acronym{RPC})
+interface, which allows remote control of the daemon via its Web
+interface, the @command{transmission-remote} command-line client, and
+similar tools.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string rpc-bind-address
+The IP address at which to listen for @acronym{RPC} connections, or
+``0.0.0.0'' to listen at all available IP addresses.
+
+Defaults to @samp{"0.0.0.0"}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} port-number rpc-port
+The port on which to listen for @acronym{RPC} connections.
+
+Defaults to @samp{9091}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string rpc-url
+The path prefix to use in the @acronym{RPC}-endpoint @acronym{URL}.
+
+Defaults to @samp{"/transmission/"}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean rpc-authentication-required?
+When @code{#t}, clients must authenticate (see @code{rpc-username} and
+@code{rpc-password}) when using the @acronym{RPC} interface.  Note this
+has the side effect of disabling host-name whitelisting (see
+@code{rpc-host-whitelist-enabled?}.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-string rpc-username
+The username required by clients to access the @acronym{RPC} interface
+when @code{rpc-authentication-required?} is @code{#t}.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-transmission-password-hash rpc-password
+The password required by clients to access the @acronym{RPC} interface
+when @code{rpc-authentication-required?} is @code{#t}.  This must be
+specified using a password hash in the format recognized by Transmission
+clients, either copied from an existing @file{settings.json} file or
+generated using the @code{transmission-password-hash} procedure.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean rpc-whitelist-enabled?
+When @code{#t}, @acronym{RPC} requests will be accepted only when they
+originate from an address specified in @code{rpc-whitelist}.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string-list rpc-whitelist
+The list of IP and IPv6 addresses from which @acronym{RPC} requests will
+be accepted when @code{rpc-whitelist-enabled?} is @code{#t}.  Wildcards
+may be specified using @samp{*}.
+
+Defaults to @samp{("127.0.0.1" "::1")}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean rpc-host-whitelist-enabled?
+When @code{#t}, @acronym{RPC} requests will be accepted only when they
+are addressed to a host named in @code{rpc-host-whitelist}.  Note that
+requests to ``localhost'' or ``localhost.'', or to a numeric address,
+are always accepted regardless of these settings.
+
+Note also this functionality is disabled when
+@code{rpc-authentication-required?} is @code{#t}.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} string-list rpc-host-whitelist
+The list of host names recognized by the @acronym{RPC} server when
+@code{rpc-host-whitelist-enabled?} is @code{#t}.
+
+Defaults to @samp{()}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} message-level message-level
+The minimum severity level of messages to be logged (to
+@file{/var/log/transmission.log}) by the daemon, one of @code{none} (no
+logging), @code{error}, @code{info} and @code{debug}.
+
+Defaults to @samp{info}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean start-added-torrents?
+When @code{#t}, torrents are started as soon as they are added;
+otherwise, they are added in ``paused'' state.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean script-torrent-done-enabled?
+When @code{#t}, the script specified by
+@code{script-torrent-done-filename} will be invoked each time a torrent
+completes.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} maybe-file-object script-torrent-done-filename
+A file name or file-like object specifying a script to run each time a
+torrent completes, when @code{script-torrent-done-enabled?} is
+@code{#t}.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean scrape-paused-torrents-enabled?
+When @code{#t}, the daemon will scrape trackers for a torrent even when
+the torrent is paused.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} non-negative-integer cache-size-mb
+The amount of memory, in megabytes, to allocate for the daemon's
+in-memory cache.  A larger value may increase performance by reducing
+the frequency of disk I/O.
+
+Defaults to @samp{4}.
+
+@end deftypevr
+
+@deftypevr {@code{transmission-daemon-configuration} parameter} boolean prefetch-enabled?
+When @code{#t}, the daemon will try to improve I/O performance by
+hinting to the operating system which data is likely to be read next
+from disk to satisfy requests from peers.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+
+@c %end of fragment
+
+
+
 @node Monitoring Services
 @subsection Monitoring Services
 
diff --git a/gnu/local.mk b/gnu/local.mk
index d098c04308..0625c6c5eb 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -605,6 +605,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/services/dns.scm				\
   %D%/services/docker.scm			\
   %D%/services/authentication.scm		\
+  %D%/services/file-sharing.scm			\
   %D%/services/games.scm			\
   %D%/services/ganeti.scm			\
   %D%/services/getmail.scm				\
diff --git a/gnu/services/file-sharing.scm b/gnu/services/file-sharing.scm
new file mode 100644
index 0000000000..72cd6478d6
--- /dev/null
+++ b/gnu/services/file-sharing.scm
@@ -0,0 +1,804 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2020 Simon South <simon@simonsouth.net>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu services file-sharing)
+  #:use-module (gcrypt base16)
+  #:use-module (gcrypt hash)
+  #:use-module (gcrypt random)
+  #:use-module (gnu services)
+  #:use-module (gnu services admin)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu packages bittorrent)
+  #:use-module (gnu packages gnupg)
+  #:use-module (gnu packages guile)
+  #:use-module (gnu system shadow)
+  #:use-module (guix diagnostics)
+  #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix modules)
+  #:use-module (guix packages)
+  #:use-module (guix records)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
+  #:export (transmission-daemon-configuration
+            transmission-daemon-service-type
+            transmission-password-hash
+            transmission-random-salt))
+
+;;;
+;;; Transmission Daemon.
+;;;
+
+(define %transmission-daemon-user "transmission")
+(define %transmission-daemon-group "transmission")
+
+(define %transmission-daemon-configuration-directory
+  "/var/lib/transmission-daemon")
+(define %transmission-daemon-log-file
+  "/var/log/transmission.log")
+
+(define %transmission-salt-length 8)
+
+(define (transmission-password-hash password salt)
+  "Returns a string containing the result of hashing @var{password} together
+with @var{salt}, in the format recognized by Transmission clients for their
+@code{rpc-password} configuration setting.
+
+@var{salt} must be an eight-character string.  The
+@code{transmission-random-salt} procedure can be used to generate a suitable
+salt value at random."
+  (if (not (and (string? salt)
+                (eq? (string-length salt) %transmission-salt-length)))
+      (raise (formatted-message
+              (G_ "salt value must be a string of ~d characters")
+              %transmission-salt-length))
+      (string-append "{"
+                     (bytevector->base16-string
+                      (sha1 (string->utf8 (string-append password salt))))
+                     salt)))
+
+(define (transmission-random-salt)
+  "Returns a string containing a random, eight-character salt value of the
+type generated and used by Transmission clients, suitable for passing to the
+@code{transmission-password-hash} procedure."
+  ;; This implementation matches a portion of Transmission's tr_ssha1
+  ;; function.  See libtransmission/crypto-utils.c in the Transmission source
+  ;; distribution.
+  (let ((salter (string-append "0123456789"
+                               "abcdefghijklmnopqrstuvwxyz"
+                               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                               "./")))
+    (list->string
+     (map (lambda (u8)
+            (string-ref salter (modulo u8 (string-length salter))))
+          (bytevector->u8-list
+           (gen-random-bv %transmission-salt-length %gcry-strong-random))))))
+
+(define (uglify-field-name field-name)
+  (string-delete #\? (symbol->string field-name)))
+
+(define (serialize-field field-name val)
+  ;; "Serialize" each configuration field as a G-expression containing a
+  ;; name-value pair, the collection of which will subsequently be serialized
+  ;; to disk as a JSON object.
+  #~(#$(uglify-field-name field-name) . #$val))
+
+(define serialize-boolean serialize-field)
+(define serialize-integer serialize-field)
+(define serialize-rational serialize-field)
+
+(define serialize-string serialize-field)
+(define-maybe string)
+;; Override the definition of "serialize-maybe-string", as we need to output a
+;; name-value pair for the JSON builder.
+(set! serialize-maybe-string
+  (lambda (field-name val)
+    (serialize-string field-name
+                      (if (and (symbol? val)
+                               (eq? val 'disabled))
+                          ""
+                          val))))
+
+(define (string-list? val)
+  (and (list? val)
+       (and-map (lambda (x)
+                  (and (string? x)
+                       (not (string-index x #\,))))
+                val)))
+(define (serialize-string-list field-name val)
+  (serialize-field field-name (string-join val ",")))
+
+(define days
+  '((sunday    . #b0000001)
+    (monday    . #b0000010)
+    (tuesday   . #b0000100)
+    (wednesday . #b0001000)
+    (thursday  . #b0010000)
+    (friday    . #b0100000)
+    (saturday  . #b1000000)))
+(define day-lists
+  (list (cons 'weekdays '(monday tuesday wednesday thursday friday))
+        (cons 'weekends '(saturday sunday))
+        (cons 'all (map car days))))
+(define (day-list? val)
+  (or (and (symbol? val)
+           (assq val day-lists))
+      (and (list? val)
+           (and-map (lambda (x)
+                      (and (symbol? x)
+                           (assq x days)))
+                    val))))
+(define (serialize-day-list field-name val)
+  (serialize-integer field-name
+                     (reduce logior
+                             #b0000000
+                             (map (lambda (day)
+                                    (assq-ref days day))
+                                  (if (symbol? val)
+                                      (assq-ref day-lists val)
+                                      val)))))
+
+(define encryption-modes
+  '((prefer-unencrypted-connections . 0)
+    (prefer-encrypted-connections   . 1)
+    (require-encrypted-connections  . 2)))
+(define (encryption-mode? val)
+  (and (symbol? val)
+       (assq val encryption-modes)))
+(define (serialize-encryption-mode field-name val)
+  (serialize-integer field-name (assq-ref encryption-modes val)))
+
+(define serialize-file-like serialize-field)
+
+(define (file-object? val)
+  (or (string? val)
+      (file-like? val)))
+(define (serialize-file-object field-name val)
+  (if (file-like? val)
+      (serialize-file-like field-name val)
+      (serialize-string field-name val)))
+(define-maybe file-object)
+(set! serialize-maybe-file-object
+  (lambda (field-name val)
+    (if (and (symbol? val)
+             (eq? val 'disabled))
+        (serialize-string field-name "")
+        (serialize-file-object field-name val))))
+
+(define (file-object-list? val)
+  (and (list? val)
+       (and-map file-object? val)))
+(define serialize-file-object-list serialize-field)
+
+(define message-levels
+  '((none  . 0)
+    (error . 1)
+    (info  . 2)
+    (debug . 3)))
+(define (message-level? val)
+  (and (symbol? val)
+       (assq val message-levels)))
+(define (serialize-message-level field-name val)
+  (serialize-integer field-name (assq-ref message-levels val)))
+
+(define (non-negative-integer? val)
+  (and (integer? val)
+       (not (negative? val))))
+(define serialize-non-negative-integer serialize-integer)
+
+(define (non-negative-rational? val)
+  (and (rational? val)
+       (not (negative? val))))
+(define serialize-non-negative-rational serialize-rational)
+
+(define (port-number? val)
+  (and (integer? val)
+       (>= val 1)
+       (<= val 65535)))
+(define serialize-port-number serialize-integer)
+
+(define preallocation-modes
+  '((none   . 0)
+    (fast   . 1)
+    (sparse . 1)
+    (full   . 2)))
+(define (preallocation-mode? val)
+  (and (symbol? val)
+       (assq val preallocation-modes)))
+(define (serialize-preallocation-mode field-name val)
+  (serialize-integer field-name (assq-ref preallocation-modes val)))
+
+(define tcp-types-of-service
+  '((default     . "default")
+    (low-cost    . "lowcost")
+    (throughput  . "throughput")
+    (low-delay   . "lowdelay")
+    (reliability . "reliability")))
+(define (tcp-type-of-service? val)
+  (and (symbol? val)
+       (assq val tcp-types-of-service)))
+(define (serialize-tcp-type-of-service field-name val)
+  (serialize-string field-name (assq-ref tcp-types-of-service val)))
+
+(define (transmission-password-hash? val)
+  (and (string? val)
+       (= (string-length val) 49)
+       (eqv? (string-ref val 0) #\{)
+       (string-every char-set:hex-digit val 1 41)))
+(define serialize-transmission-password-hash serialize-string)
+(define-maybe transmission-password-hash)
+(set! serialize-maybe-transmission-password-hash serialize-maybe-string)
+
+(define (umask? val)
+  (and (integer? val)
+       (>= val #o000)
+       (<= val #o777)))
+(define serialize-umask serialize-integer) ; must use decimal representation
+
+(define-configuration transmission-daemon-configuration
+  ;; Settings internal to this service definition.
+  (transmission
+   (package transmission)
+   "The Transmission package to use.")
+  (stop-wait-period
+   (non-negative-integer 10)
+   "The period, in seconds, to wait when stopping the service for
+@command{transmission-daemon} to exit before killing its process.  This allows
+the daemon time to complete its housekeeping and send a final update to
+trackers as it shuts down.  On slow hosts, or hosts with a slow network
+connection, this value may need to be increased.")
+
+  ;; Files and directories.
+  (download-dir
+   (string (string-append %transmission-daemon-configuration-directory
+                          "/downloads"))
+   "The directory to which torrent files are downloaded.")
+  (incomplete-dir-enabled?
+   (boolean #f)
+   "If @code{#t}, files will be held in @code{incomplete-dir} while their
+torrent is being downloaded, then moved to @code{download-dir} once the
+torrent is complete.  Otherwise, files for all torrents (including those still
+being downloaded) will be placed in @code{download-dir}.")
+  (incomplete-dir
+   (maybe-string 'disabled)
+   "The directory in which files from incompletely downloaded torrents will be
+held when @code{incomplete-dir-enabled?} is @code{#t}.")
+  (umask
+   (umask #o022)
+   "The file mode creation mask used for downloaded files.  (See the
+@command{umask} man page for more information.)")
+  (rename-partial-files?
+   (boolean #t)
+   "When @code{#t}, ``.part'' is appended to the name of partially downloaded
+files.")
+  (preallocation
+   (preallocation-mode 'fast)
+   "The mode by which space should be preallocated for downloaded files, one
+of @code{none}, @code{fast} (or @code{sparse}) and @code{full}.  Specifying
+@code{full} will minimize disk fragmentation at a cost to file-creation
+speed.")
+  (watch-dir-enabled?
+   (boolean #f)
+   "If @code{#t}, the directory specified by @code{watch-dir} will be watched
+for new @file{.torrent} files and the torrents they describe added
+automatically (and the original files removed, if
+@code{trash-original-torrent-files?} is @code{#t}).")
+  (watch-dir
+   (maybe-string 'disabled)
+   "The directory to be watched for @file{.torrent} files indicating new
+torrents to be added, when @code{watch-dir-enabled} is @code{#t}.")
+  (trash-original-torrent-files?
+   (boolean #f)
+   "When @code{#t}, @file{.torrent} files will be deleted from the watch
+directory once their torrent has been added (see
+@code{watch-directory-enabled?}).")
+
+  ;; Bandwidth limits.
+  (speed-limit-down-enabled?
+   (boolean #f)
+   "When @code{#t}, the daemon's download speed will be limited to the rate
+specified by @code{speed-limit-down}.")
+  (speed-limit-down
+   (non-negative-integer 100)
+   "The default global-maximum download speed, in kilobytes per second.")
+  (speed-limit-up-enabled?
+   (boolean #f)
+   "When @code{#t}, the daemon's upload speed will be limited to the rate
+specified by @code{speed-limit-up}.")
+  (speed-limit-up
+   (non-negative-integer 100)
+   "The default global-maximum upload speed, in kilobytes per second.")
+  (alt-speed-enabled?
+   (boolean #f)
+   "When @code{#t}, the alternate speed limits @code{alt-speed-down} and
+@code{alt-speed-up} are used (in place of @code{speed-limit-down} and
+@code{speed-limit-up}, if they are enabled) to constrain the daemon's
+bandwidth usage.  This can be scheduled to occur automatically at certain
+times during the week; see @code{alt-speed-time-enabled?}.")
+  (alt-speed-down
+   (non-negative-integer 50)
+   "The alternate global-maximum download speed, in kilobytes per second.")
+  (alt-speed-up
+   (non-negative-integer 50)
+   "The alternate global-maximum upload speed, in kilobytes per second.")
+
+  ;; Bandwidth-limit scheduling.
+  (alt-speed-time-enabled?
+   (boolean #f)
+   "When @code{#t}, the alternate speed limits @code{alt-speed-down} and
+@code{alt-speed-up} will be enabled automatically during the periods specified
+by @code{alt-speed-time-day}, @code{alt-speed-time-begin} and
+@code{alt-time-speed-end}.")
+  (alt-speed-time-day
+   (day-list 'all)
+   "The days of the week on which the alternate-speed schedule should be used,
+specified either as a list of days (@code{sunday}, @code{monday}, and so on)
+or using one of the symbols @code{weekdays}, @code{weekends} or @code{all}.")
+  (alt-speed-time-begin
+   (non-negative-integer 540)
+   "The time of day at which to enable the alternate speed limits,
+expressed as a number of minutes since midnight.")
+  (alt-speed-time-end
+   (non-negative-integer 1020)
+   "The time of day at which to disable the alternate speed limits,
+expressed as a number of minutes since midnight.")
+
+  ;; Peer networking.
+  (bind-address-ipv4
+   (string "0.0.0.0")
+   "The IP address at which to listen for peer connections, or ``0.0.0.0'' to
+listen at all available IP addresses.")
+  (bind-address-ipv6
+   (string "::")
+   "The IPv6 address at which to listen for peer connections, or ``::'' to
+listen at all available IPv6 addresses.")
+  (peer-port-random-on-start?
+   (boolean #f)
+   "If @code{#t}, when the daemon starts it will select a port at random on
+which to listen for peer connections, from the range specified (inclusively)
+by @code{peer-port-random-low} and @code{peer-port-random-high}.  Otherwise,
+it listens on the port specified by @code{peer-port}.")
+  (peer-port-random-low
+   (port-number 49152)
+   "The lowest selectable port number when @code{peer-port-random-on-start?}
+is @code{#t}.")
+  (peer-port-random-high
+   (port-number 65535)
+   "The highest selectable port number when @code{peer-port-random-on-start}
+is @code{#t}.")
+  (peer-port
+   (port-number 51413)
+   "The port on which to listen for peer connections when
+@code{peer-port-random-on-start?} is @code{#f}.")
+  (port-forwarding-enabled?
+   (boolean #t)
+   "If @code{#t}, the daemon will attempt to configure port-forwarding on an
+upstream gateway automatically using @acronym{UPnP} and @acronym{NAT-PMP}.")
+  (encryption
+   (encryption-mode 'prefer-encrypted-connections)
+   "The encryption preference for peer connections, one of
+@code{prefer-unencrypted-connections}, @code{prefer-encrypted-connections} or
+@code{require-encrypted-connections}.")
+  (peer-congestion-algorithm
+   (maybe-string 'disabled)
+   "The TCP congestion-control algorithm to use for peer connections,
+specified using a string recognized by the operating system in calls to
+@code{setsockopt} (or set to @code{disabled}, in which case the
+operating-system default is used).
+
+Note that on GNU/Linux systems, the kernel must be configured to allow
+processes to use a congestion-control algorithm not in the default set;
+otherwise, it will deny these requests with ``Operation not permitted''.  To
+see which algorithms are available on your system and which are currently
+permitted for use, look at the contents of the files
+@file{tcp_available_congestion_control} and
+@file{tcp_allowed_congestion_control} in the @file{/proc/sys/net/ipv4}
+directory.
+
+As an example, to have Transmission Daemon use
+@uref{http://www-ece.rice.edu/networks/TCP-LP/, the TCP Low Priority
+congestion-control algorithm}, you'll need to modify your kernel configuration
+to build in support for the algorithm, then update your operating-system
+configuration to allow its use by adding a @code{sysctl-service-type}
+service (or updating the existing one's configuration) with lines like the
+following:
+
+@lisp
+(service sysctl-service-type
+         (sysctl-configuration
+          (settings
+           (\"net.ipv4.tcp_allowed_congestion_control\" .
+            \"reno cubic lp\"))))
+@end lisp
+
+The Transmission Daemon configuration can then be updated with
+
+@lisp
+(peer-congestion-algorithm \"lp\")
+@end lisp
+
+and the system reconfigured to have the changes take effect.")
+  (peer-socket-tos
+   (tcp-type-of-service 'default)
+   "The type of service to request in outgoing @acronym{TCP} packets,
+one of @code{default}, @code{low-cost}, @code{throughput}, @code{low-delay}
+and @code{reliability}.")
+  (peer-limit-global
+   (non-negative-integer 200)
+   "The global limit on the number of connected peers.")
+  (peer-limit-per-torrent
+   (non-negative-integer 50)
+   "The per-torrent limit on the number of connected peers.")
+  (upload-slots-per-torrent
+   (non-negative-integer 14)
+   "The maximum number of peers to which the daemon will upload data
+simultaneously for each torrent.")
+  (peer-id-ttl-hours
+   (non-negative-integer 6)
+   "The maximum lifespan, in hours, of the peer ID associated with each public
+torrent before it is regenerated.")
+
+  ;; Peer blocklists.
+  (blocklist-enabled?
+   (boolean #f)
+   "When @code{#t}, the daemon will ignore peers mentioned in the blocklist it
+has most recently downloaded from @code{blocklist-url}.")
+  (blocklist-url
+   (maybe-string 'disabled)
+   "The URL of a peer blocklist (in @acronym{P2P}-plaintext or eMule
+@file{.dat} format) to be periodically downloaded and applied when
+@code{blocklist-enabled?} is @code{#t}.")
+
+  ;; Queueing.
+  (download-queue-enabled?
+   (boolean #t)
+   "If @code{#t}, the daemon will be limited to downloading at most
+@code{download-queue-size} non-stalled torrents simultaneously.")
+  (download-queue-size
+   (non-negative-integer 5)
+   "The size of the daemon's download queue, which limits the number of
+non-stalled torrents it will download at any one time when
+@code{download-queue-enabled?} is @code{#t}.")
+  (seed-queue-enabled?
+   (boolean #f)
+   "If @code{#t}, the daemon will be limited to seeding at most
+@code{seed-queue-size} non-stalled torrents simultaneously.")
+  (seed-queue-size
+   (non-negative-integer 10)
+   "The size of the daemon's seed queue, which limits the number of
+non-stalled torrents it will seed at any one time when
+@code{seed-queue-enabled?} is @code{#t}.")
+  (queue-stalled-enabled?
+   (boolean #t)
+   "When @code{#t}, the daemon will consider torrents for which it has not
+shared data in the past @code{queue-stalled-minutes} minutes to be stalled and
+not count them against its @code{download-queue-size} and
+@code{seed-queue-size} limits.")
+  (queue-stalled-minutes
+   (non-negative-integer 30)
+   "The maximum period, in minutes, a torrent may be idle before it is
+considered to be stalled, when @code{queue-stalled-enabled?} is @code{#t}.")
+
+  ;; Seeding limits.
+  (ratio-limit-enabled?
+   (boolean #f)
+   "When @code{#t}, a torrent being seeded will automatically be paused once
+it reaches the ratio specified by @code{ratio-limit}.")
+  (ratio-limit
+   (non-negative-rational 2.0)
+   "The ratio at which a torrent being seeded will be paused, when
+@code{ratio-limit-enabled?} is @code{#t}.")
+  (idle-seeding-limit-enabled?
+   (boolean #f)
+   "When @code{#t}, a torrent being seeded will automatically be paused once
+it has been idle for @code{idle-seeding-limit} minutes.")
+  (idle-seeding-limit
+   (non-negative-integer 30)
+   "The maximum period, in minutes, a torrent being seeded may be idle before
+it is paused, when @code{idle-seeding-limit-enabled?} is @code{#t}.")
+
+  ;; BitTorrent extensions.
+  (dht-enabled?
+   (boolean #t)
+   "Enable @uref{http://bittorrent.org/beps/bep_0005.html, the distributed
+hash table (@acronym{DHT}) protocol}, which supports the use of trackerless
+torrents.")
+  (lpd-enabled?
+   (boolean #f)
+   "Enable @url{https://en.wikipedia.org/wiki/Local_Peer_Discovery, local peer
+discovery} (@acronym{LPD}), which allows the discovery of peers on the local
+network and may reduce the amount of data sent over the public Internet.")
+  (pex-enabled?
+   (boolean #t)
+   "Enable @url{https://en.wikipedia.org/wiki/Peer_exchange, peer
+exchange} (@acronym{PEX}), which reduces the daemon's reliance on external
+trackers and may improve its performance.")
+  (utp-enabled?
+   (boolean #t)
+   "Enable @url{http://bittorrent.org/beps/bep_0029.html, the micro transport
+protocol} (@acronym{uTP}), which aims to reduce the impact of BitTorrent
+traffic on other users of the local network while maintaining full utilization
+of the available bandwidth.")
+
+  ;; Remote procedure call (RPC) interface.
+  (rpc-enabled?
+   (boolean #t)
+   "If @code{#t}, enable the remote procedure call (@acronym{RPC}) interface,
+which allows remote control of the daemon via its Web interface, the
+@command{transmission-remote} command-line client, and similar tools.")
+  (rpc-bind-address
+   (string "0.0.0.0")
+   "The IP address at which to listen for @acronym{RPC} connections, or
+``0.0.0.0'' to listen at all available IP addresses.")
+  (rpc-port
+   (port-number 9091)
+   "The port on which to listen for @acronym{RPC} connections.")
+  (rpc-url
+   (string "/transmission/")
+   "The path prefix to use in the @acronym{RPC}-endpoint @acronym{URL}.")
+  (rpc-authentication-required?
+   (boolean #f)
+   "When @code{#t}, clients must authenticate (see @code{rpc-username} and
+@code{rpc-password}) when using the @acronym{RPC} interface.  Note this has
+the side effect of disabling host-name whitelisting (see
+@code{rpc-host-whitelist-enabled?}.")
+  (rpc-username
+   (maybe-string 'disabled)
+   "The username required by clients to access the @acronym{RPC} interface
+when @code{rpc-authentication-required?} is @code{#t}.")
+  (rpc-password
+   (maybe-transmission-password-hash 'disabled)
+   "The password required by clients to access the @acronym{RPC} interface
+when @code{rpc-authentication-required?} is @code{#t}.  This must be specified
+using a password hash in the format recognized by Transmission clients, either
+copied from an existing @file{settings.json} file or generated using the
+@code{transmission-password-hash} procedure.")
+  (rpc-whitelist-enabled?
+   (boolean #t)
+   "When @code{#t}, @acronym{RPC} requests will be accepted only when they
+originate from an address specified in @code{rpc-whitelist}.")
+  (rpc-whitelist
+   (string-list '("127.0.0.1" "::1"))
+   "The list of IP and IPv6 addresses from which @acronym{RPC} requests will
+be accepted when @code{rpc-whitelist-enabled?} is @code{#t}.  Wildcards may be
+specified using @samp{*}.")
+  (rpc-host-whitelist-enabled?
+   (boolean #t)
+   "When @code{#t}, @acronym{RPC} requests will be accepted only when they are
+addressed to a host named in @code{rpc-host-whitelist}.  Note that requests to
+``localhost'' or ``localhost.'', or to a numeric address, are always accepted
+regardless of these settings.
+
+Note also this functionality is disabled when
+@code{rpc-authentication-required?} is @code{#t}.")
+  (rpc-host-whitelist
+   (string-list '())
+   "The list of host names recognized by the @acronym{RPC} server when
+@code{rpc-host-whitelist-enabled?} is @code{#t}.")
+
+  ;; Miscellaneous.
+  (message-level
+   (message-level 'info)
+   "The minimum severity level of messages to be logged (to
+@file{/var/log/transmission.log}) by the daemon, one of @code{none} (no
+logging), @code{error}, @code{info} and @code{debug}.")
+  (start-added-torrents?
+   (boolean #t)
+   "When @code{#t}, torrents are started as soon as they are added; otherwise,
+they are added in ``paused'' state.")
+  (script-torrent-done-enabled?
+   (boolean #f)
+   "When @code{#t}, the script specified by
+@code{script-torrent-done-filename} will be invoked each time a torrent
+completes.")
+  (script-torrent-done-filename
+   (maybe-file-object 'disabled)
+   "A file name or file-like object specifying a script to run each time a
+torrent completes, when @code{script-torrent-done-enabled?} is @code{#t}.")
+  (scrape-paused-torrents-enabled?
+   (boolean #t)
+   "When @code{#t}, the daemon will scrape trackers for a torrent even when
+the torrent is paused.")
+  (cache-size-mb
+   (non-negative-integer 4)
+   "The amount of memory, in megabytes, to allocate for the daemon's in-memory
+cache.  A larger value may increase performance by reducing the frequency of
+disk I/O.")
+  (prefetch-enabled?
+   (boolean #t)
+   "When @code{#t}, the daemon will try to improve I/O performance by hinting
+to the operating system which data is likely to be read next from disk to
+satisfy requests from peers."))
+
+(define (transmission-daemon-shepherd-service config)
+  "Return a <shepherd-service> for Transmission Daemon with CONFIG."
+  (let ((transmission
+         (transmission-daemon-configuration-transmission config))
+        (stop-wait-period
+         (transmission-daemon-configuration-stop-wait-period config)))
+    (list
+     (shepherd-service
+      (provision '(transmission-daemon transmission bittorrent))
+      (requirement '(networking))
+      (documentation "Share files using the BitTorrent protocol.")
+      (start #~(make-forkexec-constructor
+                '(#$(file-append transmission "/bin/transmission-daemon")
+                  "--config-dir"
+                  #$%transmission-daemon-configuration-directory
+                  "--foreground")
+                #:user #$%transmission-daemon-user
+                #:group #$%transmission-daemon-group
+                #:directory #$%transmission-daemon-configuration-directory
+                #:log-file #$%transmission-daemon-log-file
+                #:environment-variables
+                '("CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt")))
+      (stop #~(lambda (pid)
+                (kill pid SIGTERM)
+
+                ;; Transmission Daemon normally needs some time to shut down,
+                ;; as it will complete some housekeeping and send a final
+                ;; update to trackers before it exits.
+                ;;
+                ;; Wait a reasonable period for it to stop before continuing.
+                ;; If we don't do this, restarting the service can fail as the
+                ;; new daemon process finds the old one still running and
+                ;; attached to the port used for peer connections.
+                (let wait-before-killing ((period #$stop-wait-period))
+                  (if (zero? (car (waitpid pid WNOHANG)))
+                      (if (positive? period)
+                          (begin
+                            (sleep 1)
+                            (wait-before-killing (- period 1)))
+                          (begin
+                            (format #t
+                                    #$(G_ "Wait period expired; killing \
+transmission-daemon (pid ~a).~%")
+                                    pid)
+                            (display #$(G_ "(If you see this message \
+regularly, you may need to increase the value
+of 'stop-wait-period' in the service configuration.)\n"))
+                            (kill pid SIGKILL)))))
+                #f))
+      (actions
+       (list
+        (shepherd-action
+         (name 'reload)
+         (documentation "Reload the settings file from disk.")
+         (procedure #~(lambda (pid)
+                        (if pid
+                            (begin
+                              (kill pid SIGHUP)
+                              (display #$(G_ "Service transmission-daemon has \
+been asked to reload its settings file.")))
+                            (display #$(G_ "Service transmission-daemon is not \
+running."))))))))))))
+
+(define %transmission-daemon-accounts
+  (list (user-group
+         (name %transmission-daemon-group)
+         (system? #t))
+        (user-account
+         (name %transmission-daemon-user)
+         (group %transmission-daemon-group)
+         (comment "Transmission Daemon service account")
+         (home-directory %transmission-daemon-configuration-directory)
+         (shell (file-append shadow "/sbin/nologin"))
+         (system? #t))))
+
+(define %transmission-daemon-log-rotations
+  (list (log-rotation
+         (files (list %transmission-daemon-log-file)))))
+
+(define (transmission-daemon-computed-settings-file config)
+  "Return a @code{computed-file} object that, when unquoted in a G-expression,
+produces a Transmission settings file (@file{settings.json}) matching CONFIG."
+  (let ((settings
+         ;; "Serialize" the configuration settings as a list of G-expressions
+         ;; containing a name-value pair, which will ultimately be sorted and
+         ;; serialized to the settings file as a JSON object.
+         (map
+          (lambda (field)
+            ((configuration-field-serializer field)
+             (configuration-field-name field)
+             ((configuration-field-getter field) config)))
+          (filter
+           (lambda (field)
+             ;; Omit configuration fields that are used only internally by
+             ;; this service definition.
+             (not (memq (configuration-field-name field)
+                        '(transmission stop-wait-period))))
+           transmission-daemon-configuration-fields))))
+    (computed-file
+     "settings.json"
+     (with-extensions (list guile-gcrypt guile-json-4)
+       (with-imported-modules (source-module-closure '((json builder)))
+         #~(begin
+             (use-modules (json builder))
+
+             (with-output-to-file #$output
+               (lambda ()
+                 (scm->json (sort-list '(#$@settings)
+                                       (lambda (x y)
+                                         (string<=? (car x) (car y))))
+                            #:pretty #t)))))))))
+
+(define (transmission-daemon-activation config)
+  "Return the Transmission Daemon activation GEXP for CONFIG."
+  (let ((config-dir %transmission-daemon-configuration-directory)
+        (incomplete-dir-enabled
+         (transmission-daemon-configuration-incomplete-dir-enabled? config))
+        (incomplete-dir
+         (transmission-daemon-configuration-incomplete-dir config))
+        (watch-dir-enabled
+         (transmission-daemon-configuration-watch-dir-enabled? config))
+        (watch-dir
+         (transmission-daemon-configuration-watch-dir config)))
+    (with-imported-modules (source-module-closure '((guix build utils)))
+      #~(begin
+          (use-modules (guix build utils))
+
+          (let ((owner (getpwnam #$%transmission-daemon-user)))
+            (define (mkdir-p/perms directory perms)
+              (mkdir-p directory)
+              (chown directory (passwd:uid owner) (passwd:gid owner))
+              (chmod directory perms))
+
+            ;; Create the directories Transmission Daemon is configured to use
+            ;; and assign them suitable permissions.
+            (for-each (lambda (directory-specification)
+                        (apply mkdir-p/perms directory-specification))
+                      '(#$@(append
+                            `((,config-dir #o750))
+                            (if incomplete-dir-enabled
+                                `((,incomplete-dir #o750))
+                                '())
+                            (if watch-dir-enabled
+                                `((,watch-dir #o770))
+                                '())))))
+
+          ;; Generate and activate the daemon's settings file, settings.json.
+          (activate-special-files
+           '((#$(string-append config-dir "/settings.json")
+              #$(transmission-daemon-computed-settings-file config))))))))
+
+(define transmission-daemon-service-type
+  (service-type
+   (name 'transmission)
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             transmission-daemon-shepherd-service)
+          (service-extension account-service-type
+                             (const %transmission-daemon-accounts))
+          (service-extension rottlog-service-type
+                             (const %transmission-daemon-log-rotations))
+          (service-extension activation-service-type
+                             transmission-daemon-activation)))
+   (default-value (transmission-daemon-configuration))
+   (description "Share files using the BitTorrent protocol.")))
+
+(define (generate-transmission-daemon-documentation)
+  (generate-documentation
+   `((transmission-daemon-configuration
+      ,transmission-daemon-configuration-fields))
+   'transmission-daemon-configuration))
diff --git a/po/packages/POTFILES.in b/po/packages/POTFILES.in
index 9a178edfa6..398f9adfdf 100644
--- a/po/packages/POTFILES.in
+++ b/po/packages/POTFILES.in
@@ -59,5 +59,6 @@ gnu/packages/wordnet.scm
 gnu/packages/xiph.scm
 gnu/services/base.scm
 gnu/services/certbot.scm
+gnu/services/file-sharing.scm
 gnu/services/networking.scm
 gnu/services/version-control.scm
diff --git a/tests/services/file-sharing.scm b/tests/services/file-sharing.scm
new file mode 100644
index 0000000000..27bec57325
--- /dev/null
+++ b/tests/services/file-sharing.scm
@@ -0,0 +1,59 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2020 Simon South <simon@simonsouth.net>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (tests services file-sharing)
+  #:use-module (gnu services file-sharing)
+  #:use-module (srfi srfi-64))
+
+;;; Tests for the (gnu services file-sharing) module.
+
+(test-begin "file-sharing")
+
+
+;;;
+;;; Transmission Daemon.
+;;;
+
+(define %transmission-salt-length 8)
+
+(define (valid-transmission-salt? salt)
+    (and (string? salt)
+         (eqv? (string-length salt) %transmission-salt-length)))
+
+(test-assert "transmission-random-salt"
+  (valid-transmission-salt? (transmission-random-salt)))
+
+(test-equal "transmission-password-hash, typical values"
+  "{ef6fba106cdef3aac64d1410090cae353cbecde53ceVVQO2"
+  (transmission-password-hash "transmission" "3ceVVQO2"))
+
+(test-equal "transmission-password-hash, empty password"
+  "{820f816515d8969d058d07a1de018650619ee7ffCp.I5SWg"
+  (transmission-password-hash "" "Cp.I5SWg"))
+
+(test-error "transmission-password-hash, salt value too short"
+            (transmission-password-hash
+             "transmission"
+             (make-string (- %transmission-salt-length 1) #\a)))
+
+(test-error "transmission-password-hash, salt value too long"
+            (transmission-password-hash
+             "transmission"
+             (make-string (+ %transmission-salt-length 1) #\a)))
+
+(test-end "file-sharing")