@@ -136,6 +136,7 @@ Copyright @copyright{} 2024 Troy Figiel@*
Copyright @copyright{} 2024 Sharlatan Hellseher@*
Copyright @copyright{} 2024 45mg@*
Copyright @copyright{} 2025 Sören Tempel@*
+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
@@ -22669,9 +22670,296 @@ This assumes that the specified group exists.
Common configuration and data directory. The default configuration
directory 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 syncthing-config-file record (see below). If set to #f, Guix
+will not try to generate a config file, and the syncthing will generate
+a default one which will not be touched on reconfigure.
+
+@end table
+@end deftp
+
+In the below, only details specific to Guix, or related to how your
+device will ``ping'' others, are presented. Otherwise, you should
+consult @uref{https://docs.syncthing.net/users/config.html, Syncthing
+config documentation}. Camelcase is preserved below only as to be
+consistent with its appearance in Syncthing code/documentation. If you
+would like to migrate to Guix-powered Syncthing configuration, the
+generated config adds newlines/whitespace to the produced config such
+that your old config can be diff'ed with the new one. You can still
+modify Syncthing from the GUI or through ``introducer'' and
+``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 (same 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
+gui-user and gui-password (see below).
+
+@item @code{gui-tls} (default: @var{"false"})
+@item @code{gui-debugging} (default: @var{"false"})
+@item @code{gui-sendBasicAuthPrompt} (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})
+@item @code{gui-apikey} (default: @var{"Vuky3VHVseQEoSk9YgxhSkNTnjQmqYK9"})
+@item @code{gui-theme} (default: @var{"default"})
+@item @code{ldap-enabled} (default: @var{#f})
+@item @code{ldap-address} (default: @var{""})
+@item @code{ldap-bindDN} (default: @var{""})
+@item @code{ldap-transport} (default: @var{""})
+@item @code{ldap-insecureSkipVerify} (default: @var{""})
+@item @code{ldap-searchBaseDN} (default: @var{""})
+@item @code{ldap-searchFilter} (default: @var{""})
+@item @code{listenAddress} (default: @var{"default"})
+@item @code{globalAnnounceServer} (default: @var{"default"})
+@item @code{globalAnnounceEnabled} (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{localAnnounceEnabled} (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{localAnnouncePort} (default: @var{"21027"})
+@item @code{localAnnounceMCAddr} (default: @var{"[ff12::8384]:21027"})
+@item @code{maxSendKbps} (default: @var{"0"})
+@item @code{maxRecvKbps} (default: @var{"0"})
+@item @code{reconnectionIntervalS} (default: @var{"60"})
+@item @code{relaysEnabled} (default: @var{"true"})
+This option allows your Syncthing instance to coordinate with a global
+network of relays to enable syncing between devices when all other
+methods fail.
+
+@item @code{relayReconnectIntervalM} (default: @var{"10"})
+@item @code{startBrowser} (default: @var{"true"})
+@item @code{natEnabled} (default: @var{"true"})
+@item @code{natLeaseMinutes} (default: @var{"60"})
+@item @code{natRenewalMinutes} (default: @var{"30"})
+@item @code{natTimeoutSeconds} (default: @var{"10"})
+@item @code{urAccepted} (default: @var{"0"})
+ur* options control usage reporting. Set to -1 to disable, or positive
+to enable. The default (0) has reporting disabled, but you will be
+asked to decide in the GUI.
+
+@item @code{urSeen} (default: @var{"0"})
+@item @code{urUniqueID} (default: @var{""})
+@item @code{urURL} (default: @var{"https://data.syncthing.net/newdata"})
+@item @code{urPostInsecurely} (default: @var{"false"})
+@item @code{urInitialDelayS} (default: @var{"1800"})
+@item @code{autoUpgradeIntervalH} (default: @var{"12"})
+@item @code{upgradeToPreReleases} (default: @var{"false"})
+@item @code{keepTemporariesH} (default: @var{"24"})
+@item @code{cacheIgnoredFiles} (default: @var{"false"})
+@item @code{progressUpdateIntervalS} (default: @var{"5"})
+@item @code{limitBandwidthInLan} (default: @var{"false"})
+@item @code{minHomeDiskFree-unit} (default: @var{"%"})
+@item @code{minHomeDiskFree} (default: @var{"1"})
+@item @code{releasesURL} (default: @var{"https://upgrades.syncthing.net/meta.json"})
+@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"})
+@item @code{tempIndexMinBlocks} (default: @var{"10"})
+@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPassword"})
+@item @code{trafficClass} (default: @var{"0"})
+@item @code{setLowPriority} (default: @var{"true"})
+@item @code{maxFolderConcurrency} (default: @var{"0"})
+@item @code{crashReportingURL} (default: @var{"https://crash.syncthing.net/newcrash"})
+@item @code{crashReportingEnabled} (default: @var{"true"})
+@item @code{stunKeepaliveStartS} (default: @var{"180"})
+@item @code{stunKeepaliveMinS} (default: @var{"20"})
+@item @code{stunServer} (default: @var{"default"})
+@item @code{databaseTuning} (default: @var{"auto"})
+@item @code{maxConcurrentIncomingRequestKiB} (default: @var{"0"})
+@item @code{announceLANAddresses} (default: @var{"true"})
+@item @code{sendFullIndexOnUpgrade} (default: @var{"false"})
+@item @code{connectionLimitEnough} (default: @var{"0"})
+@item @code{connectionLimitMax} (default: @var{"0"})
+@item @code{insecureAllowOldTLSVersions} (default: @var{"false"})
+@item @code{connectionPriorityTcpLan} (default: @var{"10"})
+@item @code{connectionPriorityQuicLan} (default: @var{"20"})
+@item @code{connectionPriorityTcpWan} (default: @var{"30"})
+@item @code{connectionPriorityQuicWan} (default: @var{"40"})
+@item @code{connectionPriorityRelay} (default: @var{"50"})
+@item @code{connectionPriorityUpgradeThreshold} (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{"")})
+The default-* above do not affect folders and devices added by the Guix
+interface. They will, however, affect folders and devices that are
+added through the GUI, by an ``introducer'', or a device with
+``autoAcceptFolders''.
+@end table
+@end deftp
+
+@deftp {Data Type} syncthing-device
+Data type representing a device to sync with.
+
+@table @asis
+@item @code{id}
+A long hash tied to 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{""})
+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{skipIntroductionRemovals} (default: @var{"false"})
+@item @code{introducedBy} (default: @var{""})
+@item @code{addresses} (default: @var{'("dynamic")})
+List of addresses at which to search for this device. The special value
+``dynamic'' will have syncthing use several means to find the device.
+
+@item @code{paused} (default: @var{"false"})
+@item @code{autoAcceptFolders} (default: @var{"false"})
+@item @code{maxSendKbps} (default: @var{"0"})
+@item @code{maxRecvKbps} (default: @var{"0"})
+@item @code{maxRequestKiB} (default: @var{"0"})
+@item @code{untrusted} (default: @var{"false"})
+@item @code{remoteGUIPort} (default: @var{"0"})
+@item @code{numConnections} (default: @var{"0")})
+
+@end table
+@end deftp
+
+@deftp {Data Type} syncthing-folder
+Data type representing a folder to be synced.
+
+@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}
+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{rescanIntervalS} (default: @var{"3600"})
+@item @code{fsWatcherEnabled} (default: @var{"true"})
+@item @code{fsWatcherDelayS} (default: @var{"10"})
+@item @code{ignorePerms} (default: @var{"false"})
+@item @code{autoNormalize} (default: @var{"true"})
+@item @code{devices} (default: @var{'()})
+Devices 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.
+
+@item @code{filesystemType} (default: @var{"basic"})
+@item @code{minDiskFree-unit} (default: @var{"%"})
+@item @code{minDiskFree} (default: @var{"1"})
+@item @code{versioning-type} (default: @var{#f})
+@item @code{versioning-fsPath} (default: @var{""})
+@item @code{versioning-fsType} (default: @var{"basic"})
+@item @code{versioning-cleanupIntervalS} (default: @var{"3600"})
+@item @code{versioning-cleanoutDays} (default: @var{#f})
+@item @code{versioning-keep} (default: @var{#f})
+@item @code{versioning-maxAge} (default: @var{#f})
+@item @code{versioning-command} (default: @var{#f})
+@item @code{copiers} (default: @var{"0"})
+@item @code{pullerMaxPendingKiB} (default: @var{"0"})
+@item @code{hashers} (default: @var{"0"})
+@item @code{order} (default: @var{"random"})
+@item @code{ignoreDelete} (default: @var{"false"})
+@item @code{scanProgressIntervalS} (default: @var{"0"})
+@item @code{pullerPauseS} (default: @var{"0"})
+@item @code{maxConflicts} (default: @var{"10"})
+@item @code{disableSparseFiles} (default: @var{"false"})
+@item @code{disableTempIndexes} (default: @var{"false"})
+@item @code{paused} (default: @var{"false"})
+@item @code{weakHashThresholdPct} (default: @var{"25"})
+@item @code{markerName} (default: @var{".stfolder"})
+@item @code{copyOwnershipFromParent} (default: @var{"false"})
+@item @code{modTimeWindowS} (default: @var{"0"})
+@item @code{maxConcurrentWrites} (default: @var{"2"})
+@item @code{disableFsync} (default: @var{"false"})
+@item @code{blockPullOrder} (default: @var{"standard"})
+@item @code{copyRangeMethod} (default: @var{"standard"})
+@item @code{caseSensitiveFS} (default: @var{"false"})
+@item @code{junctionsAsDirs} (default: @var{"false"})
+@item @code{syncOwnership} (default: @var{"false"})
+@item @code{sendOwnership} (default: @var{"false"})
+@item @code{syncXattrs} (default: @var{"false"})
+@item @code{sendXattrs} (default: @var{"false"})
+@item @code{xattrFilter-maxSingleEntrySize} (default: @var{"1024"})
+@item @code{xattrFilter-maxTotalSize} (default: @var{"4096")})
+@end table
+@end deftp
+
+@deftp {Data Type} syncthing-folder-device
+There is some configuration which is specific to the relationship
+between a specific folder and a specific device. If you are fine
+leaving these as their default, then you can simply specify a
+syncthing-device instead of a @code{syncthing-folder-device} in
+@code{syncthing-folder}s.
+
+@table @asis
+@item @code{device}
+device should be a @code{syncthing-device} for which this configuration
+applies.
+
+@item @code{introducedBy} (default: @var{""})
+@item @code{encryptionPassword} (default: @var{""})
+if encryptionPassword is non-empty, then it will be used as a password
+to encrypt file chunks as they are synced to that device. For more info
+on syncing to devices you don't totally trust, see
+@uref{https://docs.syncthing.net/users/untrusted.html, Syncthing Documentation Untrusted}.
+Note that file transfers are always end-to-end encrypted, 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 '("mydomain.example"))))
+ (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)
+ (encryptionPassword "mypassword"))))))))))))
+@end lisp
+
+
Furthermore, @code{(gnu services ssh)} provides the following services.
@cindex SSH
@cindex SSH server
@@ -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 @@ (define-module (gnu home services syncthing)
#: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)))))
@@ -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 @@ (define-module (gnu services syncthing)
#: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,438 @@ (define-module (gnu services syncthing)
;;;
;;; 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"))
+ (skipIntroductionRemovals syncthing-device-skipIntroductionRemovals (default "false"))
+ (introducedBy syncthing-device-introducedBy (default ""))
+ (addresses syncthing-device-addresses (default '("dynamic")))
+ (paused syncthing-device-paused (default "false"))
+ (autoAcceptFolders syncthing-device-autoAcceptFolders (default "false"))
+ (maxSendKbps syncthing-device-maxSendKbps (default "0"))
+ (maxRecvKbps syncthing-device-maxRecvKbps (default "0"))
+ (maxRequestKiB syncthing-device-maxRequestKiB (default "0"))
+ (untrusted syncthing-device-untrusted (default "false"))
+ (remoteGUIPort syncthing-device-remoteGUIPort (default "0"))
+ (numConnections syncthing-device-numConnections (default "0")))
+
+(define syncthing-device->sxml
+ (match-record-lambda <syncthing-device>
+ (id name compression introducer skipIntroductionRemovals introducedBy addresses paused autoAcceptFolders maxSendKbps maxRecvKbps maxRequestKiB untrusted remoteGUIPort numConnections)
+ `(device (@ (id ,id)
+ (name ,name)
+ (compression ,compression)
+ (introducer ,introducer)
+ (skipIntroductionRemovals ,skipIntroductionRemovals)
+ (introducedBy ,introducedBy))
+ ,@(map (lambda (address) `(address ,address)) addresses)
+ (paused ,paused)
+ (autoAcceptFolders ,autoAcceptFolders)
+ (maxSendKbps ,maxSendKbps)
+ (maxRecvKbps ,maxRecvKbps)
+ (maxRequestKiB ,maxRequestKiB)
+ (untrusted ,untrusted)
+ (remoteGUIPort ,remoteGUIPort)
+ (numConnections ,numConnections))))
+
+(define-record-type* <syncthing-folder-device>
+ syncthing-folder-device make-syncthing-folder-device
+ syncthing-folder-device?
+ (device syncthing-folder-device-device)
+ (introducedBy syncthing-folder-device-introducedBy (default (syncthing-device (id ""))))
+ (encryptionPassword syncthing-folder-device-encryptionPassword (default "")))
+
+(define syncthing-folder-device->sxml
+ (match-record-lambda <syncthing-folder-device>
+ (device introducedBy encryptionPassword)
+ `(device (@ (id ,(syncthing-device-id device))
+ (introducedBy ,(syncthing-device-id introducedBy)))
+ (encryptionPassword ,encryptionPassword))))
+
+(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"))
+ (rescanIntervalS syncthing-folder-rescanIntervalS (default "3600"))
+ (fsWatcherEnabled syncthing-folder-fsWatcherEnabled (default "true"))
+ (fsWatcherDelayS syncthing-folder-fsWatcherDelayS (default "10"))
+ (fsWatcherTimeoutS syncthing-folder-fsWatcherTimeoutS (default "0"))
+ (ignorePerms syncthing-folder-ignorePerms (default "false"))
+ (autoNormalize syncthing-folder-autoNormalize (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))))
+ (filesystemType syncthing-folder-filesystemType (default "basic"))
+ (minDiskFree-unit syncthing-folder-minDiskFree-unit (default "%"))
+ (minDiskFree syncthing-folder-minDiskFree (default "1"))
+ (versioning-type syncthing-folder-versioning-type (default #f))
+ (versioning-fsPath syncthing-folder-versioning-fsPath (default ""))
+ (versioning-fsType syncthing-folder-versioning-fsType (default "basic"))
+ (versioning-cleanupIntervalS syncthing-folder-versioning-cleanupIntervalS (default "3600"))
+ (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (default #f))
+ (versioning-keep syncthing-folder-versioning-keep (default #f))
+ (versioning-maxAge syncthing-folder-versioning-maxAge (default #f))
+ (versioning-command syncthing-folder-versioning-command (default #f))
+ (copiers syncthing-folder-copiers (default "0"))
+ (pullerMaxPendingKiB syncthing-folder-pullerMaxPendingKiB (default "0"))
+ (hashers syncthing-folder-hashers (default "0"))
+ (order syncthing-folder-order (default "random"))
+ (ignoreDelete syncthing-folder-ignoreDelete (default "false"))
+ (scanProgressIntervalS syncthing-folder-scanProgressIntervalS (default "0"))
+ (pullerPauseS syncthing-folder-pullerPauseS (default "0"))
+ (maxConflicts syncthing-folder-maxConflicts (default "10"))
+ (disableSparseFiles syncthing-folder-disableSparseFiles (default "false"))
+ (disableTempIndexes syncthing-folder-disableTempIndexes (default "false"))
+ (paused syncthing-folder-paused (default "false"))
+ (weakHashThresholdPct syncthing-folder-weakHashThresholdPct (default "25"))
+ (markerName syncthing-folder-markerName (default ".stfolder"))
+ (copyOwnershipFromParent syncthing-folder-copyOwnershipFromParent (default "false"))
+ (modTimeWindowS syncthing-folder-modTimeWindowS (default "0"))
+ (maxConcurrentWrites syncthing-folder-maxConcurrentWrites (default "2"))
+ (disableFsync syncthing-folder-disableFsync (default "false"))
+ (blockPullOrder syncthing-folder-blockPullOrder (default "standard"))
+ (copyRangeMethod syncthing-folder-copyRangeMethod (default "standard"))
+ (caseSensitiveFS syncthing-folder-caseSensitiveFS (default "false"))
+ (junctionsAsDirs syncthing-folder-junctionsAsDirs (default "false"))
+ (syncOwnership syncthing-folder-syncOwnership (default "false"))
+ (sendOwnership syncthing-folder-sendOwnership (default "false"))
+ (syncXattrs syncthing-folder-syncXattrs (default "false"))
+ (sendXattrs syncthing-folder-sendXattrs (default "false"))
+ (xattrFilter-maxSingleEntrySize syncthing-folder-xattrFilter-maxSingleEntrySize (default "1024"))
+ (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (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 rescanIntervalS fsWatcherEnabled fsWatcherDelayS
+ fsWatcherTimeoutS ignorePerms autoNormalize devices filesystemType
+ minDiskFree-unit minDiskFree versioning-type versioning-fsPath
+ versioning-fsType versioning-cleanupIntervalS versioning-cleanoutDays
+ versioning-keep versioning-maxAge versioning-command copiers
+ pullerMaxPendingKiB hashers order ignoreDelete scanProgressIntervalS
+ pullerPauseS maxConflicts disableSparseFiles disableTempIndexes paused
+ weakHashThresholdPct markerName copyOwnershipFromParent modTimeWindowS
+ maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod
+ caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXattrs
+ sendXattrs xattrFilter-maxSingleEntrySize xattrFilter-maxTotalSize)
+ `(folder (@ (id ,(if id id label))
+ (label ,label)
+ (path ,path)
+ (type ,type)
+ (rescanIntervalS ,rescanIntervalS)
+ (fsWatcherEnabled ,fsWatcherEnabled)
+ (fsWatcherDelayS ,fsWatcherDelayS)
+ (fsWatcherTimeoutS ,fsWatcherTimeoutS)
+ (ignorePerms ,ignorePerms)
+ (autoNormalize ,autoNormalize))
+ (filesystemType ,filesystemType)
+ ,@(map syncthing-folder-device->sxml
+ devices)
+ (minDiskFree (@ (unit ,minDiskFree-unit))
+ ,minDiskFree)
+ (versioning ,@(if versioning-type
+ `((@ (type ,versioning-type)))
+ '())
+ ,@(maybe-param 'cleanoutDays versioning-cleanoutDays)
+ ,@(maybe-param 'keep versioning-keep)
+ ,@(maybe-param 'maxAge versioning-maxAge)
+ ,@(maybe-param 'command versioning-command)
+ (cleanupIntervalS ,versioning-cleanupIntervalS)
+ (fsPath ,versioning-fsPath)
+ (fsType ,versioning-fsType))
+ (copiers ,copiers)
+ (pullerMaxPendingKiB ,pullerMaxPendingKiB)
+ (hashers ,hashers)
+ (order ,order)
+ (ignoreDelete ,ignoreDelete)
+ (scanProgressIntervalS ,scanProgressIntervalS)
+ (pullerPauseS ,pullerPauseS)
+ (maxConflicts ,maxConflicts)
+ (disableSparseFiles ,disableSparseFiles)
+ (disableTempIndexes ,disableTempIndexes)
+ (paused ,paused)
+ (weakHashThresholdPct ,weakHashThresholdPct)
+ (markerName ,markerName)
+ (copyOwnershipFromParent ,copyOwnershipFromParent)
+ (modTimeWindowS ,modTimeWindowS)
+ (maxConcurrentWrites ,maxConcurrentWrites)
+ (disableFsync ,disableFsync)
+ (blockPullOrder ,blockPullOrder)
+ (copyRangeMethod ,copyRangeMethod)
+ (caseSensitiveFS ,caseSensitiveFS)
+ (junctionsAsDirs ,junctionsAsDirs)
+ (syncOwnership ,syncOwnership)
+ (sendOwnership ,sendOwnership)
+ (syncXattrs ,syncXattrs)
+ (sendXattrs ,sendXattrs)
+ (xattrFilter (maxSingleEntrySize ,xattrFilter-maxSingleEntrySize)
+ (maxTotalSize ,xattrFilter-maxTotalSize)))))
+
+(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-sendBasicAuthPrompt syncthing-config-gui-sendBasicAuthPrompt (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 "Vuky3VHVseQEoSk9YgxhSkNTnjQmqYK9"))
+ (gui-theme syncthing-config-gui-theme (default "default"))
+ (ldap-enabled syncthing-config-ldap-enabled (default #f))
+ (ldap-address syncthing-config-ldap-address (default ""))
+ (ldap-bindDN syncthing-config-ldap-bindDN (default ""))
+ (ldap-transport syncthing-config-ldap-transport (default ""))
+ (ldap-insecureSkipVerify syncthing-config-ldap-insecureSkipVerify (default ""))
+ (ldap-searchBaseDN syncthing-config-ldap-searchBaseDN (default ""))
+ (ldap-searchFilter syncthing-config-ldap-searchFilter (default ""))
+ (listenAddress syncthing-config-listenAddress (default "default"))
+ (globalAnnounceServer syncthing-config-globalAnnounceServer (default "default"))
+ (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "true"))
+ (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "true"))
+ (localAnnouncePort syncthing-config-localAnnouncePort (default "21027"))
+ (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff12::8384]:21027"))
+ (maxSendKbps syncthing-config-maxSendKbps (default "0"))
+ (maxRecvKbps syncthing-config-maxRecvKbps (default "0"))
+ (reconnectionIntervalS syncthing-config-reconnectionIntervalS (default "60"))
+ (relaysEnabled syncthing-config-relaysEnabled (default "true"))
+ (relayReconnectIntervalM syncthing-config-relayReconnectIntervalM (default "10"))
+ (startBrowser syncthing-config-startBrowser (default "true"))
+ (natEnabled syncthing-config-natEnabled (default "true"))
+ (natLeaseMinutes syncthing-config-natLeaseMinutes (default "60"))
+ (natRenewalMinutes syncthing-config-natRenewalMinutes (default "30"))
+ (natTimeoutSeconds syncthing-config-natTimeoutSeconds (default "10"))
+ (urAccepted syncthing-config-urAccepted (default "0"))
+ (urSeen syncthing-config-urSeen (default "0"))
+ (urUniqueID syncthing-config-urUniqueID (default ""))
+ (urURL syncthing-config-urURL (default "https://data.syncthing.net/newdata"))
+ (urPostInsecurely syncthing-config-urPostInsecurely (default "false"))
+ (urInitialDelayS syncthing-config-urInitialDelayS (default "1800"))
+ (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12"))
+ (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "false"))
+ (keepTemporariesH syncthing-config-keepTemporariesH (default "24"))
+ (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false"))
+ (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (default "5"))
+ (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "false"))
+ (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%"))
+ (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1"))
+ (releasesURL syncthing-config-releasesURL (default "https://upgrades.syncthing.net/meta.json"))
+ (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDeviceNamesOnConnect (default "false"))
+ (tempIndexMinBlocks syncthing-config-tempIndexMinBlocks (default "10"))
+ (unackedNotificationID syncthing-config-unackedNotificationID (default "authenticationUserAndPassword"))
+ (trafficClass syncthing-config-trafficClass (default "0"))
+ (setLowPriority syncthing-config-setLowPriority (default "true"))
+ (maxFolderConcurrency syncthing-config-maxFolderConcurrency (default "0"))
+ (crashReportingURL syncthing-config-crashReportingURL (default "https://crash.syncthing.net/newcrash"))
+ (crashReportingEnabled syncthing-config-crashReportingEnabled (default "true"))
+ (stunKeepaliveStartS syncthing-config-stunKeepaliveStartS (default "180"))
+ (stunKeepaliveMinS syncthing-config-stunKeepaliveMinS (default "20"))
+ (stunServer syncthing-config-stunServer (default "default"))
+ (databaseTuning syncthing-config-databaseTuning (default "auto"))
+ (maxConcurrentIncomingRequestKiB syncthing-config-maxConcurrentIncomingRequestKiB (default "0"))
+ (announceLANAddresses syncthing-config-announceLANAddresses (default "true"))
+ (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default "false"))
+ (connectionLimitEnough syncthing-config-connectionLimitEnough (default "0"))
+ (connectionLimitMax syncthing-config-connectionLimitMax (default "0"))
+ (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersions (default "false"))
+ (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (default "10"))
+ (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (default "20"))
+ (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (default "30"))
+ (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (default "40"))
+ (connectionPriorityRelay syncthing-config-connectionPriorityRelay (default "50"))
+ (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityUpgradeThreshold (default "0"))
+ (default-folder syncthing-config-defaultFolder
+ (default (syncthing-folder (label "") (path "~"))))
+ (default-device syncthing-config-defaultDevice
+ (default (syncthing-device (id ""))))
+ (default-ignores syncthing-config-defaultIgnores (default "")))
+
+(define syncthing-config-file->sxml
+ (match-record-lambda <syncthing-config-file>
+ (folders
+ devices gui-enabled gui-tls gui-debugging gui-sendBasicAuthPrompt
+ gui-address gui-user gui-password gui-apikey gui-theme ldap-enabled
+ ldap-address ldap-bindDN ldap-transport ldap-insecureSkipVerify
+ ldap-searchBaseDN ldap-searchFilter listenAddress globalAnnounceServer
+ globalAnnounceEnabled localAnnounceEnabled localAnnouncePort
+ localAnnounceMCAddr maxSendKbps maxRecvKbps reconnectionIntervalS
+ relaysEnabled relayReconnectIntervalM startBrowser natEnabled
+ natLeaseMinutes natRenewalMinutes natTimeoutSeconds urAccepted
+ urSeen urUniqueID urURL urPostInsecurely urInitialDelayS
+ autoUpgradeIntervalH upgradeToPreReleases keepTemporariesH
+ cacheIgnoredFiles progressUpdateIntervalS limitBandwidthInLan
+ minHomeDiskFree-unit minHomeDiskFree releasesURL
+ overwriteRemoteDeviceNamesOnConnect tempIndexMinBlocks
+ unackedNotificationID trafficClass setLowPriority maxFolderConcurrency
+ crashReportingURL crashReportingEnabled stunKeepaliveStartS
+ stunKeepaliveMinS stunServer databaseTuning
+ maxConcurrentIncomingRequestKiB announceLANAddresses
+ sendFullIndexOnUpgrade connectionLimitEnough connectionLimitMax
+ insecureAllowOldTLSVersions connectionPriorityTcpLan
+ connectionPriorityQuicLan connectionPriorityTcpWan
+ connectionPriorityQuicWan connectionPriorityRelay
+ connectionPriorityUpgradeThreshold 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-sendBasicAuthPrompt))
+ (address ,gui-address)
+ ,@(if gui-user `((user ,gui-user)) '())
+ ,@(if gui-password `((password ,gui-password)) '())
+ (apikey ,gui-apikey)
+ (theme ,gui-theme))
+ (ldap ,(if ldap-enabled
+ `((address ,ldap-address)
+ (bindDN ,ldap-bindDN)
+ ,@(if ldap-transport
+ `((transport ,ldap-transport))
+ '())
+ ,@(if ldap-insecureSkipVerify
+ `((insecureSkipVerify ,ldap-insecureSkipVerify))
+ '())
+ ,@(if ldap-searchBaseDN
+ `((searchBaseDN ,ldap-searchBaseDN))
+ '())
+ ,@(if ldap-searchFilter
+ `((searchFilter ,ldap-searchFilter))
+ '()))
+ ""))
+ (options (listenAddress ,listenAddress)
+ (globalAnnounceServer ,globalAnnounceServer)
+ (globalAnnounceEnabled ,globalAnnounceEnabled)
+ (localAnnounceEnabled ,localAnnounceEnabled)
+ (localAnnouncePort ,localAnnouncePort)
+ (localAnnounceMCAddr ,localAnnounceMCAddr)
+ (maxSendKbps ,maxSendKbps)
+ (maxRecvKbps ,maxRecvKbps)
+ (reconnectionIntervalS ,reconnectionIntervalS)
+ (relaysEnabled ,relaysEnabled)
+ (relayReconnectIntervalM ,relayReconnectIntervalM)
+ (startBrowser ,startBrowser)
+ (natEnabled ,natEnabled)
+ (natLeaseMinutes ,natLeaseMinutes)
+ (natRenewalMinutes ,natRenewalMinutes)
+ (natTimeoutSeconds ,natTimeoutSeconds)
+ (urAccepted ,urAccepted)
+ (urSeen ,urSeen)
+ (urUniqueID ,urUniqueID)
+ (urURL ,urURL)
+ (urPostInsecurely ,urPostInsecurely)
+ (urInitialDelayS ,urInitialDelayS)
+ (autoUpgradeIntervalH ,autoUpgradeIntervalH)
+ (upgradeToPreReleases ,upgradeToPreReleases)
+ (keepTemporariesH ,keepTemporariesH)
+ (cacheIgnoredFiles ,cacheIgnoredFiles)
+ (progressUpdateIntervalS ,progressUpdateIntervalS)
+ (limitBandwidthInLan ,limitBandwidthInLan)
+ (minHomeDiskFree (@ (unit ,minHomeDiskFree-unit))
+ ,minHomeDiskFree)
+ (releasesURL ,releasesURL)
+ (overwriteRemoteDeviceNamesOnConnect ,overwriteRemoteDeviceNamesOnConnect)
+ (tempIndexMinBlocks ,tempIndexMinBlocks)
+ (unackedNotificationID ,unackedNotificationID)
+ (trafficClass ,trafficClass)
+ (setLowPriority ,setLowPriority)
+ (maxFolderConcurrency ,maxFolderConcurrency)
+ (crashReportingURL ,crashReportingURL)
+ (crashReportingEnabled ,crashReportingEnabled)
+ (stunKeepaliveStartS ,stunKeepaliveStartS)
+ (stunKeepaliveMinS ,stunKeepaliveMinS)
+ (stunServer ,stunServer)
+ (databaseTuning ,databaseTuning)
+ (maxConcurrentIncomingRequestKiB ,maxConcurrentIncomingRequestKiB)
+ (announceLANAddresses ,announceLANAddresses)
+ (sendFullIndexOnUpgrade ,sendFullIndexOnUpgrade)
+ (connectionLimitEnough ,connectionLimitEnough)
+ (connectionLimitMax ,connectionLimitMax)
+ (insecureAllowOldTLSVersions ,insecureAllowOldTLSVersions)
+ (connectionPriorityTcpLan ,connectionPriorityTcpLan)
+ (connectionPriorityQuicLan ,connectionPriorityQuicLan)
+ (connectionPriorityTcpWan ,connectionPriorityTcpWan)
+ (connectionPriorityQuicWan ,connectionPriorityQuicWan)
+ (connectionPriorityRelay ,connectionPriorityRelay)
+ (connectionPriorityUpgradeThreshold ,connectionPriorityUpgradeThreshold))
+ (defaults
+ ,(syncthing-folder->sxml default-folder)
+ ,(syncthing-device->sxml default-device)
+ (ignores ,default-ignores)))))
+
+;; It is useful to be able to view the xml output by Guix, and to be able to
+;; diff it with a user's previous config, especially when migrating one's
+;; config to Guix. This function adds whitespace that matches the whitespace
+;; of config files managed by Syncthing for easy diffing.
+(define (indent-sxml sxml indent-increment current-indent)
+ (match sxml
+ (((tag ('@ properties ...) (subtags ..1) ..1) sibling-tags ...)
+ `(,current-indent (,tag (@ ,@properties) "\n"
+ ,@(indent-sxml subtags indent-increment
+ (string-append indent-increment current-indent))
+ ,current-indent) "\n"
+ ,@(indent-sxml sibling-tags indent-increment current-indent)))
+ (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+ `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+ ,@(indent-sxml sibling-tags indent-increment current-indent)))
+ (((tag (subtags ..1) ..1) sibling-tags ...)
+ `(,current-indent (,tag "\n"
+ ,@(indent-sxml subtags indent-increment
+ (string-append indent-increment current-indent))
+ ,current-indent) "\n"
+ ,@(indent-sxml sibling-tags indent-increment current-indent)))
+ (((tag primitive ...) sibling-tags ...)
+ `(,current-indent (,tag ,@primitive) "\n"
+ ,@(indent-sxml sibling-tags indent-increment current-indent)))
+ (() '())))
+
+(define (serialize-syncthing-config-file config)
+ (with-output-to-string
+ (lambda ()
+ (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->sxml config))
+ " "
+ ""))))))
+
(define-record-type* <syncthing-configuration>
syncthing-configuration make-syncthing-configuration
syncthing-configuration?
@@ -50,6 +494,8 @@ (define-record-type* <syncthing-configuration>
(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)))
@@ -93,10 +539,26 @@ (define syncthing-shepherd-service
(respawn? #f)
(stop #~(make-kill-destructor))))))
+
+(define syncthing-files-service
+ (match-record-lambda <syncthing-configuration> (config-file user home home-service?)
+ (if config-file
+ `((,(if home-service?
+ ".config/syncthing/config.xml"
+ (string-append (or home (passwd:dir (getpw user)))
+ "/.config/syncthing/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.")))