unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
* [bug#44435] [PATCH 0/1] services: Add Transmission Daemon
@ 2020-11-04 12:37 Simon South
  2020-11-04 12:40 ` [bug#44435] [PATCH 1/1] services: Add transmission-daemon service Simon South
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Simon South @ 2020-11-04 12:37 UTC (permalink / raw)
  To: 44435; +Cc: simon

This patch adds a service type for Transmission Daemon, the headless variant
of the Transmission BitTorrent client (https://transmissionbt.com/). Running
the client as a service this way makes it possible to share files over
BitTorrent continuously without requiring a user be logged in.

I've tried to make this as complete as possible but am especially interested
in geting feedback as this is my first attempt at creating a service
definition. A few things to point out:

- I've placed the code in a new "(gnu services file-sharing)" module and the
  documentation in a new "File-Sharing Services" section of the manual, only
  because these names seemed the most natural to me. ("Peer-to-peer" would be
  too broad a categorization, I think, while "BitTorrent" too narrow.)

- The module exports two procedures, "transmission-password-hash" and
  "transmission-random-salt", that together are my solution to the problem of
  assigning a value to the daemon's "rpc-password" configuration setting.

  Transmission clients seem to expect the user to supply a password in
  plaintext in their "settings.json" file. At startup, the client generates a
  random, eight-character salt value; hashes it and the password together; and
  writes the result back to the settings file, after which the password
  remains obscured. This obviously violates the functional nature of Guix, as
  we don't expect services to be rewriting their own configuration files and
  the use of a random salt value makes the process non-repeatable anyway.

  I've documented in the manual how a user can use these two procedures to
  create a suitable value for "rpc-password" that remains stable across system
  reconfigurations, but perhaps you know of a better (or more conventional)
  approach.

- I've added a custom "stop" procedure to the Shepherd service that gives the
  daemon time to shut down before eventually killing its process. This is
  necessary since the daemon performs some housekeeping and sends a final
  update to BitTorrent trackers before it exits, which can take several
  seconds or more; without this code, restarting the service usually fails as
  the new daemon process finds the old one is still running and attached to
  the port used for peer connections.

  Again, the approach I've used to handle this seems reasonable to me but
  perhaps you know of something better.

--
Simon South
simon@simonsouth.net


Simon South (1):
  services: Add transmission-daemon service.

 doc/guix.texi                 | 799 +++++++++++++++++++++++++++++++++
 gnu/local.mk                  |   1 +
 gnu/services/file-sharing.scm | 806 ++++++++++++++++++++++++++++++++++
 3 files changed, 1606 insertions(+)
 create mode 100644 gnu/services/file-sharing.scm

-- 
2.28.0





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH 1/1] services: Add transmission-daemon service.
  2020-11-04 12:37 [bug#44435] [PATCH 0/1] services: Add Transmission Daemon Simon South
@ 2020-11-04 12:40 ` Simon South
  2020-11-07 14:12   ` Simon South
  2020-11-08 18:06 ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Simon South
  2020-12-05 15:27 ` [bug#44435] [PATCH v3 " Simon South
  2 siblings, 1 reply; 10+ messages in thread
From: Simon South @ 2020-11-04 12:40 UTC (permalink / raw)
  To: 44435; +Cc: simon

* gnu/services/file-sharing.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (File-Sharing Services): New section.
---
 doc/guix.texi                 | 799 +++++++++++++++++++++++++++++++++
 gnu/local.mk                  |   1 +
 gnu/services/file-sharing.scm | 806 ++++++++++++++++++++++++++++++++++
 3 files changed, 1606 insertions(+)
 create mode 100644 gnu/services/file-sharing.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index b7f1bc1f00..dcf57e16e3 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -14364,6 +14364,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.
@@ -21592,6 +21593,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 d4d04c01b8..b5e2139051 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -598,6 +598,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..e31732d110
--- /dev/null
+++ b/gnu/services/file-sharing.scm
@@ -0,0 +1,806 @@
+;;; 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 gexp)
+  #: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)
+  #: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 (eq? (string-length salt) %transmission-salt-length))
+      (throw 'out-of-range
+             (format #f
+                     "salt value must be ~d characters in length"
+                     %transmission-salt-length))
+      (let ((password-digest (call-with-input-string
+                                 (string-append password salt)
+                               (lambda (port)
+                                 (port-hash (hash-algorithm sha1) port)))))
+        (string-append "{"
+                       (bytevector->base16-string password-digest)
+                       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
+                                    "Wait period expired; killing \
+transmission-daemon (pid ~a).~%"
+                                    pid)
+                            (display "(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 "Service transmission-daemon has been \
+asked to reload its settings file."))
+                            (display "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 '((guix gexp)
+                                                       (json builder)))
+         #~(begin
+             (use-modules (guix gexp)
+                          (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 ((owner (getpwnam %transmission-daemon-user))
+        (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))
+
+          (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))
-- 
2.28.0





^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH 1/1] services: Add transmission-daemon service.
  2020-11-04 12:40 ` [bug#44435] [PATCH 1/1] services: Add transmission-daemon service Simon South
@ 2020-11-07 14:12   ` Simon South
  0 siblings, 0 replies; 10+ messages in thread
From: Simon South @ 2020-11-07 14:12 UTC (permalink / raw)
  To: 44435

Simon South <simon@simonsouth.net> writes:
> +(define (transmission-daemon-activation config)
> +  "Return the Transmission Daemon activation GEXP for CONFIG."
> +  (let ((owner (getpwnam %transmission-daemon-user))

This call to "getpwnam" should be executed on the build side, not the
host side.

Also, since Ludovic's commit ca465a9c8454, configuring a system that
uses this service type issues

  gnu/services/file-sharing.scm:736:9: warning: importing module (guix config) from the host

which I assume is a problem.

I'm working on an update that addresses these things.

-- 
Simon South
simon@simonsouth.net




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon
  2020-11-04 12:37 [bug#44435] [PATCH 0/1] services: Add Transmission Daemon Simon South
  2020-11-04 12:40 ` [bug#44435] [PATCH 1/1] services: Add transmission-daemon service Simon South
@ 2020-11-08 18:06 ` Simon South
  2020-11-08 18:06   ` [bug#44435] [PATCH v2 1/1] services: Add transmission-daemon service Simon South
  2020-11-18 22:39   ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Ludovic Courtès
  2020-12-05 15:27 ` [bug#44435] [PATCH v3 " Simon South
  2 siblings, 2 replies; 10+ messages in thread
From: Simon South @ 2020-11-08 18:06 UTC (permalink / raw)
  To: 44435; +Cc: simon

Here's an updated version of the patch that

- Fixes the "importing module from host" warning by removing an unnecessary
  import of (guix gexp) in transmission-daemon-computed-settings-file; and

- Moves the execution of getpwnam in transmission-daemon-activation from the
  host side to the build side, where it belongs.

Everything else is unchanged.

For convenience, here's my original cover letter:

This patch adds a service type for Transmission Daemon, the headless variant
of the Transmission BitTorrent client (https://transmissionbt.com/). Running
the client as a service this way makes it possible to share files over
BitTorrent continuously without requiring a user be logged in.

I've tried to make this as complete as possible but am especially interested
in geting feedback as this is my first attempt at creating a service
definition. A few things to point out:

- I've placed the code in a new "(gnu services file-sharing)" module and the
  documentation in a new "File-Sharing Services" section of the manual, only
  because these names seemed the most natural to me. ("Peer-to-peer" would be
  too broad a categorization, I think, while "BitTorrent" too narrow.)

- The module exports two procedures, "transmission-password-hash" and
  "transmission-random-salt", that together are my solution to the problem of
  assigning a value to the daemon's "rpc-password" configuration setting.

  Transmission clients seem to expect the user to supply a password in
  plaintext in their "settings.json" file. At startup, the client generates a
  random, eight-character salt value; hashes it and the password together; and
  writes the result back to the settings file, after which the password
  remains obscured. This obviously violates the functional nature of Guix, as
  we don't expect services to be rewriting their own configuration files and
  the use of a random salt value makes the process non-repeatable anyway.

  I've documented in the manual how a user can use these two procedures to
  create a suitable value for "rpc-password" that remains stable across system
  reconfigurations, but perhaps you know of a better (or more conventional)
  approach.

- I've added a custom "stop" procedure to the Shepherd service that gives the
  daemon time to shut down before eventually killing its process. This is
  necessary since the daemon performs some housekeeping and sends a final
  update to BitTorrent trackers before it exits, which can take several
  seconds or more; without this code, restarting the service usually fails as
  the new daemon process finds the old one is still running and attached to
  the port used for peer connections.

  Again, the approach I've used to handle this seems reasonable to me but
  perhaps you know of something better.

--
Simon South
simon@simonsouth.net


Simon South (1):
  services: Add transmission-daemon service.

 doc/guix.texi                 | 799 +++++++++++++++++++++++++++++++++
 gnu/local.mk                  |   1 +
 gnu/services/file-sharing.scm | 803 ++++++++++++++++++++++++++++++++++
 3 files changed, 1603 insertions(+)
 create mode 100644 gnu/services/file-sharing.scm

-- 
2.29.2





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH v2 1/1] services: Add transmission-daemon service.
  2020-11-08 18:06 ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Simon South
@ 2020-11-08 18:06   ` Simon South
  2020-11-18 22:39   ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Ludovic Courtès
  1 sibling, 0 replies; 10+ messages in thread
From: Simon South @ 2020-11-08 18:06 UTC (permalink / raw)
  To: 44435; +Cc: simon

* gnu/services/file-sharing.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (File-Sharing Services): New section.
---
 doc/guix.texi                 | 799 +++++++++++++++++++++++++++++++++
 gnu/local.mk                  |   1 +
 gnu/services/file-sharing.scm | 803 ++++++++++++++++++++++++++++++++++
 3 files changed, 1603 insertions(+)
 create mode 100644 gnu/services/file-sharing.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index 217ed7a8a8..fe1c097653 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -14365,6 +14365,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.
@@ -21688,6 +21689,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 3a8f1e30fc..096c5cb41d 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -598,6 +598,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..cb773f2741
--- /dev/null
+++ b/gnu/services/file-sharing.scm
@@ -0,0 +1,803 @@
+;;; 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 gexp)
+  #: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)
+  #: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 (eq? (string-length salt) %transmission-salt-length))
+      (throw 'out-of-range
+             (format #f
+                     "salt value must be ~d characters in length"
+                     %transmission-salt-length))
+      (let ((password-digest (call-with-input-string
+                                 (string-append password salt)
+                               (lambda (port)
+                                 (port-hash (hash-algorithm sha1) port)))))
+        (string-append "{"
+                       (bytevector->base16-string password-digest)
+                       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
+                                    "Wait period expired; killing \
+transmission-daemon (pid ~a).~%"
+                                    pid)
+                            (display "(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 "Service transmission-daemon has been \
+asked to reload its settings file."))
+                            (display "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))
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon
  2020-11-08 18:06 ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Simon South
  2020-11-08 18:06   ` [bug#44435] [PATCH v2 1/1] services: Add transmission-daemon service Simon South
@ 2020-11-18 22:39   ` Ludovic Courtès
  2020-11-19  0:35     ` Simon South
  1 sibling, 1 reply; 10+ messages in thread
From: Ludovic Courtès @ 2020-11-18 22:39 UTC (permalink / raw)
  To: Simon South; +Cc: 44435

Hi Simon,

Simon South <simon@simonsouth.net> skribis:

> Here's an updated version of the patch that
>
> - Fixes the "importing module from host" warning by removing an unnecessary
>   import of (guix gexp) in transmission-daemon-computed-settings-file; and

Good.  :-)

> - I've placed the code in a new "(gnu services file-sharing)" module and the
>   documentation in a new "File-Sharing Services" section of the manual, only
>   because these names seemed the most natural to me. ("Peer-to-peer" would be
>   too broad a categorization, I think, while "BitTorrent" too narrow.)

Sounds good to me.

> - The module exports two procedures, "transmission-password-hash" and
>   "transmission-random-salt", that together are my solution to the problem of
>   assigning a value to the daemon's "rpc-password" configuration setting.
>
>   Transmission clients seem to expect the user to supply a password in
>   plaintext in their "settings.json" file. At startup, the client generates a
>   random, eight-character salt value; hashes it and the password together; and
>   writes the result back to the settings file, after which the password
>   remains obscured. This obviously violates the functional nature of Guix, as
>   we don't expect services to be rewriting their own configuration files and
>   the use of a random salt value makes the process non-repeatable anyway.
>
>   I've documented in the manual how a user can use these two procedures to
>   create a suitable value for "rpc-password" that remains stable across system
>   reconfigurations, but perhaps you know of a better (or more conventional)
>   approach.

Looks like a good idea.  At worst we’ll have to keep it in sync with
what future versions of Transmission do, but I guess it’s unlikely to
change often.

> - I've added a custom "stop" procedure to the Shepherd service that gives the
>   daemon time to shut down before eventually killing its process. This is
>   necessary since the daemon performs some housekeeping and sends a final
>   update to BitTorrent trackers before it exits, which can take several
>   seconds or more; without this code, restarting the service usually fails as
>   the new daemon process finds the old one is still running and attached to
>   the port used for peer connections.

OK.

> +@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

Great that you took the time to write good documentation with examples!

> +(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 (eq? (string-length salt) %transmission-salt-length))
> +      (throw 'out-of-range
> +             (format #f
> +                     "salt value must be ~d characters in length"
> +                     %transmission-salt-length))

I’d recommend using (srfi srfi-34), (srfi srfi-35), (guix i18n), and
(guix diagnostics) and write it like so:

  (raise (condition (formatted-message
                      (G_ "salt value …") …)))

Then you can also add this file to po/packages/POTFILE.in for
translation.

> +      (let ((password-digest (call-with-input-string
> +                                 (string-append password salt)
> +                               (lambda (port)
> +                                 (port-hash (hash-algorithm sha1) port)))))

More concise:

  (sha1 (string->utf8 (string-append password salt)))

> +      (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
> +                                    "Wait period expired; killing \
> +transmission-daemon (pid ~a).~%"
> +                                    pid)
> +                            (display "(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))))
                               ^
Missing parens.

Ideally this SIGTERM-then-SIGKILL dance would be done by shepherd
itself.  Future work!


I’m not familiar with Transmission so I can’t really comment on the
other things, but overall it LGTM apart from the details above.

Could you send an updated patch?

It would be nice to have a minimal system test to ensure at least
Transmission starts and is fine with the generated config file; we can
leave that to another patch though, if you prefer.

Thanks!

Ludo’.




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon
  2020-11-18 22:39   ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Ludovic Courtès
@ 2020-11-19  0:35     ` Simon South
  0 siblings, 0 replies; 10+ messages in thread
From: Simon South @ 2020-11-19  0:35 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 44435

Ludovic Courtès <ludo@gnu.org> writes:
> Could you send an updated patch?

Absolutely. Thanks for the review and the feedback.

> It would be nice to have a minimal system test to ensure at least
> Transmission starts and is fine with the generated config file; we can
> leave that to another patch though, if you prefer.

Nah; I can probably get that done fairly quickly, and then perhaps
everything can be committed together.

I'll follow up soon.

-- 
Simon South
simon@simonsouth.net




^ permalink raw reply	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH v3 0/1] services: Add Transmission Daemon
  2020-11-04 12:37 [bug#44435] [PATCH 0/1] services: Add Transmission Daemon Simon South
  2020-11-04 12:40 ` [bug#44435] [PATCH 1/1] services: Add transmission-daemon service Simon South
  2020-11-08 18:06 ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Simon South
@ 2020-12-05 15:27 ` Simon South
  2020-12-05 15:27   ` [bug#44435] [PATCH v3 1/1] services: Add transmission-daemon service Simon South
  2021-02-12  7:15   ` bug#44435: [PATCH v3 0/1] services: Add Transmission Daemon 宋文武
  2 siblings, 2 replies; 10+ messages in thread
From: Simon South @ 2020-12-05 15:27 UTC (permalink / raw)
  To: 44435; +Cc: Simon South

Here's a new version of this patch that incorporates feedback[0] from Ludovic:

- transmission-password-hash now signals an error in a more conventional
  manner, by raise'ing a &formatted-message condition, and uses Guile-GCrypt's
  "sha1" shorthand procedure along with string->utf8 for a more compact
  implementation.

- A missing pair of parentheses in transmission-daemon-shepherd-service has
  been restored.

- User-facing strings have been wrapped with "G_" and the file added to
  po/packages/POTFILES.in to permit internationalization. (I've done this for
  every string, not just the one in transmission-password-hash.)

I've also added a small test suite that exercises the password-related
procedures exported by the service module, using values captured from
transmission-daemon itself. I've placed the file in tests/services, though
it's not clear this is the right location; tests/networking.scm also pertains
to a service, for instance. I can re-submit with the file relocated if need
be.

Finally, despite my earlier bravado[1] I've held off adding any system tests,
only because I now realize getting the system test suite running on the
machines I have available is going to be a project in and of itself. I do
intend to add these at a later date.

[0] https://lists.gnu.org/archive/html/guix-patches/2020-11/msg00551.html
[1] https://lists.gnu.org/archive/html/guix-patches/2020-11/msg00557.html


For convenience, here's my original cover letter:

This patch adds a service type for Transmission Daemon, the headless variant
of the Transmission BitTorrent client (https://transmissionbt.com/). Running
the client as a service this way makes it possible to share files over
BitTorrent continuously without requiring a user be logged in.

I've tried to make this as complete as possible but am especially interested
in geting feedback as this is my first attempt at creating a service
definition. A few things to point out:

- I've placed the code in a new "(gnu services file-sharing)" module and the
  documentation in a new "File-Sharing Services" section of the manual, only
  because these names seemed the most natural to me. ("Peer-to-peer" would be
  too broad a categorization, I think, while "BitTorrent" too narrow.)

- The module exports two procedures, "transmission-password-hash" and
  "transmission-random-salt", that together are my solution to the problem of
  assigning a value to the daemon's "rpc-password" configuration setting.

  Transmission clients seem to expect the user to supply a password in
  plaintext in their "settings.json" file. At startup, the client generates a
  random, eight-character salt value; hashes it and the password together; and
  writes the result back to the settings file, after which the password
  remains obscured. This obviously violates the functional nature of Guix, as
  we don't expect services to be rewriting their own configuration files and
  the use of a random salt value makes the process non-repeatable anyway.

  I've documented in the manual how a user can use these two procedures to
  create a suitable value for "rpc-password" that remains stable across system
  reconfigurations, but perhaps you know of a better (or more conventional)
  approach.

- I've added a custom "stop" procedure to the Shepherd service that gives the
  daemon time to shut down before eventually killing its process. This is
  necessary since the daemon performs some housekeeping and sends a final
  update to BitTorrent trackers before it exits, which can take several
  seconds or more; without this code, restarting the service usually fails as
  the new daemon process finds the old one is still running and attached to
  the port used for peer connections.

  Again, the approach I've used to handle this seems reasonable to me but
  perhaps you know of something better.

--
Simon South
simon@simonsouth.net


Simon South (1):
  services: Add transmission-daemon service.

 Makefile.am                     |   1 +
 doc/guix.texi                   | 799 +++++++++++++++++++++++++++++++
 gnu/local.mk                    |   1 +
 gnu/services/file-sharing.scm   | 804 ++++++++++++++++++++++++++++++++
 po/packages/POTFILES.in         |   1 +
 tests/services/file-sharing.scm |  59 +++
 6 files changed, 1665 insertions(+)
 create mode 100644 gnu/services/file-sharing.scm
 create mode 100644 tests/services/file-sharing.scm

--
2.29.2





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [bug#44435] [PATCH v3 1/1] services: Add transmission-daemon service.
  2020-12-05 15:27 ` [bug#44435] [PATCH v3 " Simon South
@ 2020-12-05 15:27   ` Simon South
  2021-02-12  7:15   ` bug#44435: [PATCH v3 0/1] services: Add Transmission Daemon 宋文武
  1 sibling, 0 replies; 10+ messages in thread
From: Simon South @ 2020-12-05 15:27 UTC (permalink / raw)
  To: 44435; +Cc: Simon South

* 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.
---
 Makefile.am                     |   1 +
 doc/guix.texi                   | 799 +++++++++++++++++++++++++++++++
 gnu/local.mk                    |   1 +
 gnu/services/file-sharing.scm   | 804 ++++++++++++++++++++++++++++++++
 po/packages/POTFILES.in         |   1 +
 tests/services/file-sharing.scm |  59 +++
 6 files changed, 1665 insertions(+)
 create mode 100644 gnu/services/file-sharing.scm
 create mode 100644 tests/services/file-sharing.scm

diff --git a/Makefile.am b/Makefile.am
index 1a3ca227a4..a50540601d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -469,6 +469,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 a5c9779c86..57a2ad9ac7 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -14504,6 +14504,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.
@@ -21858,6 +21859,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 28ad119846..a61ac20854 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -600,6 +600,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")
+
+\f
+;;;
+;;; 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")
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 10+ messages in thread

* bug#44435: [PATCH v3 0/1] services: Add Transmission Daemon
  2020-12-05 15:27 ` [bug#44435] [PATCH v3 " Simon South
  2020-12-05 15:27   ` [bug#44435] [PATCH v3 1/1] services: Add transmission-daemon service Simon South
@ 2021-02-12  7:15   ` 宋文武
  1 sibling, 0 replies; 10+ messages in thread
From: 宋文武 @ 2021-02-12  7:15 UTC (permalink / raw)
  To: Simon South; +Cc: 44435-done

Simon South <simon@simonsouth.net> writes:

> Here's a new version of this patch that incorporates feedback[0] from Ludovic:
> [...]

I configure my system with this and test a torrent download.

Pushed, thank you for this patch and ludo for the review!




^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2021-02-12  7:15 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-04 12:37 [bug#44435] [PATCH 0/1] services: Add Transmission Daemon Simon South
2020-11-04 12:40 ` [bug#44435] [PATCH 1/1] services: Add transmission-daemon service Simon South
2020-11-07 14:12   ` Simon South
2020-11-08 18:06 ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Simon South
2020-11-08 18:06   ` [bug#44435] [PATCH v2 1/1] services: Add transmission-daemon service Simon South
2020-11-18 22:39   ` [bug#44435] [PATCH v2 0/1] services: Add Transmission Daemon Ludovic Courtès
2020-11-19  0:35     ` Simon South
2020-12-05 15:27 ` [bug#44435] [PATCH v3 " Simon South
2020-12-05 15:27   ` [bug#44435] [PATCH v3 1/1] services: Add transmission-daemon service Simon South
2021-02-12  7:15   ` bug#44435: [PATCH v3 0/1] services: Add Transmission Daemon 宋文武

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).