diff --git a/bin/.local/bin/ssh-agent-doctor b/bin/.local/bin/ssh-agent-doctor new file mode 100755 index 0000000..d8342af --- /dev/null +++ b/bin/.local/bin/ssh-agent-doctor @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +ssh-agent-doctor: diagnose and select the best SSH_AUTH_SOCK. + +Usage: + ssh-agent-doctor # human-readable report + ssh-agent-doctor --export # print: export SSH_AUTH_SOCK='...' + ssh-agent-doctor --json # minimal JSON-ish output (no jq required) + +Exit codes: + 0 OK (agent reachable) + 1 Socket found but agent has no identities (reachable) + 2 No usable socket found OR cannot connect to agent +EOF +} + +mode="report" +case "${1:-}" in + --export) mode="export" ;; + --json) mode="json" ;; + -h|--help) usage; exit 0 ;; + "") ;; + *) echo "Unknown arg: $1" >&2; usage; exit 2 ;; +esac + +os="$(uname -s 2>/dev/null || echo unknown)" +uid="$(id -u)" +user="$(id -un 2>/dev/null || echo unknown)" +home="${HOME:-/}" + +linux_sock="/run/user/${uid}/ssh-agent.socket" +darwin_sock="${home}/.ssh/agent.sock" +env_sock="${SSH_AUTH_SOCK:-}" + +is_sock() { [[ -n "${1:-}" && -S "$1" ]]; } + +pick_sock="" +pick_reason="" + +if is_sock "$linux_sock"; then + pick_sock="$linux_sock" + pick_reason="linux_systemd_default" +elif is_sock "$darwin_sock"; then + pick_sock="$darwin_sock" + pick_reason="darwin_launchd_default" +elif is_sock "$env_sock"; then + pick_sock="$env_sock" + pick_reason="existing_env" +fi + +check_agent() { + local sock="$1" + SSH_AUTH_SOCK="$sock" ssh-add -l >/dev/null 2>&1 + return $? +} + +if [[ "$mode" == "export" ]]; then + if [[ -z "$pick_sock" ]]; then + echo "echo 'ERROR: No ssh-agent socket found.' >&2" + echo "return 2 2>/dev/null || exit 2" + exit 0 + fi + printf "export SSH_AUTH_SOCK='%s'\n" "$pick_sock" + exit 0 +fi + +if [[ "$mode" == "json" ]]; then + if [[ -z "$pick_sock" ]]; then + cat <}" "$(is_sock "$env_sock" && echo '[socket]' || echo '[not socket]')" +echo + +if [[ -z "$pick_sock" ]]; then + echo "Result: ❌ No usable SSH agent socket found." + echo "Hint: start your agent (systemd user on Linux, launchd agent on macOS) and re-run." + exit 2 +fi + +echo "Picked:" +echo " SSH_AUTH_SOCK=$pick_sock" +echo " reason=$pick_reason" +echo + +rc=0 +check_agent "$pick_sock" || rc=$? + +if [[ $rc -eq 0 ]]; then + echo "Agent check: ✅ reachable, has identities" + SSH_AUTH_SOCK="$pick_sock" ssh-add -l || true + exit 0 +elif [[ $rc -eq 1 ]]; then + echo "Agent check: ✅ reachable, but has no identities loaded" + echo "Next: run your pass-based loader (load_keys)." + exit 1 +else + echo "Agent check: ❌ cannot connect to agent via that socket" + echo "This usually means the socket is stale or the agent died." + exit 2 +fi diff --git a/bin/.local/bin/ssh-agent-env b/bin/.local/bin/ssh-agent-env new file mode 100755 index 0000000..fc0c4cd --- /dev/null +++ b/bin/.local/bin/ssh-agent-env @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +uid="$(id -u)" + +linux_sock="/run/user/${uid}/ssh-agent.socket" +darwin_sock="${HOME}/.ssh/agent.sock" + +pick_sock="" + +# Prefer Linux systemd socket if present +if [[ -S "$linux_sock" ]]; then + pick_sock="$linux_sock" +elif [[ -S "$darwin_sock" ]]; then + pick_sock="$darwin_sock" +else + # Fall back to existing env if it's a socket + if [[ -n "${SSH_AUTH_SOCK:-}" && -S "${SSH_AUTH_SOCK}" ]]; then + pick_sock="$SSH_AUTH_SOCK" + fi +fi + +if [[ -z "$pick_sock" ]]; then + echo "echo 'ERROR: No ssh-agent socket found.' >&2" + echo "echo 'Tried: $linux_sock and $darwin_sock' >&2" + echo "return 2 2>/dev/null || exit 2" + exit 0 +fi + +cat <")))) (let ((proc (make-process :name "ssh-key-injector" :buffer buf @@ -483,16 +478,15 @@ Always open the result in `eww`." :noquery t))) (while (process-live-p proc) (accept-process-output proc 0.05)) - (let ((exit (process-exit-status proc))) - (unless (eq exit 0) - (display-buffer buf) - (user-error "SSH key injection failed (exit %d). See *ssh-key-injector*." exit)))))) + (unless (eq (process-exit-status proc) 0) + (display-buffer buf) + (user-error "SSH key injection failed. See *ssh-key-injector*."))))) (defun my/ensure-ssh-keys-loaded (&rest _ignore) (pcase (my/ssh-add-status) - (0 nil) ;; already has identities - (1 (my/run-ssh-key-injector)) ;; agent reachable but empty - (2 (user-error "No reachable ssh-agent. SSH_AUTH_SOCK=%s" + (0 nil) + (1 (my/run-ssh-key-injector)) + (2 (user-error "No reachable ssh-agent (SSH_AUTH_SOCK=%s)" (or (getenv "SSH_AUTH_SOCK") ""))) (_ (my/run-ssh-key-injector)))) diff --git a/launchd/Library/LaunchAgents/com.powellc.ssh-agent.service b/launchd/Library/LaunchAgents/com.powellc.ssh-agent.service new file mode 100644 index 0000000..3567287 --- /dev/null +++ b/launchd/Library/LaunchAgents/com.powellc.ssh-agent.service @@ -0,0 +1,25 @@ + + + + + + Label + com.user.ssh-agent + + ProgramArguments + + /usr/bin/ssh-agent + -D + -a + $HOME/.ssh/agent.sock + + + RunAtLoad + + + KeepAlive + + + + diff --git a/zsh/.zshrc b/zsh/.zshrc index 9bdc080..6a1aa99 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -7,6 +7,8 @@ export ZSH="$HOME/.oh-my-zsh" ZSH_THEME="robbyrussell" plugins=(git z fzf asdf direnv emacs yarn aws) + +eval "$($HOME/.local/bin/ssh-agent-env)" load_keys &>/dev/null source $ZSH/oh-my-zsh.sh