sops-nix/modules/sops/secrets-for-users/default.nix
r-vdp 81440e3757
sops-install-secrets: call systemctl directly when run as a systemd unit
When useSystemdActivation is enabled, sops-install-secrets.service runs
ordered Before=sysinit-reactivation.target, which switch-to-configuration
restarts *after* it has already consumed /run/nixos/activation-*-list.
Writing to those files from the service therefore does nothing on the
current switch and leaks into the next one.

NixOS 26.05 also deprecates the activation-list mechanism, printing a
warning whenever the files exist, with removal planned for 26.11.

The systemd unit now sets SOPS_RESTART_UNITS_VIA_SYSTEMCTL=1 so the
binary knows to call systemctl directly (try-restart /
try-reload-or-restart, --no-block to avoid deadlocking the sysinit
transaction). INVOCATION_ID cannot be used for this since
switch-to-configuration is almost always invoked from a process tree
rooted in some unit (sshd, getty, systemd-run), so the activation script
inherits it too. The legacy activation-script path keeps writing the
list files for backward compatibility.

Closes #934
2026-06-03 20:11:00 +03:00

88 lines
3.1 KiB
Nix

{
lib,
options,
config,
pkgs,
...
}:
let
cfg = config.sops;
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
templatesForUsers = { }; # We do not currently support `neededForUsers` for templates.
manifestFor = pkgs.callPackage ../manifest-for.nix {
inherit cfg;
inherit (pkgs) writeTextFile;
};
withEnvironment = import ../with-environment.nix {
# See also the default NixOS module.
cfg = lib.recursiveUpdate cfg {
environment.HOME = "/var/empty";
environment.PATH = lib.makeBinPath cfg.age.plugins;
};
inherit lib;
};
manifestForUsers = manifestFor "-for-users" secretsForUsers templatesForUsers {
secretsMountPoint = "/run/secrets-for-users.d";
symlinkPath = "/run/secrets-for-users";
};
sysusersEnabled = options.systemd ? sysusers && config.systemd.sysusers.enable;
useSystemdActivation =
sysusersEnabled || (options.services ? userborn && config.services.userborn.enable);
in
{
systemd.services.sops-install-secrets-for-users =
lib.mkIf (secretsForUsers != { } && useSystemdActivation)
{
wantedBy = [ "systemd-sysusers.service" ];
before = [ "systemd-sysusers.service" ];
environment = cfg.environment // {
SOPS_RESTART_UNITS_VIA_SYSTEMCTL = "1";
};
unitConfig.DefaultDependencies = "no";
path = cfg.age.plugins;
serviceConfig = {
Type = "oneshot";
ExecStart = [ "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}" ];
RemainAfterExit = true;
};
unitConfig.RequiresMountsFor = lib.concatLists [
(lib.lists.optional (cfg.gnupg.home != null) cfg.gnupg.home)
cfg.gnupg.sshKeyPaths
(lib.lists.optional (cfg.age.keyFile != null) cfg.age.keyFile)
cfg.age.sshKeyPaths
];
};
system.activationScripts = lib.mkIf (secretsForUsers != { } && !useSystemdActivation) {
setupSecretsForUsers =
lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") ''
[ -e /run/current-system ] || echo setting up secrets for users...
${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"}
''
// lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true;
};
users.deps = [ "setupSecretsForUsers" ];
};
assertions = [
{
assertion =
(lib.filterAttrs (
_: v: (v.uid != 0 && v.owner != "root") || (v.gid != 0 && v.group != "root")
) secretsForUsers) == { };
message = "neededForUsers cannot be used for secrets that are not root-owned";
}
{
assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers;
message = ''
systemd.sysusers.enable in combination with sops.secrets.<name>.neededForUsers can only work with config.users.mutableUsers enabled.
See https://github.com/Mic92/sops-nix/issues/475
'';
}
];
system.build.sops-nix-users-manifest = manifestForUsers;
}