Summary
On macOS (aarch64-darwin, nix-darwin) with deploy-rs, sops-install-secrets fails during activation with:
sops-install-secrets: Imported /etc/ssh/ssh_host_rsa_key as GPG key with fingerprint c501a8ed...274554
⭐ ❌ Nix activation script command resulted in a bad exit status: exit status: 1
Running the same sops-install-secrets binary + same manifest as root manually succeeds silently (exit 0). The failure only happens during deploy-rs activate, and the true error (seen when running as non-root) is:
sops-install-secrets: error setting up gpg keyring: cannot create gpg home in '/run/secrets.d': mkdir /run/secrets.d/gpg1698314092: permission denied
Root cause: stale launchd daemon keeps the old manifest (with GPG import enabled)
The nix-darwin activation script (activate, around line 1146+) re-installs /Library/LaunchDaemons/org.nixos.sops-install-secrets.plist and calls launchctl load -w when the plist changes. But the launchd daemon process itself is not reliably re-loaded after cp -f: the previous daemon registration (from the previous generation) keeps running with:
- the old manifest (e.g.
gsr78izq...-manifest.json with sshKeyPaths = ["/etc/ssh/ssh_host_rsa_key"]),
- the old
SOPS_GPG_EXEC=.../gpg env var.
So even when the new generation's manifest has sshKeyPaths = [] (GPG import disabled — see our config below), the stale daemon still runs sops-install-secrets with the old manifest, imports /etc/ssh/ssh_host_rsa_key as a GPG key, and then tries to create its gpg keyring home inside the secrets mount point /run/secrets.d/ (which on macOS is the mounted secretfs at /dev/disk5), hitting mkdir /run/secrets.d/gpg<timestamp>: permission denied.
Evidence
- Active successful generation (e.g.
gen 504, 26.11.57a3171) after a nix flake rollback → its activate script references manifest gsr8izq1... which has sshKeyPaths = ["/etc/ssh/ssh_host_rsa_key"] → and its launchctl daemon's ProgramArguments contains export SOPS_GPG_EXEC=.../gpg && sops-install-secrets <that manifest>.
- Next deploy (new generation
26.11.15abb8c) → activation reloads the plist (new manifest sshKeyPaths=[]), but the daemon still runs with the old manifest + GPG → failure inside /run/secrets.d.
- Manual re-run as root with the exact same (new) manifest + binary → silent success (exit 0), because by then secrets are already written and gpg keyring not needed... (but during the actual failed activate, the stale daemon still uses the old manifest.)
- Between successful gen and failed gen:
sops-install-secrets store path differs (jffi34i10... vs zbawbql73...) but the binary content is identical (same arch, strings same; md5 differs only due to build meta). So it's not a binary regression — it's the daemon reload race.
My configuration (secrets/default.nix)
sops = {
age.keyFile = "/Users/luck/Library/Application Support/sops/age/keys.txt";
age.sshKeyPaths = [ ]; # Disable SSH key import
gnupg.home = null; # Disable GPG key import
# NOTE: manifest still includes sshKeyPaths = ["/etc/ssh/ssh_host_rsa_key"]
# even with gnupg.home = null — this is what revived the stale daemon's GPG import.
# Setting gnupg.sshKeyPaths = [] makes the new manifest sshKeyPaths = [] and
# sops-install-secrets silent (no GPG import), but the stale daemon still uses
# the old one during switch.
};
Repro
- Have a nix-darwin system with
sops.age.sshKeyPaths empty but sops.gnupg.home = null (GPG import still on by default).
- Deploy with succeeded generation (e.g. after
nix flake rollback).
- Then deploy a new generation that changes the manifest (any secret/template change).
- Activation fails at
Setting up secrets... with Imported /etc/ssh/ssh_host_rsa_key as GPG key then exit 1.
Expected
- nix-darwin should fully restart (or re-bootstrap) the
org.nixos.sops-install-secrets launchd daemon after replacing its plist, and/or
sops.gnupg.sshKeyPaths = [] (or age.sshKeyPaths = [] + gnupg.home=null) should fully suppress the SSH→GPG import so it never touches /run/secrets.d.
Related (but diff)
Summary
On macOS (aarch64-darwin, nix-darwin) with deploy-rs,
sops-install-secretsfails during activation with:Running the same
sops-install-secretsbinary + same manifest as root manually succeeds silently (exit 0). The failure only happens duringdeploy-rs activate, and the true error (seen when running as non-root) is:Root cause: stale launchd daemon keeps the old manifest (with GPG import enabled)
The nix-darwin activation script (
activate, around line 1146+) re-installs/Library/LaunchDaemons/org.nixos.sops-install-secrets.plistand callslaunchctl load -wwhen the plist changes. But the launchd daemon process itself is not reliably re-loaded aftercp -f: the previous daemon registration (from the previous generation) keeps running with:gsr78izq...-manifest.jsonwithsshKeyPaths = ["/etc/ssh/ssh_host_rsa_key"]),SOPS_GPG_EXEC=.../gpgenv var.So even when the new generation's manifest has
sshKeyPaths = [](GPG import disabled — see our config below), the stale daemon still runssops-install-secretswith the old manifest, imports/etc/ssh/ssh_host_rsa_keyas a GPG key, and then tries to create its gpg keyring home inside the secrets mount point/run/secrets.d/(which on macOS is the mounted secretfs at/dev/disk5), hittingmkdir /run/secrets.d/gpg<timestamp>: permission denied.Evidence
gen 504,26.11.57a3171) after anix flake rollback→ itsactivatescript references manifestgsr8izq1...which hassshKeyPaths = ["/etc/ssh/ssh_host_rsa_key"]→ and its launchctl daemon'sProgramArgumentscontainsexport SOPS_GPG_EXEC=.../gpg && sops-install-secrets <that manifest>.26.11.15abb8c) → activation reloads the plist (new manifestsshKeyPaths=[]), but the daemon still runs with the old manifest + GPG → failure inside/run/secrets.d.sops-install-secretsstore path differs (jffi34i10...vszbawbql73...) but the binary content is identical (same arch, strings same; md5 differs only due to build meta). So it's not a binary regression — it's the daemon reload race.My configuration (secrets/default.nix)
Repro
sops.age.sshKeyPathsempty butsops.gnupg.home = null(GPG import still on by default).nix flake rollback).Setting up secrets...withImported /etc/ssh/ssh_host_rsa_key as GPG keythen exit 1.Expected
org.nixos.sops-install-secretslaunchd daemon after replacing its plist, and/orsops.gnupg.sshKeyPaths = [](orage.sshKeyPaths = []+gnupg.home=null) should fully suppress the SSH→GPG import so it never touches/run/secrets.d.Related (but diff)