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

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: guix-patches; Reported by: Zacchaeus <eikcaz@HIDDEN>; Keywords: patch; dated Fri, 31 Jan 2025 04:18:03 UTC; Maintainer for guix-patches is guix-patches@HIDDEN.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 8 Feb 2025 03:31:32 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Feb 07 22:31:32 2025
Received: from localhost ([127.0.0.1]:37679 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tgbZ4-0007G4-18
	for submit <at> debbugs.gnu.org; Fri, 07 Feb 2025 22:31:32 -0500
Received: from [47.204.136.169] (port=35300 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1tgbZ0-0007Fl-Cb
 for 75959 <at> debbugs.gnu.org; Fri, 07 Feb 2025 22:31:29 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Transfer-Encoding:Content-Type:
 MIME-Version:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=SvljkI/qtCuTnw5XWfIGZL0PDwlNtlftQ3pqO9JOsIg=; i=zacchae.us; b=oCeoXPkWuotG
 KnM1c7VEwLzwHkl5U720df0t0FdBBmNDoZhLL5dZJx+dtyI24V1RWC8N49KyleuG8zwMlVZvAg==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Transfer-Encoding:Content-Type:MIME-Version:
 Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=SvljkI/qtCuTnw5XWfIGZL0PDwlNtlftQ3pqO9JOsIg=; i=zacchae.us; b=Oi67HKr8cOk9
 2B9jkkp5b7k6c261zeYSv/dR343BvsSuE27pWLMsOz9FtRcqYxc426or0/ygphzcM3tUYzVY5Giix
 zGXEdOPLZMGypOf5mo99WA/uTO4KqJnSmdP8xKmeYZISwJ/HMn0jU/gRSGchTgIE8EoKrR8YG43+w
 oU+9IHA7W+RoFU44t89EZ8k0JpTxgrqXI9KQs62rFyRy/6gVpFQ3SOt70WCrDeSdKySgdWL5CMQBE
 fqs+LdvEVhP29xbbWwWr/EIITSRMIsrjTh5rbO1XmIA+SVCTvmzhy7gU0aaMunAdg+71MKsxcoA7m
 socV4s7T+Oz+7AUFm5ebTg==;
Received: from localhost.home ([127.0.0.1]:42370 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1tgbYt-000000005i8-1pcF;
 Fri, 07 Feb 2025 22:31:19 -0500
From: Zacchaeus Scheffer <eikcaz@HIDDEN>
To: 75959 <at> debbugs.gnu.org, Leo Famulari <leo@HIDDEN>
Subject: [PATCH v6] services: syncthing: Add support for config file
 generation.
Date: Fri, 07 Feb 2025 22:31:19 -0500
Message-ID: <87zfixf6yg.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: 2.0 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: From c63deb42caf24f34a22b1a56c37146d1cfdff4ae Mon Sep 17
 00:00:00
 2001 From: Zacchaeus <eikcaz@HIDDEN> Date: Sun, 21 Jul 2024 00:54:25
 -0700 Subject: [PATCH v6] services: syncthing: Add support fo [...] 
 Content analysis details:   (2.0 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-accredit.habeas.com]
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 -0.0 SPF_PASS               SPF: sender matches SPF record
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
 0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal
 information
X-Debbugs-Envelope-To: 75959
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.0 (+)

From c63deb42caf24f34a22b1a56c37146d1cfdff4ae Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz@HIDDEN>
Date: Sun, 21 Jul 2024 00:54:25 -0700
Subject: [PATCH v6] 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.

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
---
I noticed a bug in the documentation; the syncthing-configuration home
field does (and did) not do what it claimed.  I went ahead and updated
that documentation since I was already updating the Syncthing section.
I translated camelCase to kebab-case.  Hopefully this does not cause
much confusion for the end user.

I tried to improve my grammar from my previous patch, but I don't think
I was successful; feel free to make any of those corrections/additions
as you see fit.  I also remembered to warn the user about any sensitive
information they may be putting in /gnu/store.  I wrapped all the record
instances I could find in @code{...}, but I may have missed some.

I also updated the commit comment as per lfam's request.

 doc/guix.texi                   | 308 +++++++++++++++++-
 gnu/home/services/syncthing.scm |  17 +-
 gnu/services/syncthing.scm      | 533 ++++++++++++++++++++++++++++++--
 3 files changed, 826 insertions(+), 32 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..d8f35c5b16 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=C3=B6ren Tempel@*
+Copyright @copyright{} 2025 Zacchaeus@*
=20
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -22666,12 +22667,315 @@ The group as which the Syncthing service is to b=
e run.
 This assumes that the specified group exists.
=20
 @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 one 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/syncthnig-<user>}.
+
+@end table
+@end deftp
+
+In the below, only details specific to Guix, or related to how your
+device will connect to unrelated devices (relays, nameservers), are
+presented.  Otherwise, you should consult
+@uref{https://docs.syncthing.net/users/config.html, Syncthing config
+documentation}; camelCase there is converted to kebab-case here.  If
+you would like to migrate to a Guix-powered Syncthing configuration, the
+generated config adds newlines/whitespace to the produced config such
+that your old config can be @code{diff}'ed with the new one.  You can
+still 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
+gui-user and 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.  Also exposed
+in @file{/gnu/store}.
+
+@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 coordinate with a global
+network of relays to enable syncing between devices when all other
+methods fail.
+
+@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"})
+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{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/m=
eta.json"})
+@item @code{overwrite-remote-device-names-on-connect} (default: @var{"fals=
e"})
+@item @code{temp-index-min-blocks} (default: @var{"10"})
+@item @code{unacked-notification-id} (default: @var{"authenticationUserAnd=
Password"})
+@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.n=
et/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{"")})
+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 @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 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{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 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{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.  The special value
+``dynamic'' will have syncthing use several means to find the device.
+
+@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
+There are two pieces of configuration specific to the relationship
+between a specific folder and a specific device.  First, syncthing needs
+to know if the remote device should be able to see the files given to
+it.  Second, syncthing needs to know from where that device originates.
+See
+@href{https://docs.syncthing.net/users/config.html#config-option-folder.de=
vice,
+the Syncthing Documentation on this}.  If you leave these default, then
+you can just specify @code{syncthing-device}s instead of
+@code{syncthing-folder-device}s in a @code{syncthing-folder}'s
+@code{devices} field.
+
+@table @asis
+@item @code{device}
+device should be a @code{syncthing-device} for which this configuration
+applies.
+
+@item @code{introduced-by} (default: @var{""})
+@item @code{encryption-password} (default: @var{""})
+Beware: specifying this field will include this password as plain text
+(not encrypted) and globally visible in @file{/gnu/store/}.  If
+encryption-password 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 on Untrusted Devices}.  Note that file transfers are
+always end-to-end encrypted, regardless of this setting.
=20
 @end table
 @end deftp
=20
+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
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 =C2=A9 2023 Ludovic Court=C3=A8s <ludo@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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?))
=20
 (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-typ=
e 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..bdc00ec51f 100644
--- a/gnu/services/syncthing.scm
+++ b/gnu/services/syncthing.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright =C2=A9 2021 Oleg Pykhalov <go.wigust@HIDDEN>
 ;;; Copyright =C2=A9 2023 Justin Veilleux <terramorpha@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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))
=20
 ;;; Commentary:
 ;;;
@@ -35,6 +47,438 @@ (define-module (gnu services syncthing)
 ;;;
 ;;; Code:
=20
+(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 "fals=
e"))
+  (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 introduce=
d-by addresses paused auto-accept-folders max-send-kbps max-recv-kbps max-r=
equest-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 (defaul=
t "")))
+
+(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-inter=
val-s (default "3600"))
+  (versioning-cleanout-days syncthing-folder-versioning-cleanout-days (def=
ault #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 (def=
ault "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 "fa=
lse"))
+  (disable-temp-indexes syncthing-folder-disable-temp-indexes (default "fa=
lse"))
+  (paused syncthing-folder-paused (default "false"))
+  (weak-hash-threshold-pct syncthing-folder-weak-hash-threshold-pct (defau=
lt "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-si=
ngle-entry-size (default "1024"))
+  (xattr-filter-max-total-size syncthing-folder-xattr-filter-max-total-siz=
e (default "4096")))
+
+;; Some parameters, when empty, are fully omitted from the config file.  I=
t 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-del=
ay-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-cleanou=
t-days
+       versioning-keep versioning-max-age versioning-command copiers
+       puller-max-pending-kib hashers order ignore-delete scan-progress-in=
terval-s
+       puller-pause-s max-conflicts disable-sparse-files disable-temp-inde=
xes 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-met=
hod
+       case-sensitive-fs junctions-as-dirs sync-ownership send-ownership s=
ync-xattrs
+       send-xattrs xattr-filter-max-single-entry-size xattr-filter-max-tot=
al-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-ent=
ry-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 (d=
efault ""))
+  (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 (defau=
lt "true"))
+  (local-announce-enabled syncthing-config-local-announce-enabled (default=
 "true"))
+  (local-announce-port syncthing-config-local-announce-port (default "2102=
7"))
+  (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 (defau=
lt "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/new=
data"))
+  (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 (defau=
lt "12"))
+  (upgrade-to-pre-releases syncthing-config-upgrade-to-pre-releases (defau=
lt "false"))
+  (keep-temporaries-h syncthing-config-keep-temporaries-h (default "24"))
+  (cache-ignored-files syncthing-config-cache-ignored-files (default "fals=
e"))
+  (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 (defau=
lt "%"))
+  (min-home-disk-free syncthing-config-min-home-disk-free (default "1"))
+  (releases-url syncthing-config-releases-url (default "https://upgrades.s=
yncthing.net/meta.json"))
+  (overwrite-remote-device-names-on-connect syncthing-config-overwrite-rem=
ote-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 (defau=
lt "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 "http=
s://crash.syncthing.net/newcrash"))
+  (crash-reporting-enabled syncthing-config-crash-reporting-enabled (defau=
lt "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-inc=
oming-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 (defau=
lt "0"))
+  (connection-limit-max syncthing-config-connection-limit-max (default "0"=
))
+  (insecure-allow-old-tlsVersions syncthing-config-insecure-allow-old-tlsV=
ersions (default "false"))
+  (connection-priority-tcp-lan syncthing-config-connection-priority-tcp-la=
n (default "10"))
+  (connection-priority-quic-lan syncthing-config-connection-priority-quic-=
lan (default "20"))
+  (connection-priority-tcp-wan syncthing-config-connection-priority-tcp-wa=
n (default "30"))
+  (connection-priority-quic-wan syncthing-config-connection-priority-quic-=
wan (default "40"))
+  (connection-priority-relay syncthing-config-connection-priority-relay (d=
efault "50"))
+  (connection-priority-upgrade-threshold syncthing-config-connection-prior=
ity-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-announ=
ce-server
+       global-announce-enabled local-announce-enabled local-announce-port
+       local-announce-mcaddr max-send-kbps max-recv-kbps reconnection-inte=
rval-s
+       relays-enabled relay-reconnect-interval-m start-browser nat-enabled
+       nat-lease-minutes nat-renewal-minutes nat-timeout-seconds ur-accept=
ed
+       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-l=
an
+       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-c=
oncurrency
+       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-de=
vice-device
+                                                       (syncthing-folder-d=
evices folder)))
+                                                folders)))
+                            ;; devices are the same if their id's are equal
+                            (lambda (device1 device2)
+                              (string=3D (syncthing-device-id device1)
+                                       (syncthing-device-id device2)))))
+                    (gui (@ (enabled ,gui-enabled)
+                            (tls ,gui-tls)
+                            (debugging ,gui-debugging)
+                            (sendBasicAuthPrompt ,gui-send-basic-auth-prom=
pt))
+                         (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-insecur=
e-skip-verify))
+                                       '())
+                                 ,@(if ldap-search-base-dn
+                                       `((searchBaseDN ,ldap-search-base-d=
n))
+                                       '())
+                                 ,@(if ldap-search-filter
+                                       `((searchFilter ,ldap-search-filter=
))
+                                       '()))
+                               ""))
+                    (options (listenAddress ,listen-address)
+                             (globalAnnounceServer ,global-announce-server)
+                             (globalAnnounceEnabled ,global-announce-enabl=
ed)
+                             (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-int=
erval-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-release=
s)
+                             (keepTemporariesH ,keep-temporaries-h)
+                             (cacheIgnoredFiles ,cache-ignored-files)
+                             (progressUpdateIntervalS ,progress-update-int=
erval-s)
+                             (limitBandwidthInLan ,limit-bandwidth-in-lan)
+                             (minHomeDiskFree (@ (unit ,min-home-disk-free=
-unit))
+                                              ,min-home-disk-free)
+                             (releasesURL ,releases-url)
+                             (overwriteRemoteDeviceNamesOnConnect ,overwri=
te-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-enabl=
ed)
+                             (stunKeepaliveStartS ,stun-keepalive-start-s)
+                             (stunKeepaliveMinS ,stun-keepalive-min-s)
+                             (stunServer ,stun-server)
+                             (databaseTuning ,database-tuning)
+                             (maxConcurrentIncomingRequestKiB ,max-concurr=
ent-incoming-request-kib)
+                             (announceLANAddresses ,announce-lan-addresses)
+                             (sendFullIndexOnUpgrade ,send-full-index-on-u=
pgrade)
+                             (connectionLimitEnough ,connection-limit-enou=
gh)
+                             (connectionLimitMax ,connection-limit-max)
+                             (insecureAllowOldTLSVersions ,insecure-allow-=
old-tlsVersions)
+                             (connectionPriorityTcpLan ,connection-priorit=
y-tcp-lan)
+                             (connectionPriorityQuicLan ,connection-priori=
ty-quic-lan)
+                             (connectionPriorityTcpWan ,connection-priorit=
y-tcp-wan)
+                             (connectionPriorityQuicWan ,connection-priori=
ty-quic-wan)
+                             (connectionPriorityRelay ,connection-priority=
-relay)
+                             (connectionPriorityUpgradeThreshold ,connecti=
on-priority-upgrade-threshold))
+                    (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 whitesp=
ace
+;; 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-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+     `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag (subtags ..1) ..1) sibling-tags ...)
+     `(,current-indent (,tag "\n"
+                             ,@(indent-sxml subtags indent-increment
+                                            (string-append indent-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag primitive ...) sibling-tags ...)
+     `(,current-indent (,tag ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (() '())))
+
+(define (serialize-syncthing-config-file config)
+  (with-output-to-string
+    (lambda ()
+      (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->s=
xml config))
+                                           "    "
+                                           ""))))))
+
 (define-record-type* <syncthing-configuration>
   syncthing-configuration make-syncthing-configuration
   syncthing-configuration?
@@ -50,12 +494,14 @@ (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)))
=20
 (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-f=
ile)
     (list
      (shepherd-service
       (provision (if home-service?
@@ -64,39 +510,68 @@ (define syncthing-shepherd-service
                             (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=3D" (number->stri=
ng #$logflags)))
-                        '#$arguments)
-                #:user #$(and (not home-service?) user)
-                #:group #$(and (not home-service?) group)
-                #:environment-variables
-                (append
-                 (list
-                  (string-append "HOME=3D"
-                                 (or #$home
-                                     (passwd:dir
-                                      (getpw (if (and #$home-service?
-                                                      (not #$user))
-                                                 (getuid)
-                                                 #$user)))))
-                              "SSL_CERT_DIR=3D/etc/ssl/certs"
-                              "SSL_CERT_FILE=3D/etc/ssl/certs/ca-certifica=
tes.crt")
-                        (filter (negate       ;XXX: 'remove' is not in (gu=
ile)
-                                 (lambda (str)
-                                   (or (string-prefix? "HOME=3D" str)
-                                       (string-prefix? "SSL_CERT_DIR=3D" s=
tr)
-                                       (string-prefix? "SSL_CERT_FILE=3D" =
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 owners=
hip
+                 ;; is correct
+                 (unless (or #$(not config-file) #$home-service?)
+                   (system* "chown" #$user (string-append "/var/lib/syncth=
ing-" #$user))
+                   (system* "chmod" "700" (string-append "/var/lib/syncthi=
ng-" #$user)))
+                 (make-forkexec-constructor
+                  (append (list (string-append #$syncthing "/bin/syncthing=
")
+                                "--no-browser"
+                                "--no-restart"
+                                (string-append "--logflags=3D" (number->st=
ring #$logflags)))
+                          (if (or #$(not config-file) #$home-service?) '()
+                              (list (string-append "--home=3D/var/lib/sync=
thing-" #$user)))
+                          '#$arguments)
+                  #:user #$(and (not home-service?) user)
+                  #:group #$(and (not home-service?) group)
+                  #:environment-variables
+                  (append
+                   (list
+                    (string-append "HOME=3D"
+                                   (or #$home
+                                       (passwd:dir
+                                        (getpw (if (and #$home-service?
+                                                        (not #$user))
+                                                   (getuid)
+                                                   #$user)))))
+                    "SSL_CERT_DIR=3D/etc/ssl/certs"
+                    "SSL_CERT_FILE=3D/etc/ssl/certs/ca-certificates.crt")
+                   (filter (negate       ;XXX: 'remove' is not in (guile)
+                            (lambda (str)
+                              (or (string-prefix? "HOME=3D" str)
+                                  (string-prefix? "SSL_CERT_DIR=3D" str)
+                                  (string-prefix? "SSL_CERT_FILE=3D" str))=
))
+                           (environ))))))
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
=20
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (config-file user home ho=
me-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 roo=
t,
+        ;; 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-con=
fig-file
+                                                   config-file)))))
+        '())))
+
 (define syncthing-service-type
   (service-type (name 'syncthing)
                 (extensions (list (service-extension shepherd-root-service=
-type
-                                                     syncthing-shepherd-se=
rvice)))
+                                                     syncthing-shepherd-se=
rvice)
+                                  (service-extension special-files-service=
-type
+                                                     syncthing-files-servi=
ce)))
                 (description
                  "Run @uref{https://github.com/syncthing/syncthing, Syncth=
ing}
 decentralized continuous file system synchronization.")))
--=20
2.45.2





Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 8 Feb 2025 01:11:18 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Feb 07 20:11:18 2025
Received: from localhost ([127.0.0.1]:37465 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tgZNN-0000b4-7H
	for submit <at> debbugs.gnu.org; Fri, 07 Feb 2025 20:11:18 -0500
Received: from [47.204.136.169] (port=47538 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1tgZNJ-0000aj-9p
 for 75959 <at> debbugs.gnu.org; Fri, 07 Feb 2025 20:11:15 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Type:MIME-Version:Message-ID:Date:
 References:In-Reply-To:Subject:Cc:To:From:Sender:Reply-To:
 Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date:
 Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:
 List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=odL4VEsDcsWTknOg0TE0F2HI5vgxbodWgLftjK9vc5M=; i=zacchae.us; b=jMIbRWPtwCCN
 vqir5KzBxJ8Bv5XKO7jCuJ40ygAHfRXE45X4mMl1yyBI/EgnKZPuZ3scQc3ZlRnmjSXkOqWYDQ==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Type:MIME-Version:Message-ID:Date:References:
 In-Reply-To:Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding:
 Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender:
 Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=odL4VEsDcsWTknOg0TE0F2HI5vgxbodWgLftjK9vc5M=; i=zacchae.us; b=O5INUmgMOVk5
 I83gNzlhYk01jlU82KMTkORj9CKgi0BBpCNKH6SJNhB5pc94tiSjkikbI11O3Lz/ArG/dYkFSHycG
 181zb4tUY0ZTzHXOUXwrFzGksS4D3ti2FxZ7CgEhaNnIRRp5+Q4MNm+IASF/9gfK78lVk8ZQ+z/vp
 qOAyDwzZlick8pqqrQa98lO776obovFxhyeLS1YKcs/46SMlwDkHzSCLt2D83I5plQeJBwBe7pOf+
 6dy3Iu2+Wp4LP9SrSWWuqrP0z85xS0n/3rsuciLXzZCpo1mixpl+hfc7dXllfYuZPn1lrsPfYW8cK
 1Op683ssMpjcwgyiKJFmqQ==;
Received: from localhost.home ([127.0.0.1]:60782 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1tgZNC-000000005NQ-2sMs;
 Fri, 07 Feb 2025 20:11:07 -0500
From: Zacchaeus Scheffer <eikcaz@HIDDEN>
To: Leo Famulari <leo@HIDDEN>
Subject: Re: [bug#75959] [PATCH v5] services: syncthing: Added support for
 config file serialization.
In-Reply-To: <Z6aM5zRpu3CdICgd@HIDDEN> (Leo Famulari's message of "Fri, 7
 Feb 2025 17:44:55 -0500")
References: <20250130215954.9394-1-eikcaz@HIDDEN>
 <Z6aM5zRpu3CdICgd@HIDDEN>
Date: Fri, 07 Feb 2025 20:11:06 -0500
Message-ID: <874j15gs0l.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 2.0 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: Leo Famulari <leo@HIDDEN> writes: >> Subject: [PATCH
 v5] services: syncthing: Added support for config file >> serialization.
 > > Great! I tested the system-level service according to the config in my
 > last email and was able to succ [...] 
 Content analysis details:   (2.0 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-accredit.habeas.com]
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 -0.0 SPF_PASS               SPF: sender matches SPF record
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
X-Debbugs-Envelope-To: 75959
Cc: 75959 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.0 (+)

Leo Famulari <leo@HIDDEN> writes:

>> Subject: [PATCH v5] services: syncthing: Added support for config file
>>  serialization.
>
> Great! I tested the system-level service according to the config in my
> last email and was able to successfully integrate into my existing
> Syncthing cluster.
>
> Since we have already worked out the bugs on IRC, my feedback here will
> be largely cosmetic.
>
> I think of this patch as being more about generating Syncthing config
> files, rather than serializing them.

Makes sense.  I'll update serialization -> generation

>> * gnu/services/syncthing.scm: (syncthing-config-file) (syncthing-folder)
>> (syncthing-device) (syncthing-folder-device): New records;
>> (syncthing-service-type): added special-files-service-type extension for the
>> config file; (syncthing-files-service): service to create config file
>> * gnu/home/services/syncthing.scm: (home-syncthing-service-type): extended
>> home-files-services-type and re-exported more things from
>> gnu/services/syncthing.scm
>> * doc/guix.texi: (syncthing-service-type): document additions
>
> In the commit title and message, please capitalize and punctuate
> sentences normally, and avoid unecessary abbreviations. Try to write
> imperative sentences. For example, "Add support for configuration file
> serialization [or generation]".

Will do.

> When listing the new records / variables, they can be contained in the
> same parenthetical list, like (syncthing-config-file, syncthing-folder,
> [...]): New records.

Interesting.  I'll do as you suggest, but that's not what I've seen in
other commit messages. e.g. commit
e73cf57a204f2bf430c90930394afa08e9ec3399

>> @@ -22669,9 +22670,298 @@ 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.  Specifying this
>> +in a system service moves Syncthing's common configuration and data
>> +directory to @file{/var/lib/syncthnig-<user>}.
>
> Again, use standard capitalization, etc. Also, use texinfo markup and
> cross-references when useful:
>
> https://www.gnu.org/software/texinfo/manual/texinfo/html_node/Cross-References.html
>
> For example, "Either a file-like object that resolves to a Syncthing
> configuration XML file, or a @code{syncthing-config-file} record (see
> below).  When set to @code{#f} [...]"
>
> If you like, I can copy-edit the documentation portion of this patch for
> spelling, style, and grammar, as well as mark it up with texinfo. Let me
> know. If you'd prefer I don't, I'll send another review with feedback on
> those subjects.

I'll try to clean up these points myself first (I feel bad giving you
more work), but feel free to make changes as you see fit.  I'm aware
that my writing needs work...

>> +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.
>
> I have some misgivings about the use of camel case in user-facing
> interfaces. I do understand the utility of preserving the upstream
> names, but I wonder how much utility it really provides. In my
> experience, it's not typical to edit Syncthing's config.xml "by hand" (I
> did it some when I first started with Syncthing), so I'd expect users'
> to have little familiarity with these names, nor for them to desire a
> direct mapping between them. Additionally, there is utility in all Guix
> interfaces sharing a consistent style, and the camel case is not
> surfaced in Syncthing's own GUI.
>
> What do you think?

I explained my reasoning in this thread and in the IRC, and didn't
recieve much pushback.  However, I hadn't even realized that the
Syncthing GUI doesn't use camelcase...  I've been generating config
files via Emacs Org Mode src blocks with heavy use of noweb-refs, so my
experience is probably skewed.  If this were my library, I would
definitely use camelcase because I think that is clearest, but it's not
that important to me, and I don't think the draw-backs are that large.
I'll just put more effort into adding links to syncthing documentation
for things that don't map so obviously (e.g. ldap-transport is not found
in the documentation as ldapTransport, it is the transport field of the
ldap element).

>> +@item @code{localAnnouncePort} (default: @var{"21027"})
>> +@item @code{localAnnounceMCAddr} (default: @var{"[ff12::8384]:21027"})
>
> Not important for this patch, but do you have any ideas to facilitate
> multi-user systems, with respect to automatically using alternate
> ports?

No, and I'm not sure you should as automatically changing a port means
another machine doesn't know which port to look at (or so I assume,
maybe nameservers fix this issue, but I'd rather not bake in a
dependancy on third-party services).  Maybe this would make sense if
using guix-deploy because then you could tell each machine how the ports
match up.

>> +@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.
>
> I think this paragraph is not clear enough, although it starts to make
> sense after reading about the parameters. Still I think there is room
> for improvement.

Basically, if you have D devices and F folders, then there are D*F
relationships between those folders.  Specifically, we need to know, for
a given folder, if a device should only recieve encrypted data, and from
where we know that device (so we can automatically remove this device if
the introducing device is removed).  This could be accomplished by
having devices be listed in a folder as a tuple (device password
introducer), but I think that would be abusing lists, so a record seems
appropriate here.  I'll try and make this text more clear as to this
record's function.

>> +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}.
>
> I would write the hyperlink as "Syncthing Documentation on Untrusted
> Devices".

Will do

>> +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"))))
>
> Let's use the reserved domain name 'example.com'. Also, Syncthing
> requires these addresses to use a "protocol specific prefix", so how
> about "tcp://example.com"?
>
> https://docs.syncthing.net/users/config.html#config-option-device.address

Yes that's right

>> @@ -24,9 +25,23 @@ (define-module (gnu home services syncthing)
>
> Okay. Sounds like you've tested the home service satisfactorily.
>
>> @@ -25,9 +26,20 @@ (define-module (gnu services syncthing)
>> +;; 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.
>
> Agreed
>
>> +  (gui-apikey syncthing-config-gui-apikey (default "Vuky3VHVseQEoSk9YgxhSkNTnjQmqYK9"))
>
> I've never used the GUI API, but shouldn't this key be generated
> per-device, rather than hard-coded here?

That's a good point.  It's probably bad to have ANY default here.  I
just tested, and syncthing does not complain when apikey is omitted, so
I've made that change.  I'll add notes to this, the GUI password, and
encryption passwords that these are stored in /gnu/store and are
globally readable (GUI password is encrypted, but encryption passwords
are plain-text).

>> +;; 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)
>
> Nice. Hopefully this doesn't go stale anytime soon.

The whitespace rules have been consistent for as long as I've used
syncthing, so my hopes are high.

> Overall, it's looking good. Thanks a lot for this contribution! Let me
> know what you think about the feedback I've given here.

Thank you for reviewing!  I'll submit another patch shortly implementing
what we have discussed.




Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 7 Feb 2025 22:45:11 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Feb 07 17:45:11 2025
Received: from localhost ([127.0.0.1]:37192 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tgX5y-00023a-Je
	for submit <at> debbugs.gnu.org; Fri, 07 Feb 2025 17:45:11 -0500
Received: from fhigh-b1-smtp.messagingengine.com ([202.12.124.152]:37909)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <leo@HIDDEN>) id 1tgX5v-0001xQ-JQ
 for 75959 <at> debbugs.gnu.org; Fri, 07 Feb 2025 17:45:08 -0500
Received: from phl-compute-05.internal (phl-compute-05.phl.internal
 [10.202.2.45])
 by mailfhigh.stl.internal (Postfix) with ESMTP id C44592540150;
 Fri,  7 Feb 2025 17:45:01 -0500 (EST)
Received: from phl-mailfrontend-02 ([10.202.2.163])
 by phl-compute-05.internal (MEProxy); Fri, 07 Feb 2025 17:45:01 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=famulari.name;
 h=cc:cc:content-type:content-type:date:date:from:from
 :in-reply-to:in-reply-to:message-id:mime-version:references
 :reply-to:subject:subject:to:to; s=mesmtp; t=1738968301; x=
 1739054701; bh=gOGpv2AmNZJ4sT/a5DmHc4XiyHmq8ptC4rdDI8EPbBc=; b=W
 gSYdX64bG49YUP9Ags5k2esHPdbc/ABki+CM4GD0Rd+Xxz9IO0YFYqqGxrOs9GM8
 UmTLw1mlNEEBe/H8iNARphlA1+lzyE4ISixpB8lbIhhshVqHLRWr3nZNiD8u7WAu
 OZzNLf1vZ2x5ZM71gdm+Q4Nm9892lgxkRwOxjOL1JU=
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
 messagingengine.com; h=cc:cc:content-type:content-type:date:date
 :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to
 :message-id:mime-version:references:reply-to:subject:subject:to
 :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=
 1738968301; x=1739054701; bh=gOGpv2AmNZJ4sT/a5DmHc4XiyHmq8ptC4rd
 DI8EPbBc=; b=iR7KRfe5dz1JXshoSftyaH4QzLBlUPIiB649RE77PSmQhWpWMdN
 Jip9Izf+ShGpTwMucKCoet0AnRFwUewLLvZ7+Z/oyyhvnbC3a0YMRU1y9iS+pn3i
 pxc7JN3RWYyvqU54dBLyNAHozlskoCtqA02Iz+2DcUdj3TQaYWcCFlRjrYgf94C8
 ts/OsboCky681NCaQa10fqRSCtX2t3IBT/oDM7SztImrOFHxEuDy4i4yN/vnLta1
 sEBv2mPo20IEEeH8LluQqxOjQYQL14UxkdiaSjo8bgMds0uFicRi+uPQXUDwPSyg
 swlToSfZ4FDESazqqYMLs5sqvDq51G+tCwA==
X-ME-Sender: <xms:7YymZ8dgC3GT3AStRo9f_ZZoMo4fkjaQJdGLJuBCgCN6DSR-HoelaA>
 <xme:7YymZ-MaNWyaWxaAycJKyCRN5JEOTjJzVtrEKyWrAKjjoF764hT7gilpQGYupwlW3
 9V9o-cqwvInW3ujZQ>
X-ME-Received: <xmr:7YymZ9i8aGPqQms3HE0GQ6yLDJdukRI24On7AoYBjPjdrR9vvwsUO3X2_vthOchiFWkXqK7rUPAbeJaRAVglA0Xz>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdeftdehudcutefuodetggdotefrod
 ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp
 uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurhepfffhvf
 evuffkfhggtggujgesthdtredttddtvdenucfhrhhomhepnfgvohcuhfgrmhhulhgrrhhi
 uceolhgvohesfhgrmhhulhgrrhhirdhnrghmvgeqnecuggftrfgrthhtvghrnhepfffhff
 ehgfetgfegteejgeegfeetleeihfefueevgedvleelhfdtkeeiuefgvdfgnecuffhomhgr
 ihhnpehgnhhurdhorhhgpdhshihntghthhhinhhgrdhnvghtnecuvehluhhsthgvrhfuih
 iivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgvohesfhgrmhhulhgrrhhirdhn
 rghmvgdpnhgspghrtghpthhtohepvddpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtoh
 epvghikhgtrgiiseiirggttghhrggvrdhushdprhgtphhtthhopeejheelheelseguvggs
 sghughhsrdhgnhhurdhorhhg
X-ME-Proxy: <xmx:7YymZx_kRRl2UkNXr9maF0g3XNzAfIzoTO2TZrQOt5bDYpxnwkTQTg>
 <xmx:7YymZ4u_jfhVvgA6omgisyaqf2_qQXOwMfR1kra4fmokIIxOEpkJnA>
 <xmx:7YymZ4EMfM3AJIz8aIQ-wzCXwIq9WIZNypjtp8M3I_of3jkaymwK4A>
 <xmx:7YymZ3PKlsimVYzIuZ28fg6NLKZNSAlVyPAV_5hKKkXEmO0B6Cx64Q>
 <xmx:7YymZx4TH9VA7pqzdxjx1s1iZXiODZ6_akmpG3oZ_sdt6jUg3iSJs6sA>
Feedback-ID: i819c4023:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA; Fri,
 7 Feb 2025 17:45:00 -0500 (EST)
Date: Fri, 7 Feb 2025 17:44:55 -0500
From: Leo Famulari <leo@HIDDEN>
To: Zacchaeus <eikcaz@HIDDEN>
Subject: Re: [bug#75959] [PATCH v5] services: syncthing: Added support for
 config file serialization.
Message-ID: <Z6aM5zRpu3CdICgd@HIDDEN>
References: <20250130215954.9394-1-eikcaz@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
In-Reply-To: <20250130215954.9394-1-eikcaz@HIDDEN>
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 75959
Cc: 75959 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

> Subject: [PATCH v5] services: syncthing: Added support for config file
>  serialization.

Great! I tested the system-level service according to the config in my
last email and was able to successfully integrate into my existing
Syncthing cluster.

Since we have already worked out the bugs on IRC, my feedback here will
be largely cosmetic.

I think of this patch as being more about generating Syncthing config
files, rather than serializing them.

> * gnu/services/syncthing.scm: (syncthing-config-file) (syncthing-folder)
> (syncthing-device) (syncthing-folder-device): New records;
> (syncthing-service-type): added special-files-service-type extension for the
> config file; (syncthing-files-service): service to create config file
> * gnu/home/services/syncthing.scm: (home-syncthing-service-type): extended
> home-files-services-type and re-exported more things from
> gnu/services/syncthing.scm
> * doc/guix.texi: (syncthing-service-type): document additions

In the commit title and message, please capitalize and punctuate
sentences normally, and avoid unecessary abbreviations. Try to write
imperative sentences. For example, "Add support for configuration file
serialization [or generation]".

When listing the new records / variables, they can be contained in the
same parenthetical list, like (syncthing-config-file, syncthing-folder,
[...]): New records.

> @@ -22669,9 +22670,298 @@ 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.  Specifying this
> +in a system service moves Syncthing's common configuration and data
> +directory to @file{/var/lib/syncthnig-<user>}.

Again, use standard capitalization, etc. Also, use texinfo markup and
cross-references when useful:

https://www.gnu.org/software/texinfo/manual/texinfo/html_node/Cross-References.html

For example, "Either a file-like object that resolves to a Syncthing
configuration XML file, or a @code{syncthing-config-file} record (see
below).  When set to @code{#f} [...]"

If you like, I can copy-edit the documentation portion of this patch for
spelling, style, and grammar, as well as mark it up with texinfo. Let me
know. If you'd prefer I don't, I'll send another review with feedback on
those subjects.

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

I have some misgivings about the use of camel case in user-facing
interfaces. I do understand the utility of preserving the upstream
names, but I wonder how much utility it really provides. In my
experience, it's not typical to edit Syncthing's config.xml "by hand" (I
did it some when I first started with Syncthing), so I'd expect users'
to have little familiarity with these names, nor for them to desire a
direct mapping between them. Additionally, there is utility in all Guix
interfaces sharing a consistent style, and the camel case is not
surfaced in Syncthing's own GUI.

What do you think?

> +@item @code{localAnnouncePort} (default: @var{"21027"})
> +@item @code{localAnnounceMCAddr} (default: @var{"[ff12::8384]:21027"})

Not important for this patch, but do you have any ideas to facilitate
multi-user systems, with respect to automatically using alternate ports?

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

I think this paragraph is not clear enough, although it starts to make
sense after reading about the parameters. Still I think there is room
for improvement.

> +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}.

I would write the hyperlink as "Syncthing Documentation on Untrusted
Devices".

> +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"))))

Let's use the reserved domain name 'example.com'. Also, Syncthing
requires these addresses to use a "protocol specific prefix", so how
about "tcp://example.com"?

https://docs.syncthing.net/users/config.html#config-option-device.address

> @@ -24,9 +25,23 @@ (define-module (gnu home services syncthing)

Okay. Sounds like you've tested the home service satisfactorily.

> @@ -25,9 +26,20 @@ (define-module (gnu services syncthing)
> +;; 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.

Agreed

> +  (gui-apikey syncthing-config-gui-apikey (default "Vuky3VHVseQEoSk9YgxhSkNTnjQmqYK9"))

I've never used the GUI API, but shouldn't this key be generated
per-device, rather than hard-coded here?

> +;; 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)

Nice. Hopefully this doesn't go stale anytime soon.

Overall, it's looking good. Thanks a lot for this contribution! Let me
know what you think about the feedback I've given here.




Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 7 Feb 2025 04:40:01 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu Feb 06 23:40:01 2025
Received: from localhost ([127.0.0.1]:60309 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tgG9n-0002QS-E3
	for submit <at> debbugs.gnu.org; Thu, 06 Feb 2025 23:40:01 -0500
Received: from [47.204.136.169] (port=53254 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1tgG9i-0002Q9-JW
 for 75959 <at> debbugs.gnu.org; Thu, 06 Feb 2025 23:39:57 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Transfer-Encoding:Content-Type:
 MIME-Version:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=AUdTyVGwmWjfC7L9sbk8p6KQhJb/8tGNJvHifVJQxhg=; i=zacchae.us; b=jDMujuvWArhX
 ywR2iqB3qE0sH7m2l5RQZhkd7GveZBtKAMtr+U3vjcUyl3eN0I4vxGUHvlIMQiOCHz1LcJNLBA==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Transfer-Encoding:Content-Type:MIME-Version:
 Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=AUdTyVGwmWjfC7L9sbk8p6KQhJb/8tGNJvHifVJQxhg=; i=zacchae.us; b=SEZoaeJ0YP1y
 iQVrPy8qzRDRPqmXCE6W5DYvW6T6WX9ZtMp29S8a9LL3j0yghVviRubako/wgswYyHDte3xEeF8Dl
 tDEhmkC67KsSJ+Y/z7CrLLtUCwYQDnfveLXa371S98cJNGJZaiLB9TwgAVlzgf/RUAhM8DO/LK3bC
 H9uHTG7pFJTF1cVTUh4Hhjfg8YFaXNu03mxEbN+S5/BwJqOOlxLJVarKIJ7dLEhE1LZQqSoSW/TZ3
 NFvFQIMAzoOcQs4s5UTfKV4N3nxg1V3iYSlbf0fUpSdi9DMQTJP46nqqSS4XIk2N2H9KaMYpZNyCd
 2BrTn/XfyMK+n0w+I9Gu6Q==;
Received: from localhost.home ([127.0.0.1]:46134 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1tgG9c-000000001dJ-083E
 for 75959 <at> debbugs.gnu.org; Thu, 06 Feb 2025 23:39:48 -0500
From: Zacchaeus <eikcaz@HIDDEN>
To: 75959 <at> debbugs.gnu.org
Subject: [PATCH v5] services: syncthing: Added support for config file
 serialization.
Date: Thu, 06 Feb 2025 23:39:48 -0500
Message-ID: <878qqigygb.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: 2.0 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: From ad2bb352f8ff2cd93a2a1694faf353e02854ef2b Mon Sep 17
 00:00:00
 2001 From: Zacchaeus <eikcaz@HIDDEN> Date: Sun, 21 Jul 2024 00:54:25
 -0700 Subject: [PATCH v5] services: syncthing: Added support [...] 
 Content analysis details:   (2.0 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-accredit.habeas.com]
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 -0.0 SPF_PASS               SPF: sender matches SPF record
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
 0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal
 information
 0.0 T_FILL_THIS_FORM_FRAUD_PHISH Answer suspicious question(s)
X-Debbugs-Envelope-To: 75959
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.0 (+)

From ad2bb352f8ff2cd93a2a1694faf353e02854ef2b Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz@HIDDEN>
Date: Sun, 21 Jul 2024 00:54:25 -0700
Subject: [PATCH v5] services: syncthing: Added support for config file
 serialization.

* gnu/services/syncthing.scm: (syncthing-config-file) (syncthing-folder)
(syncthing-device) (syncthing-folder-device): New records;
(syncthing-service-type): added special-files-service-type extension for the
config file; (syncthing-files-service): service to create config file
* gnu/home/services/syncthing.scm: (home-syncthing-service-type): extended
home-files-services-type and re-exported more things from
gnu/services/syncthing.scm
* doc/guix.texi: (syncthing-service-type): document additions

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
---
The previous patch had an issue with fresh system installs: the
Syncthing service would populate ~/.config/syncthing/config.xml too
early, so ~ would be owned by root, and ~ would be non-empty so
skeletons would never be copied in.  I addressed this by moving
syncthing's config home to /var/lib/syncthing-<user>.  I only do this if
it is a system service AND you are specifying a config-file.  This is
important for backwards compatibility.  We wouldn't want people's
syncthing to suddenly start looking in /var/lib/ for their gui-managed
config after a system upgrade.

 doc/guix.texi                   | 290 +++++++++++++++++
 gnu/home/services/syncthing.scm |  17 +-
 gnu/services/syncthing.scm      | 533 ++++++++++++++++++++++++++++++--
 3 files changed, 810 insertions(+), 30 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..1fdc772a1d 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=C3=B6ren Tempel@*
+Copyright @copyright{} 2025 Zacchaeus@*
=20
 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,298 @@ 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}.
=20
+@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.  Specifying this
+in a system service moves Syncthing's common configuration and data
+directory to @file{/var/lib/syncthnig-<user>}.
+
+@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/me=
ta.json"})
+@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"})
+@item @code{tempIndexMinBlocks} (default: @var{"10"})
+@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPa=
ssword"})
+@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 Documenta=
tion Untrusted}.
+Note that file transfers are always end-to-end encrypted, regardless of
+this setting.
+
 @end table
 @end deftp
=20
+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
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 =C2=A9 2023 Ludovic Court=C3=A8s <ludo@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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?))
=20
 (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-typ=
e 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..5b9567b716 100644
--- a/gnu/services/syncthing.scm
+++ b/gnu/services/syncthing.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright =C2=A9 2021 Oleg Pykhalov <go.wigust@HIDDEN>
 ;;; Copyright =C2=A9 2023 Justin Veilleux <terramorpha@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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))
=20
 ;;; Commentary:
 ;;;
@@ -35,6 +47,438 @@ (define-module (gnu services syncthing)
 ;;;
 ;;; Code:
=20
+(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 (def=
ault "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 introducedB=
y 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-d=
evice (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-cleanupInterval=
S (default "3600"))
+  (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (defau=
lt #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 (defau=
lt "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-maxSingleEn=
trySize (default "1024"))
+  (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (def=
ault "4096")))
+
+;; Some parameters, when empty, are fully omitted from the config file.  I=
t 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-cleanoutDa=
ys
+       versioning-keep versioning-maxAge versioning-command copiers
+       pullerMaxPendingKiB hashers order ignoreDelete scanProgressIntervalS
+       pullerPauseS maxConflicts disableSparseFiles disableTempIndexes pau=
sed
+       weakHashThresholdPct markerName copyOwnershipFromParent modTimeWind=
owS
+       maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod
+       caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXat=
trs
+       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-cleanoutD=
ays)
+                         ,@(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-maxSingleEntryS=
ize)
+                          (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 (defau=
lt "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 "Vuky3VHVseQEoSk9YgxhSk=
NTnjQmqYK9"))
+  (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 (defau=
lt ""))
+  (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 "de=
fault"))
+  (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "=
true"))
+  (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "tr=
ue"))
+  (localAnnouncePort syncthing-config-localAnnouncePort (default "21027"))
+  (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff1=
2::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 (defau=
lt "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/newda=
ta"))
+  (urPostInsecurely syncthing-config-urPostInsecurely (default "false"))
+  (urInitialDelayS syncthing-config-urInitialDelayS (default "1800"))
+  (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12=
"))
+  (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "fa=
lse"))
+  (keepTemporariesH syncthing-config-keepTemporariesH (default "24"))
+  (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false"))
+  (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (defau=
lt "5"))
+  (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "fals=
e"))
+  (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%"=
))
+  (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1"))
+  (releasesURL syncthing-config-releasesURL (default "https://upgrades.syn=
cthing.net/meta.json"))
+  (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDev=
iceNamesOnConnect (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-maxConcurrentIncomingR=
equestKiB (default "0"))
+  (announceLANAddresses syncthing-config-announceLANAddresses (default "tr=
ue"))
+  (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default=
 "false"))
+  (connectionLimitEnough syncthing-config-connectionLimitEnough (default "=
0"))
+  (connectionLimitMax syncthing-config-connectionLimitMax (default "0"))
+  (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersion=
s (default "false"))
+  (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (def=
ault "10"))
+  (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (d=
efault "20"))
+  (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (def=
ault "30"))
+  (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (d=
efault "40"))
+  (connectionPriorityRelay syncthing-config-connectionPriorityRelay (defau=
lt "50"))
+  (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityU=
pgradeThreshold (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 globalAnnounceSer=
ver
+       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 maxFolderConcurre=
ncy
+       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-de=
vice-device
+                                                       (syncthing-folder-d=
evices folder)))
+                                                folders)))
+                            ;; devices are the same if their id's are equal
+                            (lambda (device1 device2)
+                              (string=3D (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-insecur=
eSkipVerify))
+                                       '())
+                                 ,@(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 ,relayReconnectInter=
valM)
+                             (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 ,progressUpdateInter=
valS)
+                             (limitBandwidthInLan ,limitBandwidthInLan)
+                             (minHomeDiskFree (@ (unit ,minHomeDiskFree-un=
it))
+                                              ,minHomeDiskFree)
+                             (releasesURL ,releasesURL)
+                             (overwriteRemoteDeviceNamesOnConnect ,overwri=
teRemoteDeviceNamesOnConnect)
+                             (tempIndexMinBlocks ,tempIndexMinBlocks)
+                             (unackedNotificationID ,unackedNotificationID)
+                             (trafficClass ,trafficClass)
+                             (setLowPriority ,setLowPriority)
+                             (maxFolderConcurrency ,maxFolderConcurrency)
+                             (crashReportingURL ,crashReportingURL)
+                             (crashReportingEnabled ,crashReportingEnabled)
+                             (stunKeepaliveStartS ,stunKeepaliveStartS)
+                             (stunKeepaliveMinS ,stunKeepaliveMinS)
+                             (stunServer ,stunServer)
+                             (databaseTuning ,databaseTuning)
+                             (maxConcurrentIncomingRequestKiB ,maxConcurre=
ntIncomingRequestKiB)
+                             (announceLANAddresses ,announceLANAddresses)
+                             (sendFullIndexOnUpgrade ,sendFullIndexOnUpgra=
de)
+                             (connectionLimitEnough ,connectionLimitEnough)
+                             (connectionLimitMax ,connectionLimitMax)
+                             (insecureAllowOldTLSVersions ,insecureAllowOl=
dTLSVersions)
+                             (connectionPriorityTcpLan ,connectionPriority=
TcpLan)
+                             (connectionPriorityQuicLan ,connectionPriorit=
yQuicLan)
+                             (connectionPriorityTcpWan ,connectionPriority=
TcpWan)
+                             (connectionPriorityQuicWan ,connectionPriorit=
yQuicWan)
+                             (connectionPriorityRelay ,connectionPriorityR=
elay)
+                             (connectionPriorityUpgradeThreshold ,connecti=
onPriorityUpgradeThreshold))
+                    (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 whitesp=
ace
+;; 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-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+     `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag (subtags ..1) ..1) sibling-tags ...)
+     `(,current-indent (,tag "\n"
+                             ,@(indent-sxml subtags indent-increment
+                                            (string-append indent-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag primitive ...) sibling-tags ...)
+     `(,current-indent (,tag ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (() '())))
+
+(define (serialize-syncthing-config-file config)
+  (with-output-to-string
+    (lambda ()
+      (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->s=
xml config))
+                                           "    "
+                                           ""))))))
+
 (define-record-type* <syncthing-configuration>
   syncthing-configuration make-syncthing-configuration
   syncthing-configuration?
@@ -50,12 +494,14 @@ (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)))
=20
 (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-f=
ile)
     (list
      (shepherd-service
       (provision (if home-service?
@@ -64,39 +510,68 @@ (define syncthing-shepherd-service
                             (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=3D" (number->stri=
ng #$logflags)))
-                        '#$arguments)
-                #:user #$(and (not home-service?) user)
-                #:group #$(and (not home-service?) group)
-                #:environment-variables
-                (append
-                 (list
-                  (string-append "HOME=3D"
-                                 (or #$home
-                                     (passwd:dir
-                                      (getpw (if (and #$home-service?
-                                                      (not #$user))
-                                                 (getuid)
-                                                 #$user)))))
-                              "SSL_CERT_DIR=3D/etc/ssl/certs"
-                              "SSL_CERT_FILE=3D/etc/ssl/certs/ca-certifica=
tes.crt")
-                        (filter (negate       ;XXX: 'remove' is not in (gu=
ile)
-                                 (lambda (str)
-                                   (or (string-prefix? "HOME=3D" str)
-                                       (string-prefix? "SSL_CERT_DIR=3D" s=
tr)
-                                       (string-prefix? "SSL_CERT_FILE=3D" =
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 owners=
hip
+                 ;; is correct
+                 (unless (or #$(not config-file) #$home-service?)
+                   (system* "chown" #$user (string-append "/var/lib/syncth=
ing-" #$user))
+                   (system* "chmod" "700" (string-append "/var/lib/syncthi=
ng-" #$user)))
+                 (make-forkexec-constructor
+                  (append (list (string-append #$syncthing "/bin/syncthing=
")
+                                "--no-browser"
+                                "--no-restart"
+                                (string-append "--logflags=3D" (number->st=
ring #$logflags)))
+                          (if (or #$(not config-file) #$home-service?) '()
+                              (list (string-append "--home=3D/var/lib/sync=
thing-" #$user)))
+                          '#$arguments)
+                  #:user #$(and (not home-service?) user)
+                  #:group #$(and (not home-service?) group)
+                  #:environment-variables
+                  (append
+                   (list
+                    (string-append "HOME=3D"
+                                   (or #$home
+                                       (passwd:dir
+                                        (getpw (if (and #$home-service?
+                                                        (not #$user))
+                                                   (getuid)
+                                                   #$user)))))
+                    "SSL_CERT_DIR=3D/etc/ssl/certs"
+                    "SSL_CERT_FILE=3D/etc/ssl/certs/ca-certificates.crt")
+                   (filter (negate       ;XXX: 'remove' is not in (guile)
+                            (lambda (str)
+                              (or (string-prefix? "HOME=3D" str)
+                                  (string-prefix? "SSL_CERT_DIR=3D" str)
+                                  (string-prefix? "SSL_CERT_FILE=3D" str))=
))
+                           (environ))))))
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
=20
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (config-file user home ho=
me-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 roo=
t,
+        ;; 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-con=
fig-file
+                                                   config-file)))))
+        '())))
+
 (define syncthing-service-type
   (service-type (name 'syncthing)
                 (extensions (list (service-extension shepherd-root-service=
-type
-                                                     syncthing-shepherd-se=
rvice)))
+                                                     syncthing-shepherd-se=
rvice)
+                                  (service-extension special-files-service=
-type
+                                                     syncthing-files-servi=
ce)))
                 (description
                  "Run @uref{https://github.com/syncthing/syncthing, Syncth=
ing}
 decentralized continuous file system synchronization.")))
--=20
2.45.2





Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 7 Feb 2025 00:21:46 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu Feb 06 19:21:46 2025
Received: from localhost ([127.0.0.1]:59919 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tgC7t-00075J-RW
	for submit <at> debbugs.gnu.org; Thu, 06 Feb 2025 19:21:46 -0500
Received: from fhigh-b4-smtp.messagingengine.com ([202.12.124.155]:51971)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <leo@HIDDEN>) id 1tgC7q-000753-Id
 for 75959 <at> debbugs.gnu.org; Thu, 06 Feb 2025 19:21:43 -0500
Received: from phl-compute-01.internal (phl-compute-01.phl.internal
 [10.202.2.41])
 by mailfhigh.stl.internal (Postfix) with ESMTP id B8E032540123;
 Thu,  6 Feb 2025 19:21:36 -0500 (EST)
Received: from phl-mailfrontend-01 ([10.202.2.162])
 by phl-compute-01.internal (MEProxy); Thu, 06 Feb 2025 19:21:36 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=famulari.name;
 h=cc:cc:content-type:content-type:date:date:from:from
 :in-reply-to:in-reply-to:message-id:mime-version:references
 :reply-to:subject:subject:to:to; s=mesmtp; t=1738887696; x=
 1738974096; bh=q3pnzeyhIL37l3AILXzdQ7SpBjnLG/Nl2AoFdfJ99wM=; b=E
 DpS/zDaqMGZJhu+y7gC1ONsPZ+4gt3xMKysqZzA9E2Kf+eU10h4S7PWjYajafVJV
 skXsmwz+sulvhSNp/zO+ty6yVfXC/8Q7F1EWhgp6Wa0Py//uYwERjG6Xnn9oCrka
 T2aWj9kW0wF11F4iDND7hPPL9GUdnmB8VJrH9Osu/o=
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
 messagingengine.com; h=cc:cc:content-type:content-type:date:date
 :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to
 :message-id:mime-version:references:reply-to:subject:subject:to
 :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=
 1738887696; x=1738974096; bh=q3pnzeyhIL37l3AILXzdQ7SpBjnLG/Nl2Ao
 FdfJ99wM=; b=UH891aW1OHM4uN7MWwy3V+vL4y8jFPe1/pjaYsVTEGSs1lHKFCA
 dq3qFVqiz5/4RsYdVpQrCwfn9KH+6Ub8bDjvu6Daxssc3POpUNa1XtcuE3mC3bdL
 9syg9n1ShRupXMHTw5NKrwPiKSBvfeo07p3dZ6wkuutlLq4V/cZo3GmZ9vjq/vt7
 auvKllzFo2roQ7JPtqOSrU8pDse7ajwGDsjcSe+dftx3N581jDGJGfJuzz+n7R4A
 Xjnig8HhBX/MTSQ3MWWmHUaOAqUe72SY9hnAoXRa5fZgPdw4tJkS1iQDuQAfO43F
 tGxoCnml84w5Bh5g0dTtuiOfW1B1zm8I+fQ==
X-ME-Sender: <xms:EFKlZ583LMAuv3Ax2rjoUci5GSLAfNdVS8Pl8N_gkrk9kIDA9zBSCg>
 <xme:EFKlZ9ukVv0TwANbkJE4sNUHf6xQ3rR2l443SKYei1nAMwDs2O4_fEehdej4yldHg
 b0XT5FjkjKLKnDHgg>
X-ME-Received: <xmr:EFKlZ3BLRZEeurpvZbMp6qRf6o62DqFiWlxb6kphQIAZ_hS5Z6deWDX_oJaS_KHuGHG42YcziObq5r7G4lB8cNHO>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddvjeejlecutefuodetggdotefrod
 ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp
 uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurhepfffhvf
 evuffkfhggtggujgesghdtreertddtvdenucfhrhhomhepnfgvohcuhfgrmhhulhgrrhhi
 uceolhgvohesfhgrmhhulhgrrhhirdhnrghmvgeqnecuggftrfgrthhtvghrnheptedvtd
 etfefffffffeelfedvkeekfeduveduieejfeeugeelteffvdeuffejleevnecuvehluhhs
 thgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgvohesfhgrmhhulh
 grrhhirdhnrghmvgdpnhgspghrtghpthhtohepvddpmhhouggvpehsmhhtphhouhhtpdhr
 tghpthhtohepvghikhgtrgiiseiirggttghhrggvrdhushdprhgtphhtthhopeejheelhe
 elseguvggssghughhsrdhgnhhurdhorhhg
X-ME-Proxy: <xmx:EFKlZ9cqawyXAEdArGfspJG9QFftQqcjhmCvyPITBYDv8jxgI90S1A>
 <xmx:EFKlZ-Mrlr6Spka52kFQjXYcb4tkX--lb8BJUElZRXZHHXT7avytfg>
 <xmx:EFKlZ_k9ieEwBTHDjfPKXUEcPUbBr_d8bWq_v72dZ2zJ5m34bNvOkQ>
 <xmx:EFKlZ4uqRtf4JIBYLuJ-9E4G913FNyLmy97GQ3BGEz90krigmpxw-g>
 <xmx:EFKlZ3ayN-hRyBMorQTDG94eeqpCFqx3PmnhdP6Aeuhf5SaEozZCI0g1>
Feedback-ID: i819c4023:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu,
 6 Feb 2025 19:21:36 -0500 (EST)
Date: Thu, 6 Feb 2025 19:21:34 -0500
From: Leo Famulari <leo@HIDDEN>
To: Zacchaeus <eikcaz@HIDDEN>
Subject: Re: [bug#75959] [PATCH] services: syncthing: Added support for
 config file serialization.
Message-ID: <Z6VSDhVzPtWXowK9@HIDDEN>
References: <20250130215954.9394-1-eikcaz@HIDDEN>
 <87frkqhg8w.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/signed; micalg=pgp-sha256;
 protocol="application/pgp-signature"; boundary="zOjsZ3goX5BS1jN8"
Content-Disposition: inline
In-Reply-To: <87frkqhg8w.fsf@HIDDEN>
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 75959
Cc: 75959 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)


--zOjsZ3goX5BS1jN8
Content-Type: multipart/mixed; boundary="/Yve2en6Dl2hfPbu"
Content-Disposition: inline


--/Yve2en6Dl2hfPbu
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

On Thu, Feb 06, 2025 at 05:15:27PM -0500, Zacchaeus wrote:
> From 7ef311e85b1198c752b2eec57caa0256227e079c Mon Sep 17 00:00:00 2001
> From: Zacchaeus <eikcaz@HIDDEN>
> Date: Sun, 21 Jul 2024 00:54:25 -0700
> Subject: [PATCH] services: syncthing: Added support for config file
>  serialization.

Thanks for the updated patch! By the way, when you generate the patches,
please use the Git option --reroll-count, so that we can keep track of
the revisions.

I applied it to the current master branch, and created a VM image based
on the lightweight-desktop template in our repo (attached).

Then, I copied the image out of the store, made it writable, and booted
it with QEMU.

------
$ ./pre-inst-env guix system image --image-type=3Dqcow2 --no-grafts doc/os-=
config-lightweight-desktop.texi-syncthing --fallback --max-jobs=3D1 --cores=
=3D12 --keep-going --image-size=3D10G -v3=20
/gnu/store/86zz6i1x55irgg1r74riil8avgl8hlp9-image.qcow2
$ cp /gnu/store/86zz6i1x55irgg1r74riil8avgl8hlp9-image.qcow2 ~/tmp/guix.qco=
w2 && chmod 600 ~/tmp/guix.qcow2
$ qemu-system-x86_64 -nic user,model=3Dvirtio-net-pci -enable-kvm -m 1024 /=
home/leo/tmp/guix.qcow2
------

However, when I have included an instance of syncthing-service-type in
the OS declaration, my user's home directory is owned by root, and I'm
unable to log in as my user. When I remove syncthing-service-type, that
problem does not exist.

Any ideas?

--/Yve2en6Dl2hfPbu
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename="os-config-lightweight-desktop.texi-syncthing"

;; -*- mode: scheme; -*-
;; This is an operating system configuration template
;; for a "desktop" setup without full-blown desktop
;; environments.

(use-modules (gnu) (gnu system nss))
(use-service-modules desktop syncthing)
(use-package-modules bootloaders emacs emacs-xyz ratpoison suckless wm
                     web-browsers
                     web
                     xdisorg
                     xorg)

(operating-system
  (host-name "antelope")
  (timezone "Europe/Paris")
  (locale "en_US.utf8")

  (kernel-arguments (list "console=ttyS0,115200"))

  ;; Use the UEFI variant of GRUB with the EFI System
  ;; Partition mounted on /boot/efi.
  (bootloader (bootloader-configuration
                (bootloader grub-bootloader)
                (targets '("/dev/sdX"))))

  ;; Assume the target root file system is labelled "my-root",
  ;; and the EFI System Partition has UUID 1234-ABCD.
  (file-systems (cons
                 (file-system
                         (device (file-system-label "my-root"))
                         (mount-point "/")
                         (type "ext4"))
                 %base-file-systems))

  (users (cons* (user-account
                 (name "leo")
                 (password "")
                 (comment "leo")
                 (group "users")
                 (supplementary-groups '("wheel" "netdev"
                                         "audio" "video")))
                %base-user-accounts))

  ;; Add a bunch of window managers; we can choose one at
  ;; the log-in screen with F1.
  (packages (append (list
                     ;; terminal emulator
                     dillo
                     netsurf
                     rxvt-unicode
                     xterm)
                    %base-packages))

  ;; Use the "desktop" services, which include the X11
  ;; log-in service, networking with NetworkManager, and more.
  (services
    (cons* (service xfce-desktop-service-type)
           (service syncthing-service-type
                    (let ((laptop (syncthing-device (id "M...")))
                          (desktop (syncthing-device (id "X...")
                                                     (addresses '("tcp://foo")))))
                      (syncthing-configuration
                       (user "leo")
                       (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 desktop)
                                                (encryptionPassword "mypassword"))))))))))))
            %desktop-services))

  ;; Allow resolution of '.local' host names with mDNS.
  (name-service-switch %mdns-host-lookup-nss))

--/Yve2en6Dl2hfPbu--

--zOjsZ3goX5BS1jN8
Content-Type: application/pgp-signature; name="signature.asc"

-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEaEByLu7k06ZO5T6saqwZY3V/R/8FAmelUgoACgkQaqwZY3V/
R/9HuRAAipDfpgEqO0zmrrmFQmaPcy1F/OSTWxQvLyLQCkInl/YH6qRY3IbPjyB7
nutSjIY6xTQnYFE9oomW7V/yVi3zXv/tMPnRvp3yi0+6Orsfbsg2ZC9jHyG76p2G
MvAIbHKB59GcsZ2rZjSVVzymmDyv13KOy8FG7ZQPYATaI5kURBxIJkpB2nrszGvW
kk76WHmbQFeNPOcrf/BV0AJ9zcRX7+eKvL90Vwhxb44KQbuR/4jJehwffFjL1yr1
rJ7TRqhfKbuyM31vTeh9WF1Pceifl66yzoZ8eHy635scucZUi6G1sxuSRy3ESdfo
KUrOksemwmTE3dpKz6Aa5KJnfB4LWozBX0F9xmhsKbC4oaIkS9FmbVQVbY47RMSk
0JHno2g258uwRChsODLLkl5G5NGB1JcJiGaOnvD6sqtx1Z7mzboVS/G3tO6ChxaD
1VYJa1PteU/4JPUS4RrrhTvyI4asCpCohEdUhn1JnLkQ2iCGXzHmIkB8F0HlmM2u
sHBXbEeMrdFIO2apqiqJmzLQGR06cIjOSh7nuMfODjoEgXK+DAsShuHGDwKPDZcl
5Ya/itPWahETcnTrv5fNq15g/NA2AukyIZyqKX2uiv4kUHvNqeAKo8DcXfxy/+z8
wlrvePVn/oZPMDZsZxE6PtylACjrpF3H60FSw44LSCzIm4hUbJw=
=M/mG
-----END PGP SIGNATURE-----

--zOjsZ3goX5BS1jN8--




Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 6 Feb 2025 22:15:42 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu Feb 06 17:15:42 2025
Received: from localhost ([127.0.0.1]:59685 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tgA9r-0006V8-U1
	for submit <at> debbugs.gnu.org; Thu, 06 Feb 2025 17:15:42 -0500
Received: from [47.204.136.169] (port=53130 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1tgA9l-0006Ui-Aq
 for 75959 <at> debbugs.gnu.org; Thu, 06 Feb 2025 17:15:36 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Transfer-Encoding:Content-Type:
 MIME-Version:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=mJ7doV/Nh9s1L8ft5BkVG7pshPsG1fOaykitFrZU0K0=; i=zacchae.us; b=bq+qKsvWnnIi
 u2fFYHatT8zVT5P13H6y1T9Bis7Z4Vf96pPsaB54kLF1EZK8PQzWxyIBJoIrM5Z+ZHeiyB2jDw==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Transfer-Encoding:Content-Type:MIME-Version:
 Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=mJ7doV/Nh9s1L8ft5BkVG7pshPsG1fOaykitFrZU0K0=; i=zacchae.us; b=IxlqJUizqVUX
 E4C/Wz6qKDcanbAfxPDUqgXuAZ3Y7zSvMd+8wpQti5PLTysrMOuL/AsLYfF8MegGC77G6s3J7W3WL
 0zBBH4N/nBj/EPleKIuen79Zl3GXdS4OPeCEChxJSt13iqa0w88WbSsc9IaZm0KLdtaidlBB3yOOe
 2bGr6D16y8MnCcKN/3qwORou40L/TzQ2M6wwrBf5A/dyzwU246GVeuXETNOQ4bCRlxWXTzJrsNDNC
 sF/GPe3fO9zKYjYwsqf3+4rNgZLHbJugZvflUnx3wEnCgJ7kO/B7cU0o8pUAz0oT3D35Jd3AlCjDt
 RSuybFcJ7JNo0Tb92LllTw==;
Received: from localhost.home ([127.0.0.1]:48950 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1tgA9e-000000000C0-3UQy
 for 75959 <at> debbugs.gnu.org; Thu, 06 Feb 2025 17:15:27 -0500
From: Zacchaeus <eikcaz@HIDDEN>
To: 75959 <at> debbugs.gnu.org
Subject: [PATCH] services: syncthing: Added support for config file
 serialization.
Date: Thu, 06 Feb 2025 17:15:27 -0500
Message-ID: <87frkqhg8w.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: 2.0 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: From 7ef311e85b1198c752b2eec57caa0256227e079c Mon Sep 17
 00:00:00
 2001 From: Zacchaeus <eikcaz@HIDDEN> Date: Sun, 21 Jul 2024 00:54:25
 -0700 Subject: [PATCH] services: syncthing: Added support for [...] 
 Content analysis details:   (2.0 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-trusted.bondedsender.org]
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 -0.0 SPF_PASS               SPF: sender matches SPF record
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
 0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal
 information
 0.0 T_FILL_THIS_FORM_FRAUD_PHISH Answer suspicious question(s)
X-Debbugs-Envelope-To: 75959
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.0 (+)

From 7ef311e85b1198c752b2eec57caa0256227e079c Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz@HIDDEN>
Date: Sun, 21 Jul 2024 00:54:25 -0700
Subject: [PATCH] services: syncthing: Added support for config file
 serialization.

* gnu/services/syncthing.scm: (syncthing-config-file) (syncthing-folder)
(syncthing-device) (syncthing-folder-device): New records;
(syncthing-service-type): added special-files-service-type extension for the
config file; (syncthing-files-service): service to create config file
* gnu/home/services/syncthing.scm: (home-syncthing-service-type): extended
home-files-services-type and re-exported more things from
gnu/services/syncthing.scm
* doc/guix.texi: (syncthing-service-type): document additions

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
---
Fixed a bug caused by running system service (home service was fine).
Also changed syncthing-config-file field of syncthing-configuration to
config-file (syncthing-config-file record name unchanged).  Hence,
setting the config file looks something like:

(syncthing-configuration (config-file (syncthing-config-file ...)))

This matches the pattern observed in other services like:

(connman-configuration (general-configuration (connman-general-configuratio=
n ...)))


 doc/guix.texi                   | 288 ++++++++++++++++++++
 gnu/home/services/syncthing.scm |  17 +-
 gnu/services/syncthing.scm      | 466 +++++++++++++++++++++++++++++++-
 3 files changed, 768 insertions(+), 3 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..2a4829a6a6 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=C3=B6ren Tempel@*
+Copyright @copyright{} 2025 Zacchaeus@*
=20
 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}.
=20
+@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/me=
ta.json"})
+@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"})
+@item @code{tempIndexMinBlocks} (default: @var{"10"})
+@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPa=
ssword"})
+@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 Documenta=
tion Untrusted}.
+Note that file transfers are always end-to-end encrypted, regardless of
+this setting.
+
 @end table
 @end deftp
=20
+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
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 =C2=A9 2023 Ludovic Court=C3=A8s <ludo@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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?))
=20
 (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-typ=
e 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..31e3dbe75f 100644
--- a/gnu/services/syncthing.scm
+++ b/gnu/services/syncthing.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright =C2=A9 2021 Oleg Pykhalov <go.wigust@HIDDEN>
 ;;; Copyright =C2=A9 2023 Justin Veilleux <terramorpha@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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))
=20
 ;;; Commentary:
 ;;;
@@ -35,6 +47,438 @@ (define-module (gnu services syncthing)
 ;;;
 ;;; Code:
=20
+(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 (def=
ault "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 introducedB=
y 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-d=
evice (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-cleanupInterval=
S (default "3600"))
+  (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (defau=
lt #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 (defau=
lt "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-maxSingleEn=
trySize (default "1024"))
+  (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (def=
ault "4096")))
+
+;; Some parameters, when empty, are fully omitted from the config file.  I=
t 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-cleanoutDa=
ys
+       versioning-keep versioning-maxAge versioning-command copiers
+       pullerMaxPendingKiB hashers order ignoreDelete scanProgressIntervalS
+       pullerPauseS maxConflicts disableSparseFiles disableTempIndexes pau=
sed
+       weakHashThresholdPct markerName copyOwnershipFromParent modTimeWind=
owS
+       maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod
+       caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXat=
trs
+       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-cleanoutD=
ays)
+                         ,@(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-maxSingleEntryS=
ize)
+                          (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 (defau=
lt "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 "Vuky3VHVseQEoSk9YgxhSk=
NTnjQmqYK9"))
+  (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 (defau=
lt ""))
+  (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 "de=
fault"))
+  (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "=
true"))
+  (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "tr=
ue"))
+  (localAnnouncePort syncthing-config-localAnnouncePort (default "21027"))
+  (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff1=
2::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 (defau=
lt "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/newda=
ta"))
+  (urPostInsecurely syncthing-config-urPostInsecurely (default "false"))
+  (urInitialDelayS syncthing-config-urInitialDelayS (default "1800"))
+  (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12=
"))
+  (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "fa=
lse"))
+  (keepTemporariesH syncthing-config-keepTemporariesH (default "24"))
+  (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false"))
+  (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (defau=
lt "5"))
+  (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "fals=
e"))
+  (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%"=
))
+  (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1"))
+  (releasesURL syncthing-config-releasesURL (default "https://upgrades.syn=
cthing.net/meta.json"))
+  (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDev=
iceNamesOnConnect (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-maxConcurrentIncomingR=
equestKiB (default "0"))
+  (announceLANAddresses syncthing-config-announceLANAddresses (default "tr=
ue"))
+  (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default=
 "false"))
+  (connectionLimitEnough syncthing-config-connectionLimitEnough (default "=
0"))
+  (connectionLimitMax syncthing-config-connectionLimitMax (default "0"))
+  (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersion=
s (default "false"))
+  (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (def=
ault "10"))
+  (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (d=
efault "20"))
+  (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (def=
ault "30"))
+  (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (d=
efault "40"))
+  (connectionPriorityRelay syncthing-config-connectionPriorityRelay (defau=
lt "50"))
+  (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityU=
pgradeThreshold (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 globalAnnounceSer=
ver
+       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 maxFolderConcurre=
ncy
+       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-de=
vice-device
+                                                       (syncthing-folder-d=
evices folder)))
+                                                folders)))
+                            ;; devices are the same if their id's are equal
+                            (lambda (device1 device2)
+                              (string=3D (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-insecur=
eSkipVerify))
+                                       '())
+                                 ,@(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 ,relayReconnectInter=
valM)
+                             (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 ,progressUpdateInter=
valS)
+                             (limitBandwidthInLan ,limitBandwidthInLan)
+                             (minHomeDiskFree (@ (unit ,minHomeDiskFree-un=
it))
+                                              ,minHomeDiskFree)
+                             (releasesURL ,releasesURL)
+                             (overwriteRemoteDeviceNamesOnConnect ,overwri=
teRemoteDeviceNamesOnConnect)
+                             (tempIndexMinBlocks ,tempIndexMinBlocks)
+                             (unackedNotificationID ,unackedNotificationID)
+                             (trafficClass ,trafficClass)
+                             (setLowPriority ,setLowPriority)
+                             (maxFolderConcurrency ,maxFolderConcurrency)
+                             (crashReportingURL ,crashReportingURL)
+                             (crashReportingEnabled ,crashReportingEnabled)
+                             (stunKeepaliveStartS ,stunKeepaliveStartS)
+                             (stunKeepaliveMinS ,stunKeepaliveMinS)
+                             (stunServer ,stunServer)
+                             (databaseTuning ,databaseTuning)
+                             (maxConcurrentIncomingRequestKiB ,maxConcurre=
ntIncomingRequestKiB)
+                             (announceLANAddresses ,announceLANAddresses)
+                             (sendFullIndexOnUpgrade ,sendFullIndexOnUpgra=
de)
+                             (connectionLimitEnough ,connectionLimitEnough)
+                             (connectionLimitMax ,connectionLimitMax)
+                             (insecureAllowOldTLSVersions ,insecureAllowOl=
dTLSVersions)
+                             (connectionPriorityTcpLan ,connectionPriority=
TcpLan)
+                             (connectionPriorityQuicLan ,connectionPriorit=
yQuicLan)
+                             (connectionPriorityTcpWan ,connectionPriority=
TcpWan)
+                             (connectionPriorityQuicWan ,connectionPriorit=
yQuicWan)
+                             (connectionPriorityRelay ,connectionPriorityR=
elay)
+                             (connectionPriorityUpgradeThreshold ,connecti=
onPriorityUpgradeThreshold))
+                    (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 whitesp=
ace
+;; 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-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+     `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag (subtags ..1) ..1) sibling-tags ...)
+     `(,current-indent (,tag "\n"
+                             ,@(indent-sxml subtags indent-increment
+                                            (string-append indent-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag primitive ...) sibling-tags ...)
+     `(,current-indent (,tag ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (() '())))
+
+(define (serialize-syncthing-config-file config)
+  (with-output-to-string
+    (lambda ()
+      (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->s=
xml 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)))
=20
@@ -93,10 +539,26 @@ (define syncthing-shepherd-service
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
=20
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (config-file user home ho=
me-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-con=
fig-file
+                                                   config-file)))))
+        '())))
+
 (define syncthing-service-type
   (service-type (name 'syncthing)
                 (extensions (list (service-extension shepherd-root-service=
-type
-                                                     syncthing-shepherd-se=
rvice)))
+                                                     syncthing-shepherd-se=
rvice)
+                                  (service-extension special-files-service=
-type
+                                                     syncthing-files-servi=
ce)))
                 (description
                  "Run @uref{https://github.com/syncthing/syncthing, Syncth=
ing}
 decentralized continuous file system synchronization.")))
--=20
2.45.2





Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 4 Feb 2025 23:17:31 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 04 18:17:31 2025
Received: from localhost ([127.0.0.1]:47204 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tfSAb-0000aV-3J
	for submit <at> debbugs.gnu.org; Tue, 04 Feb 2025 18:17:31 -0500
Received: from [47.204.136.169] (port=44522 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1tfSAW-0000aC-QE
 for 75959 <at> debbugs.gnu.org; Tue, 04 Feb 2025 18:17:27 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Transfer-Encoding:Content-Type:
 MIME-Version:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=yjVIz7HovcPAF0VP0mT4AOdTXjzRhoU/zRjAFCLRZVE=; i=zacchae.us; b=t2pWO0KyacEF
 u8lXwPYWlDcna8E7nEp3H0QVJHnX5kQ6uIDUsFdtXPX5OZ2OiZkjC8FTMAsnWdXG/+JPzfk2Cw==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Transfer-Encoding:Content-Type:MIME-Version:
 Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=yjVIz7HovcPAF0VP0mT4AOdTXjzRhoU/zRjAFCLRZVE=; i=zacchae.us; b=rLhBkmHV0sdg
 rPQKKoOehx7RmG2siCrmJC7omNyUGnHJykfwYBSYP5+mnXIKdtXdQc3tV3YSyDuTgwgJgMdMSkZRQ
 L783ARcswIr+9qC+uUIpZZtXmBPQrlr3WqXrB7VRZZIum2zhzYYqT3/YwUWY/xN525pq6+zvjmlcU
 tL1DrMDa1T8B16YZuVCpD90mnR97lah8XlpuPvZLEgbCWeUOUmKowMKCf0yJXIOoQdp/wtJBYqfes
 fKfvrh22eVSOvRQh72B+D3isuj44iykaX2PGWfCD6pH+x5CcfHkb3bkpEW60wAg/31Roe5LGCRivC
 ooo2s6R8v8v7o6mbjQmMpA==;
Received: from localhost.home ([127.0.0.1]:46484 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1tfSAQ-000000006xG-2ddL
 for 75959 <at> debbugs.gnu.org; Tue, 04 Feb 2025 18:17:18 -0500
From: Zacchaeus <eikcaz@HIDDEN>
To: 75959 <at> debbugs.gnu.org
Subject: Re: [PATCH] services: syncthing: Added support for config file
 serialization.
Date: Tue, 04 Feb 2025 18:17:18 -0500
Message-ID: <87lduli9kx.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: 2.0 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: From cb625d0548dd97bb124ae6821f3555e095823ae7 Mon Sep 17
 00:00:00
 2001 From: Zacchaeus <eikcaz@HIDDEN> Date: Sun, 21 Jul 2024 00:54:25
 -0700 Subject: [PATCH] services: syncthing: Added support for [...] 
 Content analysis details:   (2.0 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 -0.0 SPF_PASS               SPF: sender matches SPF record
 0.0 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED RBL: ADMINISTRATOR NOTICE:
 The query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-trusted.bondedsender.org]
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
 0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal
 information
 0.0 T_FILL_THIS_FORM_FRAUD_PHISH Answer suspicious question(s)
X-Debbugs-Envelope-To: 75959
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.0 (+)

From cb625d0548dd97bb124ae6821f3555e095823ae7 Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz@HIDDEN>
Date: Sun, 21 Jul 2024 00:54:25 -0700
Subject: [PATCH] services: syncthing: Added support for config file
 serialization.

* gnu/services/syncthing.scm: (syncthing-config-file) (syncthing-folder)
(syncthing-device) (syncthing-folder-device): New records;
(syncthing-service-type): added special-files-service-type extension for the
config file; (syncthing-files-service): service to create config file
* gnu/home/services/syncthing.scm: (home-syncthing-service-type): extended
home-files-services-type and re-exported more things from
gnu/services/syncthing.scm
* doc/guix.texi: (syncthing-service-type): document additions

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
---

I realized it was silly to have users specify devices in
syncthing-config-file whin 98% of the time this is obvious from the
folders.  (Usually each device shares at least one folder.)  I added
some code to extract the devices from the folders.  I also updated the
documentation accordingly.  See earlier standalone email for details
on how I verified that this service functions correctly.  I followed
those exact same steps this time.

 doc/guix.texi                   | 285 +++++++++++++++++++
 gnu/home/services/syncthing.scm |  17 +-
 gnu/services/syncthing.scm      | 466 +++++++++++++++++++++++++++++++-
 3 files changed, 765 insertions(+), 3 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..f9986d195a 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=C3=B6ren Tempel@*
+Copyright @copyright{} 2025 Zacchaeus@*
=20
 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,293 @@ 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}.
=20
+@item @code{syncthing-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).
+
+@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/me=
ta.json"})
+@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"})
+@item @code{tempIndexMinBlocks} (default: @var{"10"})
+@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPa=
ssword"})
+@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 Documenta=
tion Untrusted}.
+Note that file transfers are always end-to-end encrypted, regardless of
+this setting.
+
 @end table
 @end deftp
=20
+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")
+            (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
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 =C2=A9 2023 Ludovic Court=C3=A8s <ludo@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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?))
=20
 (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-typ=
e 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..2a58da8290 100644
--- a/gnu/services/syncthing.scm
+++ b/gnu/services/syncthing.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright =C2=A9 2021 Oleg Pykhalov <go.wigust@HIDDEN>
 ;;; Copyright =C2=A9 2023 Justin Veilleux <terramorpha@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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))
=20
 ;;; Commentary:
 ;;;
@@ -35,6 +47,438 @@ (define-module (gnu services syncthing)
 ;;;
 ;;; Code:
=20
+(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 (def=
ault "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 introducedB=
y 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-d=
evice (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-cleanupInterval=
S (default "3600"))
+  (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (defau=
lt #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 (defau=
lt "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-maxSingleEn=
trySize (default "1024"))
+  (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (def=
ault "4096")))
+
+;; Some parameters, when empty, are fully omitted from the config file.  I=
t 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-cleanoutDa=
ys
+       versioning-keep versioning-maxAge versioning-command copiers
+       pullerMaxPendingKiB hashers order ignoreDelete scanProgressIntervalS
+       pullerPauseS maxConflicts disableSparseFiles disableTempIndexes pau=
sed
+       weakHashThresholdPct markerName copyOwnershipFromParent modTimeWind=
owS
+       maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod
+       caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXat=
trs
+       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-cleanoutD=
ays)
+                         ,@(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-maxSingleEntryS=
ize)
+                          (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 (defau=
lt "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 "Vuky3VHVseQEoSk9YgxhSk=
NTnjQmqYK9"))
+  (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 (defau=
lt ""))
+  (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 "de=
fault"))
+  (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "=
true"))
+  (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "tr=
ue"))
+  (localAnnouncePort syncthing-config-localAnnouncePort (default "21027"))
+  (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff1=
2::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 (defau=
lt "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/newda=
ta"))
+  (urPostInsecurely syncthing-config-urPostInsecurely (default "false"))
+  (urInitialDelayS syncthing-config-urInitialDelayS (default "1800"))
+  (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12=
"))
+  (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "fa=
lse"))
+  (keepTemporariesH syncthing-config-keepTemporariesH (default "24"))
+  (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false"))
+  (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (defau=
lt "5"))
+  (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "fals=
e"))
+  (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%"=
))
+  (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1"))
+  (releasesURL syncthing-config-releasesURL (default "https://upgrades.syn=
cthing.net/meta.json"))
+  (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDev=
iceNamesOnConnect (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-maxConcurrentIncomingR=
equestKiB (default "0"))
+  (announceLANAddresses syncthing-config-announceLANAddresses (default "tr=
ue"))
+  (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default=
 "false"))
+  (connectionLimitEnough syncthing-config-connectionLimitEnough (default "=
0"))
+  (connectionLimitMax syncthing-config-connectionLimitMax (default "0"))
+  (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersion=
s (default "false"))
+  (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (def=
ault "10"))
+  (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (d=
efault "20"))
+  (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (def=
ault "30"))
+  (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (d=
efault "40"))
+  (connectionPriorityRelay syncthing-config-connectionPriorityRelay (defau=
lt "50"))
+  (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityU=
pgradeThreshold (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 globalAnnounceSer=
ver
+       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 maxFolderConcurre=
ncy
+       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-de=
vice-device
+                                                       (syncthing-folder-d=
evices folder)))
+                                                folders)))
+                            ;; devices are the same if their id's are equal
+                            (lambda (device1 device2)
+                              (string=3D (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-insecur=
eSkipVerify))
+                                       '())
+                                 ,@(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 ,relayReconnectInter=
valM)
+                             (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 ,progressUpdateInter=
valS)
+                             (limitBandwidthInLan ,limitBandwidthInLan)
+                             (minHomeDiskFree (@ (unit ,minHomeDiskFree-un=
it))
+                                              ,minHomeDiskFree)
+                             (releasesURL ,releasesURL)
+                             (overwriteRemoteDeviceNamesOnConnect ,overwri=
teRemoteDeviceNamesOnConnect)
+                             (tempIndexMinBlocks ,tempIndexMinBlocks)
+                             (unackedNotificationID ,unackedNotificationID)
+                             (trafficClass ,trafficClass)
+                             (setLowPriority ,setLowPriority)
+                             (maxFolderConcurrency ,maxFolderConcurrency)
+                             (crashReportingURL ,crashReportingURL)
+                             (crashReportingEnabled ,crashReportingEnabled)
+                             (stunKeepaliveStartS ,stunKeepaliveStartS)
+                             (stunKeepaliveMinS ,stunKeepaliveMinS)
+                             (stunServer ,stunServer)
+                             (databaseTuning ,databaseTuning)
+                             (maxConcurrentIncomingRequestKiB ,maxConcurre=
ntIncomingRequestKiB)
+                             (announceLANAddresses ,announceLANAddresses)
+                             (sendFullIndexOnUpgrade ,sendFullIndexOnUpgra=
de)
+                             (connectionLimitEnough ,connectionLimitEnough)
+                             (connectionLimitMax ,connectionLimitMax)
+                             (insecureAllowOldTLSVersions ,insecureAllowOl=
dTLSVersions)
+                             (connectionPriorityTcpLan ,connectionPriority=
TcpLan)
+                             (connectionPriorityQuicLan ,connectionPriorit=
yQuicLan)
+                             (connectionPriorityTcpWan ,connectionPriority=
TcpWan)
+                             (connectionPriorityQuicWan ,connectionPriorit=
yQuicWan)
+                             (connectionPriorityRelay ,connectionPriorityR=
elay)
+                             (connectionPriorityUpgradeThreshold ,connecti=
onPriorityUpgradeThreshold))
+                    (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 whitesp=
ace
+;; 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-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+     `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag (subtags ..1) ..1) sibling-tags ...)
+     `(,current-indent (,tag "\n"
+                             ,@(indent-sxml subtags indent-increment
+                                            (string-append indent-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag primitive ...) sibling-tags ...)
+     `(,current-indent (,tag ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (() '())))
+
+(define (serialize-syncthing-config-file config)
+  (with-output-to-string
+    (lambda ()
+      (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->s=
xml 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))
+  (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)))
=20
@@ -93,10 +539,26 @@ (define syncthing-shepherd-service
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
=20
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (syncthing-config-file us=
er 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-con=
fig-file
+                                                   syncthing-config-file))=
)))
+        '())))
+
 (define syncthing-service-type
   (service-type (name 'syncthing)
                 (extensions (list (service-extension shepherd-root-service=
-type
-                                                     syncthing-shepherd-se=
rvice)))
+                                                     syncthing-shepherd-se=
rvice)
+                                  (service-extension special-files-service=
-type
+                                                     syncthing-files-servi=
ce)))
                 (description
                  "Run @uref{https://github.com/syncthing/syncthing, Syncth=
ing}
 decentralized continuous file system synchronization.")))
--=20
2.45.2





Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 4 Feb 2025 01:13:56 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon Feb 03 20:13:56 2025
Received: from localhost ([127.0.0.1]:41867 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tf7Vk-0003MS-63
	for submit <at> debbugs.gnu.org; Mon, 03 Feb 2025 20:13:56 -0500
Received: from fout-a5-smtp.messagingengine.com ([103.168.172.148]:37527)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <leo@HIDDEN>) id 1tf7Vg-0003MC-L8
 for 75959 <at> debbugs.gnu.org; Mon, 03 Feb 2025 20:13:53 -0500
Received: from phl-compute-01.internal (phl-compute-01.phl.internal
 [10.202.2.41])
 by mailfout.phl.internal (Postfix) with ESMTP id 4FC9A1380241;
 Mon,  3 Feb 2025 20:13:47 -0500 (EST)
Received: from phl-mailfrontend-01 ([10.202.2.162])
 by phl-compute-01.internal (MEProxy); Mon, 03 Feb 2025 20:13:47 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=famulari.name;
 h=cc:cc:content-type:content-type:date:date:from:from
 :in-reply-to:in-reply-to:message-id:mime-version:references
 :reply-to:subject:subject:to:to; s=mesmtp; t=1738631627; x=
 1738718027; bh=HHSEDenaTm5ZBavLxJ1MHi6lYuq8AWc3Nkn0anDJgXo=; b=T
 y5Ps87t+9hrOs4kUJNhqae/m7sywEqNzMD5IHzSGCSJZZrRLpXRnf/LZJamLpTdG
 oXU1VWnImDVnCU9v1A3kZDpZ9Q31dgpAft5F1MoHVVqqYDtTgEGW1Xm6p9gnqdrU
 /5kZDnNRp2oyVnqST8cI/aPFWyn+hUCiDHa0+3t2Oc=
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
 messagingengine.com; h=cc:cc:content-type:content-type:date:date
 :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to
 :message-id:mime-version:references:reply-to:subject:subject:to
 :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=
 1738631627; x=1738718027; bh=HHSEDenaTm5ZBavLxJ1MHi6lYuq8AWc3Nkn
 0anDJgXo=; b=wGsOqD+N4wetnBCx0yMO2f0yQGfodknmq1r6ZtceTroeMvMz0Pj
 BuZ3doRPdMN9ZgeH+VRvl2B4+Fw5/ShBGjW2yKrSPhKOLXTLlKvncVrODS+eEz9w
 vZlsKCDbSEb26KfGetl9OPf1vDzfir7AMQrEbhfuIzghh9U+KTm3+F5YY9uShaJQ
 wUJU4rcpf67CdywYUhKLytFsFkWTUQ4SuIPzKFiamZd4/jiEGWb3R7zAlzRlSWe0
 YCxVySFNvrnlGmsJrxrENoj26BY0HxvwlK0R1gii2+O/AovHSCulP1CiWI/sBcCU
 6fPpGg9C3RzEg44SODT8FPNRusE4YiKkeoA==
X-ME-Sender: <xms:ymmhZyovhMhavC4AOkX5XMdgFYX-CgFWTrmCbQKsZ33DfHUXL1Yz_A>
 <xme:ymmhZwrtVVHXFaNuUR2yeFZzdFpVPzjrx-ICv8dSy2rpeoX_UgL_LyJqePqVj6a0I
 SHxsu43QWBr_264Rg>
X-ME-Received: <xmr:ymmhZ3NBJf09C9dFWWfR7Cq25y3dyRQhPZAXZJ0c10YA71WZ1tpTq-x75xdjIisyh69SmOPukHVq91MnZAKzSC8D>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdduleduhecutefuodetggdotefrod
 ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp
 uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurhepfffhvf
 evuffkfhggtggujgesthdtredttddtvdenucfhrhhomhepnfgvohcuhfgrmhhulhgrrhhi
 uceolhgvohesfhgrmhhulhgrrhhirdhnrghmvgeqnecuggftrfgrthhtvghrnhepudekvd
 dutdeuledugffhieeuieekteekudehueekuefhtdfhleethfdtveevhffhnecuffhomhgr
 ihhnpehshihntghthhhinhhgrdhnvghtnecuvehluhhsthgvrhfuihiivgeptdenucfrrg
 hrrghmpehmrghilhhfrhhomheplhgvohesfhgrmhhulhgrrhhirdhnrghmvgdpnhgspghr
 tghpthhtohepvddpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepvghikhgtrgiise
 iirggttghhrggvrdhushdprhgtphhtthhopeejheelheelseguvggssghughhsrdhgnhhu
 rdhorhhg
X-ME-Proxy: <xmx:ymmhZx6ckZabv83JLU3JjnUXbHok8nDXUzw4q82PRE4EssuKG8eLjw>
 <xmx:ymmhZx4xLhqTLL6Ip-WMpjMLoZk2BiR4iFkFeQPzXBXvtr-37j6KlQ>
 <xmx:ymmhZxh6WnKtNcpOhDBvtiKolwlqO-OKRQaE-RLAqMnD0H1HxSGgfA>
 <xmx:ymmhZ74d9cpbkZZj142qpQfIs8tmR4BaM8os6Nq_Q6-568Z-mxL7dQ>
 <xmx:y2mhZxEEl4j0YMP_tZ0Fbc2hdh8f6uL1VGii2uXvUSdp8cajVt7g-3-v>
Feedback-ID: i819c4023:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon,
 3 Feb 2025 20:13:46 -0500 (EST)
Date: Mon, 3 Feb 2025 20:13:45 -0500
From: Leo Famulari <leo@HIDDEN>
To: Zacchaeus <eikcaz@HIDDEN>
Subject: Re: [bug#75959] [PATCH] (home-)syncthing-service: added support for
 config serialization
Message-ID: <Z6FpybdJklSZ440b@HIDDEN>
References: <20250130215954.9394-1-eikcaz@HIDDEN>
 <87bjvmjb1z.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
In-Reply-To: <87bjvmjb1z.fsf@HIDDEN>
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 75959
Cc: 75959 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

On Sat, Feb 01, 2025 at 03:58:48AM -0500, Zacchaeus wrote:
>    - The default ~/Sync folder normally includes the current device.
>      The documentaiton makes it clear that this is OK (and also I have
>      tested this).

For reference:

"All mentioned devices are those that will be sharing the folder in
question. Each mentioned device must have a separate device element
later in the file. It is customary that the local device ID is included
in all folders. Syncthing will currently add this automatically if it is
not present in the configuration file."

https://docs.syncthing.net/users/config.html#config-option-folder.device




Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 1 Feb 2025 09:37:18 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Feb 01 04:37:18 2025
Received: from localhost ([127.0.0.1]:56390 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1te9wC-0004II-0m
	for submit <at> debbugs.gnu.org; Sat, 01 Feb 2025 04:37:18 -0500
Received: from [47.204.136.169] (port=59540 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1te9w7-0004I0-Pv
 for 75959 <at> debbugs.gnu.org; Sat, 01 Feb 2025 04:37:14 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Transfer-Encoding:Content-Type:
 MIME-Version:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=1Co5jEnxeXcUw96f7Z1IEBcweGN3voTJ0gUTGQfDP5E=; i=zacchae.us; b=wQuqvBfwxe0F
 SQI+eTjI4n6v0QUREdD+949uou1ORfob+Fawmuc+Vm4lZPxCu/S3B9SOit+pGH142C7kCIE7Bw==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Transfer-Encoding:Content-Type:MIME-Version:
 Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=1Co5jEnxeXcUw96f7Z1IEBcweGN3voTJ0gUTGQfDP5E=; i=zacchae.us; b=K1+i8bZNGd4X
 n5TtkAFWx4aztyzOfs1PC3chLns7K3f3qVFcy1CZQZ7M9ktHPvt0YjgOM7tDCyLRtg0+RTXUHoGlq
 m8s3RU3+t6YdjRAmnNM0pjB+q3l6NMR1QeG3m1bq07PtxGEMkzkwQssoSeAwV3XjWbkeibqn31+sO
 pUsk0iNUCTusWg4VdyCiwzx58w7osuOE+s26hQEWT9om5lCFeJcEJwIjkcIbcLlcfab7/o8AFvyQ+
 cQN3XO2yKHSd+eOaZ5fH51jAtdvuQZni8ofJMbg4s0b6evjETzxzhYiY900iLakBzqXu8lwSqQAPr
 gcATJ9fdm0DFpU/5tiXBig==;
Received: from localhost.home ([127.0.0.1]:49734 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1te9vz-000000005W8-43q2
 for 75959 <at> debbugs.gnu.org; Sat, 01 Feb 2025 04:37:04 -0500
From: Zacchaeus <eikcaz@HIDDEN>
To: 75959 <at> debbugs.gnu.org
Subject: [PATCH] services: syncthing: Added support for config file
 serialization.
Date: Sat, 01 Feb 2025 04:37:04 -0500
Message-ID: <877c6aj9a7.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: 2.0 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: From 0e0a2b727f3e898440a437edd9ed9b5924547c91 Mon Sep 17
 00:00:00
 2001 From: Zacchaeus <eikcaz@HIDDEN> Date: Sun, 21 Jul 2024 00:54:25
 -0700 Subject: [PATCH] services: syncthing: Added support for [...] 
 Content analysis details:   (2.0 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-trusted.bondedsender.org]
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 -0.0 SPF_PASS               SPF: sender matches SPF record
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
 0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal
 information
X-Debbugs-Envelope-To: 75959
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.0 (+)

From 0e0a2b727f3e898440a437edd9ed9b5924547c91 Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz@HIDDEN>
Date: Sun, 21 Jul 2024 00:54:25 -0700
Subject: [PATCH] services: syncthing: Added support for config file
 serialization.

* gnu/services/syncthing.scm: (syncthing-config-file) (syncthing-folder)
(syncthing-device) (syncthing-folder-device): New records;
(syncthing-service-type): added special-files-service-type extension for the
config file; (syncthing-files-service): service to create config file
* gnu/home/services/syncthing.scm: (home-syncthing-service-type): extended
home-files-services-type and re-exported more things from
gnu/services/syncthing.scm
* doc/guix.texi: (syncthing-service-type): document additions

Change-Id: I87eeba1ee1fdada8f29c2ee881fbc6bc4113dde9
---
Adds Syncthing config file serialization to Guix.  My original patch
commit string was not formatted correctly.  Please see my revised patch
here.  More info on how I tested that everything worked can be found in
a seperate email in this thread.

 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=C3=B6ren Tempel@*
+Copyright @copyright{} 2025 Zacchaeus@*
=20
 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}.
=20
-@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 correspondin=
g 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/me=
ta.json"})
+@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"})
+@item @code{tempIndexMinBlocks} (default: @var{"10"})
+@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPa=
ssword"})
+@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 Documenta=
tion 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
+
=20
 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 =C2=A9 2023 Ludovic Court=C3=A8s <ludo@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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?))
=20
 (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-typ=
e 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 =C2=A9 2021 Oleg Pykhalov <go.wigust@HIDDEN>
 ;;; Copyright =C2=A9 2023 Justin Veilleux <terramorpha@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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))
=20
 ;;; Commentary:
 ;;;
@@ -35,6 +47,431 @@ (define-module (gnu services syncthing)
 ;;;
 ;;; Code:
=20
+(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 (def=
ault "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 introducedB=
y 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-cleanupInterval=
S (default "3600"))
+  (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (defau=
lt #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 (defau=
lt "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-maxSingleEn=
trySize (default "1024"))
+  (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (def=
ault "4096")))
+
+;; Some parameters, when empty, are fully omitted from the config file.  I=
t 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 modTimeWind=
owS
+       maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod
+       caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXat=
trs
+       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-cleanoutD=
ays)
+                         ,@(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-maxSingleEntryS=
ize)
+                          (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 (defau=
lt "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 "Vuky3VHVseQEoSk9YgxhSk=
NTnjQmqYK9"))
+  (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 (defau=
lt ""))
+  (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 "de=
fault"))
+  (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "=
true"))
+  (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "tr=
ue"))
+  (localAnnouncePort syncthing-config-localAnnouncePort (default "21027"))
+  (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff1=
2::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 (defau=
lt "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/newda=
ta"))
+  (urPostInsecurely syncthing-config-urPostInsecurely (default "false"))
+  (urInitialDelayS syncthing-config-urInitialDelayS (default "1800"))
+  (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12=
"))
+  (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "fa=
lse"))
+  (keepTemporariesH syncthing-config-keepTemporariesH (default "24"))
+  (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false"))
+  (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (defau=
lt "5"))
+  (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "fals=
e"))
+  (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%"=
))
+  (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1"))
+  (releasesURL syncthing-config-releasesURL (default "https://upgrades.syn=
cthing.net/meta.json"))
+  (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDev=
iceNamesOnConnect (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-maxConcurrentIncomingR=
equestKiB (default "0"))
+  (announceLANAddresses syncthing-config-announceLANAddresses (default "tr=
ue"))
+  (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default=
 "false"))
+  (connectionLimitEnough syncthing-config-connectionLimitEnough (default "=
0"))
+  (connectionLimitMax syncthing-config-connectionLimitMax (default "0"))
+  (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersion=
s (default "false"))
+  (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (def=
ault "10"))
+  (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (d=
efault "20"))
+  (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (def=
ault "30"))
+  (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (d=
efault "40"))
+  (connectionPriorityRelay syncthing-config-connectionPriorityRelay (defau=
lt "50"))
+  (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityU=
pgradeThreshold (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 globalAnnounceSer=
ver
+       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 maxFolderConcurre=
ncy
+       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-insecur=
eSkipVerify))
+                                       '())
+                                 ,@(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 ,relayReconnectInter=
valM)
+                             (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 ,progressUpdateInter=
valS)
+                             (limitBandwidthInLan ,limitBandwidthInLan)
+                             (minHomeDiskFree (@ (unit ,minHomeDiskFree-un=
it))
+                                              ,minHomeDiskFree)
+                             (releasesURL ,releasesURL)
+                             (overwriteRemoteDeviceNamesOnConnect ,overwri=
teRemoteDeviceNamesOnConnect)
+                             (tempIndexMinBlocks ,tempIndexMinBlocks)
+                             (unackedNotificationID ,unackedNotificationID)
+                             (trafficClass ,trafficClass)
+                             (setLowPriority ,setLowPriority)
+                             (maxFolderConcurrency ,maxFolderConcurrency)
+                             (crashReportingURL ,crashReportingURL)
+                             (crashReportingEnabled ,crashReportingEnabled)
+                             (stunKeepaliveStartS ,stunKeepaliveStartS)
+                             (stunKeepaliveMinS ,stunKeepaliveMinS)
+                             (stunServer ,stunServer)
+                             (databaseTuning ,databaseTuning)
+                             (maxConcurrentIncomingRequestKiB ,maxConcurre=
ntIncomingRequestKiB)
+                             (announceLANAddresses ,announceLANAddresses)
+                             (sendFullIndexOnUpgrade ,sendFullIndexOnUpgra=
de)
+                             (connectionLimitEnough ,connectionLimitEnough)
+                             (connectionLimitMax ,connectionLimitMax)
+                             (insecureAllowOldTLSVersions ,insecureAllowOl=
dTLSVersions)
+                             (connectionPriorityTcpLan ,connectionPriority=
TcpLan)
+                             (connectionPriorityQuicLan ,connectionPriorit=
yQuicLan)
+                             (connectionPriorityTcpWan ,connectionPriority=
TcpWan)
+                             (connectionPriorityQuicWan ,connectionPriorit=
yQuicWan)
+                             (connectionPriorityRelay ,connectionPriorityR=
elay)
+                             (connectionPriorityUpgradeThreshold ,connecti=
onPriorityUpgradeThreshold))
+                    (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 whitesp=
ace
+;; 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-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+     `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag (subtags ..1) ..1) sibling-tags ...)
+     `(,current-indent (,tag "\n"
+                             ,@(indent-sxml subtags indent-increment
+                                            (string-append indent-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag primitive ...) sibling-tags ...)
+     `(,current-indent (,tag ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (() '())))
+
+(define (serialize-syncthing-config-file config)
+  (with-output-to-string
+    (lambda ()
+      (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->s=
xml 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)))
=20
@@ -93,10 +532,26 @@ (define syncthing-shepherd-service
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
=20
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (syncthing-config-file us=
er 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-con=
fig-file
+                                                   syncthing-config-file))=
)))
+        '())))
+
 (define syncthing-service-type
   (service-type (name 'syncthing)
                 (extensions (list (service-extension shepherd-root-service=
-type
-                                                     syncthing-shepherd-se=
rvice)))
+                                                     syncthing-shepherd-se=
rvice)
+                                  (service-extension special-files-service=
-type
+                                                     syncthing-files-servi=
ce)))
                 (description
                  "Run @uref{https://github.com/syncthing/syncthing, Syncth=
ing}
 decentralized continuous file system synchronization.")))
--=20
2.45.2





Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at 75959 <at> debbugs.gnu.org:


Received: (at 75959) by debbugs.gnu.org; 1 Feb 2025 08:58:59 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Feb 01 03:58:59 2025
Received: from localhost ([127.0.0.1]:56232 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1te9L8-0004iT-MD
	for submit <at> debbugs.gnu.org; Sat, 01 Feb 2025 03:58:59 -0500
Received: from [47.204.136.169] (port=52960 helo=hun.zacchae.us)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1te9L6-0004iD-O4
 for 75959 <at> debbugs.gnu.org; Sat, 01 Feb 2025 03:58:57 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Type:MIME-Version:Message-ID:Date:
 Subject:To:From:Sender:Reply-To:Cc:Content-Transfer-Encoding:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=0ZSYgPCZIg40wM3od4xf4ZW3dE/+RIQJH4MGOJOe6yE=; i=zacchae.us; b=JbxATJ083WmX
 QiIH48KnVtx1+VV5gvrCEzNuWMMUFiQ7ocE5IU+Nnca2GtL3tXPjiW6tIRo6QLY5eVtNfMleAw==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From:
 Sender:Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description:
 Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:
 In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:
 List-Post:List-Owner:List-Archive;
 bh=0ZSYgPCZIg40wM3od4xf4ZW3dE/+RIQJH4MGOJOe6yE=; i=zacchae.us; b=VBMr4/TpADRT
 90WcHr9oV8mu9b7leI+wj87Jorbd/6HPKES3o6ryWMtCQc4kcOkxupqzgiSaPLDVKFjo5ntebdXro
 FkF2QWFZvKJsK3mm62P3PyLsCD2TxrYxlSO6xWbFWWCxBEA9QB1xpJFl+UhNYpzxuGeJCbWl0yNMg
 orXE1v/NjCOCLi0+W2x/D/fe+n3q4i3rCFPIWdpqAYTAdMOhzLjTS9r42UM72RxhK5jihSUNf+G6y
 qxOe3yoJ6LsulKhA+P3/FBq5wDGTA6SP2YY7szOh0N3NNwZ1wYmVNBxmxUtZp9gN1GnnOJ5LvAwUh
 WqOuW+4zc3Q2DrPsDi/NVA==;
Received: from localhost.home ([127.0.0.1]:37806 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1te9Ky-000000005Mo-1jXp
 for 75959 <at> debbugs.gnu.org; Sat, 01 Feb 2025 03:58:48 -0500
From: Zacchaeus <eikcaz@HIDDEN>
To: 75959 <at> debbugs.gnu.org
Subject: Re: [PATCH] (home-)syncthing-service: added support for config
 serialization
Date: Sat, 01 Feb 2025 03:58:48 -0500
Message-ID: <87bjvmjb1z.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 1.3 (+)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview:  Hi Guix! In order to test my implementation, I compared two
 pairs of configs: - the default config generated by syncthing with the default
 config generated by (syncthing-configuration (syncthing-config-file
 (syncthing-config-file))).
 Content analysis details:   (1.3 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in sa-accredit.habeas.com]
 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
 -0.0 SPF_PASS               SPF: sender matches SPF record
 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The
 query to Validity was blocked.  See
 https://knowledge.validity.com/hc/en-us/articles/20961730681243
 for more information.
 [47.204.136.169 listed in bl.score.senderscore.com]
 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
X-Debbugs-Envelope-To: 75959
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 0.3 (/)

Hi Guix!


In order to test my implementation, I compared two pairs of configs:

 - the default config generated by syncthing with the default config
   generated by (syncthing-configuration (syncthing-config-file
   (syncthing-config-file))).

 - my syncthing-gui-generated config with the config generated by a
   syncthing-config-file reflecting my (rather complex) setup.

There are a few differences:

 - The core differences hinge around the fact that the current device ID
   is not known in advance.  Of course, if you have already run
   syncthing and know your device ID, you can specify that in your
   config.

   - The default ~/Sync folder normally includes the current device.
     The documentaiton makes it clear that this is OK (and also I have
     tested this).

   - The config does not include the current device.  The documentation
     says "One or more device elements must be present in the file", but
     it seems to work fine dispite this.

   - The default folder template normally includes the current device.
     Also fine.

 - There are also three miscelaneous differences:

   - The default device template normally omits the "name" field.  I
     suppose I could have added code to fix this, but I know it doesn't
     break anything so I opted for slightly simpler code.

   - The API key doesn't match.  Of course it doesn't.

   - The ~/Sync folder is normally specified by /home/<user>/Sync, but
     since I know ~/Sync works, I found that better than trying to
     compute ~/ in scheme.
   
In summary, there are differences, but those differences have been
accounted for and don't introduce bugs.  There were a few undocumented
behaviors around the absense or presense of a value.  In such cases, I
implemented whatever behavior was observed in a config file maintained
by the Syncthing GUI.

I considered submitting this as two patches, one for the home service
and one for the system service, but patching just the system service
broke the home service.  Is that a valid reason to make it one patch?
If not, I can split up the patches and resubmit.

Addressing the elephant in the room, as I mentioned in the
documentation, I used camelCase only to match syncthing documentation.
For instance, the ldap searchFilter setting is configured by
(syncthing-config-file (ldap-searchFilter "...")).  As nauseous as it
made me to write something like that, I think translating names to
snake-case adds too much confusion/complexity no matter where you add
it.  It is useful when refencencing Syncthing documentation to know
exactly what keyword too look for.  Though I believe exceptions exist to
every rule, and that this is maybe the only ecxeption to the no
camelcase rule, please do tell me if there is a better way and I will
resubmit the patch.


eikcaz-




Information forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 31 Jan 2025 04:17:38 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu Jan 30 23:17:38 2025
Received: from localhost ([127.0.0.1]:48851 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tdiTH-0001pQ-Vs
	for submit <at> debbugs.gnu.org; Thu, 30 Jan 2025 23:17:38 -0500
Received: from lists.gnu.org ([2001:470:142::17]:54118)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eikcaz@HIDDEN>) id 1tdccl-0007O5-Vc
 for submit <at> debbugs.gnu.org; Thu, 30 Jan 2025 17:03:02 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <eikcaz@HIDDEN>) id 1tdccc-0006O8-CK
 for guix-patches@HIDDEN; Thu, 30 Jan 2025 17:02:50 -0500
Received: from [47.204.136.169] (helo=hun.zacchae.us)
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <eikcaz@HIDDEN>) id 1tdccY-0003xA-Id
 for guix-patches@HIDDEN; Thu, 30 Jan 2025 17:02:50 -0500
DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed;
 d=zacchae.us; s=my_ed_sel; h=Content-Transfer-Encoding:Content-Type:
 MIME-Version:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=R5kdJCRRt3O/oAWwGScGfxmSrZTOLpVp23UzckEGeco=; i=zacchae.us; b=+TEHxkxlELoD
 FMDhHTEZhtLqqYixJBK42lS0KbPCaDoUOexuiSXTnxl181x9QpS5hqo0M+Zug63TLYzFCrHWDQ==; 
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zacchae.us; 
 s=my_rsa_sel;
 h=Content-Transfer-Encoding:Content-Type:MIME-Version:
 Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID:
 Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc
 :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:
 List-Subscribe:List-Post:List-Owner:List-Archive;
 bh=R5kdJCRRt3O/oAWwGScGfxmSrZTOLpVp23UzckEGeco=; i=zacchae.us; b=UbnHx/F1vj9z
 lOmioXoACwf0AhMrId+nBe5d542p+0nAO+G3i3zWdUrHT4g5NLPoYS3GEufGVQwp4Ay2ggz6RQ6O7
 vOY3VoGr5mFJU3QRgut7WuGZYTkhH9/So6kn1oYBj5FbvbGYvmQHRe8vX1FPE85H+Q2LAxD8zgtjV
 ozRFE/QdY8h7dbZuVFpttZcKYPCBtHqrebd+CKfAzQfjcIVz3Ie7hQrVy664ftsAxdrB8iZvgfhnQ
 GCadIVV+eZrW6jtcleCeJS7YZPhvTWRY7V+gZbj0SmN1VFQQZAF4eeHTZfeUivBRazNeG8N9lHc5t
 kVKs260Q2bfDMr02NhUChg==;
Received: from localhost.home ([127.0.0.1]:43730 helo=hun)
 by hun.zacchae.us with esmtps (TLS1.3) tls
 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98)
 (envelope-from <eikcaz@HIDDEN>) id 1tdccV-000000000Iw-2hrB;
 Thu, 30 Jan 2025 17:02:44 -0500
From: Zacchaeus <eikcaz@HIDDEN>
To: guix-patches@HIDDEN
Subject: [PATCH] (home-)syncthing-service: added support for config
 serialization
Date: Thu, 30 Jan 2025 13:59:12 -0800
Message-ID: <20250130215954.9394-1-eikcaz@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Host-Lookup-Failed: Reverse DNS lookup failed for 47.204.136.169 (failed)
Received-SPF: pass client-ip=47.204.136.169; envelope-from=eikcaz@HIDDEN;
 helo=hun.zacchae.us
X-Spam_score_int: -7
X-Spam_score: -0.8
X-Spam_bar: /
X-Spam_report: (-0.8 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1,
 DKIM_SIGNED=0.1, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001,
 RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001,
 SPF_PASS=-0.001, T_FILL_THIS_FORM_SHORT=0.01,
 URIBL_SBL_A=0.1 autolearn=no autolearn_force=no
X-Spam_action: no action
X-Spam-Score: 1.7 (+)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview: From 48c227546ea15aadbd5f5832d8cd30887f65ace9 Mon Sep 17
 00:00:00
 2001 From: Zacchaeus <eikcaz@HIDDEN> Date: Sun, 21 Jul 2024 00:54:25
 -0700 Subject: [PATCH] (home-)syncthing-service: added suppor [...] 
 Content analysis details:   (1.7 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL
 blocklist [URIs: docs.syncthing.net]
 -0.0 RCVD_IN_DNSWL_NONE     RBL: Sender listed at https://www.dnswl.org/,
 no trust [2001:470:142:0:0:0:0:17 listed in] [list.dnswl.org]
 -0.0 SPF_HELO_PASS          SPF: HELO matches SPF record
 0.9 SPF_FAIL               SPF: sender does not match SPF record (fail)
 [SPF failed: Please see http://www.openspf.org/Why?s=mfrom;
 id=eikcaz%40zacchae.us; ip=2001%3A470%3A142%3A%3A17; r=debbugs.gnu.org]
 0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal
 information
X-Debbugs-Envelope-To: submit
X-Mailman-Approved-At: Thu, 30 Jan 2025 23:17:30 -0500
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 0.7 (/)

From 48c227546ea15aadbd5f5832d8cd30887f65ace9 Mon Sep 17 00:00:00 2001
From: Zacchaeus <eikcaz@HIDDEN>
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=C3=B6ren Tempel@*
+Copyright @copyright{} 2025 Zacchaeus@*
=20
 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}.
=20
-@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 correspondin=
g 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/me=
ta.json"})
+@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"})
+@item @code{tempIndexMinBlocks} (default: @var{"10"})
+@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPa=
ssword"})
+@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 Documenta=
tion 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
+
=20
 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 =C2=A9 2023 Ludovic Court=C3=A8s <ludo@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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?))
=20
 (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-typ=
e 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 =C2=A9 2021 Oleg Pykhalov <go.wigust@HIDDEN>
 ;;; Copyright =C2=A9 2023 Justin Veilleux <terramorpha@HIDDEN>
+;;; Copyright =C2=A9 2025 Zacchaeus <eikcaz@HIDDEN>
 ;;;
 ;;; 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))
=20
 ;;; Commentary:
 ;;;
@@ -35,6 +47,431 @@ (define-module (gnu services syncthing)
 ;;;
 ;;; Code:
=20
+(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 (def=
ault "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 introducedB=
y 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-cleanupInterval=
S (default "3600"))
+  (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (defau=
lt #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 (defau=
lt "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-maxSingleEn=
trySize (default "1024"))
+  (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (def=
ault "4096")))
+
+;; Some parameters, when empty, are fully omitted from the config file.  I=
t 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 modTimeWind=
owS
+       maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod
+       caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXat=
trs
+       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-cleanoutD=
ays)
+                         ,@(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-maxSingleEntryS=
ize)
+                          (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 (defau=
lt "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 "Vuky3VHVseQEoSk9YgxhSk=
NTnjQmqYK9"))
+  (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 (defau=
lt ""))
+  (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 "de=
fault"))
+  (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "=
true"))
+  (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "tr=
ue"))
+  (localAnnouncePort syncthing-config-localAnnouncePort (default "21027"))
+  (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff1=
2::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 (defau=
lt "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/newda=
ta"))
+  (urPostInsecurely syncthing-config-urPostInsecurely (default "false"))
+  (urInitialDelayS syncthing-config-urInitialDelayS (default "1800"))
+  (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12=
"))
+  (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "fa=
lse"))
+  (keepTemporariesH syncthing-config-keepTemporariesH (default "24"))
+  (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false"))
+  (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (defau=
lt "5"))
+  (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "fals=
e"))
+  (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%"=
))
+  (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1"))
+  (releasesURL syncthing-config-releasesURL (default "https://upgrades.syn=
cthing.net/meta.json"))
+  (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDev=
iceNamesOnConnect (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-maxConcurrentIncomingR=
equestKiB (default "0"))
+  (announceLANAddresses syncthing-config-announceLANAddresses (default "tr=
ue"))
+  (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default=
 "false"))
+  (connectionLimitEnough syncthing-config-connectionLimitEnough (default "=
0"))
+  (connectionLimitMax syncthing-config-connectionLimitMax (default "0"))
+  (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersion=
s (default "false"))
+  (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (def=
ault "10"))
+  (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (d=
efault "20"))
+  (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (def=
ault "30"))
+  (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (d=
efault "40"))
+  (connectionPriorityRelay syncthing-config-connectionPriorityRelay (defau=
lt "50"))
+  (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityU=
pgradeThreshold (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 globalAnnounceSer=
ver
+       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 maxFolderConcurre=
ncy
+       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-insecur=
eSkipVerify))
+                                       '())
+                                 ,@(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 ,relayReconnectInter=
valM)
+                             (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 ,progressUpdateInter=
valS)
+                             (limitBandwidthInLan ,limitBandwidthInLan)
+                             (minHomeDiskFree (@ (unit ,minHomeDiskFree-un=
it))
+                                              ,minHomeDiskFree)
+                             (releasesURL ,releasesURL)
+                             (overwriteRemoteDeviceNamesOnConnect ,overwri=
teRemoteDeviceNamesOnConnect)
+                             (tempIndexMinBlocks ,tempIndexMinBlocks)
+                             (unackedNotificationID ,unackedNotificationID)
+                             (trafficClass ,trafficClass)
+                             (setLowPriority ,setLowPriority)
+                             (maxFolderConcurrency ,maxFolderConcurrency)
+                             (crashReportingURL ,crashReportingURL)
+                             (crashReportingEnabled ,crashReportingEnabled)
+                             (stunKeepaliveStartS ,stunKeepaliveStartS)
+                             (stunKeepaliveMinS ,stunKeepaliveMinS)
+                             (stunServer ,stunServer)
+                             (databaseTuning ,databaseTuning)
+                             (maxConcurrentIncomingRequestKiB ,maxConcurre=
ntIncomingRequestKiB)
+                             (announceLANAddresses ,announceLANAddresses)
+                             (sendFullIndexOnUpgrade ,sendFullIndexOnUpgra=
de)
+                             (connectionLimitEnough ,connectionLimitEnough)
+                             (connectionLimitMax ,connectionLimitMax)
+                             (insecureAllowOldTLSVersions ,insecureAllowOl=
dTLSVersions)
+                             (connectionPriorityTcpLan ,connectionPriority=
TcpLan)
+                             (connectionPriorityQuicLan ,connectionPriorit=
yQuicLan)
+                             (connectionPriorityTcpWan ,connectionPriority=
TcpWan)
+                             (connectionPriorityQuicWan ,connectionPriorit=
yQuicWan)
+                             (connectionPriorityRelay ,connectionPriorityR=
elay)
+                             (connectionPriorityUpgradeThreshold ,connecti=
onPriorityUpgradeThreshold))
+                    (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 whitesp=
ace
+;; 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-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag ('@ properties ...) primitive ...) sibling-tags ...)
+     `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag (subtags ..1) ..1) sibling-tags ...)
+     `(,current-indent (,tag "\n"
+                             ,@(indent-sxml subtags indent-increment
+                                            (string-append indent-incremen=
t current-indent))
+                             ,current-indent) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (((tag primitive ...) sibling-tags ...)
+     `(,current-indent (,tag ,@primitive) "\n"
+                       ,@(indent-sxml sibling-tags indent-increment curren=
t-indent)))
+    (() '())))
+
+(define (serialize-syncthing-config-file config)
+  (with-output-to-string
+    (lambda ()
+      (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->s=
xml 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)))
=20
@@ -93,10 +532,26 @@ (define syncthing-shepherd-service
       (respawn? #f)
       (stop #~(make-kill-destructor))))))
=20
+
+(define syncthing-files-service
+  (match-record-lambda <syncthing-configuration> (syncthing-config-file us=
er 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-con=
fig-file
+                                                   syncthing-config-file))=
)))
+        '())))
+
 (define syncthing-service-type
   (service-type (name 'syncthing)
                 (extensions (list (service-extension shepherd-root-service=
-type
-                                                     syncthing-shepherd-se=
rvice)))
+                                                     syncthing-shepherd-se=
rvice)
+                                  (service-extension special-files-service=
-type
+                                                     syncthing-files-servi=
ce)))
                 (description
                  "Run @uref{https://github.com/syncthing/syncthing, Syncth=
ing}
 decentralized continuous file system synchronization.")))
--=20
2.45.2





Acknowledgement sent to Zacchaeus <eikcaz@HIDDEN>:
New bug report received and forwarded. Copy sent to guix-patches@HIDDEN. Full text available.
Report forwarded to guix-patches@HIDDEN:
bug#75959; Package guix-patches. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Sat, 8 Feb 2025 03:45:02 UTC

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