Created
July 2, 2026 10:53
-
-
Save miohtama/b0c116af1e6c12af7b843c1549a3070e to your computer and use it in GitHub Desktop.
See this gist for usage instructions https://gist.github.com/miohtama/fb62300611a8ea29403277c71ebc772d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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