123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800 |
- ;;; 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 (maybe-value 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 (maybe-value-set? val)
- (serialize-file-object field-name val)
- (serialize-string field-name ""))))
- (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
- (file-like 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
- "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
- "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
- "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 leave it unset, 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
- "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
- "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
- "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
- "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))
|