Running Syncthing as a System User on NixOS
Syncthing on NixOS runs as a system service by default and provides an option to run the service as your user. To run syncthing as the system user named syncthing, and also share folders in your user’s home directory, there are a few permissions we’ll need to set.
The file access capabilities we need are as follows:
- syncthing has permission to read/write files in the mounted directories
- user has permission to read/write files in the mounted directories
- syncthing has permission to read/write new files created by user
- user has permission to read/write new files created by syncthing
To satisfy the above requirements, we can set the following to ensure correct permissions:
- syncthing in the “users” group
- syncthing has access to the home directory
- user in the “syncthing” group
- a umask for the syncthing service to ensure created files have group read/write permissions.
- a setgid flag for the shared folder to ensure “syncthing” group ownership of new files
The first four are set as follows:
let
user = "alan";
in
{
users.users.syncthing.extraGroups = [ "users" ];
users.users."${user}".extraGroups = [ "syncthing" ];
systemd.services.syncthing.serviceConfig.UMask = "0007";
systemd.tmpfiles.rules = [
"d /home/${user} 0750 alan syncthing"
];
}
For the setgid flag permissions (2770), let’s set the permissions for all of our shared folders:
let
user = "alan";
folders = {
"text" = {
id = "text";
path = "/home/${user}/text";
ignorePerms = true;
};
"documents" = {
id = "documents";
path = "/home/${user}/documents";
ignorePerms = true;
};
};
inherit (builtins) map attrValues;
in
{
systemd.tmpfiles.rules = (map (folder: "d ${folder.path} 2770 ${user} syncthing") (attrValues folders));
}
A Remaining Edgecase
The only permissions issue I’ve observed is that moving directories and files into the shared folder won’t trigger the setgid flag. The following find commands will fix the group permissions:
find "$FOLDER" -type f \( ! -group syncthing -or ! -perm -g=rw \) -not -path "*/.st*" -exec chgrp syncthing {} \; -exec chmod g+rw {} \;
find "$FOLDER" -type d \( ! -group syncthing -or ! -perm -g=rwxs \) -not -path "*/.st*" -exec chgrp syncthing {} \; -exec chmod g+rwxs {} \;
To automate this, two solutions are:
- A timed job to update permissions that aren’t set correctly.
- A daemon that watches for file creation (with entr or inotifywait) and modifies permissions as appropriate.
A timed job is simpler, so let’s go with that. Using the tasks module I previously wrote about, we can run our find command that fixes permissions for each syncthing directory.
tasks.fix-syncthing-permissions = {
user = "alan";
onCalendar = "*-*-* 18:00:00";
script = let
folders = pkgs.lib.concatMapStringsSep " " (folder: folder.path) (builtins.attrValues config.services.syncthing.folders);
in ''
for FOLDER in ${folders}; do
find "$FOLDER" -type f \( ! -group syncthing -or ! -perm -g=rw \) -not -path "*/.st*" -exec chgrp syncthing {} \; -exec chmod g+rw {} \;
find "$FOLDER" -type d \( ! -group syncthing -or ! -perm -g=rwxs \) -not -path "*/.st*" -exec chgrp syncthing {} \; -exec chmod g+rwxs {} \;
done
'';
};
Remarks
I love how simple it is to template my operating system configuration with NixOS. If I add a new syncthing folder, an appropriate tmpfiles rule will be set for it and the timed job to fix permissions will run for it as well. I don’t need to remember where the files are on my system, or even which files need to be changed. And if I do need to change something, it’s all specified in the code.
Future Work
All of these permissions shenanigans may be avoided entirely by using a bindfs fuse mount that remaps permissions:
bindfs --map=237/1000,@237/@100 /var/lib/syncthing/folder /home/alan/folder
It isn’t clear to me if this is supported by the `filesystems` option.