GNU bug report logs - #75959
[PATCH] (home-)syncthing-service: added support for config serialization

Previous Next

Package: guix-patches;

Reported by: Zacchaeus <eikcaz <at> zacchae.us>

Date: Fri, 31 Jan 2025 04:18:03 UTC

Severity: normal

Tags: patch

Done: Leo Famulari <leo <at> famulari.name>

Bug is archived. No further changes may be made.

Full log


View this message in rfc822 format

From: help-debbugs <at> gnu.org (GNU bug Tracking System)
To: Leo Famulari <leo <at> famulari.name>
Cc: tracker <at> debbugs.gnu.org
Subject: bug#75959: closed ([PATCH] (home-)syncthing-service: added
 support for config serialization)
Date: Mon, 17 Feb 2025 06:16:01 +0000
[Message part 1 (text/plain, inline)]
Your message dated Mon, 17 Feb 2025 01:14:51 -0500
with message-id <Z7LT29gC0lnKfjFX <at> jasmine.lan>
and subject line Re: [PATCH v10] services: syncthing: Add support for config file generation.
has caused the debbugs.gnu.org bug report #75959,
regarding [PATCH] (home-)syncthing-service: added support for config serialization
to be marked as done.

(If you believe you have received this mail in error, please contact
help-debbugs <at> gnu.org.)


-- 
75959: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=75959
GNU Bug Tracking System
Contact help-debbugs <at> gnu.org with problems
[Message part 2 (message/rfc822, inline)]
From: Zacchaeus <eikcaz <at> zacchae.us>
To: guix-patches <at> gnu.org
Subject: [PATCH] (home-)syncthing-service: added support for config
 serialization
Date: Thu, 30 Jan 2025 13:59:12 -0800
From 48c227546ea15aadbd5f5832d8cd30887f65ace9 Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz <at> zacchae.us>
Date: Sun, 21 Jul 2024 00:54:25 -0700
Subject: [PATCH] (home-)syncthing-service: added support for config
 serialization

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
---
 doc/guix.texi                   | 281 ++++++++++++++++++-
 gnu/home/services/syncthing.scm |  17 +-
 gnu/services/syncthing.scm      | 459 +++++++++++++++++++++++++++++++-
 3 files changed, 752 insertions(+), 5 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..966fe852a4 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -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,8 +22670,284 @@ 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}.
 
-@end table
-@end deftp
+@item @code{syncthing-config-file} (default: @var{#f})
+Either a file-like object that resolves to a syncthing configuraton xml
+file, or a syncthing-config-file record (see below).
+
+@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-powerd 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.
+
+@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, or strings corresponding to
+the device ids.  A device entry corresponding to the current device is
+silently ignored by Syncthing.
+
+@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 belowe).
+
+@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 eachother 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, or by an ``introducer''.
+@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 inpsecting 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.  If the current
+device is included, it is silently ignored by syncthing (which makes for
+lazier scheme code).  Each device can be listed as a string representing
+the device id, a @code{syncthing-device} object, or a
+@code{syncthing-folder-device} object.
+
+@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 syncthing-folder-device.
+
+@table @asis
+@item @code{id} (default: @var{""})
+id can be provided as a string of the id, or a @code{syncthing-device}.
+
+@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.com"))))
+               (bob-desktop "KYIMEGO-...-FT77EAO"))
+           (syncthing-configuration
+            (user "alice")
+            (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
+                                     (id bob-desktop)
+                                     (encryptionPassword "mypassword")))))))
+             ;; any device used above should be in this list
+             (devices (list laptop desktop bob-desktop))))
+@end lisp
+
 
 Furthermore, @code{(gnu services ssh)} provides the following services.
 @cindex SSH
diff --git a/gnu/home/services/syncthing.scm b/gnu/home/services/syncthing.scm
index 8d66a167ce..dd6c752ee4 100644
--- a/gnu/home/services/syncthing.scm
+++ b/gnu/home/services/syncthing.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2023 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2025 Zacchaeus <eikcaz <at> 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)))))
diff --git a/gnu/services/syncthing.scm b/gnu/services/syncthing.scm
index a7a9c6aadd..4f0d4c1082 100644
--- a/gnu/services/syncthing.scm
+++ b/gnu/services/syncthing.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2021 Oleg Pykhalov <go.wigust <at> gmail.com>
 ;;; Copyright © 2023 Justin Veilleux <terramorpha <at> cock.li>
+;;; Copyright © 2025 Zacchaeus <eikcaz <at> 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,431 @@ (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 (id-or-device->id id-or-device)
+  (if (syncthing-device? id-or-device)
+      (syncthing-device-id id-or-device)
+      id-or-device))
+
+(define-record-type* <syncthing-folder-device>
+  syncthing-folder-device make-syncthing-folder-device
+  syncthing-folder-device?
+  (id syncthing-folder-device-id
+      (sanitize id-or-device->id))
+  (introducedBy syncthing-folder-device-introducedBy (default "")
+                (sanitize id-or-device->id))
+  (encryptionPassword syncthing-folder-device-encryptionPassword (default "")))
+
+(define syncthing-folder-device->sxml
+  (match-record-lambda <syncthing-folder-device>
+      (id introducedBy encryptionPassword)
+    `(device (@ (id ,id)
+                (introducedBy ,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"))
+  (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 (id 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
+       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)
+                (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)
+                    ,@(map syncthing-device->sxml
+                           devices)
+                    (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 users 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 +487,8 @@ (define-record-type* <syncthing-configuration>
              (default "users"))
   (home      syncthing-configuration-home      ;string
              (default #f))
+  (syncthing-config-file syncthing-configuration-syncthing-config-file
+                         (default #f))         ; syncthing-config-file or file-like
   (home-service? syncthing-configuration-home-service?
                  (default for-home?) (innate)))
 
@@ -93,10 +532,26 @@ (define syncthing-shepherd-service
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
 
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (syncthing-config-file user home home-service?)
+    (if syncthing-config-file
+        `((,(if home-service?
+                ".config/syncthing/config.xml"
+                (string-join (or home (passwd:dir (getpw user)))
+                             "/.config/syncthing/config.xml"))
+           ,(if (file-like? syncthing-config-file)
+                syncthing-config-file
+                (plain-file "syncthin-config.xml" (serialize-syncthing-config-file
+                                                   syncthing-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.")))
-- 
2.45.2



[Message part 3 (message/rfc822, inline)]
From: Leo Famulari <leo <at> famulari.name>
To: Zacchaeus Scheffer <eikcaz <at> zacchae.us>
Cc: Bruno Victal <mirai <at> makinata.eu>, 75959-done <at> debbugs.gnu.org
Subject: Re: [PATCH v10] services: syncthing: Add support for config file
 generation.
Date: Mon, 17 Feb 2025 01:14:51 -0500
On Sun, Feb 16, 2025 at 04:35:20AM -0500, Zacchaeus Scheffer wrote:
> From a573fd78e6b8d10b32eb10a753423073c7bbaeef Mon Sep 17 00:00:00 2001
> From: Zacchaeus <eikcaz <at> zacchae.us>
> Date: Sun, 21 Jul 2024 00:54:25 -0700
> Subject: [PATCH v10] services: syncthing: Add support for config file
>  generation.
> 
> * 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.

Pushed as 651f8765b657e35baf85ac74a1f6b09ff71691cb

Thank you for your contribution to Guix!


This bug report was last modified 145 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.