From patchwork Thu Feb 6 22:15:27 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Zacchaeus Scheffer X-Patchwork-Id: 38331 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id BF2CE27BBE2; Thu, 6 Feb 2025 22:16:46 +0000 (GMT) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-8.5 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,MAILING_LIST_MULTI,RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H2,RCVD_IN_VALIDITY_CERTIFIED,RCVD_IN_VALIDITY_RPBL, RCVD_IN_VALIDITY_SAFE,SPF_HELO_PASS,URIBL_BLOCKED,URIBL_SBL_A autolearn=unavailable autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id 2BCE827BBE9 for ; Thu, 6 Feb 2025 22:16:41 +0000 (GMT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1tgAAY-00055P-JU; Thu, 06 Feb 2025 17:16:23 -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 ) id 1tgAAG-0004zD-5Y for guix-patches@gnu.org; Thu, 06 Feb 2025 17:16:07 -0500 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1tgAAF-0002RE-R0 for guix-patches@gnu.org; Thu, 06 Feb 2025 17:16:03 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debbugs.gnu.org; s=debbugs-gnu-org; h=MIME-Version:Date:From:To:In-Reply-To:References:Subject; bh=mJ7doV/Nh9s1L8ft5BkVG7pshPsG1fOaykitFrZU0K0=; b=XEy5PPJu7oZDPG1mbG5y3bFLYFJAGAvJzxs/CtSFxHphKps/A0FBGrpvmzGZab1Lr+UbVyE69kyfZfHTC4z3fQ5fsQHsBkPtTTBPk58Z1g/bWPu24oeEadUe9mQwAm/QqD7LHS1knF7Rvf8egCzuXqynjuqi8PEk5o+omhbhYPzIoehCWhRYtbXlWrhyru/9OwLVuAIgqCq7FFnPKZEhf5Vbv0/gnWtZF3v/AudIc/KBjhOoi0vM3fusKRYEfzartZEHL1GXmT+F9cUTbGRBxcbCX4NmdmnJEd6P0HmOgeHX2cALXR9P9zCNtgTJE5D5NUkJuarWZa3DrAq7LACUCw==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1tgAAE-0006Vr-E0 for guix-patches@gnu.org; Thu, 06 Feb 2025 17:16:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#75959] [PATCH] services: syncthing: Added support for config file serialization. References: <20250130215954.9394-1-eikcaz@zacchae.us> In-Reply-To: <20250130215954.9394-1-eikcaz@zacchae.us> Resent-From: Zacchaeus Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Thu, 06 Feb 2025 22:16:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 75959 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 75959@debbugs.gnu.org Received: via spool by 75959-submit@debbugs.gnu.org id=B75959.173888014225002 (code B ref 75959); Thu, 06 Feb 2025 22:16:02 +0000 Received: (at 75959) by debbugs.gnu.org; 6 Feb 2025 22:15:42 +0000 Received: from localhost ([127.0.0.1]:59685 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1tgA9r-0006V8-U1 for submit@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 ) id 1tgA9l-0006Ui-Aq for 75959@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 ) id 1tgA9e-000000000C0-3UQy for 75959@debbugs.gnu.org; Thu, 06 Feb 2025 17:15:27 -0500 From: Zacchaeus Date: Thu, 06 Feb 2025 17:15:27 -0500 Message-ID: <87frkqhg8w.fsf@zacchae.us> MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches From 7ef311e85b1198c752b2eec57caa0256227e079c Mon Sep 17 00:00:00 2001 From: Zacchaeus 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-configuration ...))) 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ören Tempel@* +Copyright @copyright{} 2025 Zacchaeus@* Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or @@ -22669,9 +22670,296 @@ This assumes that the specified group exists. Common configuration and data directory. The default configuration directory is @file{$HOME} of the specified Syncthing @code{user}. +@item @code{config-file} (default: @var{#f}) +Either a file-like object that resolves to a syncthing configuration xml +file, or a syncthing-config-file record (see below). If set to #f, Guix +will not try to generate a config file, and the syncthing will generate +a default one which will not be touched on reconfigure. + +@end table +@end deftp + +In the below, only details specific to Guix, or related to how your +device will ``ping'' others, are presented. Otherwise, you should +consult @uref{https://docs.syncthing.net/users/config.html, Syncthing +config documentation}. Camelcase is preserved below only as to be +consistent with its appearance in Syncthing code/documentation. If you +would like to migrate to Guix-powered Syncthing configuration, the +generated config adds newlines/whitespace to the produced config such +that your old config can be diff'ed with the new one. You can still +modify Syncthing from the GUI or through ``introducer'' and +``autoAcceptFolders'' mechanisms, but such changes will be reset on +reconfigure. + +@deftp {Data Type} syncthing-config-file +Data type representing the configuration file read by the syncthing +daemon. + +@table @asis +@item @code{folders} (default: @var{(list (syncthing-folder (id "default") (label "Default Folder") (path "~/Sync")))} +The default here is the same as Syncthing's default. The value should +be a list of @code{syncthing-folder}s. + +@item @code{devices} (default: @var{'()} +This should be a list of @code{syncthing-device}s. Guix will +automatically add any devices specified in any `folders' to this list. +There are instances when you want to connect to a device despite not +(initially) sharing any folders (such as a device with +autoAcceptFolders). In such instances, you should specify those devices +here. If multiple versions of the same device (same id) are discovered, +the one in this list is prioritized. Otherwise, the first instance in +the first folder is used. + +@item @code{gui-enabled} (default: @var{"true"}) +By default, any user on the computer can access the GUI and make changes +to Syncthing. If you leave this enabled, you should probably set +gui-user and gui-password (see below). + +@item @code{gui-tls} (default: @var{"false"}) +@item @code{gui-debugging} (default: @var{"false"}) +@item @code{gui-sendBasicAuthPrompt} (default: @var{"false"}) +@item @code{gui-address} (default: @var{"127.0.0.1:8384"}) +@item @code{gui-user} (default: @var{#f}) +@item @code{gui-password} (default: @var{#f}) +@item @code{gui-apikey} (default: @var{"Vuky3VHVseQEoSk9YgxhSkNTnjQmqYK9"}) +@item @code{gui-theme} (default: @var{"default"}) +@item @code{ldap-enabled} (default: @var{#f}) +@item @code{ldap-address} (default: @var{""}) +@item @code{ldap-bindDN} (default: @var{""}) +@item @code{ldap-transport} (default: @var{""}) +@item @code{ldap-insecureSkipVerify} (default: @var{""}) +@item @code{ldap-searchBaseDN} (default: @var{""}) +@item @code{ldap-searchFilter} (default: @var{""}) +@item @code{listenAddress} (default: @var{"default"}) +@item @code{globalAnnounceServer} (default: @var{"default"}) +@item @code{globalAnnounceEnabled} (default: @var{"true"}) +Global discovery servers can be used to help connect devices at unknown +IP addresses by storing the last known IP address. + +@item @code{localAnnounceEnabled} (default: @var{"true"}) +This makes devices find each other very easily on the same LAN. Often, +this will allow you to just plug an Ethernet between two devices, or +connect one device to the other's hotspot and start syncing. + +@item @code{localAnnouncePort} (default: @var{"21027"}) +@item @code{localAnnounceMCAddr} (default: @var{"[ff12::8384]:21027"}) +@item @code{maxSendKbps} (default: @var{"0"}) +@item @code{maxRecvKbps} (default: @var{"0"}) +@item @code{reconnectionIntervalS} (default: @var{"60"}) +@item @code{relaysEnabled} (default: @var{"true"}) +This option allows your Syncthing instance to coordinate with a global +network of relays to enable syncing between devices when all other +methods fail. + +@item @code{relayReconnectIntervalM} (default: @var{"10"}) +@item @code{startBrowser} (default: @var{"true"}) +@item @code{natEnabled} (default: @var{"true"}) +@item @code{natLeaseMinutes} (default: @var{"60"}) +@item @code{natRenewalMinutes} (default: @var{"30"}) +@item @code{natTimeoutSeconds} (default: @var{"10"}) +@item @code{urAccepted} (default: @var{"0"}) +ur* options control usage reporting. Set to -1 to disable, or positive +to enable. The default (0) has reporting disabled, but you will be +asked to decide in the GUI. + +@item @code{urSeen} (default: @var{"0"}) +@item @code{urUniqueID} (default: @var{""}) +@item @code{urURL} (default: @var{"https://data.syncthing.net/newdata"}) +@item @code{urPostInsecurely} (default: @var{"false"}) +@item @code{urInitialDelayS} (default: @var{"1800"}) +@item @code{autoUpgradeIntervalH} (default: @var{"12"}) +@item @code{upgradeToPreReleases} (default: @var{"false"}) +@item @code{keepTemporariesH} (default: @var{"24"}) +@item @code{cacheIgnoredFiles} (default: @var{"false"}) +@item @code{progressUpdateIntervalS} (default: @var{"5"}) +@item @code{limitBandwidthInLan} (default: @var{"false"}) +@item @code{minHomeDiskFree-unit} (default: @var{"%"}) +@item @code{minHomeDiskFree} (default: @var{"1"}) +@item @code{releasesURL} (default: @var{"https://upgrades.syncthing.net/meta.json"}) +@item @code{overwriteRemoteDeviceNamesOnConnect} (default: @var{"false"}) +@item @code{tempIndexMinBlocks} (default: @var{"10"}) +@item @code{unackedNotificationID} (default: @var{"authenticationUserAndPassword"}) +@item @code{trafficClass} (default: @var{"0"}) +@item @code{setLowPriority} (default: @var{"true"}) +@item @code{maxFolderConcurrency} (default: @var{"0"}) +@item @code{crashReportingURL} (default: @var{"https://crash.syncthing.net/newcrash"}) +@item @code{crashReportingEnabled} (default: @var{"true"}) +@item @code{stunKeepaliveStartS} (default: @var{"180"}) +@item @code{stunKeepaliveMinS} (default: @var{"20"}) +@item @code{stunServer} (default: @var{"default"}) +@item @code{databaseTuning} (default: @var{"auto"}) +@item @code{maxConcurrentIncomingRequestKiB} (default: @var{"0"}) +@item @code{announceLANAddresses} (default: @var{"true"}) +@item @code{sendFullIndexOnUpgrade} (default: @var{"false"}) +@item @code{connectionLimitEnough} (default: @var{"0"}) +@item @code{connectionLimitMax} (default: @var{"0"}) +@item @code{insecureAllowOldTLSVersions} (default: @var{"false"}) +@item @code{connectionPriorityTcpLan} (default: @var{"10"}) +@item @code{connectionPriorityQuicLan} (default: @var{"20"}) +@item @code{connectionPriorityTcpWan} (default: @var{"30"}) +@item @code{connectionPriorityQuicWan} (default: @var{"40"}) +@item @code{connectionPriorityRelay} (default: @var{"50"}) +@item @code{connectionPriorityUpgradeThreshold} (default: @var{"0"}) +@item @code{default-folder} (default: @var{(syncthing-folder (label ""))}) +@item @code{default-device} (default: @var{(syncthing-device (id ""))}) +@item @code{default-ignores} (default: @var{"")}) +The default-* above do not affect folders and devices added by the Guix +interface. They will, however, affect folders and devices that are +added through the GUI, by an ``introducer'', or a device with +``autoAcceptFolders''. +@end table +@end deftp + +@deftp {Data Type} syncthing-device +Data type representing a device to sync with. + +@table @asis +@item @code{id} +A long hash tied to the keys generated by Syncthing on the first launch. +You can obtain this from the Syncthing GUI or by inspecting an existing +Syncthing configuration file. + +@item @code{name} (default: @var{""}) +Human readable device name for viewing in the GUI or in scheme. + +@item @code{compression} (default: @var{"metadata"}) +@item @code{introducer} (default: @var{"false"}) +@item @code{skipIntroductionRemovals} (default: @var{"false"}) +@item @code{introducedBy} (default: @var{""}) +@item @code{addresses} (default: @var{'("dynamic")}) +List of addresses at which to search for this device. The special value +``dynamic'' will have syncthing use several means to find the device. + +@item @code{paused} (default: @var{"false"}) +@item @code{autoAcceptFolders} (default: @var{"false"}) +@item @code{maxSendKbps} (default: @var{"0"}) +@item @code{maxRecvKbps} (default: @var{"0"}) +@item @code{maxRequestKiB} (default: @var{"0"}) +@item @code{untrusted} (default: @var{"false"}) +@item @code{remoteGUIPort} (default: @var{"0"}) +@item @code{numConnections} (default: @var{"0")}) + +@end table +@end deftp + +@deftp {Data Type} syncthing-folder +Data type representing a folder to be synced. + +@table @asis +@item @code{id} (default: @var{#f}) +This id cannot match the id of any other folder on this device. If left +unspecified, it will default to the label (see below). + +@item @code{label} +Human readable label for the folder. + +@item @code{path} +The path at which to store this folder. + +@item @code{type} (default: @var{"sendreceive"}) +@item @code{rescanIntervalS} (default: @var{"3600"}) +@item @code{fsWatcherEnabled} (default: @var{"true"}) +@item @code{fsWatcherDelayS} (default: @var{"10"}) +@item @code{ignorePerms} (default: @var{"false"}) +@item @code{autoNormalize} (default: @var{"true"}) +@item @code{devices} (default: @var{'()}) +Devices should be a list of other Syncthing devices. You do not need to +specify the current device. Each device can be listed as a a +@code{syncthing-device} record or a @code{syncthing-folder-device} +record if you want files to be encrypted on disk. + +@item @code{filesystemType} (default: @var{"basic"}) +@item @code{minDiskFree-unit} (default: @var{"%"}) +@item @code{minDiskFree} (default: @var{"1"}) +@item @code{versioning-type} (default: @var{#f}) +@item @code{versioning-fsPath} (default: @var{""}) +@item @code{versioning-fsType} (default: @var{"basic"}) +@item @code{versioning-cleanupIntervalS} (default: @var{"3600"}) +@item @code{versioning-cleanoutDays} (default: @var{#f}) +@item @code{versioning-keep} (default: @var{#f}) +@item @code{versioning-maxAge} (default: @var{#f}) +@item @code{versioning-command} (default: @var{#f}) +@item @code{copiers} (default: @var{"0"}) +@item @code{pullerMaxPendingKiB} (default: @var{"0"}) +@item @code{hashers} (default: @var{"0"}) +@item @code{order} (default: @var{"random"}) +@item @code{ignoreDelete} (default: @var{"false"}) +@item @code{scanProgressIntervalS} (default: @var{"0"}) +@item @code{pullerPauseS} (default: @var{"0"}) +@item @code{maxConflicts} (default: @var{"10"}) +@item @code{disableSparseFiles} (default: @var{"false"}) +@item @code{disableTempIndexes} (default: @var{"false"}) +@item @code{paused} (default: @var{"false"}) +@item @code{weakHashThresholdPct} (default: @var{"25"}) +@item @code{markerName} (default: @var{".stfolder"}) +@item @code{copyOwnershipFromParent} (default: @var{"false"}) +@item @code{modTimeWindowS} (default: @var{"0"}) +@item @code{maxConcurrentWrites} (default: @var{"2"}) +@item @code{disableFsync} (default: @var{"false"}) +@item @code{blockPullOrder} (default: @var{"standard"}) +@item @code{copyRangeMethod} (default: @var{"standard"}) +@item @code{caseSensitiveFS} (default: @var{"false"}) +@item @code{junctionsAsDirs} (default: @var{"false"}) +@item @code{syncOwnership} (default: @var{"false"}) +@item @code{sendOwnership} (default: @var{"false"}) +@item @code{syncXattrs} (default: @var{"false"}) +@item @code{sendXattrs} (default: @var{"false"}) +@item @code{xattrFilter-maxSingleEntrySize} (default: @var{"1024"}) +@item @code{xattrFilter-maxTotalSize} (default: @var{"4096")}) +@end table +@end deftp + +@deftp {Data Type} syncthing-folder-device +There is some configuration which is specific to the relationship +between a specific folder and a specific device. If you are fine +leaving these as their default, then you can simply specify a +syncthing-device instead of a @code{syncthing-folder-device} in +@code{syncthing-folder}s. + +@table @asis +@item @code{device} +device should be a @code{syncthing-device} for which this configuration +applies. + +@item @code{introducedBy} (default: @var{""}) +@item @code{encryptionPassword} (default: @var{""}) +if encryptionPassword is non-empty, then it will be used as a password +to encrypt file chunks as they are synced to that device. For more info +on syncing to devices you don't totally trust, see +@uref{https://docs.syncthing.net/users/untrusted.html, Syncthing Documentation Untrusted}. +Note that file transfers are always end-to-end encrypted, regardless of +this setting. + @end table @end deftp +Here is a more complex example configuration for illustrative purposes: +@lisp +(service syncthing-service-type + (let ((laptop (syncthing-device (id "VHOD2D6-...-7XRMDEN"))) + (desktop (syncthing-device (id "64SAZ37-...-FZJ5GUA") + (addresses '("mydomain.example")))) + (bob-desktop (syncthing-device (id "KYIMEGO-...-FT77EAO")))) + (syncthing-configuration + (user "alice") + (config-file + (syncthing-config-file + (folders (list (syncthing-folder + (label "some-files") + (path "~/data") + (devices (list desktop laptop))) + (syncthing-folder + (label "critical-files") + (path "~/secrets") + (devices + (list desktop + laptop + (syncthing-folder-device + (device bob-desktop) + (encryptionPassword "mypassword")))))))))))) +@end lisp + + Furthermore, @code{(gnu services ssh)} provides the following services. @cindex SSH @cindex SSH server diff --git a/gnu/home/services/syncthing.scm b/gnu/home/services/syncthing.scm index 8d66a167ce..dd6c752ee4 100644 --- a/gnu/home/services/syncthing.scm +++ b/gnu/home/services/syncthing.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2023 Ludovic Courtès +;;; Copyright © 2025 Zacchaeus ;;; ;;; This file is part of GNU Guix. ;;; @@ -24,9 +25,23 @@ (define-module (gnu home services syncthing) #:use-module (gnu home services shepherd) #:export (home-syncthing-service-type) #:re-export (syncthing-configuration - syncthing-configuration?)) + syncthing-configuration? + syncthing-config-file + syncthing-config-file? + syncthing-device + syncthing-device? + syncthing-folder + syncthing-folder? + syncthing-folder-device + syncthing-folder-device?)) (define home-syncthing-service-type (service-type (inherit (system->home-service-type syncthing-service-type)) + ;; system->home-service-type does not convert special-files-service-type to + ;; home-files-service-type, so redefine extensios + (extensions (list (service-extension home-files-service-type + syncthing-files-service) + (service-extension home-shepherd-service-type + syncthing-shepherd-service))) (default-value (for-home (syncthing-configuration))))) diff --git a/gnu/services/syncthing.scm b/gnu/services/syncthing.scm index a7a9c6aadd..31e3dbe75f 100644 --- a/gnu/services/syncthing.scm +++ b/gnu/services/syncthing.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2021 Oleg Pykhalov ;;; Copyright © 2023 Justin Veilleux +;;; Copyright © 2025 Zacchaeus ;;; ;;; This file is part of GNU Guix. ;;; @@ -25,9 +26,20 @@ (define-module (gnu services syncthing) #:use-module (guix records) #:use-module (ice-9 match) #:use-module (srfi srfi-1) + #:use-module (sxml simple) #:export (syncthing-configuration syncthing-configuration? - syncthing-service-type)) + syncthing-device + syncthing-device? + syncthing-config-file + syncthing-config-file? + syncthing-folder-device + syncthing-folder-device? + syncthing-folder + syncthing-folder? + syncthing-service-type + syncthing-shepherd-service + syncthing-files-service)) ;;; Commentary: ;;; @@ -35,6 +47,438 @@ (define-module (gnu services syncthing) ;;; ;;; Code: +(define-record-type* + syncthing-device make-syncthing-device + syncthing-device? + (id syncthing-device-id) + (name syncthing-device-name (default "")) + (compression syncthing-device-compression (default "metadata")) + (introducer syncthing-device-introducer (default "false")) + (skipIntroductionRemovals syncthing-device-skipIntroductionRemovals (default "false")) + (introducedBy syncthing-device-introducedBy (default "")) + (addresses syncthing-device-addresses (default '("dynamic"))) + (paused syncthing-device-paused (default "false")) + (autoAcceptFolders syncthing-device-autoAcceptFolders (default "false")) + (maxSendKbps syncthing-device-maxSendKbps (default "0")) + (maxRecvKbps syncthing-device-maxRecvKbps (default "0")) + (maxRequestKiB syncthing-device-maxRequestKiB (default "0")) + (untrusted syncthing-device-untrusted (default "false")) + (remoteGUIPort syncthing-device-remoteGUIPort (default "0")) + (numConnections syncthing-device-numConnections (default "0"))) + +(define syncthing-device->sxml + (match-record-lambda + (id name compression introducer skipIntroductionRemovals introducedBy addresses paused autoAcceptFolders maxSendKbps maxRecvKbps maxRequestKiB untrusted remoteGUIPort numConnections) + `(device (@ (id ,id) + (name ,name) + (compression ,compression) + (introducer ,introducer) + (skipIntroductionRemovals ,skipIntroductionRemovals) + (introducedBy ,introducedBy)) + ,@(map (lambda (address) `(address ,address)) addresses) + (paused ,paused) + (autoAcceptFolders ,autoAcceptFolders) + (maxSendKbps ,maxSendKbps) + (maxRecvKbps ,maxRecvKbps) + (maxRequestKiB ,maxRequestKiB) + (untrusted ,untrusted) + (remoteGUIPort ,remoteGUIPort) + (numConnections ,numConnections)))) + +(define-record-type* + syncthing-folder-device make-syncthing-folder-device + syncthing-folder-device? + (device syncthing-folder-device-device) + (introducedBy syncthing-folder-device-introducedBy (default (syncthing-device (id "")))) + (encryptionPassword syncthing-folder-device-encryptionPassword (default ""))) + +(define syncthing-folder-device->sxml + (match-record-lambda + (device introducedBy encryptionPassword) + `(device (@ (id ,(syncthing-device-id device)) + (introducedBy ,(syncthing-device-id introducedBy))) + (encryptionPassword ,encryptionPassword)))) + +(define-record-type* + syncthing-folder make-syncthing-folder + syncthing-folder? + (id syncthing-folder-id (default #f)) + (label syncthing-folder-label) + (path syncthing-folder-path) + (type syncthing-folder-type (default "sendreceive")) + (rescanIntervalS syncthing-folder-rescanIntervalS (default "3600")) + (fsWatcherEnabled syncthing-folder-fsWatcherEnabled (default "true")) + (fsWatcherDelayS syncthing-folder-fsWatcherDelayS (default "10")) + (fsWatcherTimeoutS syncthing-folder-fsWatcherTimeoutS (default "0")) + (ignorePerms syncthing-folder-ignorePerms (default "false")) + (autoNormalize syncthing-folder-autoNormalize (default "true")) + (devices syncthing-folder-devices (default '()) + (sanitize (lambda (folder-device-list) + (map (lambda (device) + (if (syncthing-folder-device? device) + device + (syncthing-folder-device (device device)))) + folder-device-list)))) + (filesystemType syncthing-folder-filesystemType (default "basic")) + (minDiskFree-unit syncthing-folder-minDiskFree-unit (default "%")) + (minDiskFree syncthing-folder-minDiskFree (default "1")) + (versioning-type syncthing-folder-versioning-type (default #f)) + (versioning-fsPath syncthing-folder-versioning-fsPath (default "")) + (versioning-fsType syncthing-folder-versioning-fsType (default "basic")) + (versioning-cleanupIntervalS syncthing-folder-versioning-cleanupIntervalS (default "3600")) + (versioning-cleanoutDays syncthing-folder-versioning-cleanoutDays (default #f)) + (versioning-keep syncthing-folder-versioning-keep (default #f)) + (versioning-maxAge syncthing-folder-versioning-maxAge (default #f)) + (versioning-command syncthing-folder-versioning-command (default #f)) + (copiers syncthing-folder-copiers (default "0")) + (pullerMaxPendingKiB syncthing-folder-pullerMaxPendingKiB (default "0")) + (hashers syncthing-folder-hashers (default "0")) + (order syncthing-folder-order (default "random")) + (ignoreDelete syncthing-folder-ignoreDelete (default "false")) + (scanProgressIntervalS syncthing-folder-scanProgressIntervalS (default "0")) + (pullerPauseS syncthing-folder-pullerPauseS (default "0")) + (maxConflicts syncthing-folder-maxConflicts (default "10")) + (disableSparseFiles syncthing-folder-disableSparseFiles (default "false")) + (disableTempIndexes syncthing-folder-disableTempIndexes (default "false")) + (paused syncthing-folder-paused (default "false")) + (weakHashThresholdPct syncthing-folder-weakHashThresholdPct (default "25")) + (markerName syncthing-folder-markerName (default ".stfolder")) + (copyOwnershipFromParent syncthing-folder-copyOwnershipFromParent (default "false")) + (modTimeWindowS syncthing-folder-modTimeWindowS (default "0")) + (maxConcurrentWrites syncthing-folder-maxConcurrentWrites (default "2")) + (disableFsync syncthing-folder-disableFsync (default "false")) + (blockPullOrder syncthing-folder-blockPullOrder (default "standard")) + (copyRangeMethod syncthing-folder-copyRangeMethod (default "standard")) + (caseSensitiveFS syncthing-folder-caseSensitiveFS (default "false")) + (junctionsAsDirs syncthing-folder-junctionsAsDirs (default "false")) + (syncOwnership syncthing-folder-syncOwnership (default "false")) + (sendOwnership syncthing-folder-sendOwnership (default "false")) + (syncXattrs syncthing-folder-syncXattrs (default "false")) + (sendXattrs syncthing-folder-sendXattrs (default "false")) + (xattrFilter-maxSingleEntrySize syncthing-folder-xattrFilter-maxSingleEntrySize (default "1024")) + (xattrFilter-maxTotalSize syncthing-folder-xattrFilter-maxTotalSize (default "4096"))) + +;; Some parameters, when empty, are fully omitted from the config file. It is +;; unknown if this causes a functional difference, but stick to the normal +;; program's behavior to be safe. +(define (maybe-param symbol value) + (if value `((param (@ (key ,(symbol->string symbol)) (val ,value)) "")) '())) + +(define syncthing-folder->sxml + (match-record-lambda + (id + label path type rescanIntervalS fsWatcherEnabled fsWatcherDelayS + fsWatcherTimeoutS ignorePerms autoNormalize devices filesystemType + minDiskFree-unit minDiskFree versioning-type versioning-fsPath + versioning-fsType versioning-cleanupIntervalS versioning-cleanoutDays + versioning-keep versioning-maxAge versioning-command copiers + pullerMaxPendingKiB hashers order ignoreDelete scanProgressIntervalS + pullerPauseS maxConflicts disableSparseFiles disableTempIndexes paused + weakHashThresholdPct markerName copyOwnershipFromParent modTimeWindowS + maxConcurrentWrites disableFsync blockPullOrder copyRangeMethod + caseSensitiveFS junctionsAsDirs syncOwnership sendOwnership syncXattrs + sendXattrs xattrFilter-maxSingleEntrySize xattrFilter-maxTotalSize) + `(folder (@ (id ,(if id id label)) + (label ,label) + (path ,path) + (type ,type) + (rescanIntervalS ,rescanIntervalS) + (fsWatcherEnabled ,fsWatcherEnabled) + (fsWatcherDelayS ,fsWatcherDelayS) + (fsWatcherTimeoutS ,fsWatcherTimeoutS) + (ignorePerms ,ignorePerms) + (autoNormalize ,autoNormalize)) + (filesystemType ,filesystemType) + ,@(map syncthing-folder-device->sxml + devices) + (minDiskFree (@ (unit ,minDiskFree-unit)) + ,minDiskFree) + (versioning ,@(if versioning-type + `((@ (type ,versioning-type))) + '()) + ,@(maybe-param 'cleanoutDays versioning-cleanoutDays) + ,@(maybe-param 'keep versioning-keep) + ,@(maybe-param 'maxAge versioning-maxAge) + ,@(maybe-param 'command versioning-command) + (cleanupIntervalS ,versioning-cleanupIntervalS) + (fsPath ,versioning-fsPath) + (fsType ,versioning-fsType)) + (copiers ,copiers) + (pullerMaxPendingKiB ,pullerMaxPendingKiB) + (hashers ,hashers) + (order ,order) + (ignoreDelete ,ignoreDelete) + (scanProgressIntervalS ,scanProgressIntervalS) + (pullerPauseS ,pullerPauseS) + (maxConflicts ,maxConflicts) + (disableSparseFiles ,disableSparseFiles) + (disableTempIndexes ,disableTempIndexes) + (paused ,paused) + (weakHashThresholdPct ,weakHashThresholdPct) + (markerName ,markerName) + (copyOwnershipFromParent ,copyOwnershipFromParent) + (modTimeWindowS ,modTimeWindowS) + (maxConcurrentWrites ,maxConcurrentWrites) + (disableFsync ,disableFsync) + (blockPullOrder ,blockPullOrder) + (copyRangeMethod ,copyRangeMethod) + (caseSensitiveFS ,caseSensitiveFS) + (junctionsAsDirs ,junctionsAsDirs) + (syncOwnership ,syncOwnership) + (sendOwnership ,sendOwnership) + (syncXattrs ,syncXattrs) + (sendXattrs ,sendXattrs) + (xattrFilter (maxSingleEntrySize ,xattrFilter-maxSingleEntrySize) + (maxTotalSize ,xattrFilter-maxTotalSize))))) + +(define-record-type* + syncthing-config-file make-syncthing-config-file + syncthing-config-file? + (folders syncthing-config-folders + ; this matches syncthing's default + (default (list (syncthing-folder (id "default") + (label "Default Folder") + (path "~/Sync"))))) + (devices syncthing-config-devices + (default '())) + (gui-enabled syncthing-config-gui-enabled (default "true")) + (gui-tls syncthing-config-gui-tls (default "false")) + (gui-debugging syncthing-config-gui-debugging (default "false")) + (gui-sendBasicAuthPrompt syncthing-config-gui-sendBasicAuthPrompt (default "false")) + (gui-address syncthing-config-gui-address (default "127.0.0.1:8384")) + (gui-user syncthing-config-gui-user (default #f)) + (gui-password syncthing-config-gui-password (default #f)) + (gui-apikey syncthing-config-gui-apikey (default "Vuky3VHVseQEoSk9YgxhSkNTnjQmqYK9")) + (gui-theme syncthing-config-gui-theme (default "default")) + (ldap-enabled syncthing-config-ldap-enabled (default #f)) + (ldap-address syncthing-config-ldap-address (default "")) + (ldap-bindDN syncthing-config-ldap-bindDN (default "")) + (ldap-transport syncthing-config-ldap-transport (default "")) + (ldap-insecureSkipVerify syncthing-config-ldap-insecureSkipVerify (default "")) + (ldap-searchBaseDN syncthing-config-ldap-searchBaseDN (default "")) + (ldap-searchFilter syncthing-config-ldap-searchFilter (default "")) + (listenAddress syncthing-config-listenAddress (default "default")) + (globalAnnounceServer syncthing-config-globalAnnounceServer (default "default")) + (globalAnnounceEnabled syncthing-config-globalAnnounceEnabled (default "true")) + (localAnnounceEnabled syncthing-config-localAnnounceEnabled (default "true")) + (localAnnouncePort syncthing-config-localAnnouncePort (default "21027")) + (localAnnounceMCAddr syncthing-config-localAnnounceMCAddr (default "[ff12::8384]:21027")) + (maxSendKbps syncthing-config-maxSendKbps (default "0")) + (maxRecvKbps syncthing-config-maxRecvKbps (default "0")) + (reconnectionIntervalS syncthing-config-reconnectionIntervalS (default "60")) + (relaysEnabled syncthing-config-relaysEnabled (default "true")) + (relayReconnectIntervalM syncthing-config-relayReconnectIntervalM (default "10")) + (startBrowser syncthing-config-startBrowser (default "true")) + (natEnabled syncthing-config-natEnabled (default "true")) + (natLeaseMinutes syncthing-config-natLeaseMinutes (default "60")) + (natRenewalMinutes syncthing-config-natRenewalMinutes (default "30")) + (natTimeoutSeconds syncthing-config-natTimeoutSeconds (default "10")) + (urAccepted syncthing-config-urAccepted (default "0")) + (urSeen syncthing-config-urSeen (default "0")) + (urUniqueID syncthing-config-urUniqueID (default "")) + (urURL syncthing-config-urURL (default "https://data.syncthing.net/newdata")) + (urPostInsecurely syncthing-config-urPostInsecurely (default "false")) + (urInitialDelayS syncthing-config-urInitialDelayS (default "1800")) + (autoUpgradeIntervalH syncthing-config-autoUpgradeIntervalH (default "12")) + (upgradeToPreReleases syncthing-config-upgradeToPreReleases (default "false")) + (keepTemporariesH syncthing-config-keepTemporariesH (default "24")) + (cacheIgnoredFiles syncthing-config-cacheIgnoredFiles (default "false")) + (progressUpdateIntervalS syncthing-config-progressUpdateIntervalS (default "5")) + (limitBandwidthInLan syncthing-config-limitBandwidthInLan (default "false")) + (minHomeDiskFree-unit syncthing-config-minHomeDiskFree-unit (default "%")) + (minHomeDiskFree syncthing-config-minHomeDiskFree (default "1")) + (releasesURL syncthing-config-releasesURL (default "https://upgrades.syncthing.net/meta.json")) + (overwriteRemoteDeviceNamesOnConnect syncthing-config-overwriteRemoteDeviceNamesOnConnect (default "false")) + (tempIndexMinBlocks syncthing-config-tempIndexMinBlocks (default "10")) + (unackedNotificationID syncthing-config-unackedNotificationID (default "authenticationUserAndPassword")) + (trafficClass syncthing-config-trafficClass (default "0")) + (setLowPriority syncthing-config-setLowPriority (default "true")) + (maxFolderConcurrency syncthing-config-maxFolderConcurrency (default "0")) + (crashReportingURL syncthing-config-crashReportingURL (default "https://crash.syncthing.net/newcrash")) + (crashReportingEnabled syncthing-config-crashReportingEnabled (default "true")) + (stunKeepaliveStartS syncthing-config-stunKeepaliveStartS (default "180")) + (stunKeepaliveMinS syncthing-config-stunKeepaliveMinS (default "20")) + (stunServer syncthing-config-stunServer (default "default")) + (databaseTuning syncthing-config-databaseTuning (default "auto")) + (maxConcurrentIncomingRequestKiB syncthing-config-maxConcurrentIncomingRequestKiB (default "0")) + (announceLANAddresses syncthing-config-announceLANAddresses (default "true")) + (sendFullIndexOnUpgrade syncthing-config-sendFullIndexOnUpgrade (default "false")) + (connectionLimitEnough syncthing-config-connectionLimitEnough (default "0")) + (connectionLimitMax syncthing-config-connectionLimitMax (default "0")) + (insecureAllowOldTLSVersions syncthing-config-insecureAllowOldTLSVersions (default "false")) + (connectionPriorityTcpLan syncthing-config-connectionPriorityTcpLan (default "10")) + (connectionPriorityQuicLan syncthing-config-connectionPriorityQuicLan (default "20")) + (connectionPriorityTcpWan syncthing-config-connectionPriorityTcpWan (default "30")) + (connectionPriorityQuicWan syncthing-config-connectionPriorityQuicWan (default "40")) + (connectionPriorityRelay syncthing-config-connectionPriorityRelay (default "50")) + (connectionPriorityUpgradeThreshold syncthing-config-connectionPriorityUpgradeThreshold (default "0")) + (default-folder syncthing-config-defaultFolder + (default (syncthing-folder (label "") (path "~")))) + (default-device syncthing-config-defaultDevice + (default (syncthing-device (id "")))) + (default-ignores syncthing-config-defaultIgnores (default ""))) + +(define syncthing-config-file->sxml + (match-record-lambda + (folders + devices gui-enabled gui-tls gui-debugging gui-sendBasicAuthPrompt + gui-address gui-user gui-password gui-apikey gui-theme ldap-enabled + ldap-address ldap-bindDN ldap-transport ldap-insecureSkipVerify + ldap-searchBaseDN ldap-searchFilter listenAddress globalAnnounceServer + globalAnnounceEnabled localAnnounceEnabled localAnnouncePort + localAnnounceMCAddr maxSendKbps maxRecvKbps reconnectionIntervalS + relaysEnabled relayReconnectIntervalM startBrowser natEnabled + natLeaseMinutes natRenewalMinutes natTimeoutSeconds urAccepted + urSeen urUniqueID urURL urPostInsecurely urInitialDelayS + autoUpgradeIntervalH upgradeToPreReleases keepTemporariesH + cacheIgnoredFiles progressUpdateIntervalS limitBandwidthInLan + minHomeDiskFree-unit minHomeDiskFree releasesURL + overwriteRemoteDeviceNamesOnConnect tempIndexMinBlocks + unackedNotificationID trafficClass setLowPriority maxFolderConcurrency + crashReportingURL crashReportingEnabled stunKeepaliveStartS + stunKeepaliveMinS stunServer databaseTuning + maxConcurrentIncomingRequestKiB announceLANAddresses + sendFullIndexOnUpgrade connectionLimitEnough connectionLimitMax + insecureAllowOldTLSVersions connectionPriorityTcpLan + connectionPriorityQuicLan connectionPriorityTcpWan + connectionPriorityQuicWan connectionPriorityRelay + connectionPriorityUpgradeThreshold default-folder default-device + default-ignores) + `(configuration (@ (version "37")) + ,@(map syncthing-folder->sxml + folders) + ;; collect any devices in any folders, as well as any + ;; devices explicitly added. + ,@(map syncthing-device->sxml + (delete-duplicates + (append devices + (apply append + (map (lambda (folder) + (map syncthing-folder-device-device + (syncthing-folder-devices folder))) + folders))) + ;; devices are the same if their id's are equal + (lambda (device1 device2) + (string= (syncthing-device-id device1) + (syncthing-device-id device2))))) + (gui (@ (enabled ,gui-enabled) + (tls ,gui-tls) + (debugging ,gui-debugging) + (sendBasicAuthPrompt ,gui-sendBasicAuthPrompt)) + (address ,gui-address) + ,@(if gui-user `((user ,gui-user)) '()) + ,@(if gui-password `((password ,gui-password)) '()) + (apikey ,gui-apikey) + (theme ,gui-theme)) + (ldap ,(if ldap-enabled + `((address ,ldap-address) + (bindDN ,ldap-bindDN) + ,@(if ldap-transport + `((transport ,ldap-transport)) + '()) + ,@(if ldap-insecureSkipVerify + `((insecureSkipVerify ,ldap-insecureSkipVerify)) + '()) + ,@(if ldap-searchBaseDN + `((searchBaseDN ,ldap-searchBaseDN)) + '()) + ,@(if ldap-searchFilter + `((searchFilter ,ldap-searchFilter)) + '())) + "")) + (options (listenAddress ,listenAddress) + (globalAnnounceServer ,globalAnnounceServer) + (globalAnnounceEnabled ,globalAnnounceEnabled) + (localAnnounceEnabled ,localAnnounceEnabled) + (localAnnouncePort ,localAnnouncePort) + (localAnnounceMCAddr ,localAnnounceMCAddr) + (maxSendKbps ,maxSendKbps) + (maxRecvKbps ,maxRecvKbps) + (reconnectionIntervalS ,reconnectionIntervalS) + (relaysEnabled ,relaysEnabled) + (relayReconnectIntervalM ,relayReconnectIntervalM) + (startBrowser ,startBrowser) + (natEnabled ,natEnabled) + (natLeaseMinutes ,natLeaseMinutes) + (natRenewalMinutes ,natRenewalMinutes) + (natTimeoutSeconds ,natTimeoutSeconds) + (urAccepted ,urAccepted) + (urSeen ,urSeen) + (urUniqueID ,urUniqueID) + (urURL ,urURL) + (urPostInsecurely ,urPostInsecurely) + (urInitialDelayS ,urInitialDelayS) + (autoUpgradeIntervalH ,autoUpgradeIntervalH) + (upgradeToPreReleases ,upgradeToPreReleases) + (keepTemporariesH ,keepTemporariesH) + (cacheIgnoredFiles ,cacheIgnoredFiles) + (progressUpdateIntervalS ,progressUpdateIntervalS) + (limitBandwidthInLan ,limitBandwidthInLan) + (minHomeDiskFree (@ (unit ,minHomeDiskFree-unit)) + ,minHomeDiskFree) + (releasesURL ,releasesURL) + (overwriteRemoteDeviceNamesOnConnect ,overwriteRemoteDeviceNamesOnConnect) + (tempIndexMinBlocks ,tempIndexMinBlocks) + (unackedNotificationID ,unackedNotificationID) + (trafficClass ,trafficClass) + (setLowPriority ,setLowPriority) + (maxFolderConcurrency ,maxFolderConcurrency) + (crashReportingURL ,crashReportingURL) + (crashReportingEnabled ,crashReportingEnabled) + (stunKeepaliveStartS ,stunKeepaliveStartS) + (stunKeepaliveMinS ,stunKeepaliveMinS) + (stunServer ,stunServer) + (databaseTuning ,databaseTuning) + (maxConcurrentIncomingRequestKiB ,maxConcurrentIncomingRequestKiB) + (announceLANAddresses ,announceLANAddresses) + (sendFullIndexOnUpgrade ,sendFullIndexOnUpgrade) + (connectionLimitEnough ,connectionLimitEnough) + (connectionLimitMax ,connectionLimitMax) + (insecureAllowOldTLSVersions ,insecureAllowOldTLSVersions) + (connectionPriorityTcpLan ,connectionPriorityTcpLan) + (connectionPriorityQuicLan ,connectionPriorityQuicLan) + (connectionPriorityTcpWan ,connectionPriorityTcpWan) + (connectionPriorityQuicWan ,connectionPriorityQuicWan) + (connectionPriorityRelay ,connectionPriorityRelay) + (connectionPriorityUpgradeThreshold ,connectionPriorityUpgradeThreshold)) + (defaults + ,(syncthing-folder->sxml default-folder) + ,(syncthing-device->sxml default-device) + (ignores ,default-ignores))))) + +;; It is useful to be able to view the xml output by Guix, and to be able to +;; diff it with a user's previous config, especially when migrating one's +;; config to Guix. This function adds whitespace that matches the whitespace +;; of config files managed by Syncthing for easy diffing. +(define (indent-sxml sxml indent-increment current-indent) + (match sxml + (((tag ('@ properties ...) (subtags ..1) ..1) sibling-tags ...) + `(,current-indent (,tag (@ ,@properties) "\n" + ,@(indent-sxml subtags indent-increment + (string-append indent-increment current-indent)) + ,current-indent) "\n" + ,@(indent-sxml sibling-tags indent-increment current-indent))) + (((tag ('@ properties ...) primitive ...) sibling-tags ...) + `(,current-indent (,tag (@ ,@properties) ,@primitive) "\n" + ,@(indent-sxml sibling-tags indent-increment current-indent))) + (((tag (subtags ..1) ..1) sibling-tags ...) + `(,current-indent (,tag "\n" + ,@(indent-sxml subtags indent-increment + (string-append indent-increment current-indent)) + ,current-indent) "\n" + ,@(indent-sxml sibling-tags indent-increment current-indent))) + (((tag primitive ...) sibling-tags ...) + `(,current-indent (,tag ,@primitive) "\n" + ,@(indent-sxml sibling-tags indent-increment current-indent))) + (() '()))) + +(define (serialize-syncthing-config-file config) + (with-output-to-string + (lambda () + (sxml->xml (cons '*TOP* (indent-sxml (list (syncthing-config-file->sxml config)) + " " + "")))))) + (define-record-type* syncthing-configuration make-syncthing-configuration syncthing-configuration? @@ -50,6 +494,8 @@ (define-record-type* (default "users")) (home syncthing-configuration-home ;string (default #f)) + (config-file syncthing-configuration-config-file + (default #f)) ; syncthing-config-file or file-like (home-service? syncthing-configuration-home-service? (default for-home?) (innate))) @@ -93,10 +539,26 @@ (define syncthing-shepherd-service (respawn? #f) (stop #~(make-kill-destructor)))))) + +(define syncthing-files-service + (match-record-lambda (config-file user home home-service?) + (if config-file + `((,(if home-service? + ".config/syncthing/config.xml" + (string-append (or home (passwd:dir (getpw user))) + "/.config/syncthing/config.xml")) + ,(if (file-like? config-file) + config-file + (plain-file "syncthin-config.xml" (serialize-syncthing-config-file + config-file))))) + '()))) + (define syncthing-service-type (service-type (name 'syncthing) (extensions (list (service-extension shepherd-root-service-type - syncthing-shepherd-service))) + syncthing-shepherd-service) + (service-extension special-files-service-type + syncthing-files-service))) (description "Run @uref{https://github.com/syncthing/syncthing, Syncthing} decentralized continuous file system synchronization.")))