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
97 lines
3.7 KiB
Markdown
97 lines
3.7 KiB
Markdown
# 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:
|
|
|
|
```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:
|
|
|
|
```gitconfig
|
|
[includeIf "gitdir:~/work/"]
|
|
path = ~/.config/git/config.work
|
|
```
|