dotfiles/docs/config/git.md
Felipe M. 54965e17aa
Add Laugh-Tale and fix three faults that blocked any macOS host
Registering the first Mac surfaced problems that would have hit every
future one, not just this machine:

metapac keys off a raw gethostname(), which on macOS carries the mDNS
".local" suffix, while the registry is keyed by chezmoi's
.chezmoi.hostname, which stops at the first dot. A bare key never
matched and metapac refused to run at all. config.toml.tmpl now emits
each host twice, bare and ".local", so nothing in the registry has to
declare which machines are Macs; the spare key is inert elsewhere.

chezmoi was rewriting ~/Library from 0700 to 0755 on every apply,
loosening a directory macOS deliberately closes. Both levels now carry
the private_ prefix.

dot_gitconfig.tmpl would have destroyed real config on adoption: the
git-lfs filters, seven aliases, diff.noprefix, rerere and the gh
credential helper. All folded in. The lfs block and the GitHub helper
are lookPath-gated like the signing block -- lfs filters run on every
checkout and abort wholesale when the binary is missing, so asserting
them unconditionally breaks a minimal box rather than degrading. gh's
path comes from lookPath, not a hardcoded /opt/homebrew/bin/gh.

Laugh-Tale takes common + a new darwin group, the macOS counterpart to
archlinux.toml, holding only the tooling a backend needs. Its baseline
captures 325 packages, 24 taps and 21 App Store apps -- without it
`metapac clean` would offer to remove every one of them.

Verified on Laugh-Tale: chezmoi status clean after apply, ~/Library
still 0700, `metapac unmanaged` reports nothing, `metapac sync` is a
no-op, and the status line renders correctly under macOS bash 3.2.

Shared groups are still arch-only, so this host draws nothing from
common yet; that gap is documented, not closed.

Claude-Session: https://claude.ai/code/session_01BvpSVPsLrUK4N53LVTofR8
2026-08-25 23:08:01 +02:00

3.7 KiB

Git

Signing uses SSH, not GPG

1Password has no GPG agent and ships only op-ssh-sign, so SSH signing is the only thing it can actually do. Git has supported it since 2.34, and git.nakama.town (Forgejo 14) verifies it, as do GitHub, GitLab and Bitbucket. The private key never leaves 1Password.

Storing a GPG key in 1Password as a Document is possible, but 1Password cannot serve it to gpg — the key would have to be imported into ~/.gnupg on every machine, which is exactly what this avoids.

Only the public half is committed, in .chezmoi.toml.tmpl under [data.user]. It feeds both the gitconfig and allowed_signers, so rotating the key is a one-line change.

The signing block is capability-gated

dot_gitconfig.tmpl emits it only where the signer binary exists — /opt/1Password/op-ssh-sign on Linux, /Applications/1Password.app/Contents/ MacOS/op-ssh-sign on macOS — checked with stat at apply time. A machine with only the 1Password CLI gets a working git with signing off, rather than one that fails on every commit. No per-host declaration to keep in sync.

allowed_signers

~/.config/git/allowed_signers is managed so git log --show-signature can verify our own commits locally. Without it signing works but every signature reads as an unknown key.

Registering the key with a forge

Adding the key to Forgejo or GitHub requires signing a challenge token with it. The obvious command from their UI assumes a private key on disk, which does not exist here; the key has to come from the agent instead. In fish:

set -l sock $HOME/.1password/agent.sock
env SSH_AUTH_SOCK=$sock ssh-add -L | grep 'Git Signing Key' > /tmp/sig.pub
echo -n 'TOKEN' | env SSH_AUTH_SOCK=$sock ssh-keygen -Y sign -n git.nakama.town -f /tmp/sig.pub

env is required because fish has no VAR=value cmd form, and SSH_AUTH_SOCK is not set anywhere. The grep matters — the agent holds more than one key, and an auth key will fail to sign. Change -n to match the host asking.

The key must be added as a signing key, separately from any auth key.

Credentials

git-credential-op answers credential queries from 1Password at auth time, so no token is ever written to disk. It is scoped per host via credential.<url>.helper, and reads whatever GIT_CREDENTIAL_OP_REF points at — the script itself never sees a URL.

Other capability gates

Two more blocks follow the same rule as the signing block — emitted only where the binary exists, checked with lookPath at apply time.

filter "lfs" is the dangerous one. Those filters are declared per-repository work that git runs on every checkout, so asserting them on a machine without git-lfs breaks checkouts wholesale rather than degrading. Gating it means the same gitconfig is safe on a minimal remote box.

The GitHub credential helper is emitted with gh's absolute path from lookPath, not a hardcoded /opt/homebrew/bin/gh, so the same template serves Homebrew on Apple silicon, Homebrew on Intel and a distro package. The empty helper = line before it is deliberate: it resets any helper inherited from the system config, so the stack cannot silently fall back to a stale keychain entry.

Aliases

br branch
cane commit --amend --no-edit
co checkout
d diff
fp fetch all remotes with prune, then pull --ff-only
last log -1 HEAD
lo log --oneline -n 10
pr pull --rebase
s status

fp refuses to merge or rebase a diverged branch, matching dotfiles update.

Work identity

When the work laptop arrives, its identity belongs in git's own conditional include, not a new registry field:

[includeIf "gitdir:~/work/"]
	path = ~/.config/git/config.work