From 85118f187ebdaea64edc47f3932d6b194d9249e4 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Tue, 9 Jun 2026 04:59:35 -0700 Subject: [PATCH] nixos/home-manager: materialize ~/.ssh/config as user-owned copy /nix/store is owned by nobody:nogroup on this host, so the home-manager-generated ~/.ssh/config symlink resolves to a nobody-owned store file and OpenSSH rejects it with "Bad owner or permissions", breaking ssh and git-over-ssh. Add a post-writeBoundary activation step that replaces the symlink with a real 0600 copy owned by the user. Co-Authored-By: Claude Opus 4.8 --- nixos/home-manager.nix | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nixos/home-manager.nix b/nixos/home-manager.nix index 750eee20..6aa1a757 100644 --- a/nixos/home-manager.nix +++ b/nixos/home-manager.nix @@ -328,6 +328,24 @@ in { home.file.".ssh/config".force = true; + # OpenSSH only reads ~/.ssh/config when it is owned by root or the invoking + # user. On this host /nix/store is owned by nobody:nogroup, so the + # home-manager-generated symlink resolves to a nobody-owned store file and + # OpenSSH bails with "Bad owner or permissions on ~/.ssh/config", breaking + # ssh and git-over-ssh. After writeBoundary the symlink has been linked into + # place; replace it with a real 0600 copy owned by the user so the ownership + # check passes. home.file."...".force re-links the symlink on each switch, + # then this re-materializes it. + home.activation.materializeSshConfig = lib.hm.dag.entryAfter ["writeBoundary"] '' + ssh_config="$HOME/.ssh/config" + if [ -L "$ssh_config" ]; then + resolved="$(readlink -f "$ssh_config")" + tmp="$ssh_config.hm-real" + install -m600 "$resolved" "$tmp" + mv -f "$tmp" "$ssh_config" + fi + ''; + services.gpg-agent = { enable = true; defaultCacheTtl = 8 * 60 * 60;