1
Fork 0
mirror of https://https.git.savannah.gnu.org/git/guix.git/ synced 2025-07-15 11:30:44 +02:00

services: syncthing: Add support for declarative configuration.

* gnu/services/syncthing.scm: (syncthing-config-file, syncthing-folder,
syncthing-device, syncthing-folder-device): New records.
(syncthing-service-type): Add special-files-service-type extension for
the config file.
(syncthing-files-service): Add service to create config file.
* gnu/home/services/syncthing.scm: (home-syncthing-service-type):
Extend home-files-services-type and re-exported more things from
gnu/services/syncthing.scm.
* doc/guix.texi: (syncthing-service-type): Document changes.

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
Signed-off-by: Leo Famulari <leo@famulari.name>
This commit is contained in:
Zacchaeus 2025-02-16 12:56:16 -05:00 committed by Leo Famulari
parent ad74dedb9f
commit 651f8765b6
No known key found for this signature in database
GPG key ID: 6AAC1963757F47FF
3 changed files with 838 additions and 33 deletions

View file

@ -137,6 +137,7 @@ Copyright @copyright{} 2024 Sharlatan Hellseher@*
Copyright @copyright{} 2024 45mg@*
Copyright @copyright{} 2025 Sören Tempel@*
Copyright @copyright{} 2025 Rostislav Svoboda@*
Copyright @copyright{} 2025 Zacchaeus@*
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or
@ -22738,7 +22739,7 @@ client.
The @code{(gnu services syncthing)} module provides the following services:
@cindex syncthing
You might want a syncthing daemon if you have files between two or more
You might want a Syncthing daemon if you have files between two or more
computers and want to sync them in real time, safely protected from
prying eyes.
@ -22784,12 +22785,343 @@ The group as which the Syncthing service is to be run.
This assumes that the specified group exists.
@item @code{home} (default: @var{#f})
Common configuration and data directory. The default configuration
directory is @file{$HOME} of the specified Syncthing @code{user}.
Sets the @code{HOME} variable for the Syncthing daemon. The default is
@file{$HOME} of the specified Syncthing @code{user}.
@item @code{config-file} (default: @var{#f})
Either a file-like object that resolves to a Syncthing configuration XML
file, or a @code{syncthing-config-file} record (see below). If set to
@code{#f}, Guix will not try to generate a config file, and Syncthing
will generate a default configuration which will not be touched on
reconfigure. Specifying this in a system service moves Syncthing's
common configuration and data directory (@code{--home} in
@uref{https://docs.syncthing.net/users/syncthing.html}) to
@file{/var/lib/syncthing-<user>}.
@end table
@end deftp
This section documents a subset of the Syncthing configuration
options—specifically those related to Guix or those affecting how your
computer will connect to other computers over the network (such as
Syncthing relays or discovery servers). The configuration is fully
documented in the upstream
@uref{https://docs.syncthing.net/users/config.html, Syncthing config
documentation}; camelCase there is converted to kebab-case here. If you
are migrating from a Syncthing-managed configuration to one managed by
Guix, you can check what changes were introduced by @code{diff}ing the
respective @file{config.xml} files. Note that you will need to add
whitespace with 4-space indentation to the file generated by Guix, using
the @code{xmllint} program from the @code{libxml2} package like so:
@example
XMLLINT_INDENT=" " xmllint --format /path/to/new/config.xml | diff /path/to/old/config.xml -
@end example
When generating a configuration file through Guix, you can still
temporarily modify Syncthing from the GUI or through @code{introducer}
and @code{autoAcceptFolders} mechanisms, but such changes will be reset
on reconfigure.
@deftp {Data Type} syncthing-config-file
Data type representing the configuration file read by the Syncthing
daemon.
@table @asis
@item @code{folders} (default: @var{(list (syncthing-folder (id "default") (label "Default Folder") (path "~/Sync")))}
The default here is the same as Syncthing's default. The value should
be a list of @code{syncthing-folder}s.
@item @code{devices} (default: @var{'()}
This should be a list of @code{syncthing-device}s. Guix will
automatically add any devices specified in any `folders' to this list.
There are instances when you want to connect to a device despite not
(initially) sharing any folders (such as a device with
autoAcceptFolders). In such instances, you should specify those devices
here. If multiple versions of the same device (as determined by
comparing device ID) are discovered, the one in this list is
prioritized. Otherwise, the first instance in the first folder is used.
@item @code{gui-enabled} (default: @var{"true"})
By default, any user on the computer can access the GUI and make changes
to Syncthing. If you leave this enabled, you should probably set
@code{gui-user} and @code{gui-password} (see below).
@item @code{gui-tls} (default: @var{"false"})
@item @code{gui-debugging} (default: @var{"false"})
@item @code{gui-send-basic-auth-prompt} (default: @var{"false"})
@item @code{gui-address} (default: @var{"127.0.0.1:8384"})
@item @code{gui-user} (default: @var{#f})
@item @code{gui-password} (default: @var{#f})
A bcrypt hash of the GUI password. Remember that this will be globally
exposed in @file{/gnu/store}.
@item @code{gui-apikey} (default: @var{#f})
You must specify this to use the Syncthing REST interface. This key is
kept in @file{/gnu/store} and is accessible to all users of the system.
@item @code{gui-theme} (default: @var{"default"})
@item @code{ldap-enabled} (default: @var{#f})
@item @code{ldap-address} (default: @var{""})
@item @code{ldap-bind-dn} (default: @var{""})
@item @code{ldap-transport} (default: @var{""})
@item @code{ldap-insecure-skip-verify} (default: @var{""})
@item @code{ldap-search-base-dn} (default: @var{""})
@item @code{ldap-search-filter} (default: @var{""})
@item @code{listen-address} (default: @var{"default"})
@item @code{global-announce-server} (default: @var{"default"})
@item @code{global-announce-enabled} (default: @var{"true"})
Global discovery servers can be used to help connect devices at unknown
IP addresses by storing the last known IP address.
@item @code{local-announce-enabled} (default: @var{"true"})
This makes devices find each other very easily on the same LAN. Often,
this will allow you to just plug an Ethernet between two devices, or
connect one device to the other's hotspot and start syncing.
@item @code{local-announce-port} (default: @var{"21027"})
@item @code{local-announce-mcaddr} (default: @var{"[ff12::8384]:21027"})
@item @code{max-send-kbps} (default: @var{"0"})
@item @code{max-recv-kbps} (default: @var{"0"})
@item @code{reconnection-interval-s} (default: @var{"60"})
@item @code{relays-enabled} (default: @var{"true"})
This option allows your Syncthing instance to use a global network of
@uref{https://docs.syncthing.net/users/relaying.html, relays} to enable
syncing between devices when all other methods fail. As always,
Syncthing traffic is encrypted in transport and the relays are unable to
decrypt it.
@item @code{relay-reconnect-interval-m} (default: @var{"10"})
@item @code{start-browser} (default: @var{"true"})
@item @code{nat-enabled} (default: @var{"true"})
@item @code{nat-lease-minutes} (default: @var{"60"})
@item @code{nat-renewal-minutes} (default: @var{"30"})
@item @code{nat-timeout-seconds} (default: @var{"10"})
@item @code{ur-accepted} (default: @var{"0"})
Options whose names begin with `ur-' control usage reporting. Set to -1
to disable, or to a positive value to enable. The default (0) disables
reporting, but causes a usage reporting consent prompt to be displayed
in the Syncthing GUI.
@item @code{ur-seen} (default: @var{"0"})
@item @code{ur-unique-id} (default: @var{""})
@item @code{ur-url} (default: @var{"https://data.syncthing.net/newdata"})
@item @code{ur-post-insecurely} (default: @var{"false"})
@item @code{ur-initial-delay-s} (default: @var{"1800"})
@item @code{auto-upgrade-interval-h} (default: @var{"12"})
@item @code{upgrade-to-pre-releases} (default: @var{"false"})
@item @code{keep-temporaries-h} (default: @var{"24"})
@item @code{cache-ignored-files} (default: @var{"false"})
@item @code{progress-update-interval-s} (default: @var{"5"})
@item @code{limit-bandwidth-in-lan} (default: @var{"false"})
@item @code{min-home-disk-free-unit} (default: @var{"%"})
@item @code{min-home-disk-free} (default: @var{"1"})
@item @code{releases-url} (default: @var{"https://upgrades.syncthing.net/meta.json"})
@item @code{overwrite-remote-device-names-on-connect} (default: @var{"false"})
@item @code{temp-index-min-blocks} (default: @var{"10"})
@item @code{unacked-notification-id} (default: @var{"authenticationUserAndPassword"})
@item @code{traffic-class} (default: @var{"0"})
@item @code{set-low-priority} (default: @var{"true"})
@item @code{max-folder-concurrency} (default: @var{"0"})
@item @code{crash-reporting-url} (default: @var{"https://crash.syncthing.net/newcrash"})
@item @code{crash-reporting-enabled} (default: @var{"true"})
@item @code{stun-keepalive-start-s} (default: @var{"180"})
@item @code{stun-keepalive-min-s} (default: @var{"20"})
@item @code{stun-server} (default: @var{"default"})
@item @code{database-tuning} (default: @var{"auto"})
@item @code{max-concurrent-incoming-request-kib} (default: @var{"0"})
@item @code{announce-lan-addresses} (default: @var{"true"})
@item @code{send-full-index-on-upgrade} (default: @var{"false"})
@item @code{connection-limit-enough} (default: @var{"0"})
@item @code{connection-limit-max} (default: @var{"0"})
@item @code{insecure-allow-old-tls-versions} (default: @var{"false"})
@item @code{connection-priority-tcp-lan} (default: @var{"10"})
@item @code{connection-priority-quic-lan} (default: @var{"20"})
@item @code{connection-priority-tcp-wan} (default: @var{"30"})
@item @code{connection-priority-quic-wan} (default: @var{"40"})
@item @code{connection-priority-relay} (default: @var{"50"})
@item @code{connection-priority-upgrade-threshold} (default: @var{"0"})
@item @code{default-folder} (default: @var{(syncthing-folder (label ""))})
@item @code{default-device} (default: @var{(syncthing-device (id ""))})
@item @code{default-ignores} (default: @var{"")})
Options whose names begin with `default-' above do not affect folders
and devices added through the Guix configuration interface. They will,
however, affect folders and devices that are added through the Syncthing
GUI, by an @code{introducer}, or a device with
@code{auto-accept-folders}.
@end table
@end deftp
@deftp {Data Type} syncthing-folder
Data type representing a folder to be synchronized.
@table @asis
@item @code{id} (default: @var{#f})
This ID cannot match the ID of any other folder on this device. If left
unspecified, it will default to the label (see below).
@item @code{label}
A human readable label for the folder.
@item @code{path}
The path at which to store this folder.
@item @code{type} (default: @var{"sendreceive"})
@item @code{rescan-interval-s} (default: @var{"3600"})
@item @code{fs-watcher-enabled} (default: @var{"true"})
@item @code{fs-watcher-delay-s} (default: @var{"10"})
@item @code{ignore-perms} (default: @var{"false"})
@item @code{auto-normalize} (default: @var{"true"})
@item @code{devices} (default: @var{'()})
This should be a list of other Syncthing devices. You do not need to
specify the current device. Each device can be listed as a a
@code{syncthing-device} record or a @code{syncthing-folder-device}
record if you want files to be encrypted on disk. See below.
@item @code{filesystem-type} (default: @var{"basic"})
@item @code{min-disk-free-unit} (default: @var{"%"})
@item @code{min-disk-free} (default: @var{"1"})
@item @code{versioning-type} (default: @var{#f})
@item @code{versioning-fs-path} (default: @var{""})
@item @code{versioning-fs-type} (default: @var{"basic"})
@item @code{versioning-cleanup-interval-s} (default: @var{"3600"})
@item @code{versioning-cleanout-days} (default: @var{#f})
@item @code{versioning-keep} (default: @var{#f})
@item @code{versioning-max-age} (default: @var{#f})
@item @code{versioning-command} (default: @var{#f})
@item @code{copiers} (default: @var{"0"})
@item @code{puller-max-pending-kib} (default: @var{"0"})
@item @code{hashers} (default: @var{"0"})
@item @code{order} (default: @var{"random"})
@item @code{ignore-delete} (default: @var{"false"})
@item @code{scan-progress-interval-s} (default: @var{"0"})
@item @code{puller-pause-s} (default: @var{"0"})
@item @code{max-conflicts} (default: @var{"10"})
@item @code{disable-sparse-files} (default: @var{"false"})
@item @code{disable-temp-indexes} (default: @var{"false"})
@item @code{paused} (default: @var{"false"})
@item @code{weak-hash-threshold-pct} (default: @var{"25"})
@item @code{marker-name} (default: @var{".stfolder"})
@item @code{copy-ownership-from-parent} (default: @var{"false"})
@item @code{mod-time-window-s} (default: @var{"0"})
@item @code{max-concurrent-writes} (default: @var{"2"})
@item @code{disable-fsync} (default: @var{"false"})
@item @code{block-pull-order} (default: @var{"standard"})
@item @code{copy-range-method} (default: @var{"standard"})
@item @code{case-sensitive-fs} (default: @var{"false"})
@item @code{junctions-as-dirs} (default: @var{"false"})
@item @code{sync-ownership} (default: @var{"false"})
@item @code{send-ownership} (default: @var{"false"})
@item @code{sync-xattrs} (default: @var{"false"})
@item @code{send-xattrs} (default: @var{"false"})
@item @code{xattr-filter-max-single-entry-size} (default: @var{"1024"})
@item @code{xattr-filter-max-total-size} (default: @var{"4096")})
@end table
@end deftp
@deftp {Data Type} syncthing-device
Data type representing a device to synchronize folders with.
@table @asis
@item @code{id}
A long hash representing the keys generated by Syncthing on the first
launch. You can obtain this from the Syncthing GUI or by inspecting an
existing Syncthing configuration file.
@item @code{name} (default: @var{""})
A human readable device name for viewing in the GUI or in Scheme.
@item @code{compression} (default: @var{"metadata"})
@item @code{introducer} (default: @var{"false"})
@item @code{skip-introduction-removals} (default: @var{"false"})
@item @code{introduced-by} (default: @var{""})
@item @code{addresses} (default: @var{'("dynamic")})
List of addresses at which to search for this device. When the special
value ``dynamic'' is included, Syncthing will search for the device
locally as well as via the Syncthing project's
@uref{https://docs.syncthing.net/users/security.html#global-discovery,
global discovery} servers.
@item @code{paused} (default: @var{"false"})
@item @code{auto-accept-folders} (default: @var{"false"})
@item @code{max-send-kbps} (default: @var{"0"})
@item @code{max-recv-kbps} (default: @var{"0"})
@item @code{max-request-kib} (default: @var{"0"})
@item @code{untrusted} (default: @var{"false"})
@item @code{remote-gui-port} (default: @var{"0"})
@item @code{num-connections} (default: @var{"0")})
@end table
@end deftp
@deftp {Data Type} syncthing-folder-device
This data type offers two folder-specific device options:
@code{introduced-by} and @code{encryption-password}.
@code{syncthing-folder-device} corresponds to the
@uref{https://docs.syncthing.net/users/config.html#config-option-folder.device,
`device'} option in the upstream `folder' element.
If you don't need to use these options, then you can just use
@code{syncthing-device} instead of @code{syncthing-folder-device} in the
@code{devices} field of a @code{syncthing-folder} instance.
@table @asis
@item @code{device}
The @code{syncthing-device} for which this configuration applies.
@item @code{introduced-by} (default: @var{""})
The name of the device that "introduced" our device to the device
sharing this folder. This is only used when "introduced" devices are
removed by the introducer. See
@uref{https://docs.syncthing.net/users/introducer.html, Syncthing
introductions}.
@item @code{encryption-password} (default: @var{""})
The password used to encrypt data that is synchronized to untrusted
devices.
Beware: specifying this field will include this password as plain text
(not encrypted) and globally visible in @file{/gnu/store/}. If the
encryption-password is non-empty, then it will be used as a password to
encrypt file chunks as they are synchronized to untrusted devices. For
more information on syncing to devices you don't totally trust, see
Syncthing's documentation on
@uref{https://docs.syncthing.net/users/untrusted.html, Untrusted
(Encrypted) Devices}. Note that data transfer is always encrypted while
in transport ("end-to-end encryption"), regardless of this setting.
@end table
@end deftp
Here is a more complex example configuration for illustrative purposes:
@lisp
(service syncthing-service-type
(let ((laptop (syncthing-device (id "VHOD2D6-...-7XRMDEN")))
(desktop (syncthing-device (id "64SAZ37-...-FZJ5GUA")
(addresses '("tcp://example.com"))))
(bob-desktop (syncthing-device (id "KYIMEGO-...-FT77EAO"))))
(syncthing-configuration
(user "alice")
(config-file
(syncthing-config-file
(folders (list (syncthing-folder
(label "some-files")
(path "~/data")
(devices (list desktop laptop)))
(syncthing-folder
(label "critical-files")
(path "~/secrets")
(devices
(list desktop
laptop
(syncthing-folder-device
(device bob-desktop)
(encryption-password "mypassword"))))))))))))
@end lisp
Furthermore, @code{(gnu services ssh)} provides the following services.
@cindex SSH
@cindex SSH server

View file

@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2023 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2025 Zacchaeus <eikcaz@zacchae.us>
;;;
;;; This file is part of GNU Guix.
;;;
@ -24,9 +25,23 @@
#:use-module (gnu home services shepherd)
#:export (home-syncthing-service-type)
#:re-export (syncthing-configuration
syncthing-configuration?))
syncthing-configuration?
syncthing-config-file
syncthing-config-file?
syncthing-device
syncthing-device?
syncthing-folder
syncthing-folder?
syncthing-folder-device
syncthing-folder-device?))
(define home-syncthing-service-type
(service-type
(inherit (system->home-service-type syncthing-service-type))
;; system->home-service-type does not convert special-files-service-type to
;; home-files-service-type, so redefine extensios
(extensions (list (service-extension home-files-service-type
syncthing-files-service)
(service-extension home-shepherd-service-type
syncthing-shepherd-service)))
(default-value (for-home (syncthing-configuration)))))

View file

@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Oleg Pykhalov <go.wigust@gmail.com>
;;; Copyright © 2023 Justin Veilleux <terramorpha@cock.li>
;;; Copyright © 2025 Zacchaeus <eikcaz@zacchae.us>
;;;
;;; This file is part of GNU Guix.
;;;
@ -25,9 +26,20 @@
#:use-module (guix records)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (sxml simple)
#:export (syncthing-configuration
syncthing-configuration?
syncthing-service-type))
syncthing-device
syncthing-device?
syncthing-config-file
syncthing-config-file?
syncthing-folder-device
syncthing-folder-device?
syncthing-folder
syncthing-folder?
syncthing-service-type
syncthing-shepherd-service
syncthing-files-service))
;;; Commentary:
;;;
@ -35,6 +47,414 @@
;;;
;;; Code:
(define-record-type* <syncthing-device>
syncthing-device make-syncthing-device
syncthing-device?
(id syncthing-device-id)
(name syncthing-device-name (default ""))
(compression syncthing-device-compression (default "metadata"))
(introducer syncthing-device-introducer (default "false"))
(skip-introduction-removals syncthing-device-skip-introduction-removals (default "false"))
(introduced-by syncthing-device-introduced-by (default ""))
(addresses syncthing-device-addresses (default '("dynamic")))
(paused syncthing-device-paused (default "false"))
(auto-accept-folders syncthing-device-auto-accept-folders (default "false"))
(max-send-kbps syncthing-device-max-send-kbps (default "0"))
(max-recv-kbps syncthing-device-max-recv-kbps (default "0"))
(max-request-kib syncthing-device-max-request-kib (default "0"))
(untrusted syncthing-device-untrusted (default "false"))
(remote-gui-port syncthing-device-remote-gui-port (default "0"))
(num-connections syncthing-device-num-connections (default "0")))
(define syncthing-device->sxml
(match-record-lambda <syncthing-device>
(id
name compression introducer skip-introduction-removals introduced-by
addresses paused auto-accept-folders max-send-kbps max-recv-kbps
max-request-kib untrusted remote-gui-port num-connections)
`(device (@ (id ,id)
(name ,name)
(compression ,compression)
(introducer ,introducer)
(skipIntroductionRemovals ,skip-introduction-removals)
(introducedBy ,introduced-by))
,@(map (lambda (address) `(address ,address)) addresses)
(paused ,paused)
(autoAcceptFolders ,auto-accept-folders)
(maxSendKbps ,max-send-kbps)
(maxRecvKbps ,max-recv-kbps)
(maxRequestKiB ,max-request-kib)
(untrusted ,untrusted)
(remoteGUIPort ,remote-gui-port)
(numConnections ,num-connections))))
(define-record-type* <syncthing-folder-device>
syncthing-folder-device make-syncthing-folder-device
syncthing-folder-device?
(device syncthing-folder-device-device)
(introduced-by syncthing-folder-device-introduced-by (default (syncthing-device (id ""))))
(encryption-password syncthing-folder-device-encryption-password (default "")))
(define syncthing-folder-device->sxml
(match-record-lambda <syncthing-folder-device>
(device introduced-by encryption-password)
`(device (@ (id ,(syncthing-device-id device))
(introducedBy ,(syncthing-device-id introduced-by)))
(encryptionPassword ,encryption-password))))
(define-record-type* <syncthing-folder>
syncthing-folder make-syncthing-folder
syncthing-folder?
(id syncthing-folder-id (default #f))
(label syncthing-folder-label)
(path syncthing-folder-path)
(type syncthing-folder-type (default "sendreceive"))
(rescan-interval-s syncthing-folder-rescan-interval-s (default "3600"))
(fs-watcher-enabled syncthing-folder-fs-watcher-enabled (default "true"))
(fs-watcher-delay-s syncthing-folder-fs-watcher-delay-s (default "10"))
(fs-watcher-timeout-s syncthing-folder-fs-watcher-timeout-s (default "0"))
(ignore-perms syncthing-folder-ignore-perms (default "false"))
(auto-normalize syncthing-folder-auto-normalize (default "true"))
(devices syncthing-folder-devices (default '())
(sanitize (lambda (folder-device-list)
(map (lambda (device)
(if (syncthing-folder-device? device)
device
(syncthing-folder-device (device device))))
folder-device-list))))
(filesystem-type syncthing-folder-filesystem-type (default "basic"))
(min-disk-free-unit syncthing-folder-min-disk-free-unit (default "%"))
(min-disk-free syncthing-folder-min-disk-free (default "1"))
(versioning-type syncthing-folder-versioning-type (default #f))
(versioning-fs-path syncthing-folder-versioning-fs-path (default ""))
(versioning-fs-type syncthing-folder-versioning-fs-type (default "basic"))
(versioning-cleanup-interval-s syncthing-folder-versioning-cleanup-interval-s (default "3600"))
(versioning-cleanout-days syncthing-folder-versioning-cleanout-days (default #f))
(versioning-keep syncthing-folder-versioning-keep (default #f))
(versioning-max-age syncthing-folder-versioning-max-age (default #f))
(versioning-command syncthing-folder-versioning-command (default #f))
(copiers syncthing-folder-copiers (default "0"))
(puller-max-pending-kib syncthing-folder-puller-max-pending-kib (default "0"))
(hashers syncthing-folder-hashers (default "0"))
(order syncthing-folder-order (default "random"))
(ignore-delete syncthing-folder-ignore-delete (default "false"))
(scan-progress-interval-s syncthing-folder-scan-progress-interval-s (default "0"))
(puller-pause-s syncthing-folder-puller-pause-s (default "0"))
(max-conflicts syncthing-folder-max-conflicts (default "10"))
(disable-sparse-files syncthing-folder-disable-sparse-files (default "false"))
(disable-temp-indexes syncthing-folder-disable-temp-indexes (default "false"))
(paused syncthing-folder-paused (default "false"))
(weak-hash-threshold-pct syncthing-folder-weak-hash-threshold-pct (default "25"))
(marker-name syncthing-folder-marker-name (default ".stfolder"))
(copy-ownership-from-parent syncthing-folder-copy-ownership-from-parent (default "false"))
(mod-time-window-s syncthing-folder-mod-time-window-s (default "0"))
(max-concurrent-writes syncthing-folder-max-concurrent-writes (default "2"))
(disable-fsync syncthing-folder-disable-fsync (default "false"))
(block-pull-order syncthing-folder-block-pull-order (default "standard"))
(copy-range-method syncthing-folder-copy-range-method (default "standard"))
(case-sensitive-fs syncthing-folder-case-sensitive-fs (default "false"))
(junctions-as-dirs syncthing-folder-junctions-as-dirs (default "false"))
(sync-ownership syncthing-folder-sync-ownership (default "false"))
(send-ownership syncthing-folder-send-ownership (default "false"))
(sync-xattrs syncthing-folder-sync-xattrs (default "false"))
(send-xattrs syncthing-folder-send-xattrs (default "false"))
(xattr-filter-max-single-entry-size syncthing-folder-xattr-filter-max-single-entry-size (default "1024"))
(xattr-filter-max-total-size syncthing-folder-xattr-filter-max-total-size (default "4096")))
;; Some parameters, when empty, are fully omitted from the config file. It is
;; unknown if this causes a functional difference, but stick to the normal
;; program's behavior to be safe.
(define (maybe-param symbol value)
(if value `((param (@ (key ,(symbol->string symbol)) (val ,value)) "")) '()))
(define syncthing-folder->sxml
(match-record-lambda <syncthing-folder>
(id
label path type rescan-interval-s fs-watcher-enabled fs-watcher-delay-s
fs-watcher-timeout-s ignore-perms auto-normalize devices filesystem-type
min-disk-free-unit min-disk-free versioning-type versioning-fs-path
versioning-fs-type versioning-cleanup-interval-s versioning-cleanout-days
versioning-keep versioning-max-age versioning-command copiers
puller-max-pending-kib hashers order ignore-delete scan-progress-interval-s
puller-pause-s max-conflicts disable-sparse-files disable-temp-indexes paused
weak-hash-threshold-pct marker-name copy-ownership-from-parent mod-time-window-s
max-concurrent-writes disable-fsync block-pull-order copy-range-method
case-sensitive-fs junctions-as-dirs sync-ownership send-ownership sync-xattrs
send-xattrs xattr-filter-max-single-entry-size xattr-filter-max-total-size)
`(folder (@ (id ,(if id id label))
(label ,label)
(path ,path)
(type ,type)
(rescanIntervalS ,rescan-interval-s)
(fsWatcherEnabled ,fs-watcher-enabled)
(fsWatcherDelayS ,fs-watcher-delay-s)
(fsWatcherTimeoutS ,fs-watcher-timeout-s)
(ignorePerms ,ignore-perms)
(autoNormalize ,auto-normalize))
(filesystemType ,filesystem-type)
,@(map syncthing-folder-device->sxml
devices)
(minDiskFree (@ (unit ,min-disk-free-unit))
,min-disk-free)
(versioning ,@(if versioning-type
`((@ (type ,versioning-type)))
'())
,@(maybe-param 'cleanoutDays versioning-cleanout-days)
,@(maybe-param 'keep versioning-keep)
,@(maybe-param 'maxAge versioning-max-age)
,@(maybe-param 'command versioning-command)
(cleanupIntervalS ,versioning-cleanup-interval-s)
(fsPath ,versioning-fs-path)
(fsType ,versioning-fs-type))
(copiers ,copiers)
(pullerMaxPendingKiB ,puller-max-pending-kib)
(hashers ,hashers)
(order ,order)
(ignoreDelete ,ignore-delete)
(scanProgressIntervalS ,scan-progress-interval-s)
(pullerPauseS ,puller-pause-s)
(maxConflicts ,max-conflicts)
(disableSparseFiles ,disable-sparse-files)
(disableTempIndexes ,disable-temp-indexes)
(paused ,paused)
(weakHashThresholdPct ,weak-hash-threshold-pct)
(markerName ,marker-name)
(copyOwnershipFromParent ,copy-ownership-from-parent)
(modTimeWindowS ,mod-time-window-s)
(maxConcurrentWrites ,max-concurrent-writes)
(disableFsync ,disable-fsync)
(blockPullOrder ,block-pull-order)
(copyRangeMethod ,copy-range-method)
(caseSensitiveFS ,case-sensitive-fs)
(junctionsAsDirs ,junctions-as-dirs)
(syncOwnership ,sync-ownership)
(sendOwnership ,send-ownership)
(syncXattrs ,sync-xattrs)
(sendXattrs ,send-xattrs)
(xattrFilter (maxSingleEntrySize ,xattr-filter-max-single-entry-size)
(maxTotalSize ,xattr-filter-max-total-size)))))
(define-record-type* <syncthing-config-file>
syncthing-config-file make-syncthing-config-file
syncthing-config-file?
(folders syncthing-config-folders
; this matches syncthing's default
(default (list (syncthing-folder (id "default")
(label "Default Folder")
(path "~/Sync")))))
(devices syncthing-config-devices
(default '()))
(gui-enabled syncthing-config-gui-enabled (default "true"))
(gui-tls syncthing-config-gui-tls (default "false"))
(gui-debugging syncthing-config-gui-debugging (default "false"))
(gui-send-basic-auth-prompt syncthing-config-gui-send-basic-auth-prompt (default "false"))
(gui-address syncthing-config-gui-address (default "127.0.0.1:8384"))
(gui-user syncthing-config-gui-user (default #f))
(gui-password syncthing-config-gui-password (default #f))
(gui-apikey syncthing-config-gui-apikey (default #f))
(gui-theme syncthing-config-gui-theme (default "default"))
(ldap-enabled syncthing-config-ldap-enabled (default #f))
(ldap-address syncthing-config-ldap-address (default ""))
(ldap-bind-dn syncthing-config-ldap-bind-dn (default ""))
(ldap-transport syncthing-config-ldap-transport (default ""))
(ldap-insecure-skip-verify syncthing-config-ldap-insecure-skip-verify (default ""))
(ldap-search-base-dn syncthing-config-ldap-search-base-dn (default ""))
(ldap-search-filter syncthing-config-ldap-search-filter (default ""))
(listen-address syncthing-config-listen-address (default "default"))
(global-announce-server syncthing-config-global-announce-server (default "default"))
(global-announce-enabled syncthing-config-global-announce-enabled (default "true"))
(local-announce-enabled syncthing-config-local-announce-enabled (default "true"))
(local-announce-port syncthing-config-local-announce-port (default "21027"))
(local-announce-mcaddr syncthing-config-local-announce-mcaddr (default "[ff12::8384]:21027"))
(max-send-kbps syncthing-config-max-send-kbps (default "0"))
(max-recv-kbps syncthing-config-max-recv-kbps (default "0"))
(reconnection-interval-s syncthing-config-reconnection-interval-s (default "60"))
(relays-enabled syncthing-config-relays-enabled (default "true"))
(relay-reconnect-interval-m syncthing-config-relay-reconnect-interval-m (default "10"))
(start-browser syncthing-config-start-browser (default "true"))
(nat-enabled syncthing-config-nat-enabled (default "true"))
(nat-lease-minutes syncthing-config-nat-lease-minutes (default "60"))
(nat-renewal-minutes syncthing-config-nat-renewal-minutes (default "30"))
(nat-timeout-seconds syncthing-config-nat-timeout-seconds (default "10"))
(ur-accepted syncthing-config-ur-accepted (default "0"))
(ur-seen syncthing-config-ur-seen (default "0"))
(ur-unique-id syncthing-config-ur-unique-id (default ""))
(ur-url syncthing-config-ur-url (default "https://data.syncthing.net/newdata"))
(ur-post-insecurely syncthing-config-ur-post-insecurely (default "false"))
(ur-initial-delay-s syncthing-config-ur-initial-delay-s (default "1800"))
(auto-upgrade-interval-h syncthing-config-auto-upgrade-interval-h (default "12"))
(upgrade-to-pre-releases syncthing-config-upgrade-to-pre-releases (default "false"))
(keep-temporaries-h syncthing-config-keep-temporaries-h (default "24"))
(cache-ignored-files syncthing-config-cache-ignored-files (default "false"))
(progress-update-interval-s syncthing-config-progress-update-interval-s (default "5"))
(limit-bandwidth-in-lan syncthing-config-limit-bandwidth-in-lan (default "false"))
(min-home-disk-free-unit syncthing-config-min-home-disk-free-unit (default "%"))
(min-home-disk-free syncthing-config-min-home-disk-free (default "1"))
(releases-url syncthing-config-releases-url (default "https://upgrades.syncthing.net/meta.json"))
(overwrite-remote-device-names-on-connect syncthing-config-overwrite-remote-device-names-on-connect (default "false"))
(temp-index-min-blocks syncthing-config-temp-index-min-blocks (default "10"))
(unacked-notification-id syncthing-config-unacked-notification-id (default "authenticationUserAndPassword"))
(traffic-class syncthing-config-traffic-class (default "0"))
(set-low-priority syncthing-config-set-low-priority (default "true"))
(max-folder-concurrency syncthing-config-max-folder-concurrency (default "0"))
(crash-reporting-url syncthing-config-crash-reporting-url (default "https://crash.syncthing.net/newcrash"))
(crash-reporting-enabled syncthing-config-crash-reporting-enabled (default "true"))
(stun-keepalive-start-s syncthing-config-stun-keepalive-start-s (default "180"))
(stun-keepalive-min-s syncthing-config-stun-keepalive-min-s (default "20"))
(stun-server syncthing-config-stun-server (default "default"))
(database-tuning syncthing-config-database-tuning (default "auto"))
(max-concurrent-incoming-request-kib syncthing-config-max-concurrent-incoming-request-kib (default "0"))
(announce-lan-addresses syncthing-config-announce-lan-addresses (default "true"))
(send-full-index-on-upgrade syncthing-config-send-full-index-on-upgrade (default "false"))
(connection-limit-enough syncthing-config-connection-limit-enough (default "0"))
(connection-limit-max syncthing-config-connection-limit-max (default "0"))
(insecure-allow-old-tlsVersions syncthing-config-insecure-allow-old-tlsVersions (default "false"))
(connection-priority-tcp-lan syncthing-config-connection-priority-tcp-lan (default "10"))
(connection-priority-quic-lan syncthing-config-connection-priority-quic-lan (default "20"))
(connection-priority-tcp-wan syncthing-config-connection-priority-tcp-wan (default "30"))
(connection-priority-quic-wan syncthing-config-connection-priority-quic-wan (default "40"))
(connection-priority-relay syncthing-config-connection-priority-relay (default "50"))
(connection-priority-upgrade-threshold syncthing-config-connection-priority-upgrade-threshold (default "0"))
(default-folder syncthing-config-default-folder
(default (syncthing-folder (label "") (path "~"))))
(default-device syncthing-config-default-device
(default (syncthing-device (id ""))))
(default-ignores syncthing-config-default-ignores (default "")))
(define syncthing-config-file->sxml
(match-record-lambda <syncthing-config-file>
(folders
devices gui-enabled gui-tls gui-debugging gui-send-basic-auth-prompt
gui-address gui-user gui-password gui-apikey gui-theme ldap-enabled
ldap-address ldap-bind-dn ldap-transport ldap-insecure-skip-verify
ldap-search-base-dn ldap-search-filter listen-address global-announce-server
global-announce-enabled local-announce-enabled local-announce-port
local-announce-mcaddr max-send-kbps max-recv-kbps reconnection-interval-s
relays-enabled relay-reconnect-interval-m start-browser nat-enabled
nat-lease-minutes nat-renewal-minutes nat-timeout-seconds ur-accepted
ur-seen ur-unique-id ur-url ur-post-insecurely ur-initial-delay-s
auto-upgrade-interval-h upgrade-to-pre-releases keep-temporaries-h
cache-ignored-files progress-update-interval-s limit-bandwidth-in-lan
min-home-disk-free-unit min-home-disk-free releases-url
overwrite-remote-device-names-on-connect temp-index-min-blocks
unacked-notification-id traffic-class set-low-priority max-folder-concurrency
crash-reporting-url crash-reporting-enabled stun-keepalive-start-s
stun-keepalive-min-s stun-server database-tuning
max-concurrent-incoming-request-kib announce-lan-addresses
send-full-index-on-upgrade connection-limit-enough connection-limit-max
insecure-allow-old-tlsVersions connection-priority-tcp-lan
connection-priority-quic-lan connection-priority-tcp-wan
connection-priority-quic-wan connection-priority-relay
connection-priority-upgrade-threshold default-folder default-device
default-ignores)
`(configuration (@ (version "37"))
,@(map syncthing-folder->sxml
folders)
;; collect any devices in any folders, as well as any
;; devices explicitly added.
,@(map syncthing-device->sxml
(delete-duplicates
(append devices
(apply append
(map (lambda (folder)
(map syncthing-folder-device-device
(syncthing-folder-devices folder)))
folders)))
;; devices are the same if their id's are equal
(lambda (device1 device2)
(string= (syncthing-device-id device1)
(syncthing-device-id device2)))))
(gui (@ (enabled ,gui-enabled)
(tls ,gui-tls)
(debugging ,gui-debugging)
(sendBasicAuthPrompt ,gui-send-basic-auth-prompt))
(address ,gui-address)
,@(if gui-user `((user ,gui-user)) '())
,@(if gui-password `((password ,gui-password)) '())
,@(if gui-apikey `((apikey ,gui-apikey)) '())
(theme ,gui-theme))
(ldap ,(if ldap-enabled
`((address ,ldap-address)
(bindDN ,ldap-bind-dn)
,@(if ldap-transport
`((transport ,ldap-transport))
'())
,@(if ldap-insecure-skip-verify
`((insecureSkipVerify ,ldap-insecure-skip-verify))
'())
,@(if ldap-search-base-dn
`((searchBaseDN ,ldap-search-base-dn))
'())
,@(if ldap-search-filter
`((searchFilter ,ldap-search-filter))
'()))
""))
(options (listenAddress ,listen-address)
(globalAnnounceServer ,global-announce-server)
(globalAnnounceEnabled ,global-announce-enabled)
(localAnnounceEnabled ,local-announce-enabled)
(localAnnouncePort ,local-announce-port)
(localAnnounceMCAddr ,local-announce-mcaddr)
(maxSendKbps ,max-send-kbps)
(maxRecvKbps ,max-recv-kbps)
(reconnectionIntervalS ,reconnection-interval-s)
(relaysEnabled ,relays-enabled)
(relayReconnectIntervalM ,relay-reconnect-interval-m)
(startBrowser ,start-browser)
(natEnabled ,nat-enabled)
(natLeaseMinutes ,nat-lease-minutes)
(natRenewalMinutes ,nat-renewal-minutes)
(natTimeoutSeconds ,nat-timeout-seconds)
(urAccepted ,ur-accepted)
(urSeen ,ur-seen)
(urUniqueID ,ur-unique-id)
(urURL ,ur-url)
(urPostInsecurely ,ur-post-insecurely)
(urInitialDelayS ,ur-initial-delay-s)
(autoUpgradeIntervalH ,auto-upgrade-interval-h)
(upgradeToPreReleases ,upgrade-to-pre-releases)
(keepTemporariesH ,keep-temporaries-h)
(cacheIgnoredFiles ,cache-ignored-files)
(progressUpdateIntervalS ,progress-update-interval-s)
(limitBandwidthInLan ,limit-bandwidth-in-lan)
(minHomeDiskFree (@ (unit ,min-home-disk-free-unit))
,min-home-disk-free)
(releasesURL ,releases-url)
(overwriteRemoteDeviceNamesOnConnect ,overwrite-remote-device-names-on-connect)
(tempIndexMinBlocks ,temp-index-min-blocks)
(unackedNotificationID ,unacked-notification-id)
(trafficClass ,traffic-class)
(setLowPriority ,set-low-priority)
(maxFolderConcurrency ,max-folder-concurrency)
(crashReportingURL ,crash-reporting-url)
(crashReportingEnabled ,crash-reporting-enabled)
(stunKeepaliveStartS ,stun-keepalive-start-s)
(stunKeepaliveMinS ,stun-keepalive-min-s)
(stunServer ,stun-server)
(databaseTuning ,database-tuning)
(maxConcurrentIncomingRequestKiB ,max-concurrent-incoming-request-kib)
(announceLANAddresses ,announce-lan-addresses)
(sendFullIndexOnUpgrade ,send-full-index-on-upgrade)
(connectionLimitEnough ,connection-limit-enough)
(connectionLimitMax ,connection-limit-max)
(insecureAllowOldTLSVersions ,insecure-allow-old-tlsVersions)
(connectionPriorityTcpLan ,connection-priority-tcp-lan)
(connectionPriorityQuicLan ,connection-priority-quic-lan)
(connectionPriorityTcpWan ,connection-priority-tcp-wan)
(connectionPriorityQuicWan ,connection-priority-quic-wan)
(connectionPriorityRelay ,connection-priority-relay)
(connectionPriorityUpgradeThreshold ,connection-priority-upgrade-threshold))
(defaults
,(syncthing-folder->sxml default-folder)
,(syncthing-device->sxml default-device)
(ignores ,default-ignores)))))
(define (serialize-syncthing-config-file config)
(with-output-to-string
(lambda ()
(sxml->xml (cons '*TOP* (list (syncthing-config-file->sxml config)))))))
(define-record-type* <syncthing-configuration>
syncthing-configuration make-syncthing-configuration
syncthing-configuration?
@ -50,12 +470,14 @@
(default "users"))
(home syncthing-configuration-home ;string
(default #f))
(config-file syncthing-configuration-config-file
(default #f)) ; syncthing-config-file or file-like
(home-service? syncthing-configuration-home-service?
(default for-home?) (innate)))
(define syncthing-shepherd-service
(match-record-lambda <syncthing-configuration>
(syncthing arguments logflags user group home home-service?)
(syncthing arguments logflags user group home home-service? config-file)
(list
(shepherd-service
(provision (if home-service?
@ -64,39 +486,75 @@
(string-append "syncthing-" user)))))
(documentation "Run syncthing.")
(requirement (if home-service? '() '(loopback user-processes)))
(start #~(make-forkexec-constructor
(append (list (string-append #$syncthing "/bin/syncthing")
"--no-browser"
"--no-restart"
(string-append "--logflags=" (number->string #$logflags)))
'#$arguments)
#:user #$(and (not home-service?) user)
#:group #$(and (not home-service?) group)
#:environment-variables
(append
(list
(string-append "HOME="
(or #$home
(passwd:dir
(getpw (if (and #$home-service?
(not #$user))
(getuid)
#$user)))))
"SSL_CERT_DIR=/etc/ssl/certs"
"SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt")
(filter (negate ;XXX: 'remove' is not in (guile)
(lambda (str)
(or (string-prefix? "HOME=" str)
(string-prefix? "SSL_CERT_DIR=" str)
(string-prefix? "SSL_CERT_FILE=" str))))
(environ)))))
(start #~(lambda _
;; If we are managing the config, and it's not a home
;; service, then exepect the config file at
;; /var/lib/syncthing-<user>. This makes sure the ownership
;; is correct.
(unless (or #$(not config-file) #$home-service?)
(let ((user-pw (getpw #$user)))
(chown (string-append "/var/lib/syncthing-" #$user)
(passwd:uid user-pw)
(passwd:gid user-pw)))
(chmod (string-append "/var/lib/syncthing-" #$user) #o700))
(make-forkexec-constructor
(append (list (string-append #$syncthing "/bin/syncthing")
;; Do not try to try to launch a web browser on startup.
"--no-browser"
;; If syncthing crashes, let the service fail.
"--no-restart"
(string-append "--logflags=" (number->string #$logflags)))
;; Optionally move data and configuration home to
;; /var/lib/syncthing-<user>.
(if (or #$(not config-file) #$home-service?) '()
(list (string-append "--home=/var/lib/syncthing-" #$user)))
'#$arguments)
#:user #$(and (not home-service?) user)
#:group #$(and (not home-service?) group)
#:environment-variables
(append
(list
(string-append "HOME="
(or #$home
(passwd:dir
(getpw (if (and #$home-service?
(not #$user))
(getuid)
#$user)))))
"SSL_CERT_DIR=/etc/ssl/certs"
"SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt")
(filter (negate ;XXX: 'remove' is not in (guile)
(lambda (str)
(or (string-prefix? "HOME=" str)
(string-prefix? "SSL_CERT_DIR=" str)
(string-prefix? "SSL_CERT_FILE=" str))))
(environ))))))
(respawn? #f)
(stop #~(make-kill-destructor))))))
(define syncthing-files-service
(match-record-lambda <syncthing-configuration> (config-file user home home-service?)
(if config-file
;; When used as a system service, this service might be executed
;; before a user's home even exists, causing it to be owned by root,
;; and the skeletons to never be applied to that user's home. In such
;; cases, put the config at /var/lib/syncthnig-<user>/config.xml
`((,(if home-service?
".config/syncthing/config.xml"
(string-append "/var/lib/syncthing-" user "/config.xml"))
,(if (file-like? config-file)
config-file
(plain-file "syncthin-config.xml" (serialize-syncthing-config-file
config-file)))))
'())))
(define syncthing-service-type
(service-type (name 'syncthing)
(extensions (list (service-extension shepherd-root-service-type
syncthing-shepherd-service)))
syncthing-shepherd-service)
(service-extension special-files-service-type
syncthing-files-service)))
(description
"Run @uref{https://github.com/syncthing/syncthing, Syncthing}
decentralized continuous file system synchronization.")))