1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

syncthing: handle per-folder encryptionPasswordFile

This change allows configuring per-folder encryption passwords declaratively (and without leaking them into the store), modeled after the upstream Nixpkgs changes from https://github.com/NixOS/nixpkgs/pull/383442.

Closes https://github.com/nix-community/home-manager/issues/7318.

Co-authored-by: Jeremy Fleischman <jeremyfleischman@gmail.com>
Co-authored-by: Aurimas Blažulionis <0x60@pm.me>
This commit is contained in:
Alex Ionescu 2025-09-20 21:54:40 +02:00 committed by Austin Horstman
parent ef3762499b
commit cb68b5cd6a

View file

@ -51,11 +51,11 @@ let
devices = map (
device:
if builtins.isString device then
{
deviceId = cfg.settings.devices.${device}.id;
}
{ deviceId = cfg.settings.devices.${device}.id; }
else if builtins.isAttrs device then
{ deviceId = cfg.settings.devices.${device.name}.id; } // device
else
device
throw "Invalid type for devices in folder '${folder.label}'; expected list or attrset."
) folder.devices;
}
) (lib.filterAttrs (_: folder: folder.enable) cfg.settings.folders);
@ -152,9 +152,79 @@ let
# don't exist in the array given. That's why we use here `POST`, and
# only if s.override == true then we DELETE the relevant folders
# afterwards.
(map (new_cfg: ''
curl -d ${lib.escapeShellArg (builtins.toJSON new_cfg)} -X POST ${s.baseAddress}
''))
(map (
new_cfg:
let
jsonPreSecretsFile = pkgs.writeTextFile {
name = "${conf_type}-${new_cfg.id}-conf-pre-secrets.json";
text = builtins.toJSON new_cfg;
};
injectSecretsJqCmd =
{
# There are no secrets in `devs`, so no massaging needed.
"devs" = "${jq} .";
"dirs" =
let
folder = new_cfg;
devicesWithSecrets = lib.pipe folder.devices [
(lib.filter (device: (builtins.isAttrs device) && device ? encryptionPasswordFile))
(map (device: {
deviceId = device.deviceId;
variableName = "secret_${builtins.hashString "sha256" device.encryptionPasswordFile}";
secretPath = device.encryptionPasswordFile;
}))
];
# At this point, `jsonPreSecretsFile` looks something like this:
#
# {
# ...,
# "devices": [
# {
# "deviceId": "id1",
# "encryptionPasswordFile": "/etc/bar-encryption-password",
# "name": "..."
# }
# ],
# }
#
# We now generate a `jq` command that can replace those
# `encryptionPasswordFile`s with `encryptionPassword`.
# The `jq` command ends up looking like this:
#
# jq --rawfile secret_DEADBEEF /etc/bar-encryption-password '
# .devices[] |= (
# if .deviceId == "id1" then
# del(.encryptionPasswordFile) |
# .encryptionPassword = $secret_DEADBEEF
# else
# .
# end
# )
# '
jqUpdates = map (device: ''
.devices[] |= (
if .deviceId == "${device.deviceId}" then
del(.encryptionPasswordFile) |
.encryptionPassword = ''$${device.variableName}
else
.
end
)
'') devicesWithSecrets;
jqRawFiles = map (
device: "--rawfile ${device.variableName} ${lib.escapeShellArg device.secretPath}"
) devicesWithSecrets;
in
"${jq} ${lib.concatStringsSep " " jqRawFiles} ${
lib.escapeShellArg (lib.concatStringsSep "|" ([ "." ] ++ jqUpdates))
}";
}
.${conf_type};
in
''
${injectSecretsJqCmd} ${jsonPreSecretsFile} | curl --json @- -X POST ${s.baseAddress}
''
))
(lib.concatStringsSep "\n")
]
/*
@ -478,11 +548,45 @@ in
};
devices = mkOption {
type = with types; listOf str;
type = types.listOf (
types.oneOf [
types.str
(types.submodule {
freeformType = settingsFormat.type;
options = {
name = mkOption {
type = types.str;
default = null;
description = ''
The name of a device defined in the
[devices](#opt-services.syncthing.settings.devices)
option.
'';
};
encryptionPasswordFile = mkOption {
type = types.nullOr (
types.pathWith {
inStore = false;
absolute = true;
}
);
default = null;
description = ''
Path to encryption password. If set, the file will be read during
service activation, without being embedded in derivation.
'';
};
};
})
]
);
default = [ ];
description = ''
The devices this folder should be shared with. Each device must
be defined in the [devices](#opt-services.syncthing.settings.devices) option.
A list of either strings or attribute sets, where values
are device names or device configurations.
'';
};