From bd8882708b42270672adf39a1e76a44fbb51e255 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Wed, 12 Aug 2026 12:19:51 +0200 Subject: [PATCH 1/2] devops: load the signing key from the Keychain, an empty agent prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gpg.format = ssh` signs through `ssh-keygen -Y sign`, which asks ssh-agent for the private half of user.signingkey and only falls back to reading the key file — and prompting — when the agent has not got it. macOS starts every login with an empty agent, so that fallback is the normal case: the first commit after a reboot asks for a passphrase, usually from the IDE, which has nowhere to ask. `make git` now stores the passphrase with --apple-use-keychain, and the macOS rc loads it back with --apple-load-keychain, guarded on the agent already holding keys. --- Makefile | 9 +++++++++ README.md | 8 ++++++++ os/macos/.zshrc | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/Makefile b/Makefile index 335101b..003862a 100644 --- a/Makefile +++ b/Makefile @@ -334,6 +334,14 @@ 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. @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 +352,7 @@ 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"; 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)" From 7ab1eba26710978f197ff7f59de676fffd6b4ec8 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Wed, 12 Aug 2026 12:36:52 +0200 Subject: [PATCH 2/2] devops: say when the keychain add failed, the symptom is a reboot away MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Ctrl-C at the passphrase prompt left `make git` finishing quietly, and the only sign of it is the next reboot prompting for a passphrase again — which reads as the signing setup never having worked. Still not fatal: the rest of the target is worth writing either way. --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 003862a..d13605d 100644 --- a/Makefile +++ b/Makefile @@ -341,7 +341,10 @@ git: ## set up this machine: hook in .gitconfig, set email, set up commit signin @# --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. + @# 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"; \ @@ -352,7 +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"; 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"; \