Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion git/gitconfig.local.symlink.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@
[user]
name = AUTHORNAME
email = AUTHOREMAIL
signingkey = ~/.ssh/agent.pub
localSigningKey = LOCAL_SIGNING_KEY
[credential]
helper = GIT_CREDENTIAL_HELPER
helper = GIT_CREDENTIAL_HELPER
[gpg]
format = ssh
[gpg "ssh"]
allowedSignersFile = ~/.dotfiles/git/allowed_signers
[commit]
gpgsign = true
21 changes: 17 additions & 4 deletions macos/LaunchAgents/com.haacked.secretive-ssh-auth.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@
<dict>
<key>Label</key>
<string>com.haacked.secretive-ssh-auth</string>
<!--
Points the session-wide SSH_AUTH_SOCK default at the stable agent
symlink (see zsh/zshrc.symlink) rather than Secretive's socket
directly, so processes that never source .zshrc (GUI apps, tools
that spawn non-interactive shells) still pick up a forwarded agent
when one is live. Seeds the symlink to the local Secretive socket
first in case this runs before any shell has initialized it. Also
seeds agent.pub from user.localSigningKey so GUI apps that sign a
commit before any interactive shell has run don't find it missing.
Bare `git` resolves fine here: launchd runs jobs with PATH=/usr/bin:
/bin:/usr/sbin:/sbin, and /usr/bin/git (the Xcode CLT shim, a
Homebrew prerequisite already assumed elsewhere in this repo) is on
that PATH.
-->
<key>ProgramArguments</key>
<array>
<string>/bin/launchctl</string>
<string>setenv</string>
<string>SSH_AUTH_SOCK</string>
<string>/Users/haacked/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh</string>
<string>/bin/sh</string>
<string>-c</string>
<string>mkdir -p "$HOME/.ssh" &amp;&amp; ln -sf "$HOME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh" "$HOME/.ssh/agent.sock" &amp;&amp; /bin/launchctl setenv SSH_AUTH_SOCK "$HOME/.ssh/agent.sock"; LOCAL_SIGNING_KEY=$(git config --global --get user.localSigningKey 2>/dev/null); [ -n "$LOCAL_SIGNING_KEY" ] &amp;&amp; [ -f "$LOCAL_SIGNING_KEY" ] &amp;&amp; cat "$LOCAL_SIGNING_KEY" &gt; "$HOME/.ssh/agent.pub"</string>
Comment thread
haacked marked this conversation as resolved.
</array>
<key>RunAtLoad</key>
<true/>
Expand Down
25 changes: 24 additions & 1 deletion script/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,30 @@ setup_gitconfig () {
user ' - What is your github author email?'
read -e git_authoremail

sed -e "s/AUTHORNAME/$git_authorname/g" -e "s/AUTHOREMAIL/$git_authoremail/g" -e "s/GIT_CREDENTIAL_HELPER/$git_credential/g" git/gitconfig.local.symlink.template > git/gitconfig.local.symlink
local_signing_key=$(find "$HOME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/PublicKeys" -name '*.pub' 2>/dev/null | sort | head -1)
if [ -z "$local_signing_key" ]
then
local_signing_key='TODO: create a key in Secretive, then set this to its .pub path'
user ' - No Secretive key found yet; localSigningKey left as a placeholder in gitconfig.local. Commit signing is on (commit.gpgsign=true), so git commit will fail until you create a key in Secretive and fill in user.localSigningKey (or set commit.gpgsign=false there temporarily).'
fi

# Backslash and & are special in sed replacement text, and each value
# below also needs its substitution's own delimiter escaped so a literal
# delimiter character can't terminate the command early. Note: inside a
# bracket expression like [\&/], backslash has no special meaning per
# POSIX (it's just another literal member of the set), so listing it
# alongside the other characters is sufficient; it does not need its own
# separate escape. The name/email/credential substitutions use / as their
# delimiter, so escape / too (via a |-delimited sed command, since / is
# also the character being matched here). The signing-key substitution
# uses | as its delimiter (the key path may itself contain /), so escape
# any literal | in that value instead.
escaped_authorname=$(printf '%s' "$git_authorname" | sed -e 's|[\&/]|\\&|g')
escaped_authoremail=$(printf '%s' "$git_authoremail" | sed -e 's|[\&/]|\\&|g')
escaped_credential=$(printf '%s' "$git_credential" | sed -e 's|[\&/]|\\&|g')
escaped_signing_key=$(printf '%s' "$local_signing_key" | sed -e 's/[\&|]/\\&/g')

sed -e "s/AUTHORNAME/$escaped_authorname/g" -e "s/AUTHOREMAIL/$escaped_authoremail/g" -e "s/GIT_CREDENTIAL_HELPER/$escaped_credential/g" -e "s|LOCAL_SIGNING_KEY|$escaped_signing_key|g" git/gitconfig.local.symlink.template > git/gitconfig.local.symlink

success 'gitconfig'
fi
Expand Down
31 changes: 28 additions & 3 deletions zsh/zshrc.symlink
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,39 @@ fi
# the local Secretive agent. Because it's an indirection, a Claude Code
# session started hours ago picks up whichever agent is current the next
# time it signs something, without needing to restart.
#
# git commit -S needs the exact public key to ask the agent for, so
# user.signingkey (~/.gitconfig.local) points at agent.pub, a matching
# indirection kept in sync alongside the socket. user.localSigningKey names
# this machine's own key and is the fallback source; machines that haven't
# set it just keep whatever signingkey they had before this existed.
SSH_AGENT_SOCK="$HOME/.ssh/agent.sock"
SSH_AGENT_PUB="$HOME/.ssh/agent.pub"
SECRETIVE_SOCK="$HOME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh"
mkdir -p "$HOME/.ssh"

# The LaunchAgent (macos/LaunchAgents/com.haacked.secretive-ssh-auth.plist)
# pre-seeds agent.sock to the local Secretive socket at login, so by the time
# a shell starts it's usually already a valid socket. Treat the symlink as
# "local" (safe to re-seed agent.pub) whenever it's missing/broken or still
# pointing at Secretive, and only skip it when another pane already redirected
# it to a live forwarded agent.
LOCAL_AGENT_LINKED=false
if [ ! -S "$SSH_AGENT_SOCK" ] || [ "$(readlink "$SSH_AGENT_SOCK")" = "$SECRETIVE_SOCK" ]; then
LOCAL_AGENT_LINKED=true
fi

if [ -n "$SSH_CONNECTION" ] && [ -S "$SSH_AUTH_SOCK" ]; then
ln -sf "$SSH_AUTH_SOCK" "$SSH_AGENT_SOCK"
elif [ ! -S "$SSH_AGENT_SOCK" ] && [ -S "$HOME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh" ]; then
ln -sf "$HOME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh" "$SSH_AGENT_SOCK"
FORWARDED_KEY=$(SSH_AUTH_SOCK="$SSH_AGENT_SOCK" ssh-add -L 2>/dev/null | head -1)
[ -n "$FORWARDED_KEY" ] && echo "$FORWARDED_KEY" > "$SSH_AGENT_PUB"
elif [ "$LOCAL_AGENT_LINKED" = true ] && [ -S "$SECRETIVE_SOCK" ]; then
ln -sf "$SECRETIVE_SOCK" "$SSH_AGENT_SOCK"
LOCAL_SIGNING_KEY=$(git config --global --get user.localSigningKey 2>/dev/null)
[ -n "$LOCAL_SIGNING_KEY" ] && [ -f "$LOCAL_SIGNING_KEY" ] && cat "$LOCAL_SIGNING_KEY" > "$SSH_AGENT_PUB"
fi
[ -S "$SSH_AGENT_SOCK" ] && export SSH_AUTH_SOCK="$SSH_AGENT_SOCK"
unset SSH_AGENT_SOCK
unset SSH_AGENT_SOCK SSH_AGENT_PUB SECRETIVE_SOCK LOCAL_AGENT_LINKED LOCAL_SIGNING_KEY FORWARDED_KEY

# --- Secrets (never committed) ---
if [ -f "$HOME/.secrets" ]; then
Expand Down