Skip to content

Instantly share code, notes, and snippets.

@miohtama
Created July 2, 2026 10:53
Show Gist options
  • Select an option

  • Save miohtama/b0c116af1e6c12af7b843c1549a3070e to your computer and use it in GitHub Desktop.

Select an option

Save miohtama/b0c116af1e6c12af7b843c1549a3070e to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# switch-to-homebrew-ssh-agent.sh
#
# Switches macOS to use the Homebrew OpenSSH ssh-agent (which supports
# FIDO2/sk keys like ed25519-sk) instead of Apple's bundled agent.
#
# - Installs a launchd user agent running the Homebrew ssh-agent on a
# fixed socket at ~/.ssh/agent.sock
# - Disables Apple's com.openssh.ssh-agent for your user
# - Appends SSH_AUTH_SOCK export to your shell rc (with your consent)
#
# Usage: ./switch-to-homebrew-ssh-agent.sh
# Undo: ./switch-to-homebrew-ssh-agent.sh --undo
#
set -euo pipefail
LABEL="homebrew.ssh-agent"
PLIST="$HOME/Library/LaunchAgents/${LABEL}.plist"
SOCK="$HOME/.ssh/agent.sock"
GUI_TARGET="gui/$(id -u)"
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m ✓ \033[0m %s\n' "$*"; }
err() { printf '\033[1;31m ✗ \033[0m %s\n' "$*" >&2; }
# --- undo mode --------------------------------------------------------------
if [[ "${1:-}" == "--undo" ]]; then
info "Reverting to Apple's ssh-agent..."
launchctl bootout "$GUI_TARGET/$LABEL" 2>/dev/null || true
rm -f "$PLIST" "$SOCK"
launchctl enable "$GUI_TARGET/com.openssh.ssh-agent" || true
ok "Homebrew agent service removed and Apple agent re-enabled."
echo "Remove the SSH_AUTH_SOCK export from your shell rc manually,"
echo "then log out and back in."
exit 0
fi
# --- preflight ---------------------------------------------------------------
if [[ "$(uname)" != "Darwin" ]]; then
err "This script is for macOS only."
exit 1
fi
# Locate Homebrew ssh-agent (Apple Silicon vs Intel prefixes)
if command -v brew >/dev/null 2>&1; then
BREW_PREFIX="$(brew --prefix)"
elif [[ -x /opt/homebrew/bin/brew ]]; then
BREW_PREFIX="/opt/homebrew"
elif [[ -x /usr/local/bin/brew ]]; then
BREW_PREFIX="/usr/local"
else
err "Homebrew not found. Install it first: https://brew.sh"
exit 1
fi
AGENT_BIN="$BREW_PREFIX/bin/ssh-agent"
if [[ ! -x "$AGENT_BIN" ]]; then
err "Homebrew OpenSSH not found at $AGENT_BIN"
echo "Install it with: brew install openssh"
exit 1
fi
ok "Found Homebrew ssh-agent: $AGENT_BIN ($("$AGENT_BIN" -h 2>&1 | head -1 || true))"
mkdir -p "$HOME/.ssh" "$HOME/Library/LaunchAgents"
chmod 700 "$HOME/.ssh"
# --- stop any previous instance of our service -------------------------------
if launchctl print "$GUI_TARGET/$LABEL" >/dev/null 2>&1; then
info "Stopping existing $LABEL service..."
launchctl bootout "$GUI_TARGET/$LABEL" || true
fi
rm -f "$SOCK" # stale socket prevents the agent from binding
# --- write the launchd plist --------------------------------------------------
# NOTE: launchd does not expand ~, so absolute paths are required.
info "Writing $PLIST"
cat > "$PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>${AGENT_BIN}</string>
<string>-D</string>
<string>-a</string>
<string>${SOCK}</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF
ok "Plist written."
# --- load the service ----------------------------------------------------------
info "Loading service..."
launchctl bootstrap "$GUI_TARGET" "$PLIST"
# Wait for the socket to appear (up to ~5s)
for _ in $(seq 1 25); do
[[ -S "$SOCK" ]] && break
sleep 0.2
done
if [[ ! -S "$SOCK" ]]; then
err "Agent did not create $SOCK."
echo "Debug it manually with:"
echo " $AGENT_BIN -d -a $SOCK"
exit 1
fi
ok "Agent is running on $SOCK"
# --- sanity check the agent responds -----------------------------------------
if SSH_AUTH_SOCK="$SOCK" "$BREW_PREFIX/bin/ssh-add" -l >/dev/null 2>&1 \
|| [[ $? -eq 1 ]]; then
# exit 1 = "agent has no identities" which is fine
ok "Agent responds to ssh-add."
else
err "Agent socket exists but is not responding."
exit 1
fi
# --- disable Apple's agent -----------------------------------------------------
info "Disabling Apple's bundled ssh-agent for your user..."
launchctl disable "$GUI_TARGET/com.openssh.ssh-agent" || true
ok "Disabled (takes full effect after logout/login)."
# --- shell rc ------------------------------------------------------------------
EXPORT_LINE="export SSH_AUTH_SOCK=\"\$HOME/.ssh/agent.sock\""
case "${SHELL##*/}" in
zsh) RC="$HOME/.zshrc" ;;
bash) RC="$HOME/.bashrc" ;;
*) RC="" ;;
esac
if [[ -n "$RC" ]]; then
if grep -qF "$EXPORT_LINE" "$RC" 2>/dev/null; then
ok "SSH_AUTH_SOCK export already present in $RC"
else
printf '\n'
read -r -p "Append SSH_AUTH_SOCK export to $RC? [y/N] " reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
{
echo ""
echo "# Use Homebrew ssh-agent (supports FIDO2/sk keys)"
echo "$EXPORT_LINE"
} >> "$RC"
ok "Appended to $RC"
else
echo "Skipped. Add this line yourself:"
echo " $EXPORT_LINE"
fi
fi
else
echo "Unknown shell; add this to your shell rc manually:"
echo " $EXPORT_LINE"
fi
# --- done ----------------------------------------------------------------------
printf '\n'
info "Done. Next steps:"
cat <<EOF
1. Open a new terminal (or: export SSH_AUTH_SOCK="\$HOME/.ssh/agent.sock")
2. Add your key: ssh-add ~/.ssh/yubikey-new
3. Verify: ssh-add -l
4. Log out and back in so GUI apps stop seeing Apple's agent.
For per-host use without changing your environment, you can instead set
in ~/.ssh/config:
IdentityAgent ~/.ssh/agent.sock
To revert everything: $0 --undo
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment