diff --git a/Makefile b/Makefile index 335101b..d13605d 100644 --- a/Makefile +++ b/Makefile @@ -334,6 +334,17 @@ git: ## set up this machine: hook in .gitconfig, set email, set up commit signin || git config --global --add include.path '$(CURDIR)/.gitconfig' @read -p "email [$$(git config --file '$(CURDIR)/.gitconfig' user.email)]: " e; \ [ -z "$$e" ] || git config --global user.email "$$e" + @# Signing goes through the *agent*, not the key file: with a .pub as + @# user.signingkey, `ssh-keygen -Y sign` asks ssh-agent for the private + @# half, and only falls back to reading the file — and prompting — when the + @# agent has not got it. macOS starts every login with an empty agent, so + @# --apple-use-keychain puts the passphrase in the login Keychain here and + @# os/macos/.zshrc loads it back from there. Idempotent, and silent once + @# stored. Darwin only: the flag is Apple's, and the Linux boxes unlock + @# their agent through the desktop keyring instead. Not fatal when it + @# fails — a Ctrl-C at the passphrase prompt leaves the rest of the signing + @# setup worth writing — but it says so, because the symptom otherwise + @# arrives a reboot later and looks like the config never worked. @email="$$(git config user.email)"; \ case "$$email" in ""|*"*"*) echo "set a real user.email first - the repo default is a placeholder"; exit 1;; esac; \ default="$$HOME/.ssh/landsman_git_signing.pub"; \ @@ -344,6 +355,8 @@ git: ## set up this machine: hook in .gitconfig, set email, set up commit signin echo "restore it from 1Password, chmod 600 it, then run make git again"; \ exit 1; \ fi; \ + if [ "$$(uname -s)" = Darwin ]; then ssh-add --apple-use-keychain "$$priv" \ + || echo "passphrase not stored - commits will keep prompting, rerun make git"; fi; \ signers="$$HOME/.ssh/allowed_signers"; \ git config --global user.signingkey "$$k"; \ git config --global gpg.ssh.allowedSignersFile "$$signers"; \ diff --git a/README.md b/README.md index 9ff87c1..21da5a0 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,14 @@ Docker Hub and says so. - **`.gitconfig` is not stowed** — it is included into `~/.gitconfig` instead, so machine-specific values (email, signing key) stay out of the repo. A symlink would make `git config --global ...` write here. +- **Commit signing needs the agent, not the key file** — `gpg.format = ssh` signs + through `ssh-keygen -Y sign`, which asks ssh-agent for the private half of + `user.signingkey` and only reads the file (and prompts) when the agent has not + got it. So on macOS `make git` stores the passphrase in the login Keychain with + `ssh-add --apple-use-keychain`, and `os/macos/.zshrc` loads it back with + `--apple-load-keychain`, because every login starts with an empty agent. Drop + either and the first commit after a reboot asks for a passphrase — usually from + the IDE, which has nowhere to ask. - **`.bashrc` is not stowed** — it is a fragment, so symlinking it over `~/.bashrc` would drop everything the distro puts there. `make shell` appends a `. /.bashrc` line. (It used to `cat` the fragment in, which meant later diff --git a/os/macos/.zshrc b/os/macos/.zshrc index 0c625a7..488dded 100644 --- a/os/macos/.zshrc +++ b/os/macos/.zshrc @@ -60,4 +60,13 @@ for f in ~/.config/bash_aliases.d/*.sh(N); do done unset f +# ssh-agent. macOS launches one per login session and it comes up empty — the +# passphrases are in the login Keychain, but nothing has loaded them from there +# since Monterey dropped that half. Nothing notices until the first commit of +# the day: signing is SSH (`make git`), `ssh-keygen -Y sign` asks the agent for +# the key, and an empty agent sends it back to the key file to prompt — from a +# GUI git client, with nowhere to type. Guarded on the agent already holding +# keys, so the usual shell pays one fork and not a Keychain round trip. +ssh-add -l >/dev/null 2>&1 || ssh-add --apple-load-keychain 2>/dev/null + eval "$(mise activate zsh)"