From 3a4a0614f8935aa4ce3a68f73a2c67dd3e8a440e Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 12 Aug 2016 00:59:42 -0400 Subject: [PATCH 01/45] Allow specifying socket and user@host - Make script POSIX compliant. - Search `$TMPDIR` for agents as well, per OpenSSH man page. - Accept socket passed in. - Accept `user@host` parameter (ala **AddKeysToAgent**) feature, so can use `sshag user@domain` instead of `ssh user@domain`. --- README.markdown | 37 ++++++--- sshag.sh | 194 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 164 insertions(+), 67 deletions(-) diff --git a/README.markdown b/README.markdown index b868ae5..d900fa6 100644 --- a/README.markdown +++ b/README.markdown @@ -13,9 +13,8 @@ or other profile script. It can also be run as an executable script, and its output stored in the relevant environment variable. -This is particularly useful -when it is desired to configure a non-shell environment, -for example that of a text editor. +This is particularly useful when it is desired +to configure a non-shell environment, for example, that of a text editor. Messages are emitted on standard out; the output will always consist of just the socket location. @@ -24,25 +23,45 @@ the output will always consist of just the socket location. Sourced: +```sh $ ssh alotta@fagina.example.com Enter passphrase for key '/home/austin/.ssh/id_dsa': ^C - $ source sshag.sh - $ sshag + $ . ~/lib/sshag/sshag.sh + $ sshag alotta@fagina.example.com Keys: 2048 0d:db:a1:1a:cc:01:ad:ec:ab:00:d1:ed:eb:ac:1e:00 /home/austin/.ssh/id_dsa (DSA) /tmp/ssh-5ock3tt0m3/agent.6969 - $ ssh alotta@fagina.example.com - ... +``` Invoked: - $ export SSH_AGENT_SOCK=`sshag.sh` +```sh + $ export SSH_AGENT_SOCK=$(sh ~/lib/sshag/sshag.sh) Output should be assigned to the environment variable $SSH_AUTH_SOCK. Keys: 2048 0d:db:a1:1a:cc:01:ad:ec:ab:00:d1:ed:eb:ac:1e:00 /home/austin/.ssh/id_dsa (DSA) +``` Appended to `~/.bashrc`: - source ~/lib/sshag/sshag.sh; sshag &>/dev/null + +```sh + . ~/lib/sshag/sshag.sh; sshag >/dev/null 2>&1 +``` + +## History + +- v0.0.20100514 + - *Zed*: http://superuser.com/a/141241 +- v1.0.20100726 + - *intuited*: renamed from `sagent` to `sshag`. Add readme and license documents. +- v1.1.20110220 + - *intuited*: Made it convenient to run the script in a subshell. +- v1.2.20160825 + - *go2null*: Make script POSIX compliant. + - *go2null*: Search `$TMPDIR` for agents as well, per OpenSSH man page. + - *go2null*: Accept socket passed in. + - *go2null*: Accept `user@host` parameter (ala **AddKeysToAgent**) feature, + so can use `sshag user@domain` instead of `ssh user@domain`. ## Licensing diff --git a/sshag.sh b/sshag.sh index 4dc2c28..d7d8b51 100755 --- a/sshag.sh +++ b/sshag.sh @@ -1,83 +1,161 @@ -#!/bin/bash +#!/bin/sh + # acquired courtesy of # http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 -function sshag_findsockets { - find /tmp -uid $(id -u) -type s -name agent.\* 2>/dev/null +main() { + # If we are not being sourced, but rather running as a subshell, + # let people know how to use the output. + if [ "${0#*sshag}" != "$0" ]; then + sshad_msg='Output should be assigned to the environment variable $SSH_AUTH_SOCK.' + sshag "$@" + fi } -function sshag_testsocket { - if [ ! -x "$(which ssh-add)" ] ; then - echo "ssh-add is not available; agent testing aborted" >&2 - return 1 - fi +sshag() { + # ssh agent sockets can be attached to an ssh daemon process + # or an ssh-agent process. - if [ X"$1" != X ] ; then - export SSH_AUTH_SOCK=$1 - fi + sshag_require_ssh + unset agent_found + unset agent_socket + unset user_hostname + + # check any params + while [ "$1" ]; do + if [ -e "$1" ]; then + [ -S "$1" ] && agent_socket="$1" + else + user_hostname="$1" + fi + shift + done - if [ X"$SSH_AUTH_SOCK" = X ] ; then - return 2 + # Attempt to use socket passed in, or + # find and use the ssh-agent in the current environment + if sshag_vet_socket "$agent_socket"; then + agent_found=1 + else + # If there is no agent in the environment, + # search for possible agents to reuse + # before starting a fresh ssh-agent process. + for agent_socket in $(sshag_get_sockets) ; do + sshag_vet_socket "$agent_socket" && agent_found=1 && break + done fi - if [ -S $SSH_AUTH_SOCK ] ; then - ssh-add -l > /dev/null - if [ $? = 2 ] ; then - echo "Socket $SSH_AUTH_SOCK is dead! Deleting!" >&2 - rm -f $SSH_AUTH_SOCK - return 4 - else - return 0 - fi + # If at this point we still haven't located an agent, it's time to + # start a new one + [ "$agent_found" ] || eval "$(ssh-agent)" + + if [ "$user_hostname" ]; then + sshag_do_ssh "$user_hostname" else - echo "$SSH_AUTH_SOCK is not a socket!" >&2 - return 3 + # Display the found socket + if [ "$sshad_msg" ]; then + print_stderr "$sshad_msg" + unset sshad_msg + fi + + # Display keys currently loaded in the agent + print_stderr "Keys:" + print_stderr "$(ssh-add -l | sed 's/^/ /')" + + print_return "$SSH_AUTH_SOCK" fi + + # Clean up + unset agent_found + unset agent_socket + unset user_hostname } -function sshag_init { - # ssh agent sockets can be attached to a ssh daemon process or an - # ssh-agent process. +# Load first key for specified user@hostname and start `ssh`. +sshag_do_ssh() { + # load identity if one is defined for the user@hostname. + identity="$(ssh -G $1 | awk ' /identityfile/ { print $2 } ' | head -n 1)" + if [ "$identity" ]; then + # leading tilde causes `ssh-add` to fail. + identity="$(expand_tilde "$identity")" - AGENTFOUND=0 + # load identity if needed + if ! ssh-add -L | grep "$(cat ${identity}.pub)" >/dev/null; then + if ! ssh-add "$identity"; then + print_error "Unable to load identity '$identity'!" + fi + fi + fi - # Attempt to find and use the ssh-agent in the current environment - if sshag_testsocket ; then AGENTFOUND=1 ; fi + # start `ssh` + ssh "$1" - # If there is no agent in the environment, search /tmp for - # possible agents to reuse before starting a fresh ssh-agent - # process. - if [ $AGENTFOUND = 0 ] ; then - for agentsocket in $(sshag_findsockets) ; do - if [ $AGENTFOUND != 0 ] ; then break ; fi - if sshag_testsocket $agentsocket ; then AGENTFOUND=1 ; fi - done + # Clean up + unset identity +} + +sshag_get_sockets() { + for dir in '/tmp' "$TMPDIR"; do + find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null + done | sort -u +} + +sshag_vet_socket() { + [ "$1" ] && export SSH_AUTH_SOCK="$1" + + if [ -z "$SSH_AUTH_SOCK" ]; then + return 1 + elif [ -S "$SSH_AUTH_SOCK" ]; then + ssh-add -l >/dev/null 2>&1 + if [ $? -eq 2 ]; then + rm -f "$SSH_AUTH_SOCK" + print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleting!" + fi + else + print_warning "'$SSH_AUTH_SOCK' is not a socket!" fi +} - # If at this point we still haven't located an agent, it's time to - # start a new one - if [ $AGENTFOUND = 0 ] ; then - eval `ssh-agent` +sshag_require_ssh() { + if [ ! -x "$(command -v ssh)" ]; then + exit_error "'ssh' is not available! Aborting!" + elif [ ! -x "$(command -v ssh-add)" ]; then + exit_error "'ssh-add' is not available! Aborting!" + elif [ ! -x "$(command -v ssh-agent)" ]; then + exit_error "'ssh-agent' is not available! Aborting!" fi +} - # Clean up - unset AGENTFOUND - unset agentsocket +# HELPERS + +expand_tilde() { + tilde_less="${1#\~/}" + [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" + printf "$tilde_less" +} + +exit_error() { + printf "ERROR: $@\n" >&2 + exit 1 +} - { echo "Keys:"; ssh-add -l | sed 's/^/ /'; } >&2 +print_error() { + printf "ERROR: $@\n" >&2 + return 1 +} - # Display the found socket - echo $SSH_AUTH_SOCK; +print_return() { + printf "$@\n" } +# Do not send messages to 'stdout' +# - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell +print_stderr() { + printf "$@\n" >&2 +} + +print_warning() { + printf "WARNING: $@\n" >&2 + return 1 +} -# If we are not being sourced, but rather running as a subshell, -# let people know how to use the output. -if [[ $0 =~ sshag ]]; then - echo 'Output should be assigned to the environment variable $SSH_AUTH_SOCK.' >&2 - sshag_init -# Otherwise, make it convenient to invoke the search. -# When the alias is invoked, it will modify the shell environment. -else - alias sshag="sshag_init" -fi +main "$@" From 47ad73f98e8e00c150e292f5a66c381edefb77ea Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 17 Aug 2017 21:38:21 -0400 Subject: [PATCH 02/45] FIX detection of identity file --- sshag.sh | 267 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 153 insertions(+), 114 deletions(-) diff --git a/sshag.sh b/sshag.sh index d7d8b51..8b3f040 100755 --- a/sshag.sh +++ b/sshag.sh @@ -4,158 +4,197 @@ # http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 main() { - # If we are not being sourced, but rather running as a subshell, - # let people know how to use the output. - if [ "${0#*sshag}" != "$0" ]; then - sshad_msg='Output should be assigned to the environment variable $SSH_AUTH_SOCK.' - sshag "$@" - fi + # If we are not being sourced, but rather running as a subshell, + # let people know how to use the output. + if [ "${0#*sshag}" != "$0" ]; then + sshad_msg='Output should be assigned to the environment variable $SSH_AUTH_SOCK.' + sshag "$@" + fi } sshag() { - # ssh agent sockets can be attached to an ssh daemon process - # or an ssh-agent process. - - sshag_require_ssh - unset agent_found - unset agent_socket - unset user_hostname - - # check any params - while [ "$1" ]; do - if [ -e "$1" ]; then - [ -S "$1" ] && agent_socket="$1" - else - user_hostname="$1" - fi - shift - done - - # Attempt to use socket passed in, or - # find and use the ssh-agent in the current environment - if sshag_vet_socket "$agent_socket"; then - agent_found=1 - else - # If there is no agent in the environment, - # search for possible agents to reuse - # before starting a fresh ssh-agent process. - for agent_socket in $(sshag_get_sockets) ; do - sshag_vet_socket "$agent_socket" && agent_found=1 && break - done - fi - - # If at this point we still haven't located an agent, it's time to - # start a new one - [ "$agent_found" ] || eval "$(ssh-agent)" - - if [ "$user_hostname" ]; then - sshag_do_ssh "$user_hostname" - else - # Display the found socket - if [ "$sshad_msg" ]; then - print_stderr "$sshad_msg" - unset sshad_msg - fi - - # Display keys currently loaded in the agent - print_stderr "Keys:" - print_stderr "$(ssh-add -l | sed 's/^/ /')" - - print_return "$SSH_AUTH_SOCK" - fi - - # Clean up - unset agent_found - unset agent_socket - unset user_hostname + # ssh agent sockets can be attached to an ssh daemon process + # or an ssh-agent process. + + sshag_require_ssh + unset agent_found + unset agent_socket + unset user_hostname + + # check any params + while [ -n "$1" ]; do + if [ -e "$1" ] ; then + [ -S "$1" ] && agent_socket="$1" + else + user_hostname="$1" + fi + shift + done + + # Attempt to use socket passed in, or + # find and use the ssh-agent in the current environment + if sshag_vet_socket "$agent_socket"; then + agent_found=1 + else + # If there is no agent in the environment, + # search for possible agents to reuse + # before starting a fresh ssh-agent process. + for agent_socket in $(sshag_get_sockets) ; do + sshag_vet_socket "$agent_socket" && agent_found=1 && break + done + fi + + # If at this point we still haven't located an agent, + # then it's time to start a new one + [ "$agent_found" ] || eval "$(ssh-agent)" + + if [ "$user_hostname" ]; then + sshag_do_ssh "$user_hostname" + else + # Display the found socket + if [ "$sshad_msg" ]; then + print_stderr "$sshad_msg" + unset sshad_msg + fi + + # Display keys currently loaded in the agent + print_stderr "Keys:" + print_stderr "$(ssh-add -l | sed 's/^/ /')" + + print_return "$SSH_AUTH_SOCK" + fi + + # Clean up + unset agent_found + unset agent_socket + unset user_hostname } # Load first key for specified user@hostname and start `ssh`. sshag_do_ssh() { - # load identity if one is defined for the user@hostname. - identity="$(ssh -G $1 | awk ' /identityfile/ { print $2 } ' | head -n 1)" - if [ "$identity" ]; then - # leading tilde causes `ssh-add` to fail. - identity="$(expand_tilde "$identity")" - - # load identity if needed - if ! ssh-add -L | grep "$(cat ${identity}.pub)" >/dev/null; then - if ! ssh-add "$identity"; then - print_error "Unable to load identity '$identity'!" - fi - fi - fi - - # start `ssh` - ssh "$1" - - # Clean up - unset identity + # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent + sshag_add_key_to_agent "$1" + + # start `ssh` + ssh "$1" +} + +sshag_add_key_to_agent() { + # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent + # Or if the local ssh client support AddKeysToAgent, + # but it is not set in the ~/.ssh/config + + # OpenSSH v7.2 added support for AddKeysToAgent. + # Honor it if it is used in ssh_config. + # Otherwise, attempt to load identityfile as user may use a common ssh_config + # on multiple machines where only some support AddKeysToAgent. + # (OpenSSH before v7.2 barfs on params it doesn't know about so can't use + # it in a common ssh_config where some machines have pre v7.2 OpenSSH.) + + # Honor AddKeysToAgent settings + if grep '^[[:blank:]]*AddKeysToAgent' \ + "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null + then + return + fi + + # check if identity is already loaded + if ! sshag_is_identity_loaded "$1"; then + + # load identity if one is defined for the user@hostname. + identity="$(sshag_get_identity "$1")" + if [ -n "$identity" ] && ! ssh-add "$identity"; then + print_error "Unable to load identity '$identity'!" + fi + fi + + # Clean up + unset identity +} + +sshag_is_identity_loaded() { + echo 'exit' | ssh -o BatchMode=yes $1 2>/dev/null + [ $? -eq 0 ] && return 0 || return 1 +} + +sshag_get_identity() { + #identity="$(ssh -G $1 | awk ' /identityfile/ { print $2 } ' | head -n 1)" + identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ + | awk ' /identity file/ { print $4 } ' \ + | head -n 1)" + + if [ -n "$identity" ]; then + # leading tilde causes `ssh-add` to fail. + identity="$(expand_tilde "$identity")" + fi + + printf "$identity" } sshag_get_sockets() { - for dir in '/tmp' "$TMPDIR"; do - find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null - done | sort -u + for dir in '/tmp' "$TMPDIR"; do + find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null + done | sort -u } sshag_vet_socket() { - [ "$1" ] && export SSH_AUTH_SOCK="$1" - - if [ -z "$SSH_AUTH_SOCK" ]; then - return 1 - elif [ -S "$SSH_AUTH_SOCK" ]; then - ssh-add -l >/dev/null 2>&1 - if [ $? -eq 2 ]; then - rm -f "$SSH_AUTH_SOCK" - print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleting!" - fi - else - print_warning "'$SSH_AUTH_SOCK' is not a socket!" - fi + [ "$1" ] && export SSH_AUTH_SOCK="$1" + + if [ -z "$SSH_AUTH_SOCK" ]; then + return 1 + elif [ -S "$SSH_AUTH_SOCK" ]; then + ssh-add -l >/dev/null 2>&1 + if [ $? -eq 2 ]; then + rm -f "$SSH_AUTH_SOCK" + print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleting!" + fi + else + print_warning "'$SSH_AUTH_SOCK' is not a socket!" + fi } sshag_require_ssh() { - if [ ! -x "$(command -v ssh)" ]; then - exit_error "'ssh' is not available! Aborting!" - elif [ ! -x "$(command -v ssh-add)" ]; then - exit_error "'ssh-add' is not available! Aborting!" - elif [ ! -x "$(command -v ssh-agent)" ]; then - exit_error "'ssh-agent' is not available! Aborting!" - fi + if [ ! -x "$(command -v ssh)" ]; then + exit_error "'ssh' is not available! Aborting!" + elif [ ! -x "$(command -v ssh-add)" ]; then + exit_error "'ssh-add' is not available! Aborting!" + elif [ ! -x "$(command -v ssh-agent)" ]; then + exit_error "'ssh-agent' is not available! Aborting!" + fi } # HELPERS expand_tilde() { - tilde_less="${1#\~/}" - [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" - printf "$tilde_less" + tilde_less="${1#\~/}" + [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" + printf "$tilde_less" } exit_error() { - printf "ERROR: $@\n" >&2 - exit 1 + printf "ERROR: $@\n" >&2 + exit 1 } print_error() { - printf "ERROR: $@\n" >&2 - return 1 + printf "ERROR: $@\n" >&2 + return 1 } print_return() { - printf "$@\n" + printf "$@\n" } # Do not send messages to 'stdout' # - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell print_stderr() { - printf "$@\n" >&2 + printf "$@\n" >&2 } print_warning() { - printf "WARNING: $@\n" >&2 - return 1 + printf "WARNING: $@\n" >&2 + return 1 } main "$@" + From 082babfa5d77e6bccdfd07504a6981568adc2673 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 3 Oct 2017 16:30:24 -0400 Subject: [PATCH 03/45] check if ssh support AddKeysToAgent --- sshag.sh | 160 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/sshag.sh b/sshag.sh index 8b3f040..fa1b1a5 100755 --- a/sshag.sh +++ b/sshag.sh @@ -7,7 +7,7 @@ main() { # If we are not being sourced, but rather running as a subshell, # let people know how to use the output. if [ "${0#*sshag}" != "$0" ]; then - sshad_msg='Output should be assigned to the environment variable $SSH_AUTH_SOCK.' + sshag_msg='Output should be assigned to the environment variable $SSH_AUTH_SOCK.' sshag "$@" fi } @@ -46,22 +46,22 @@ sshag() { # If at this point we still haven't located an agent, # then it's time to start a new one - [ "$agent_found" ] || eval "$(ssh-agent)" + [ -z "$agent_found" ] && eval "$(ssh-agent)" - if [ "$user_hostname" ]; then + if [ -n "$user_hostname" ]; then sshag_do_ssh "$user_hostname" else # Display the found socket - if [ "$sshad_msg" ]; then - print_stderr "$sshad_msg" - unset sshad_msg + if [ "$sshag_msg" ]; then + print_stderr "$sshag_msg" + unset sshag_msg fi # Display keys currently loaded in the agent print_stderr "Keys:" print_stderr "$(ssh-add -l | sed 's/^/ /')" - print_return "$SSH_AUTH_SOCK" + print_line "$SSH_AUTH_SOCK" fi # Clean up @@ -70,16 +70,41 @@ sshag() { unset user_hostname } -# Load first key for specified user@hostname and start `ssh`. -sshag_do_ssh() { - # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent - sshag_add_key_to_agent "$1" +sshag_require_ssh() { + if [ ! -x "$(command -v ssh)" ]; then + exit_error "'ssh' is not available! Aborting!" + elif [ ! -x "$(command -v ssh-add)" ]; then + exit_error "'ssh-add' is not available! Aborting!" + elif [ ! -x "$(command -v ssh-agent)" ]; then + exit_error "'ssh-agent' is not available! Aborting!" + fi +} - # start `ssh` - ssh "$1" +sshag_vet_socket() { + [ "$1" ] && export SSH_AUTH_SOCK="$1" + + if [ -z "$SSH_AUTH_SOCK" ]; then + return 1 + elif [ -S "$SSH_AUTH_SOCK" ]; then + ssh-add -l >/dev/null 2>&1 + if [ $? -eq 2 ]; then + rm -f "$SSH_AUTH_SOCK" + print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleting!" + fi + else + print_warning "'$SSH_AUTH_SOCK' is not a socket!" + fi } -sshag_add_key_to_agent() { +sshag_get_sockets() { + # OpenSSH only uses these two dirs + for dir in '/tmp' "$TMPDIR"; do + find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null + done | sort -u +} + +# Load first key for specified user@hostname and start `ssh`. +sshag_do_ssh() { # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, # but it is not set in the ~/.ssh/config @@ -91,110 +116,99 @@ sshag_add_key_to_agent() { # (OpenSSH before v7.2 barfs on params it doesn't know about so can't use # it in a common ssh_config where some machines have pre v7.2 OpenSSH.) - # Honor AddKeysToAgent settings - if grep '^[[:blank:]]*AddKeysToAgent' \ - "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null - then - return + if sshag_config_has_add_keys; then + # Honor AddKeysToAgent settings + : # do nothing + elif ssh -o AddKeysToAgent 2>&1 | grep 'missing argument' >/dev/null; then + # If this ssh supports AddKeyToAgent, then use it + sshag_args='-o AddKeysToAgent=yes' + else + # This is needed for OpenSSH pre v7.2, when AddKeysToAgent was added + sshag_add_key_to_agent "$1" fi + ssh "$sshag_args" "$1" + unset sshag_args +} + +# Checks is ~/.ssh/config has +sshag_config_has_add_keys() { + grep '^[[:blank:]]*AddKeysToAgent' \ + "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null + return $? +} + +sshag_add_key_to_agent() { + # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent + # Or if the local ssh client support AddKeysToAgent, + # but it is not set in the ~/.ssh/config + # check if identity is already loaded if ! sshag_is_identity_loaded "$1"; then # load identity if one is defined for the user@hostname. - identity="$(sshag_get_identity "$1")" - if [ -n "$identity" ] && ! ssh-add "$identity"; then - print_error "Unable to load identity '$identity'!" + sshag_get_identity "$1" + if [ -n "$sshag_identity" ] && ! ssh-add "$sshag_identity"; then + print_error "Unable to load identity '$sshag_identity'!" fi fi # Clean up - unset identity + unset sshag_identity } sshag_is_identity_loaded() { - echo 'exit' | ssh -o BatchMode=yes $1 2>/dev/null - [ $? -eq 0 ] && return 0 || return 1 + echo 'exit' | ssh -o BatchMode=yes -- $1 2>/dev/null + #[ $? -eq 0 ] && return 0 || return 1 + return $? } +# SETS identity sshag_get_identity() { #identity="$(ssh -G $1 | awk ' /identityfile/ { print $2 } ' | head -n 1)" - identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ - | awk ' /identity file/ { print $4 } ' \ - | head -n 1)" + sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ + | awk ' /identity file/ { print $4 } ' \ + | head -n 1)" - if [ -n "$identity" ]; then + if [ -n "$sshag_identity" ]; then # leading tilde causes `ssh-add` to fail. - identity="$(expand_tilde "$identity")" - fi - - printf "$identity" -} - -sshag_get_sockets() { - for dir in '/tmp' "$TMPDIR"; do - find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null - done | sort -u -} - -sshag_vet_socket() { - [ "$1" ] && export SSH_AUTH_SOCK="$1" - - if [ -z "$SSH_AUTH_SOCK" ]; then - return 1 - elif [ -S "$SSH_AUTH_SOCK" ]; then - ssh-add -l >/dev/null 2>&1 - if [ $? -eq 2 ]; then - rm -f "$SSH_AUTH_SOCK" - print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleting!" - fi - else - print_warning "'$SSH_AUTH_SOCK' is not a socket!" - fi -} - -sshag_require_ssh() { - if [ ! -x "$(command -v ssh)" ]; then - exit_error "'ssh' is not available! Aborting!" - elif [ ! -x "$(command -v ssh-add)" ]; then - exit_error "'ssh-add' is not available! Aborting!" - elif [ ! -x "$(command -v ssh-agent)" ]; then - exit_error "'ssh-agent' is not available! Aborting!" + sshag_identity="$(expand_tilde "$sshag_identity")" fi } # HELPERS +# Call in a subshell expand_tilde() { - tilde_less="${1#\~/}" - [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" - printf "$tilde_less" + expand_tilde_="${1#\~/}" + [ "$1" != "$expand_tilde_" ] && expand_tilde_="$HOME/$expand_tilde_" + printf "$expand_tilde_" + unset expand_tilde_ } exit_error() { - printf "ERROR: $@\n" >&2 + print_error "$@" exit 1 } print_error() { - printf "ERROR: $@\n" >&2 + print_stderr "ERROR: $@" return 1 } -print_return() { +print_line() { printf "$@\n" } # Do not send messages to 'stdout' # - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell print_stderr() { - printf "$@\n" >&2 + print_line "$@" >&2 } print_warning() { - printf "WARNING: $@\n" >&2 + print_stderr "WARNING: $@" return 1 } main "$@" - From fe215406dfb9bdfe57d7151c746d0a03b34fd7ff Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Sat, 7 Oct 2017 12:04:21 -0400 Subject: [PATCH 04/45] quite grep when config file not found --- sshag.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sshag.sh b/sshag.sh index fa1b1a5..8cfe834 100755 --- a/sshag.sh +++ b/sshag.sh @@ -134,7 +134,7 @@ sshag_do_ssh() { # Checks is ~/.ssh/config has sshag_config_has_add_keys() { grep '^[[:blank:]]*AddKeysToAgent' \ - "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null + "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null 2>&1 return $? } From 96558d0e8511794eb30e505ac48dcd9080fc9e23 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 17 Jan 2018 21:51:11 -0500 Subject: [PATCH 05/45] add `install` and `update` functions --- README.markdown | 74 +++++++++++++++++++++++++++++++-------------- sshag.sh | 80 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 24 deletions(-) diff --git a/README.markdown b/README.markdown index d900fa6..fb66bcc 100644 --- a/README.markdown +++ b/README.markdown @@ -3,51 +3,77 @@ ## "Socket to me, baby!" -This is a sourceable shell include file which provides a `sshag` function -to conveniently hook up with an operating ssh-agent. +This is a sourceable shell include file which provides a `sshag` function to +conveniently hook up with an operating `ssh-agent`. It will start a new agent session if it doesn't find an agent to connect with. -You might want to source it from within your `~/.bashrc` file -or other profile script. +You might want to source it from within your `~/.bashrc` file or other profile +script. -It can also be run as an executable script, -and its output stored in the relevant environment variable. -This is particularly useful when it is desired +It can also be run as an executable script, and its output stored in the +relevant environment variable. This is particularly useful when it is desired to configure a non-shell environment, for example, that of a text editor. Messages are emitted on standard out; the output will always consist of just the socket location. -## Usage: -Sourced: +## Installation: + +Automatic Install: ```sh - $ ssh alotta@fagina.example.com - Enter passphrase for key '/home/austin/.ssh/id_dsa': ^C - $ . ~/lib/sshag/sshag.sh - $ sshag alotta@fagina.example.com - Keys: - 2048 0d:db:a1:1a:cc:01:ad:ec:ab:00:d1:ed:eb:ac:1e:00 /home/austin/.ssh/id_dsa (DSA) - /tmp/ssh-5ock3tt0m3/agent.6969 +shag install [] ``` -Invoked: +Automatic Update: + +```sh +sshag update [] +``` + +Manual Install: + +```sh +cd "$custom_install_path" +git clone 'https://github.com/go2null/sshag.git' + +# then add the following to your shell startup file (`~/.bashrc`, `~/.zshrc`): +# note the leading `.` +. ; sshag >/dev/null +``` +Manual Update: ```sh - $ export SSH_AGENT_SOCK=$(sh ~/lib/sshag/sshag.sh) - Output should be assigned to the environment variable $SSH_AUTH_SOCK. - Keys: - 2048 0d:db:a1:1a:cc:01:ad:ec:ab:00:d1:ed:eb:ac:1e:00 /home/austin/.ssh/id_dsa (DSA) +cd "$custom_install_path/sshag" +git pull ``` -Appended to `~/.bashrc`: + +## Usage: + +Sourced: ```sh - . ~/lib/sshag/sshag.sh; sshag >/dev/null 2>&1 +$ ssh alotta@fagina.example.com +Enter passphrase for key '/home/austin/.ssh/id_ed25519': ^C +$ sshag alotta@fagina.example.com +Keys: + 256 SHA256:2TWr3x/H6eGvE+vx9Ur8uFQWBIXTBH3jT12yHBB4TJY austin@powers (ED25519) +/tmp/ssh-5ock3tt0m3/agent.6969 ``` +Invoked: + +```sh +$ export SSH_AGENT_SOCK=$(sh ~/.local/lib/sshag/sshag.sh) +Output should be assigned to the environment variable SSH_AUTH_SOCK. +Keys: + 256 SHA256:2TWr3x/H6eGvE+vx9Ur8uFQWBIXTBH3jT12yHBB4TJY austin@powers (ED25519) +``` + + ## History - v0.0.20100514 @@ -62,6 +88,8 @@ Appended to `~/.bashrc`: - *go2null*: Accept socket passed in. - *go2null*: Accept `user@host` parameter (ala **AddKeysToAgent**) feature, so can use `sshag user@domain` instead of `ssh user@domain`. +- v1.3.20180117 + - *go2null*: New `install` and `update` functions. ## Licensing diff --git a/sshag.sh b/sshag.sh index 8cfe834..7929a53 100755 --- a/sshag.sh +++ b/sshag.sh @@ -7,7 +7,7 @@ main() { # If we are not being sourced, but rather running as a subshell, # let people know how to use the output. if [ "${0#*sshag}" != "$0" ]; then - sshag_msg='Output should be assigned to the environment variable $SSH_AUTH_SOCK.' + sshag_msg='Output should be assigned to the environment variable SSH_AUTH_SOCK.' sshag "$@" fi } @@ -15,6 +15,10 @@ main() { sshag() { # ssh agent sockets can be attached to an ssh daemon process # or an ssh-agent process. + case "$1" in + install) sshag_install; return $? ;; + update) sshag_update; return $? ;; + esac sshag_require_ssh unset agent_found @@ -176,6 +180,80 @@ sshag_get_identity() { fi } +# INSTALL + +sshag_install() ( + # check system or user installation + if [ "$USER" = 'root' ]; then + lib='/usr/local/lib' + else + lib="${XDG_DATA_HOME:=$HOME/.local/share}/../lib" + fi + mkdir -p "$lib" + + # download + cd "$lib" + git clone 'https://github.com/go2null/sshag.git' + print_line "'sshag' installed to '$lib'" + + # add to shell startup files + CONFIG=". $lib/sshag/sshag.sh; sshag >/dev/null" + if [ "$USER" = 'root' ]; then + if touch '/etc/profile.d/sshag.sh' 2>/dev/null; then + sshag_install_profile '/etc/profile.d/sshag.sh' + else + sshag_install_profile '/etc/profile' + fi + else + sshag_install_profile "$HOME/.bash_profile" \ + || sshag_install_profile "$HOME/.profile" + fi + sshag_install_profile "$HOME/.bashrc" + sshag_install_profile "$HOME/.zshrc" + + # allow user to do it manually as well + print_line "Add the following to any additional shell startup files:" + print_line " $CONFIG" +) + +sshag_install_profile() { + if [ -e "$1" ]; then + print_line "$CONFIG" >> "$1" + print_line "'sshag' added to startup file '$1'" + return 0 + else + return 1 + fi +} + +sshag_update() ( + unset repo + unset SUDO + + custom_repo="$2" + user_repo="${XDG_DATA_HOME:=$HOME/.local/share}/../lib/sshag" + system_repo='/usr/local/sshag' + + [ -n "$custom_repo" ] && [ -d "$custom_repo" ] && repo="$custom_repo" + [ -n "$custom_repo" ] && [ -d "$custom_repo/sshag" ] && repo="$custom_repo/sshag" + [ -z "$repo" ] && [ -d "$user_repo" ] && repo="$user_repo" + [ -z "$repo" ] && [ -d "$system_repo" ] && repo="$system_repo" + + if [ -z "$repo" ]; then + print_error "Cannot find 'sshag' repo at either" + [ -n "$custom_repo" ] && print_error " '$custom_repo', or" + print_error " '$user_repo', or" + exit_error " '$system_repo'." + else + print_line "Updating 'sshag' at '$repo'." + fi + + [ "$repo" = "$system_repo" ] && SUDO='sudo' + + cd "$repo" + $SUDO sh -c 'git pull' +) + # HELPERS # Call in a subshell From f3cc53c9bc8f2bb4548c92a072002522b15631fe Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 23 Jan 2018 17:59:41 -0500 Subject: [PATCH 06/45] make sshag_install match sshag_update --- sshag.sh | 64 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/sshag.sh b/sshag.sh index 7929a53..d585b26 100755 --- a/sshag.sh +++ b/sshag.sh @@ -16,8 +16,8 @@ sshag() { # ssh agent sockets can be attached to an ssh daemon process # or an ssh-agent process. case "$1" in - install) sshag_install; return $? ;; - update) sshag_update; return $? ;; + install) shift; sshag_install "$@"; return $? ;; + update) shift; sshag_update "$@"; return $? ;; esac sshag_require_ssh @@ -183,21 +183,33 @@ sshag_get_identity() { # INSTALL sshag_install() ( - # check system or user installation - if [ "$USER" = 'root' ]; then - lib='/usr/local/lib' + unset dir + + custom_dir="$1" + user_dir="${XDG_DATA_HOME:=$HOME/.local/share}/../lib" + system_dir='/usr/local/lib' + + [ -n "$custom_dir" ] && mkdir -p "$custom_dir" && dir="$custom_dir" + [ -z "$dir" ] && [ "$USER" = 'root' ] && mkdir -p "$system_dir" && dir="$system_dir" + [ -z "$dir" ] && mkdir -p "$user_dir" && dir="$user_dir" + + if [ -z "$dir" ]; then + print_error "Cannot create 'sshag' repo at either" + [ -n "$custom_dir" ] && print_error " '$custom_dir', or" + print_error " '$user_dir', or" + exit_error " '$system_dir'." else - lib="${XDG_DATA_HOME:=$HOME/.local/share}/../lib" + print_line "Installing 'sshag' to '$dir'." fi - mkdir -p "$lib" # download - cd "$lib" + cd "$dir" + dir="$PWD" # get rid off `/../` in `user_dir` git clone 'https://github.com/go2null/sshag.git' - print_line "'sshag' installed to '$lib'" + print_line "'sshag' installed to '$dir'" # add to shell startup files - CONFIG=". $lib/sshag/sshag.sh; sshag >/dev/null" + CONFIG=". $dir/sshag/sshag.sh; sshag >/dev/null" if [ "$USER" = 'root' ]; then if touch '/etc/profile.d/sshag.sh' 2>/dev/null; then sshag_install_profile '/etc/profile.d/sshag.sh' @@ -220,37 +232,37 @@ sshag_install_profile() { if [ -e "$1" ]; then print_line "$CONFIG" >> "$1" print_line "'sshag' added to startup file '$1'" - return 0 + return else return 1 fi } sshag_update() ( - unset repo + unset dir unset SUDO - custom_repo="$2" - user_repo="${XDG_DATA_HOME:=$HOME/.local/share}/../lib/sshag" - system_repo='/usr/local/sshag' + custom_dir="$1" + user_dir="${XDG_DATA_HOME:=$HOME/.local/share}/../lib/sshag" + system_dir='/usr/local/lib/sshag' - [ -n "$custom_repo" ] && [ -d "$custom_repo" ] && repo="$custom_repo" - [ -n "$custom_repo" ] && [ -d "$custom_repo/sshag" ] && repo="$custom_repo/sshag" - [ -z "$repo" ] && [ -d "$user_repo" ] && repo="$user_repo" - [ -z "$repo" ] && [ -d "$system_repo" ] && repo="$system_repo" + [ -n "$custom_dir" ] && [ -d "$custom_dir" ] && dir="$custom_dir" + [ -n "$custom_dir" ] && [ -d "$custom_dir/sshag" ] && dir="$custom_dir/sshag" + [ -z "$dir" ] && [ -d "$user_dir" ] && dir="$user_dir" + [ -z "$dir" ] && [ -d "$system_dir" ] && dir="$system_dir" - if [ -z "$repo" ]; then + if [ -z "$dir" ]; then print_error "Cannot find 'sshag' repo at either" - [ -n "$custom_repo" ] && print_error " '$custom_repo', or" - print_error " '$user_repo', or" - exit_error " '$system_repo'." + [ -n "$custom_dir" ] && print_error " '$custom_dir', or" + print_error " '$user_dir', or" + exit_error " '$system_dir'." else - print_line "Updating 'sshag' at '$repo'." + print_line "Updating 'sshag' at '$dir'." fi - [ "$repo" = "$system_repo" ] && SUDO='sudo' + [ "$dir" = "$system_dir" ] && SUDO='sudo' - cd "$repo" + cd "$dir" $SUDO sh -c 'git pull' ) From e91687ae0cc705e20a82edd125c3a9ae0ffac9fb Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 23 Jan 2018 18:45:06 -0500 Subject: [PATCH 07/45] allow passing options to ssh --- README.markdown | 2 ++ sshag.sh | 49 +++++++++++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/README.markdown b/README.markdown index fb66bcc..8d771d1 100644 --- a/README.markdown +++ b/README.markdown @@ -90,6 +90,8 @@ Keys: so can use `sshag user@domain` instead of `ssh user@domain`. - v1.3.20180117 - *go2null*: New `install` and `update` functions. +- v1.4.20180123 + - *go2null*: Allow passing arguments/options to `ssh`. ## Licensing diff --git a/sshag.sh b/sshag.sh index d585b26..4b0e6bb 100755 --- a/sshag.sh +++ b/sshag.sh @@ -12,29 +12,39 @@ main() { fi } +# calling syntax +# sshag install [dest_dir] - install +# sshag update [dest_dir] - update +# sshag - start/use agent +# sshag agent-socket - use specified agent +# sshag user@host [ssh-options-and-args] - start agent and ssh to user@host sshag() { # ssh agent sockets can be attached to an ssh daemon process # or an ssh-agent process. - case "$1" in - install) shift; sshag_install "$@"; return $? ;; - update) shift; sshag_update "$@"; return $? ;; - esac - sshag_require_ssh + unset ssh_args unset agent_found unset agent_socket unset user_hostname - # check any params - while [ -n "$1" ]; do - if [ -e "$1" ] ; then - [ -S "$1" ] && agent_socket="$1" - else - user_hostname="$1" - fi + while [ $# -gt 0 ]; do + case "$1" in + install) shift; sshag_install "$@"; return $? ;; + update) shift; sshag_update "$@"; return $? ;; + -*) ssh_args="$ssh_args $1" ;; + *) + if [ -e "$1" ] ; then + [ -S "$1" ] && agent_socket="$1" + else + user_hostname="$1" + fi + ;; + esac shift done + sshag_require_ssh + # Attempt to use socket passed in, or # find and use the ssh-agent in the current environment if sshag_vet_socket "$agent_socket"; then @@ -53,7 +63,7 @@ sshag() { [ -z "$agent_found" ] && eval "$(ssh-agent)" if [ -n "$user_hostname" ]; then - sshag_do_ssh "$user_hostname" + sshag_do_ssh "$user_hostname" "$ssh_args" else # Display the found socket if [ "$sshag_msg" ]; then @@ -108,7 +118,7 @@ sshag_get_sockets() { } # Load first key for specified user@hostname and start `ssh`. -sshag_do_ssh() { +sshag_do_ssh() ( # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, # but it is not set in the ~/.ssh/config @@ -120,20 +130,23 @@ sshag_do_ssh() { # (OpenSSH before v7.2 barfs on params it doesn't know about so can't use # it in a common ssh_config where some machines have pre v7.2 OpenSSH.) + user_host="$1" + shift + ssh_args="$@" + if sshag_config_has_add_keys; then # Honor AddKeysToAgent settings : # do nothing elif ssh -o AddKeysToAgent 2>&1 | grep 'missing argument' >/dev/null; then # If this ssh supports AddKeyToAgent, then use it - sshag_args='-o AddKeysToAgent=yes' + ssh_args="$ssh_args -o AddKeysToAgent=yes" else # This is needed for OpenSSH pre v7.2, when AddKeysToAgent was added sshag_add_key_to_agent "$1" fi - ssh "$sshag_args" "$1" - unset sshag_args -} + ssh $ssh_args "$user_host" +) # Checks is ~/.ssh/config has sshag_config_has_add_keys() { From bfb3283bc3923d5993ab34ff95a14799d40d4613 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Sun, 18 Feb 2018 10:08:48 -0500 Subject: [PATCH 08/45] Adopted KeepAChangeLog.com style --- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++ README.markdown => README.md | 31 ++++++-------------- 2 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 CHANGELOG.md rename README.markdown => README.md (84%) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4641ec7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,56 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com) +and this project adheres to [Semantic Versioning](http://semver.org). + + +## [Unreleased] - 2018-02-18 +### Added +- *go2null*: Added annotated `git` tags with changelog. + +### Changed +- *go2null*: Moved **History** section from `README.markdown` to `CHANGELOG.md`. +- *go2null*: Renamed `README.markdown` to `README.md`. + +## [1.3.0] - 2018-01-17 +### Added +- *go2null*: Allow passing arguments/options to `ssh`. +- *go2null*: New `install` and `update` functions. + +## [1.2.1] - 2017-10-07 +## Added +- *go2null*: Check if `ssh` supports `AddKeysToAgent` flag. + +## Changed +- Fixed detection of identity files. +- Fixed grep error when config file not found. + +## [1.2.0] - 2016-08-25 +### Added +- *go2null*: Search `$TMPDIR` for agents as well, per OpenSSH man page. +- *go2null*: Accept socket passed in. +- *go2null*: Accept `user@host` parameter (ala **AddKeysToAgent**) feature, + so can use `sshag user@domain` instead of `ssh user@domain`. + +### Changed +- *go2null*: Make script POSIX compliant. + + +## [1.1.0] - 2011-02-20 +### Added +- *intuited*: Made it convenient to run the script in a subshell. + + +## [1.0.0] - 2010-07-26 +### Added +- *intuited*: Add readme and license documents. + +### Changed +- *intuited*: Renamed from `sagent` to `sshag`. + + +## [0.0.0] - 2010-05-14 +### Added +- *Zed*: http://superuser.com/a/141241 diff --git a/README.markdown b/README.md similarity index 84% rename from README.markdown rename to README.md index 8d771d1..728f472 100644 --- a/README.markdown +++ b/README.md @@ -76,22 +76,7 @@ Keys: ## History -- v0.0.20100514 - - *Zed*: http://superuser.com/a/141241 -- v1.0.20100726 - - *intuited*: renamed from `sagent` to `sshag`. Add readme and license documents. -- v1.1.20110220 - - *intuited*: Made it convenient to run the script in a subshell. -- v1.2.20160825 - - *go2null*: Make script POSIX compliant. - - *go2null*: Search `$TMPDIR` for agents as well, per OpenSSH man page. - - *go2null*: Accept socket passed in. - - *go2null*: Accept `user@host` parameter (ala **AddKeysToAgent**) feature, - so can use `sshag user@domain` instead of `ssh user@domain`. -- v1.3.20180117 - - *go2null*: New `install` and `update` functions. -- v1.4.20180123 - - *go2null*: Allow passing arguments/options to `ssh`. +See [CHANGELOG.md]. ## Licensing @@ -101,7 +86,7 @@ Creative Commons Attribution-Sharealike License, so I'm attributing it to the superuser.com user [Zed]. SU currently links to [version 2.5] of the license. -A copy of [the full license] is distributed herein in the file COPYING. +A copy of [the full license] is distributed herein in the file [COPYING]. ### The basic gist of the license @@ -139,12 +124,14 @@ A copy of [the full license] is distributed herein in the file COPYING. to others the license terms of this work. The best way to do this is with a link to this web page. -[response]: http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 +[CHANGELOG.md]: https://github.com/go2null/sshag/blob/master/CHANGELOG.md +[COPYING]: https://github.com/go2null/sshag/blob/master/COPYING [Zed]: http://superuser.com/users/33648/zed -[version 2.5]: http://creativecommons.org/licenses/by-sa/2.5/ -[the full license]: http://creativecommons.org/licenses/by-sa/2.5/legalcode -[waived]: http://wiki.creativecommons.org/Frequently_Asked_Questions#Can_I_change_the_terms_of_a_CC_license_or_waive_some_of_its_conditions.3F -[public domain]: http://wiki.creativecommons.org/Public_domain [fair use]: http://wiki.creativecommons.org/Frequently_Asked_Questions#Do_Creative_Commons_licenses_affect_fair_use.2C_fair_dealing_or_other_exceptions_to_copyright.3F [moral]: http://wiki.creativecommons.org/Frequently_Asked_Questions#I_don.E2.80.99t_like_the_way_a_person_has_used_my_work_in_a_derivative_work_or_included_it_in_a_collective_work.3B_what_can_I_do.3F +[public domain]: http://wiki.creativecommons.org/Public_domain [publicity]: http://wiki.creativecommons.org/Frequently_Asked_Questions#When_are_publicity_rights_relevant.3F +[response]: http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 +[the full license]: http://creativecommons.org/licenses/by-sa/2.5/legalcode +[version 2.5]: http://creativecommons.org/licenses/by-sa/2.5/ +[waived]: http://wiki.creativecommons.org/Frequently_Asked_Questions#Can_I_change_the_terms_of_a_CC_license_or_waive_some_of_its_conditions.3F From fa9ea48b0b0283276f9e103f7dec6247de75875f Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Mon, 19 Feb 2018 09:57:42 -0500 Subject: [PATCH 09/45] Added support for `pearl` shell package manager --- CHANGELOG.md | 24 ++++++++++++++++-------- README.md | 21 +++++++++++++++++++++ pearl-config/config.sh | 2 ++ pearl-config/install.sh | 8 ++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 pearl-config/config.sh create mode 100644 pearl-config/install.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 4641ec7..8b32369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,37 +2,40 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com) -and this project adheres to [Semantic Versioning](http://semver.org). +The format is based on [Keep a Changelog] +and this project adheres to [Semantic Versioning]. -## [Unreleased] - 2018-02-18 +## [1.3.1] - 2018-02-19 ### Added -- *go2null*: Added annotated `git` tags with changelog. +- *go2null*: Added support for [pearl] shell package manager. ### Changed +- *go2null*: Replaced regular `git` tags with annotated tags with changelog. - *go2null*: Moved **History** section from `README.markdown` to `CHANGELOG.md`. - *go2null*: Renamed `README.markdown` to `README.md`. + ## [1.3.0] - 2018-01-17 ### Added - *go2null*: Allow passing arguments/options to `ssh`. - *go2null*: New `install` and `update` functions. + ## [1.2.1] - 2017-10-07 ## Added - *go2null*: Check if `ssh` supports `AddKeysToAgent` flag. ## Changed -- Fixed detection of identity files. -- Fixed grep error when config file not found. +- *go2null*: Fixed detection of identity files. +- *go2null*: Fixed grep error when config file not found. + ## [1.2.0] - 2016-08-25 ### Added - *go2null*: Search `$TMPDIR` for agents as well, per OpenSSH man page. - *go2null*: Accept socket passed in. -- *go2null*: Accept `user@host` parameter (ala **AddKeysToAgent**) feature, - so can use `sshag user@domain` instead of `ssh user@domain`. +- *go2null*: Can now use `sshag user@domain` instead of `ssh user@domain`. ### Changed - *go2null*: Make script POSIX compliant. @@ -54,3 +57,8 @@ and this project adheres to [Semantic Versioning](http://semver.org). ## [0.0.0] - 2010-05-14 ### Added - *Zed*: http://superuser.com/a/141241 + + +[Keep a Changelog]: http://keepachangelog.com +[Semantic Versioning]: http://semver.org +[pearl]: https://github.com/pearl-core/pearl#installation diff --git a/README.md b/README.md index 728f472..98692fd 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,25 @@ the output will always consist of just the socket location. ## Installation: +## Recommended installation method + +Use [pearl] package manager. +```sh +# add repo +printf '%s=%s\n' "PEARL_PACKAGES['sshag']" \ + "'https://github.com/go2null/sshag.git'" \ + >> "$HOME/.config/pearl/pearl.conf" +printf '%s=%s\n' "PEARL_PACKAGES_DESCR['sshag']" \ + "'Hook up with an operating or new SSH agent'" \ + >> "$HOME/.config/pearl/pearl.conf" +# install +pearl install sshag +# update +pearl update sshag +``` + +### Built-in installation + Automatic Install: ```sh @@ -124,11 +143,13 @@ A copy of [the full license] is distributed herein in the file [COPYING]. to others the license terms of this work. The best way to do this is with a link to this web page. + [CHANGELOG.md]: https://github.com/go2null/sshag/blob/master/CHANGELOG.md [COPYING]: https://github.com/go2null/sshag/blob/master/COPYING [Zed]: http://superuser.com/users/33648/zed [fair use]: http://wiki.creativecommons.org/Frequently_Asked_Questions#Do_Creative_Commons_licenses_affect_fair_use.2C_fair_dealing_or_other_exceptions_to_copyright.3F [moral]: http://wiki.creativecommons.org/Frequently_Asked_Questions#I_don.E2.80.99t_like_the_way_a_person_has_used_my_work_in_a_derivative_work_or_included_it_in_a_collective_work.3B_what_can_I_do.3F +[pearl]: https://github.com/pearl-core/pearl#installation [public domain]: http://wiki.creativecommons.org/Public_domain [publicity]: http://wiki.creativecommons.org/Frequently_Asked_Questions#When_are_publicity_rights_relevant.3F [response]: http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 diff --git a/pearl-config/config.sh b/pearl-config/config.sh new file mode 100644 index 0000000..8781126 --- /dev/null +++ b/pearl-config/config.sh @@ -0,0 +1,2 @@ +# load `sshag` into current environment +. "$PEARL_PKGDIR/sshag.sh" diff --git a/pearl-config/install.sh b/pearl-config/install.sh new file mode 100644 index 0000000..e2d0d5d --- /dev/null +++ b/pearl-config/install.sh @@ -0,0 +1,8 @@ +post_install() { + . "$PEARL_PKGDIR/pearl-config/config.sh" # configure + sshag # invoke +} + +post_update() { + post_install +} From ce0198ef61d75110ee37ef61f632cf2b8fcfbd1b Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 23 Feb 2018 06:37:29 -0500 Subject: [PATCH 10/45] :memo: rename COPYING to LICENSE per current norms --- COPYING => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename COPYING => LICENSE (100%) diff --git a/COPYING b/LICENSE similarity index 100% rename from COPYING rename to LICENSE From 1f44dbdab34ef7c03de5e5619d5d2e311f160a36 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 30 Mar 2022 06:16:37 -0400 Subject: [PATCH 11/45] fix bug #2 path to LICENCE --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 98692fd..a935469 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Creative Commons Attribution-Sharealike License, so I'm attributing it to the superuser.com user [Zed]. SU currently links to [version 2.5] of the license. -A copy of [the full license] is distributed herein in the file [COPYING]. +A copy of [the full license] is distributed herein in the file [LICENSE]. ### The basic gist of the license @@ -145,7 +145,7 @@ A copy of [the full license] is distributed herein in the file [COPYING]. [CHANGELOG.md]: https://github.com/go2null/sshag/blob/master/CHANGELOG.md -[COPYING]: https://github.com/go2null/sshag/blob/master/COPYING +[LICENSE]: https://github.com/go2null/sshag/blob/master/LICENSE [Zed]: http://superuser.com/users/33648/zed [fair use]: http://wiki.creativecommons.org/Frequently_Asked_Questions#Do_Creative_Commons_licenses_affect_fair_use.2C_fair_dealing_or_other_exceptions_to_copyright.3F [moral]: http://wiki.creativecommons.org/Frequently_Asked_Questions#I_don.E2.80.99t_like_the_way_a_person_has_used_my_work_in_a_derivative_work_or_included_it_in_a_collective_work.3B_what_can_I_do.3F From 72722d5da3e9c654fb11b5988df778cead896bb6 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 08:15:39 -0400 Subject: [PATCH 12/45] change install default locations * system - only default to system path if user is root (or sudo) * user - default to abosulte path per systemd --- CHANGELOG.md | 64 +++++----- README.md | 42 +++--- sshag.sh | 351 ++++++++++++++++++++++++--------------------------- 3 files changed, 218 insertions(+), 239 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b32369..85afd21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,60 +5,64 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog] and this project adheres to [Semantic Versioning]. - +## [Unreleased] +### Fixed +* _go2null_: Fix bug #2 path to LICENSE. +### Changed +* _go2null_: install now defaults to _system_ only if running as `root`. +* _go2null_: install now defaults to `~/.local/lib` per `systemd` standard. + ## [1.3.1] - 2018-02-19 ### Added -- *go2null*: Added support for [pearl] shell package manager. - +* _go2null_: Added support for [pearl] shell package manager. ### Changed -- *go2null*: Replaced regular `git` tags with annotated tags with changelog. -- *go2null*: Moved **History** section from `README.markdown` to `CHANGELOG.md`. -- *go2null*: Renamed `README.markdown` to `README.md`. - +* _go2null_: Replaced regular `git` tags with annotated tags with changelog. +* _go2null_: Moved __History__ section from `README.markdown` to `CHANGELOG.md`. +* _go2null_: Renamed `README.markdown` to `README.md`. ## [1.3.0] - 2018-01-17 ### Added -- *go2null*: Allow passing arguments/options to `ssh`. -- *go2null*: New `install` and `update` functions. - +* _go2null_: Allow passing arguments/options to `ssh`. +* _go2null_: New `install` and `update` functions. ## [1.2.1] - 2017-10-07 ## Added -- *go2null*: Check if `ssh` supports `AddKeysToAgent` flag. - +* _go2null_: Check if `ssh` supports `AddKeysToAgent` flag. ## Changed -- *go2null*: Fixed detection of identity files. -- *go2null*: Fixed grep error when config file not found. - +* _go2null_: Fixed detection of identity files. +* _go2null_: Fixed grep error when config file not found. ## [1.2.0] - 2016-08-25 ### Added -- *go2null*: Search `$TMPDIR` for agents as well, per OpenSSH man page. -- *go2null*: Accept socket passed in. -- *go2null*: Can now use `sshag user@domain` instead of `ssh user@domain`. - +* _go2null_: Search `$TMPDIR` for agents as well, per OpenSSH man page. +* _go2null_: Accept socket passed in. +* _go2null_: Can now use `sshag user@domain` instead of `ssh user@domain`. ### Changed -- *go2null*: Make script POSIX compliant. - +* _go2null_: Make script POSIX compliant. ## [1.1.0] - 2011-02-20 ### Added -- *intuited*: Made it convenient to run the script in a subshell. - +* _intuited_: Made it convenient to run the script in a subshell. ## [1.0.0] - 2010-07-26 ### Added -- *intuited*: Add readme and license documents. - +* _intuited_: Add readme and license documents. ### Changed -- *intuited*: Renamed from `sagent` to `sshag`. - +* _intuited_: Renamed from `sagent` to `sshag`. ## [0.0.0] - 2010-05-14 ### Added -- *Zed*: http://superuser.com/a/141241 +* _Zed_: http://superuser.com/a/141241 -[Keep a Changelog]: http://keepachangelog.com +[Keep a Changelog]: http://keepachangelog.com [Semantic Versioning]: http://semver.org -[pearl]: https://github.com/pearl-core/pearl#installation +[pearl]: https://github.com/pearl-core/pearl#installation + +[Unreleased]: https://github.com/go2null/sshag/compare/1.3.1...HEAD +[1.3.1]: https://github.com/go2null/sshag/compare/1.3.0....1.3.1 +[1.3.0]: https://github.com/go2null/sshag/compare/1.2.1....1.3.0 +[1.2.1]: https://github.com/go2null/sshag/compare/1.2.0....1.2.1 +[1.2.0]: https://github.com/go2null/sshag/compare/1.1.0....1.2.0 +[1.1.0]: https://github.com/go2null/sshag/compare/0.0.0....1.1.0 +[0.0.0]: https://github.com/go2null/sshag/releases/tag/0.0.0 diff --git a/README.md b/README.md index a935469..8f1ac3e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -`sshag` -======= +# `sshag` ## "Socket to me, baby!" @@ -19,17 +18,19 @@ Messages are emitted on standard out; the output will always consist of just the socket location. -## Installation: +## Installation -## Recommended installation method +### Recommended installation method Use [pearl] package manager. ```sh # add repo -printf '%s=%s\n' "PEARL_PACKAGES['sshag']" \ +printf '%s=%s\n' \ + "PEARL_PACKAGES['sshag']" \ "'https://github.com/go2null/sshag.git'" \ >> "$HOME/.config/pearl/pearl.conf" -printf '%s=%s\n' "PEARL_PACKAGES_DESCR['sshag']" \ +printf '%s=%s\n' \ + "PEARL_PACKAGES_DESCR['sshag']" \ "'Hook up with an operating or new SSH agent'" \ >> "$HOME/.config/pearl/pearl.conf" # install @@ -40,40 +41,35 @@ pearl update sshag ### Built-in installation -Automatic Install: - +Automatic Install ```sh -shag install [] +sshag install [] ``` -Automatic Update: - +Automatic Update ```sh sshag update [] ``` -Manual Install: - +Manual Install ```sh -cd "$custom_install_path" +cd /custom/install/path git clone 'https://github.com/go2null/sshag.git' # then add the following to your shell startup file (`~/.bashrc`, `~/.zshrc`): -# note the leading `.` +# note the leading dot (`.`) which is the POSIX `source` command. . ; sshag >/dev/null ``` -Manual Update: +Manual Update ```sh -cd "$custom_install_path/sshag" +cd /custom/install/path/sshag" git pull ``` +## Usage -## Usage: - -Sourced: - +Sourced ```sh $ ssh alotta@fagina.example.com Enter passphrase for key '/home/austin/.ssh/id_ed25519': ^C @@ -83,8 +79,7 @@ Keys: /tmp/ssh-5ock3tt0m3/agent.6969 ``` -Invoked: - +Invoked ```sh $ export SSH_AGENT_SOCK=$(sh ~/.local/lib/sshag/sshag.sh) Output should be assigned to the environment variable SSH_AUTH_SOCK. @@ -92,7 +87,6 @@ Keys: 256 SHA256:2TWr3x/H6eGvE+vx9Ur8uFQWBIXTBH3jT12yHBB4TJY austin@powers (ED25519) ``` - ## History See [CHANGELOG.md]. diff --git a/sshag.sh b/sshag.sh index 4b0e6bb..193ed7e 100755 --- a/sshag.sh +++ b/sshag.sh @@ -3,34 +3,31 @@ # acquired courtesy of # http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 -main() { - # If we are not being sourced, but rather running as a subshell, - # let people know how to use the output. - if [ "${0#*sshag}" != "$0" ]; then - sshag_msg='Output should be assigned to the environment variable SSH_AUTH_SOCK.' - sshag "$@" - fi +sshag_running_as_command() { + [ "${0#*sshag}" != "$0" ] } -# calling syntax -# sshag install [dest_dir] - install -# sshag update [dest_dir] - update -# sshag - start/use agent -# sshag agent-socket - use specified agent -# sshag user@host [ssh-options-and-args] - start agent and ssh to user@host +# only allow to source once. +# this simplifies the installation by adding to all the dot profiles and only source once. +type sshag 2>dev/null | grep 'is a function' \ + && ! sshag_running_as_command \ + && return + +# USAGE +# sshag install [DEST_DIR] - install/update +# sshag update [DEST_DIR] - update +# sshag - start/use agent +# sshag AGENT_SOCKET - use specified agent +# sshag USER@HOST [SSH_OPTIONS_AND_ARGS] - start agent and ssh to USER@HOST sshag() { - # ssh agent sockets can be attached to an ssh daemon process - # or an ssh-agent process. - unset ssh_args - unset agent_found unset agent_socket unset user_hostname while [ $# -gt 0 ]; do case "$1" in - install) shift; sshag_install "$@"; return $? ;; - update) shift; sshag_update "$@"; return $? ;; + install) shift; sshag_install 'install' "$@"; return $? ;; + update) shift; sshag_install 'update' "$@"; return $? ;; -*) ssh_args="$ssh_args $1" ;; *) if [ -e "$1" ] ; then @@ -44,81 +41,95 @@ sshag() { done sshag_require_ssh + sshag_agent_get_socket "$agent_socket" || sshag_agent_new_socket - # Attempt to use socket passed in, or - # find and use the ssh-agent in the current environment - if sshag_vet_socket "$agent_socket"; then - agent_found=1 + if [ -n "$user_hostname" ]; then + sshag_ssh "$user_hostname" "$ssh_args" else - # If there is no agent in the environment, - # search for possible agents to reuse - # before starting a fresh ssh-agent process. - for agent_socket in $(sshag_get_sockets) ; do - sshag_vet_socket "$agent_socket" && agent_found=1 && break - done + sshag_running_as_command && sshag_agent_print_notice + sshag_agent_print_keys fi +} - # If at this point we still haven't located an agent, - # then it's time to start a new one - [ -z "$agent_found" ] && eval "$(ssh-agent)" +sshag_require_ssh() { + for util in ssh ssh-add ssh-agent; do + require_command "$util" + done +} - if [ -n "$user_hostname" ]; then - sshag_do_ssh "$user_hostname" "$ssh_args" - else - # Display the found socket - if [ "$sshag_msg" ]; then - print_stderr "$sshag_msg" - unset sshag_msg - fi +# == Get/Start SSH-AGENT == - # Display keys currently loaded in the agent - print_stderr "Keys:" - print_stderr "$(ssh-add -l | sed 's/^/ /')" +# $1 - Agent Socket +sshag_agent_get_socket() { + unset found_agent - print_line "$SSH_AUTH_SOCK" - fi + # Attempt to use socket passed in + sshag_agent_vet_socket "$1" && return - # Clean up - unset agent_found - unset agent_socket - unset user_hostname -} + # Attempt to use the ssh-agent in the current environment + sshag_agent_vet_socket "$SSH_AUTH_SOCK" && return -sshag_require_ssh() { - if [ ! -x "$(command -v ssh)" ]; then - exit_error "'ssh' is not available! Aborting!" - elif [ ! -x "$(command -v ssh-add)" ]; then - exit_error "'ssh-add' is not available! Aborting!" - elif [ ! -x "$(command -v ssh-agent)" ]; then - exit_error "'ssh-agent' is not available! Aborting!" - fi + # If there is no agent in the environment, + # search for possible agents to reuse + # before starting a fresh ssh-agent process. + # ssh agent sockets can be attached to an ssh daemon process + # or an ssh-agent process. + for agent_socket in $(sshag_agent_find_sockets) ; do + sshag_agent_vet_socket "$agent_socket" && return + done + + return 1 } -sshag_vet_socket() { - [ "$1" ] && export SSH_AUTH_SOCK="$1" +# $1 - Agent Socket +sshag_agent_vet_socket() { + [ -z "$1" ] && return 1 - if [ -z "$SSH_AUTH_SOCK" ]; then - return 1 - elif [ -S "$SSH_AUTH_SOCK" ]; then + if [ -S "$1" ]; then + export SSH_AUTH_SOCK="$1" ssh-add -l >/dev/null 2>&1 if [ $? -eq 2 ]; then rm -f "$SSH_AUTH_SOCK" - print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleting!" + print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleted!" fi else print_warning "'$SSH_AUTH_SOCK' is not a socket!" fi } -sshag_get_sockets() { +sshag_agent_find_sockets() { # OpenSSH only uses these two dirs for dir in '/tmp' "$TMPDIR"; do find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null done | sort -u } +sshag_agent_new_socket() { + eval "$(ssh-agent)" +} + +sshag_agent_print_notice() { + print_info "$(cat <<- NOTICE + Do the following to add the ssh-agent to your current session + export SSH_AGENT_SOCK="\$(sh '$0')" + Or, simply source the file + source '$0' + If it is already sourced, but your agent is dead, then just + sshag + NOTICE + )" +} + +# Display keys currently loaded in the agent +sshag_agent_print_keys() { + print_info "Keys:" + print_info "$(ssh-add -l | sed 's/^/ /')" +} + +# == SSH wrapper == + # Load first key for specified user@hostname and start `ssh`. -sshag_do_ssh() ( +sshag_ssh() ( # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, # but it is not set in the ~/.ssh/config @@ -134,95 +145,98 @@ sshag_do_ssh() ( shift ssh_args="$@" - if sshag_config_has_add_keys; then + if sshag_ssh_config_has_add_keys; then # Honor AddKeysToAgent settings : # do nothing elif ssh -o AddKeysToAgent 2>&1 | grep 'missing argument' >/dev/null; then # If this ssh supports AddKeyToAgent, then use it ssh_args="$ssh_args -o AddKeysToAgent=yes" else - # This is needed for OpenSSH pre v7.2, when AddKeysToAgent was added - sshag_add_key_to_agent "$1" + # This is needed for OpenSSH pre v7.2, before AddKeysToAgent was added + sshag_ssh_add_key_to_agent "$1" fi ssh $ssh_args "$user_host" ) -# Checks is ~/.ssh/config has -sshag_config_has_add_keys() { +# Checks if ~/.ssh/config has AddKeysToAgent +sshag_ssh_config_has_add_keys() { grep '^[[:blank:]]*AddKeysToAgent' \ "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null 2>&1 return $? } -sshag_add_key_to_agent() { - # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent - # Or if the local ssh client support AddKeysToAgent, - # but it is not set in the ~/.ssh/config - - # check if identity is already loaded - if ! sshag_is_identity_loaded "$1"; then +# This is needed for OpenSSH before v7.2 which added support AddKeysToAgent +# Or if the local ssh client support AddKeysToAgent, +# but it is not set in the ~/.ssh/config +sshag_ssh_add_key_to_agent() { + sshag_ssh_is_identity_loaded "$1" && return - # load identity if one is defined for the user@hostname. - sshag_get_identity "$1" - if [ -n "$sshag_identity" ] && ! ssh-add "$sshag_identity"; then - print_error "Unable to load identity '$sshag_identity'!" - fi + # load identity if one is defined for the user@hostname. + sshag_identity="$(sshag_ssh_get_identity "$1")" + if [ -n "$sshag_identity" ] && ! ssh-add "$sshag_identity"; then + print_error "Unable to load identity '$sshag_identity'!" fi - - # Clean up - unset sshag_identity } -sshag_is_identity_loaded() { +sshag_ssh_is_identity_loaded() { echo 'exit' | ssh -o BatchMode=yes -- $1 2>/dev/null - #[ $? -eq 0 ] && return 0 || return 1 return $? } -# SETS identity -sshag_get_identity() { - #identity="$(ssh -G $1 | awk ' /identityfile/ { print $2 } ' | head -n 1)" - sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ +sshag_ssh_get_identity() { + sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ | awk ' /identity file/ { print $4 } ' \ | head -n 1)" - if [ -n "$sshag_identity" ]; then - # leading tilde causes `ssh-add` to fail. - sshag_identity="$(expand_tilde "$sshag_identity")" - fi + [ -n "$sshag_identity" ] \ + && sshag_identity="$(realpath -m "$sshag_identity")" \ + && printf '%s' "$sshag_identify" } -# INSTALL +# == INSTALL == sshag_install() ( - unset dir + require_command 'git' + dir="$(sshag_install_path "$2")" + + if [ "$1" = 'update' ] || [ -d "$dir/sshag" ]; then + sshag_update "$dir" + return $? + fi + + print_info "Installing 'sshag' to '$dir'." + __SSHAG_CONFIG=". '$dir/sshag/sshag.sh'; sshag >/dev/null" + sshag_install_download "$dir" + sshag_install_profiles "$dir" + sshag_install_manual +) - custom_dir="$1" - user_dir="${XDG_DATA_HOME:=$HOME/.local/share}/../lib" +sshag_install_path() { + unset dir system_dir='/usr/local/lib' + user_dir="$HOME/.local/lib" - [ -n "$custom_dir" ] && mkdir -p "$custom_dir" && dir="$custom_dir" - [ -z "$dir" ] && [ "$USER" = 'root' ] && mkdir -p "$system_dir" && dir="$system_dir" - [ -z "$dir" ] && mkdir -p "$user_dir" && dir="$user_dir" - - if [ -z "$dir" ]; then - print_error "Cannot create 'sshag' repo at either" - [ -n "$custom_dir" ] && print_error " '$custom_dir', or" - print_error " '$user_dir', or" - exit_error " '$system_dir'." - else - print_line "Installing 'sshag' to '$dir'." + if [ -n "$1" ]; then + dir="$(realpath -m "$1" 2>/dev/null)" + [ -z "$dir" ] && print_fatal "Invalid directory '$1'" fi - # download - cd "$dir" - dir="$PWD" # get rid off `/../` in `user_dir` - git clone 'https://github.com/go2null/sshag.git' - print_line "'sshag' installed to '$dir'" + [ -z "$dir" ] && [ "$USER" = 'root' ] && dir="$system_dir" + [ -z "$dir" ] && dir="$user_dir" - # add to shell startup files - CONFIG=". $dir/sshag/sshag.sh; sshag >/dev/null" + [ -d "$dir" ] || mkdir -p "$dir" || print_fatal "Cannot create directory '$dir'" + printf '%s' "$dir" +} + +sshag_install_download() { + cd "$1" + git clone 'https://github.com/go2null/sshag.git' + print_info "'sshag' installed to '$1'" +} + +# add to shell startup files +sshag_install_profiles() { if [ "$USER" = 'root' ]; then if touch '/etc/profile.d/sshag.sh' 2>/dev/null; then sshag_install_profile '/etc/profile.d/sshag.sh' @@ -230,88 +244,55 @@ sshag_install() ( sshag_install_profile '/etc/profile' fi else - sshag_install_profile "$HOME/.bash_profile" \ - || sshag_install_profile "$HOME/.profile" + sshag_install_profile "$HOME/.profile" + sshag_install_profile "$HOME/.bash_profile" + sshag_install_profile "$HOME/.bashrc" + sshag_install_profile "$HOME/.zshrc" fi - sshag_install_profile "$HOME/.bashrc" - sshag_install_profile "$HOME/.zshrc" - - # allow user to do it manually as well - print_line "Add the following to any additional shell startup files:" - print_line " $CONFIG" -) +} sshag_install_profile() { - if [ -e "$1" ]; then - print_line "$CONFIG" >> "$1" - print_line "'sshag' added to startup file '$1'" - return - else - return 1 + [ -w "$1" ] || return 1 + + + if grep "^[ \t]*$__SSHAG_CONFIG" "$1" >/dev/null; then + print_info "'sshag' already in startup file '$1'" + return fi + + print_line "$__SSHAG_CONFIG" >> "$1" + print_info "'sshag' added to startup file '$1'" +} + +sshag_install_manual() { + print_info "Add the following to any additional shell startup files:" + print_info " $__SSHAG_CONFIG" } sshag_update() ( - unset dir - unset SUDO - - custom_dir="$1" - user_dir="${XDG_DATA_HOME:=$HOME/.local/share}/../lib/sshag" - system_dir='/usr/local/lib/sshag' - - [ -n "$custom_dir" ] && [ -d "$custom_dir" ] && dir="$custom_dir" - [ -n "$custom_dir" ] && [ -d "$custom_dir/sshag" ] && dir="$custom_dir/sshag" - [ -z "$dir" ] && [ -d "$user_dir" ] && dir="$user_dir" - [ -z "$dir" ] && [ -d "$system_dir" ] && dir="$system_dir" - - if [ -z "$dir" ]; then - print_error "Cannot find 'sshag' repo at either" - [ -n "$custom_dir" ] && print_error " '$custom_dir', or" - print_error " '$user_dir', or" - exit_error " '$system_dir'." - else - print_line "Updating 'sshag' at '$dir'." - fi - - [ "$dir" = "$system_dir" ] && SUDO='sudo' + [ -d "$1/sshag" ] && dir="$1/sshag" || dir="$1" + print_info "Updating 'sshag' at '$dir'." cd "$dir" - $SUDO sh -c 'git pull' + git pull ) -# HELPERS +# == HELPERS == -# Call in a subshell -expand_tilde() { - expand_tilde_="${1#\~/}" - [ "$1" != "$expand_tilde_" ] && expand_tilde_="$HOME/$expand_tilde_" - printf "$expand_tilde_" - unset expand_tilde_ -} +print_error() { print_stderr "ERROR: $@"; return 1; } +print_fatal() { print_stderr "FATAL: $@"; exit 1; } +print_info() { print_stderr "INFO: $@"; return 1; } +print_warning() { print_stderr "WARNING: $@"; return 1; } -exit_error() { - print_error "$@" - exit 1 -} - -print_error() { - print_stderr "ERROR: $@" - return 1 -} +print_stderr() { print_line "$@" >&2; } # Do not send messages to 'stdout' +# - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell -print_line() { - printf "$@\n" -} +print_line() { printf "$@\n"; } -# Do not send messages to 'stdout' -# - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell -print_stderr() { - print_line "$@" >&2 +require_command() { + [ ! -x "$(command -v "$1")" ] && print_fatal "'$1' is not available! aborting!" } -print_warning() { - print_stderr "WARNING: $@" - return 1 -} +# == HOOK == -main "$@" +sshag_running_as_command && sshag "$@" From 58c10b86c57f82e5e88779a61e033e8eb7b3e3de Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 09:36:59 -0400 Subject: [PATCH 13/45] fix spellcheck errors and warnings --- pearl-config/config.sh | 2 + pearl-config/install.sh | 2 + sshag.sh | 103 +++++++++++++++++++++++----------------- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/pearl-config/config.sh b/pearl-config/config.sh index 8781126..86829ce 100644 --- a/pearl-config/config.sh +++ b/pearl-config/config.sh @@ -1,2 +1,4 @@ +# shellcheck shell=sh + # load `sshag` into current environment . "$PEARL_PKGDIR/sshag.sh" diff --git a/pearl-config/install.sh b/pearl-config/install.sh index e2d0d5d..af8e544 100644 --- a/pearl-config/install.sh +++ b/pearl-config/install.sh @@ -1,3 +1,5 @@ +# shellcheck shell=sh + post_install() { . "$PEARL_PKGDIR/pearl-config/config.sh" # configure sshag # invoke diff --git a/sshag.sh b/sshag.sh index 193ed7e..3defcbc 100755 --- a/sshag.sh +++ b/sshag.sh @@ -14,26 +14,25 @@ type sshag 2>dev/null | grep 'is a function' \ && return # USAGE -# sshag install [DEST_DIR] - install/update -# sshag update [DEST_DIR] - update +# sshag install [TARGET_DIR] - install/update +# sshag update [TARGET_DIR] - update # sshag - start/use agent # sshag AGENT_SOCKET - use specified agent # sshag USER@HOST [SSH_OPTIONS_AND_ARGS] - start agent and ssh to USER@HOST sshag() { - unset ssh_args unset agent_socket - unset user_hostname + unset user_host while [ $# -gt 0 ]; do case "$1" in install) shift; sshag_install 'install' "$@"; return $? ;; update) shift; sshag_install 'update' "$@"; return $? ;; - -*) ssh_args="$ssh_args $1" ;; + -*) break ;; # ssh options *) if [ -e "$1" ] ; then - [ -S "$1" ] && agent_socket="$1" + agent_socket="$1" else - user_hostname="$1" + user_host="$1" fi ;; esac @@ -43,8 +42,8 @@ sshag() { sshag_require_ssh sshag_agent_get_socket "$agent_socket" || sshag_agent_new_socket - if [ -n "$user_hostname" ]; then - sshag_ssh "$user_hostname" "$ssh_args" + if [ -n "$user_host" ]; then + sshag_ssh "$user_host" "$@" else sshag_running_as_command && sshag_agent_print_notice sshag_agent_print_keys @@ -52,17 +51,15 @@ sshag() { } sshag_require_ssh() { - for util in ssh ssh-add ssh-agent; do - require_command "$util" + for app in ssh ssh-add ssh-agent; do + require_command "$app" done } # == Get/Start SSH-AGENT == -# $1 - Agent Socket +# $1 - optional. Agent Socket sshag_agent_get_socket() { - unset found_agent - # Attempt to use socket passed in sshag_agent_vet_socket "$1" && return @@ -81,7 +78,7 @@ sshag_agent_get_socket() { return 1 } -# $1 - Agent Socket +# $1 - optional. Agent Socket sshag_agent_vet_socket() { [ -z "$1" ] && return 1 @@ -100,7 +97,7 @@ sshag_agent_vet_socket() { sshag_agent_find_sockets() { # OpenSSH only uses these two dirs for dir in '/tmp' "$TMPDIR"; do - find "$dir" -user $(id -u) -type s -path '*/ssh-*/agent.*' 2>/dev/null + find "$dir" -user "$(id -u)" -type s -path '*/ssh-*/agent.*' 2>/dev/null done | sort -u } @@ -129,6 +126,8 @@ sshag_agent_print_keys() { # == SSH wrapper == # Load first key for specified user@hostname and start `ssh`. +# $1 - required. user@host +# $@ - optional. ssh options sshag_ssh() ( # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, @@ -140,23 +139,26 @@ sshag_ssh() ( # on multiple machines where only some support AddKeysToAgent. # (OpenSSH before v7.2 barfs on params it doesn't know about so can't use # it in a common ssh_config where some machines have pre v7.2 OpenSSH.) + + unset ssh_opts user_host="$1" shift - ssh_args="$@" if sshag_ssh_config_has_add_keys; then # Honor AddKeysToAgent settings : # do nothing elif ssh -o AddKeysToAgent 2>&1 | grep 'missing argument' >/dev/null; then # If this ssh supports AddKeyToAgent, then use it - ssh_args="$ssh_args -o AddKeysToAgent=yes" + ssh_opts='-o AddKeysToAgent=yes' else # This is needed for OpenSSH pre v7.2, before AddKeysToAgent was added - sshag_ssh_add_key_to_agent "$1" + sshag_ssh_add_key_to_agent "$user_host" fi - ssh $ssh_args "$user_host" + # `$ssh_opts` may be unset, quoting it will pass an empty string to `ssh` + # shellcheck disable=SC2086,SC2029 + ssh "$@" $ssh_opts "$user_host" ) # Checks if ~/.ssh/config has AddKeysToAgent @@ -169,6 +171,7 @@ sshag_ssh_config_has_add_keys() { # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, # but it is not set in the ~/.ssh/config +# $1 - required. user@host sshag_ssh_add_key_to_agent() { sshag_ssh_is_identity_loaded "$1" && return @@ -179,11 +182,13 @@ sshag_ssh_add_key_to_agent() { fi } +# $1 - required. user@host sshag_ssh_is_identity_loaded() { - echo 'exit' | ssh -o BatchMode=yes -- $1 2>/dev/null + echo 'exit' | ssh -o BatchMode=yes -- "$1" 2>/dev/null return $? } +# $1 - required. user@host sshag_ssh_get_identity() { sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ | awk ' /identity file/ { print $4 } ' \ @@ -191,11 +196,13 @@ sshag_ssh_get_identity() { [ -n "$sshag_identity" ] \ && sshag_identity="$(realpath -m "$sshag_identity")" \ - && printf '%s' "$sshag_identify" + && printf '%s' "$sshag_identity" } # == INSTALL == +# $1 - required. action - install or update +# $2 - optional. install directory sshag_install() ( require_command 'git' dir="$(sshag_install_path "$2")" @@ -206,12 +213,14 @@ sshag_install() ( fi print_info "Installing 'sshag' to '$dir'." - __SSHAG_CONFIG=". '$dir/sshag/sshag.sh'; sshag >/dev/null" sshag_install_download "$dir" - sshag_install_profiles "$dir" - sshag_install_manual + + sshag_config=". '$dir/sshag/sshag.sh'; sshag >/dev/null" + sshag_install_profiles "$sshag_config" + sshag_install_manual "$sshag_config" ) +# $1 - optional. install directory sshag_install_path() { unset dir system_dir='/usr/local/lib' @@ -229,65 +238,71 @@ sshag_install_path() { printf '%s' "$dir" } +# $1 - required. install directory sshag_install_download() { - cd "$1" + cd "$1" || print_fatal "Cannot accees '$1'" git clone 'https://github.com/go2null/sshag.git' print_info "'sshag' installed to '$1'" } # add to shell startup files +# $1 - required. sshag config line sshag_install_profiles() { if [ "$USER" = 'root' ]; then if touch '/etc/profile.d/sshag.sh' 2>/dev/null; then - sshag_install_profile '/etc/profile.d/sshag.sh' + sshag_install_profile "$1" '/etc/profile.d/sshag.sh' else - sshag_install_profile '/etc/profile' + sshag_install_profile "$1" '/etc/profile' fi else - sshag_install_profile "$HOME/.profile" - sshag_install_profile "$HOME/.bash_profile" - sshag_install_profile "$HOME/.bashrc" - sshag_install_profile "$HOME/.zshrc" + sshag_install_profile "$1" "$HOME/.profile" + sshag_install_profile "$1" "$HOME/.bash_profile" + sshag_install_profile "$1" "$HOME/.bashrc" + sshag_install_profile "$1" "$HOME/.zshrc" fi } +# $1 - required. config line +# $2 - required. config file sshag_install_profile() { - [ -w "$1" ] || return 1 + [ -w "$2" ] || return 1 - if grep "^[ \t]*$__SSHAG_CONFIG" "$1" >/dev/null; then - print_info "'sshag' already in startup file '$1'" + if grep "^[ \t]*$1" "$2" >/dev/null; then + print_info "'sshag' already in startup file '$2'" return fi - print_line "$__SSHAG_CONFIG" >> "$1" - print_info "'sshag' added to startup file '$1'" + print_line "$1" >> "$2" + print_info "'sshag' added to startup file '$2'" } +# $1 - required. config line sshag_install_manual() { print_info "Add the following to any additional shell startup files:" - print_info " $__SSHAG_CONFIG" + print_info " $1" } +# $1 - required. install directory sshag_update() ( [ -d "$1/sshag" ] && dir="$1/sshag" || dir="$1" print_info "Updating 'sshag' at '$dir'." - cd "$dir" + cd "$dir" || print_fatal "Cannot accees '$dir'" git pull ) # == HELPERS == -print_error() { print_stderr "ERROR: $@"; return 1; } -print_fatal() { print_stderr "FATAL: $@"; exit 1; } -print_info() { print_stderr "INFO: $@"; return 1; } -print_warning() { print_stderr "WARNING: $@"; return 1; } +print_error() { print_stderr "ERROR: $*"; return 1; } +print_fatal() { print_stderr "FATAL: $*"; exit 1; } +print_info() { print_stderr "INFO: $*"; return 1; } +print_warning() { print_stderr "WARNING: $*"; return 1; } print_stderr() { print_line "$@" >&2; } # Do not send messages to 'stdout' # - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell -print_line() { printf "$@\n"; } +print_line() { printf '%s\n' "$*"; } require_command() { [ ! -x "$(command -v "$1")" ] && print_fatal "'$1' is not available! aborting!" From d98ddf06dfe7912ca2b9b805fcfc00a98000bfaa Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 09:47:05 -0400 Subject: [PATCH 14/45] match pearn adn manual install behaviour --- pearl-config/config.sh | 3 ++- pearl-config/install.sh | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pearl-config/config.sh b/pearl-config/config.sh index 86829ce..a5c0b5e 100644 --- a/pearl-config/config.sh +++ b/pearl-config/config.sh @@ -1,4 +1,5 @@ # shellcheck shell=sh # load `sshag` into current environment -. "$PEARL_PKGDIR/sshag.sh" +. "$PEARL_PKGDIR/sshag.sh" # configure +sshag >/dev/null # invoke diff --git a/pearl-config/install.sh b/pearl-config/install.sh index af8e544..dbee36c 100644 --- a/pearl-config/install.sh +++ b/pearl-config/install.sh @@ -1,8 +1,7 @@ # shellcheck shell=sh post_install() { - . "$PEARL_PKGDIR/pearl-config/config.sh" # configure - sshag # invoke + . "$PEARL_PKGDIR/pearl-config/config.sh" } post_update() { From 0feb96e6b1aba2a51b033e1cedc9860b0b387317 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 11:00:52 -0400 Subject: [PATCH 15/45] ignore vim swap files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..566bdf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# vim +*.swp From 245735aaea2dd7d51787b4064800e75beb304eaf Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 11:10:48 -0400 Subject: [PATCH 16/45] fix check if already sourced --- sshag.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sshag.sh b/sshag.sh index 3defcbc..ef6043c 100755 --- a/sshag.sh +++ b/sshag.sh @@ -9,7 +9,7 @@ sshag_running_as_command() { # only allow to source once. # this simplifies the installation by adding to all the dot profiles and only source once. -type sshag 2>dev/null | grep 'is a function' \ +type sshag 2>/dev/null | grep 'is a function' \ && ! sshag_running_as_command \ && return From c5639e36f19853a37f32a290dea44d242f031a2c Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 12:09:26 -0400 Subject: [PATCH 17/45] simplify installation instructions --- README.md | 37 +++++++++++-------------------------- sshag.sh | 5 +++-- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 8f1ac3e..14ab0b8 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ the output will always consist of just the socket location. ### Recommended installation method -Use [pearl] package manager. +Use the [pearl] package manager. ```sh -# add repo +# install printf '%s=%s\n' \ "PEARL_PACKAGES['sshag']" \ "'https://github.com/go2null/sshag.git'" \ @@ -33,38 +33,23 @@ printf '%s=%s\n' \ "PEARL_PACKAGES_DESCR['sshag']" \ "'Hook up with an operating or new SSH agent'" \ >> "$HOME/.config/pearl/pearl.conf" -# install pearl install sshag + # update pearl update sshag ``` -### Built-in installation - -Automatic Install -```sh -sshag install [] -``` - -Automatic Update -```sh -sshag update [] -``` +### Manual installation -Manual Install ```sh -cd /custom/install/path -git clone 'https://github.com/go2null/sshag.git' - -# then add the following to your shell startup file (`~/.bashrc`, `~/.zshrc`): -# note the leading dot (`.`) which is the POSIX `source` command. -. ; sshag >/dev/null -``` +# install +wget https://raw.githubusercontent.com/go2null/sshag/stable/sshag.sh +. sshag.sh +rm sshag.sh +sshag install -Manual Update -```sh -cd /custom/install/path/sshag" -git pull +# update +sshag update ``` ## Usage diff --git a/sshag.sh b/sshag.sh index ef6043c..2b87969 100755 --- a/sshag.sh +++ b/sshag.sh @@ -1,7 +1,8 @@ #!/bin/sh # acquired courtesy of -# http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 +# http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 +# Project at: https://github.com/go2null/sshag sshag_running_as_command() { [ "${0#*sshag}" != "$0" ] @@ -71,7 +72,7 @@ sshag_agent_get_socket() { # before starting a fresh ssh-agent process. # ssh agent sockets can be attached to an ssh daemon process # or an ssh-agent process. - for agent_socket in $(sshag_agent_find_sockets) ; do + for agent_socket in $(sshag_agent_find_sockets); do sshag_agent_vet_socket "$agent_socket" && return done From 14d441d511524f3b018460f3ba058f41f3397c04 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 1 Apr 2022 16:49:33 -0400 Subject: [PATCH 18/45] add uninstall action --- CHANGELOG.md | 16 +++++--- README.md | 3 ++ sshag.sh | 106 +++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 95 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85afd21..48dedb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,15 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic Versioning]. ## [Unreleased] + +## [2.0.0] - 2022-04-01 ### Fixed * _go2null_: Fix bug #2 path to LICENSE. +### Added +* _go2null_: Ability to uninstall. ### Changed -* _go2null_: install now defaults to _system_ only if running as `root`. -* _go2null_: install now defaults to `~/.local/lib` per `systemd` standard. +* _go2null_: __BREAKING__: install now defaults to _system_ only if running as `root`. +* _go2null_: __BREAKING__: install now defaults to `~/.local/lib` per `systemd` standard. ## [1.3.1] - 2018-02-19 ### Added @@ -48,7 +52,7 @@ and this project adheres to [Semantic Versioning]. ### Added * _intuited_: Add readme and license documents. ### Changed -* _intuited_: Renamed from `sagent` to `sshag`. +* _intuited_: __BREAKING__: Renamed from `sagent` to `sshag`. ## [0.0.0] - 2010-05-14 ### Added @@ -59,10 +63,12 @@ and this project adheres to [Semantic Versioning]. [Semantic Versioning]: http://semver.org [pearl]: https://github.com/pearl-core/pearl#installation -[Unreleased]: https://github.com/go2null/sshag/compare/1.3.1...HEAD +[Unreleased]: https://github.com/go2null/sshag/compare/2.0.0...HEAD +[2.0.0]: https://github.com/go2null/sshag/compare/1.3.0....2.0.0 [1.3.1]: https://github.com/go2null/sshag/compare/1.3.0....1.3.1 [1.3.0]: https://github.com/go2null/sshag/compare/1.2.1....1.3.0 [1.2.1]: https://github.com/go2null/sshag/compare/1.2.0....1.2.1 [1.2.0]: https://github.com/go2null/sshag/compare/1.1.0....1.2.0 -[1.1.0]: https://github.com/go2null/sshag/compare/0.0.0....1.1.0 +[1.1.0]: https://github.com/go2null/sshag/compare/1.0.0....1.1.0 +[1.0.0]: https://github.com/go2null/sshag/compare/0.0.0....1.0.0 [0.0.0]: https://github.com/go2null/sshag/releases/tag/0.0.0 diff --git a/README.md b/README.md index 14ab0b8..1f27d7d 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ sshag install # update sshag update + +# uninstall/remove +sshag remove ``` ## Usage diff --git a/sshag.sh b/sshag.sh index 2b87969..fa2d7e6 100755 --- a/sshag.sh +++ b/sshag.sh @@ -8,6 +8,8 @@ sshag_running_as_command() { [ "${0#*sshag}" != "$0" ] } +printf '%s\n' "$0" >&2 + # only allow to source once. # this simplifies the installation by adding to all the dot profiles and only source once. type sshag 2>/dev/null | grep 'is a function' \ @@ -15,8 +17,9 @@ type sshag 2>/dev/null | grep 'is a function' \ && return # USAGE -# sshag install [TARGET_DIR] - install/update -# sshag update [TARGET_DIR] - update +# sshag install [TARGET_DIR] - install/update +# sshag update [TARGET_DIR] - update +# sshag uninstall [TARGET_DIR] - uninstall # sshag - start/use agent # sshag AGENT_SOCKET - use specified agent # sshag USER@HOST [SSH_OPTIONS_AND_ARGS] - start agent and ssh to USER@HOST @@ -26,8 +29,10 @@ sshag() { while [ $# -gt 0 ]; do case "$1" in - install) shift; sshag_install 'install' "$@"; return $? ;; - update) shift; sshag_install 'update' "$@"; return $? ;; + install) shift; sshag_install 'install' "$@"; return $? ;; + update) shift; sshag_install 'update' "$@"; return $? ;; + uninstall) shift; sshag_install 'remove' "$@"; return $? ;; + remove) shift; sshag_install 'remove' "$@"; return $? ;; -*) break ;; # ssh options *) if [ -e "$1" ] ; then @@ -202,20 +207,34 @@ sshag_ssh_get_identity() { # == INSTALL == -# $1 - required. action - install or update +# $1 - required. action - install, update, or remove # $2 - optional. install directory sshag_install() ( require_command 'git' + dir="$(sshag_install_path "$2")" + [ "$dir" != "${dir%/sshag}" ] && dir="${dir%/sshag}" + + if [ -d "$dir/sshag" ]; then + case "$1" in + install|update) + sshag_update "$dir" + return $? + ;; + remove) + sshag_remove "$dir" + return $? + ;; + esac + fi - if [ "$1" = 'update' ] || [ -d "$dir/sshag" ]; then - sshag_update "$dir" - return $? - fi + [ "$1" = 'remove' ] \ + && print_fatal "Cannot detect where 'sshag' is installed" - print_info "Installing 'sshag' to '$dir'." + print_info "Installing to $dir." sshag_install_download "$dir" + print_info "Adding to startup files" sshag_config=". '$dir/sshag/sshag.sh'; sshag >/dev/null" sshag_install_profiles "$sshag_config" sshag_install_manual "$sshag_config" @@ -229,21 +248,22 @@ sshag_install_path() { if [ -n "$1" ]; then dir="$(realpath -m "$1" 2>/dev/null)" - [ -z "$dir" ] && print_fatal "Invalid directory '$1'" + [ -z "$dir" ] && print_fatal " Invalid directory $1." fi [ -z "$dir" ] && [ "$USER" = 'root' ] && dir="$system_dir" [ -z "$dir" ] && dir="$user_dir" - [ -d "$dir" ] || mkdir -p "$dir" || print_fatal "Cannot create directory '$dir'" + [ -d "$dir" ] || mkdir -p "$dir" || print_fatal " Cannot create directory $dir." printf '%s' "$dir" } # $1 - required. install directory sshag_install_download() { - cd "$1" || print_fatal "Cannot accees '$1'" - git clone 'https://github.com/go2null/sshag.git' - print_info "'sshag' installed to '$1'" + cd "$1" || print_fatal " Cannot accees $1." + + git clone 'https://github.com/go2null/sshag.git' \ + || print_fatal " 'git clone' failed with above error." } # add to shell startup files @@ -270,28 +290,64 @@ sshag_install_profile() { if grep "^[ \t]*$1" "$2" >/dev/null; then - print_info "'sshag' already in startup file '$2'" + print_info " SKIPPED $2, already added." return fi print_line "$1" >> "$2" - print_info "'sshag' added to startup file '$2'" + print_info " ADDED to '$2'" } # $1 - required. config line sshag_install_manual() { - print_info "Add the following to any additional shell startup files:" - print_info " $1" + print_info "Add the following to any additional shell startup files" + print_info " $1" } # $1 - required. install directory -sshag_update() ( - [ -d "$1/sshag" ] && dir="$1/sshag" || dir="$1" - - print_info "Updating 'sshag' at '$dir'." - cd "$dir" || print_fatal "Cannot accees '$dir'" +sshag_update() { + print_info "Updating 'sshag' at $1." + cd "$1/sshag" || print_fatal " Cannot accees $1/sshag." git pull -) +} + +# $1 - required. install directory +sshag_remove() { + print_info "Removing 'sshag' at $1." + rm -rf "$1/sshag" + + print_info "Removing from startup files" + sshag_remove_profiles +} + +sshag_remove_profiles() { + file='/etc/profile.d/sshag.sh' + [ -w "$file" ] && print_info " REMOVED $file." && rm "$file" + + files="/etc/profile +$HOME/.profile +$HOME/.bash_profile +$HOME/.bashrc +$HOME/.zshrc" + + while IFS='' read -r file; do + sshag_remove_profile "$file" + done <<- EOF + $files + EOF +} + +# $1 - required. config file +sshag_remove_profile() { + [ -e "$1" ] || return + grep 'sshag.sh' "$1" 1>/dev/null 2>&1 || return + + print_info " $1" + [ ! -w "$1" ] && print_warning " SKIPPED, cannot edit file" && return + + sed -i.bak '/.*sshag.sh.*/ d' "$1" \ + || print_warning " FAILED to remove" +} # == HELPERS == From 840e4f88532d929c4d14351c89ed0341572fd32d Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Sat, 2 Apr 2022 14:27:16 -0400 Subject: [PATCH 19/45] fix bug causing sshag function to run for each startup file * return 1 and change config line from ; to && --- sshag.sh | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/sshag.sh b/sshag.sh index fa2d7e6..14e1334 100755 --- a/sshag.sh +++ b/sshag.sh @@ -4,17 +4,17 @@ # http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 # Project at: https://github.com/go2null/sshag +sshag_function_is_defined() { + type sshag >/dev/null 2>&1 +} + sshag_running_as_command() { [ "${0#*sshag}" != "$0" ] } -printf '%s\n' "$0" >&2 - -# only allow to source once. +# only allow to source file once. # this simplifies the installation by adding to all the dot profiles and only source once. -type sshag 2>/dev/null | grep 'is a function' \ - && ! sshag_running_as_command \ - && return +sshag_function_is_defined && ! sshag_running_as_command && return 1 # USAGE # sshag install [TARGET_DIR] - install/update @@ -113,6 +113,7 @@ sshag_agent_new_socket() { sshag_agent_print_notice() { print_info "$(cat <<- NOTICE + Do the following to add the ssh-agent to your current session export SSH_AGENT_SOCK="\$(sh '$0')" Or, simply source the file @@ -235,7 +236,7 @@ sshag_install() ( sshag_install_download "$dir" print_info "Adding to startup files" - sshag_config=". '$dir/sshag/sshag.sh'; sshag >/dev/null" + sshag_config=". '$dir/sshag/sshag.sh' && sshag >/dev/null" sshag_install_profiles "$sshag_config" sshag_install_manual "$sshag_config" ) @@ -351,20 +352,21 @@ sshag_remove_profile() { # == HELPERS == +print_line() { printf '%s\n' "$*"; } + +print_stderr() { print_line "$@" >&2; } +# Do not send messages to 'stdout' +# - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell + print_error() { print_stderr "ERROR: $*"; return 1; } print_fatal() { print_stderr "FATAL: $*"; exit 1; } print_info() { print_stderr "INFO: $*"; return 1; } print_warning() { print_stderr "WARNING: $*"; return 1; } -print_stderr() { print_line "$@" >&2; } # Do not send messages to 'stdout' -# - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell - -print_line() { printf '%s\n' "$*"; } - require_command() { [ ! -x "$(command -v "$1")" ] && print_fatal "'$1' is not available! aborting!" } # == HOOK == -sshag_running_as_command && sshag "$@" +sshag "$@" From 7e2d10bf65b9e626ae006ae204662398dc263757 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 2 Apr 2025 11:54:37 -0400 Subject: [PATCH 20/45] =?UTF-8?q?=F0=9F=90=9B=20fix=20detecting=20when=20s?= =?UTF-8?q?ourced=20in=20ZSH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/sshag.sh b/sshag.sh index 14e1334..e24f7fc 100755 --- a/sshag.sh +++ b/sshag.sh @@ -8,13 +8,20 @@ sshag_function_is_defined() { type sshag >/dev/null 2>&1 } -sshag_running_as_command() { - [ "${0#*sshag}" != "$0" ] +sshag_is_sourced() { + # zsh appends `:file` to `$ZSH_EVAL_CONTEXT` when sourced + if [ -n "$ZSH_VERSION" ]; then + [ "${ZSH_EVAL_CONTEXT#*:file}" != "$ZSH_EVAL_CONTEXT" ] && return 0 + return 1 + fi + + [ "${0#*sshag}" = "$0" ] } -# only allow to source file once. -# this simplifies the installation by adding to all the dot profiles and only source once. -sshag_function_is_defined && ! sshag_running_as_command && return 1 +# Only allow to source file once. +# This simplifies the installation by adding to all the dot profiles +# and only source once. +sshag_function_is_defined && sshag_is_sourced && return 1 # USAGE # sshag install [TARGET_DIR] - install/update @@ -51,7 +58,7 @@ sshag() { if [ -n "$user_host" ]; then sshag_ssh "$user_host" "$@" else - sshag_running_as_command && sshag_agent_print_notice + sshag_is_sourced || sshag_agent_print_notice sshag_agent_print_keys fi } From f9b65b34447242756d089cd9ddf33a67f7519288 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 2 Apr 2025 12:07:35 -0400 Subject: [PATCH 21/45] =?UTF-8?q?=F0=9F=8E=A8=20consistent=20use=20of=20TA?= =?UTF-8?q?B=20for=20indentation=20and=20SPACE=20for=20alignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/sshag.sh b/sshag.sh index e24f7fc..537eca2 100755 --- a/sshag.sh +++ b/sshag.sh @@ -1,7 +1,7 @@ #!/bin/sh # acquired courtesy of -# http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 +# http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 # Project at: https://github.com/go2null/sshag sshag_function_is_defined() { @@ -122,11 +122,11 @@ sshag_agent_print_notice() { print_info "$(cat <<- NOTICE Do the following to add the ssh-agent to your current session - export SSH_AGENT_SOCK="\$(sh '$0')" + export SSH_AGENT_SOCK="\$(sh '$0')" Or, simply source the file - source '$0' + source '$0' If it is already sourced, but your agent is dead, then just - sshag + sshag NOTICE )" } @@ -153,7 +153,7 @@ sshag_ssh() ( # on multiple machines where only some support AddKeysToAgent. # (OpenSSH before v7.2 barfs on params it doesn't know about so can't use # it in a common ssh_config where some machines have pre v7.2 OpenSSH.) - + unset ssh_opts user_host="$1" @@ -204,11 +204,11 @@ sshag_ssh_is_identity_loaded() { # $1 - required. user@host sshag_ssh_get_identity() { - sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ - | awk ' /identity file/ { print $4 } ' \ - | head -n 1)" + sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ + | awk ' /identity file/ { print $4 } ' \ + | head -n 1)" - [ -n "$sshag_identity" ] \ + [ -n "$sshag_identity" ] \ && sshag_identity="$(realpath -m "$sshag_identity")" \ && printf '%s' "$sshag_identity" } @@ -221,19 +221,19 @@ sshag_install() ( require_command 'git' dir="$(sshag_install_path "$2")" - [ "$dir" != "${dir%/sshag}" ] && dir="${dir%/sshag}" + [ "$dir" != "${dir%/sshag}" ] && dir="${dir%/sshag}" if [ -d "$dir/sshag" ]; then case "$1" in install|update) - sshag_update "$dir" - return $? - ;; - remove) - sshag_remove "$dir" - return $? - ;; - esac + sshag_update "$dir" + return $? + ;; + remove) + sshag_remove "$dir" + return $? + ;; + esac fi [ "$1" = 'remove' ] \ @@ -243,7 +243,7 @@ sshag_install() ( sshag_install_download "$dir" print_info "Adding to startup files" - sshag_config=". '$dir/sshag/sshag.sh' && sshag >/dev/null" + sshag_config=". '$dir/sshag/sshag.sh' && sshag >/dev/null" sshag_install_profiles "$sshag_config" sshag_install_manual "$sshag_config" ) @@ -299,13 +299,13 @@ sshag_install_profile() { if grep "^[ \t]*$1" "$2" >/dev/null; then print_info " SKIPPED $2, already added." - return + return fi print_line "$1" >> "$2" print_info " ADDED to '$2'" } - + # $1 - required. config line sshag_install_manual() { print_info "Add the following to any additional shell startup files" @@ -338,7 +338,7 @@ $HOME/.bash_profile $HOME/.bashrc $HOME/.zshrc" - while IFS='' read -r file; do + while IFS='' read -r file; do sshag_remove_profile "$file" done <<- EOF $files @@ -361,7 +361,7 @@ sshag_remove_profile() { print_line() { printf '%s\n' "$*"; } -print_stderr() { print_line "$@" >&2; } +print_stderr() { print_line "$@" >&2; } # Do not send messages to 'stdout' # - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell From 0152470153d7cb95c3eaa6d6834c3a1fd36b20bc Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 2 Apr 2025 12:21:26 -0400 Subject: [PATCH 22/45] =?UTF-8?q?=F0=9F=8E=A8=20remove=20unnecessary=20hig?= =?UTF-8?q?hlights=20in=20output=20strings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/sshag.sh b/sshag.sh index 537eca2..3c55912 100755 --- a/sshag.sh +++ b/sshag.sh @@ -100,10 +100,10 @@ sshag_agent_vet_socket() { ssh-add -l >/dev/null 2>&1 if [ $? -eq 2 ]; then rm -f "$SSH_AUTH_SOCK" - print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleted!" + print_warning "Socket $SSH_AUTH_SOCK is dead! Deleted!" fi else - print_warning "'$SSH_AUTH_SOCK' is not a socket!" + print_warning "$SSH_AUTH_SOCK is not a socket!" fi } @@ -192,7 +192,7 @@ sshag_ssh_add_key_to_agent() { # load identity if one is defined for the user@hostname. sshag_identity="$(sshag_ssh_get_identity "$1")" if [ -n "$sshag_identity" ] && ! ssh-add "$sshag_identity"; then - print_error "Unable to load identity '$sshag_identity'!" + print_error "Unable to load identity $sshag_identity!" fi } @@ -268,7 +268,7 @@ sshag_install_path() { # $1 - required. install directory sshag_install_download() { - cd "$1" || print_fatal " Cannot accees $1." + cd "$1" || print_fatal " Cannot access $1." git clone 'https://github.com/go2null/sshag.git' \ || print_fatal " 'git clone' failed with above error." @@ -296,14 +296,13 @@ sshag_install_profiles() { sshag_install_profile() { [ -w "$2" ] || return 1 - if grep "^[ \t]*$1" "$2" >/dev/null; then print_info " SKIPPED $2, already added." return fi print_line "$1" >> "$2" - print_info " ADDED to '$2'" + print_info " ADDED to $2." } # $1 - required. config line @@ -351,10 +350,10 @@ sshag_remove_profile() { grep 'sshag.sh' "$1" 1>/dev/null 2>&1 || return print_info " $1" - [ ! -w "$1" ] && print_warning " SKIPPED, cannot edit file" && return + [ ! -w "$1" ] && print_warning " SKIPPED, cannot edit file." && return sed -i.bak '/.*sshag.sh.*/ d' "$1" \ - || print_warning " FAILED to remove" + || print_warning " FAILED to remove from file." } # == HELPERS == @@ -371,7 +370,7 @@ print_info() { print_stderr "INFO: $*"; return 1; } print_warning() { print_stderr "WARNING: $*"; return 1; } require_command() { - [ ! -x "$(command -v "$1")" ] && print_fatal "'$1' is not available! aborting!" + [ ! -x "$(command -v "$1")" ] && print_fatal "$1 is not available! aborting!" } # == HOOK == From f17746d91da96b688a5a90b8396ecd1283e0bd13 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 2 Apr 2025 12:40:38 -0400 Subject: [PATCH 23/45] =?UTF-8?q?=F0=9F=92=A5=20install=20to=20XDG=5FDATA?= =?UTF-8?q?=5FDIR/lib=20Directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit change default install path from $HOME/.local/lib to $XDG_DATA_HOME/lib ($HOME/.local/share/lib) --- README.md | 2 +- sshag.sh | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1f27d7d..ba9549c 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Keys: Invoked ```sh -$ export SSH_AGENT_SOCK=$(sh ~/.local/lib/sshag/sshag.sh) +$ export SSH_AGENT_SOCK=$(sh ~/.local/share/lib/sshag/sshag.sh) Output should be assigned to the environment variable SSH_AUTH_SOCK. Keys: 256 SHA256:2TWr3x/H6eGvE+vx9Ur8uFQWBIXTBH3jT12yHBB4TJY austin@powers (ED25519) diff --git a/sshag.sh b/sshag.sh index 3c55912..16c5576 100755 --- a/sshag.sh +++ b/sshag.sh @@ -221,7 +221,7 @@ sshag_install() ( require_command 'git' dir="$(sshag_install_path "$2")" - [ "$dir" != "${dir%/sshag}" ] && dir="${dir%/sshag}" + dir="${dir%/sshag}" # strip 'sshag' from path, as necessary if [ -d "$dir/sshag" ]; then case "$1" in @@ -248,20 +248,22 @@ sshag_install() ( sshag_install_manual "$sshag_config" ) -# $1 - optional. install directory +# $1 - optional. install parent directory sshag_install_path() { unset dir - system_dir='/usr/local/lib' - user_dir="$HOME/.local/lib" if [ -n "$1" ]; then dir="$(realpath -m "$1" 2>/dev/null)" [ -z "$dir" ] && print_fatal " Invalid directory $1." + else + if [ "$USER" = 'root' ]; then + dir="${XDG_DATA_DIRS%%:*}" # use first entry + dir="${dir:-/usr/local/share}/lib" # default value + else + dir="${XDG_DATA_HOME:-$HOME/.local/share}/lib" # prefer XDG + fi fi - [ -z "$dir" ] && [ "$USER" = 'root' ] && dir="$system_dir" - [ -z "$dir" ] && dir="$user_dir" - [ -d "$dir" ] || mkdir -p "$dir" || print_fatal " Cannot create directory $dir." printf '%s' "$dir" } From 6d632fbd30599cf996f53f49ce083cd8f4fee189 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 2 Apr 2025 18:37:12 -0400 Subject: [PATCH 24/45] =?UTF-8?q?=E2=9C=A8=20migrate=20existing=20installs?= =?UTF-8?q?=20to=20the=20new=20location?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/sshag.sh b/sshag.sh index 16c5576..284ce9a 100755 --- a/sshag.sh +++ b/sshag.sh @@ -258,9 +258,16 @@ sshag_install_path() { else if [ "$USER" = 'root' ]; then dir="${XDG_DATA_DIRS%%:*}" # use first entry - dir="${dir:-/usr/local/share}/lib" # default value + dir="${dir:-/usr/local/share}/lib" # default XDG value + + sshag_install_migrate '/usr/local/lib' "$dir" # v1.3.0 path else - dir="${XDG_DATA_HOME:-$HOME/.local/share}/lib" # prefer XDG + : "${XDG_DATA_HOME:=$HOME/.local/share}" # set XDG + + dir="$XDG_DATA_HOME/lib" + + sshag_install_migrate "$XDG_DATA_HOME/../lib" "$dir" # v1.3.0 path + sshag_install_migrate "$HOME/.local/lib" "$dir" # v2.0.0 path fi fi @@ -268,6 +275,19 @@ sshag_install_path() { printf '%s' "$dir" } +# $1 - from path +# $2 - to path +sshag_install_migrate() { + [ -d "$1/sshag" ] || return 0 + + print_info ' Migrating previous installation' + print_info " from $1/sshag" + print_info " to $2" + + mkdir -p "$2" + mv "$1/sshag" "$2/" +} + # $1 - required. install directory sshag_install_download() { cd "$1" || print_fatal " Cannot access $1." From 9e3356493ba900e41d842a83c1cc0c112bb7f3e4 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 3 Apr 2025 11:47:49 -0400 Subject: [PATCH 25/45] =?UTF-8?q?=F0=9F=91=94=20cleanup=20logic=20when=20s?= =?UTF-8?q?ourced?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sshag.sh b/sshag.sh index 284ce9a..d977b57 100755 --- a/sshag.sh +++ b/sshag.sh @@ -58,8 +58,11 @@ sshag() { if [ -n "$user_host" ]; then sshag_ssh "$user_host" "$@" else - sshag_is_sourced || sshag_agent_print_notice - sshag_agent_print_keys + if sshag_is_sourced; then + sshag_print_or_add_keys + else + sshag_agent_print_notice + fi fi } @@ -121,12 +124,10 @@ sshag_agent_new_socket() { sshag_agent_print_notice() { print_info "$(cat <<- NOTICE - Do the following to add the ssh-agent to your current session - export SSH_AGENT_SOCK="\$(sh '$0')" - Or, simply source the file - source '$0' - If it is already sourced, but your agent is dead, then just - sshag + Do the following to add the ssh-agent to your current session + export SSH_AGENT_SOCK="\$(sh '$0')" + Or, simply source the file + source '$0' NOTICE )" } @@ -243,7 +244,7 @@ sshag_install() ( sshag_install_download "$dir" print_info "Adding to startup files" - sshag_config=". '$dir/sshag/sshag.sh' && sshag >/dev/null" + sshag_config=". '$dir/sshag/sshag.sh'" sshag_install_profiles "$sshag_config" sshag_install_manual "$sshag_config" ) From 9dc5b2fe133907956049474b2bf74684c539cfe0 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 3 Apr 2025 11:51:23 -0400 Subject: [PATCH 26/45] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20remove=20unnecessary?= =?UTF-8?q?=20sort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sshag.sh b/sshag.sh index d977b57..7b5c8c9 100755 --- a/sshag.sh +++ b/sshag.sh @@ -114,7 +114,7 @@ sshag_agent_find_sockets() { # OpenSSH only uses these two dirs for dir in '/tmp' "$TMPDIR"; do find "$dir" -user "$(id -u)" -type s -path '*/ssh-*/agent.*' 2>/dev/null - done | sort -u + done } sshag_agent_new_socket() { From 21e2b9cd61f64dd8ea56eaa97ca92856c06d123d Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 3 Apr 2025 11:51:56 -0400 Subject: [PATCH 27/45] =?UTF-8?q?=E2=9C=A8=20now=20runs=20`ssh-add`=20auto?= =?UTF-8?q?matically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sshag.sh b/sshag.sh index 7b5c8c9..e9edcf2 100755 --- a/sshag.sh +++ b/sshag.sh @@ -132,10 +132,15 @@ sshag_agent_print_notice() { )" } -# Display keys currently loaded in the agent -sshag_agent_print_keys() { - print_info "Keys:" - print_info "$(ssh-add -l | sed 's/^/ /')" +# Ensure keys are loaded +sshag_print_or_add_keys() { + if keys="$(ssh-add -l 2>/dev/null)"; then + # Display keys currently loaded in the agent + print_info "Keys:" + print_info "$(printf '\t%s' "$keys")" + else + ssh-add + fi } # == SSH wrapper == From 5f13cdcc573b5435fffca3b8ea3c58011ad7774f Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Wed, 2 Apr 2025 18:43:09 -0400 Subject: [PATCH 28/45] =?UTF-8?q?=F0=9F=94=96=20release=20v3.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48dedb6..bb59404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic Versioning]. ## [Unreleased] +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security + +## [3.0.0] - 2025-04-03 +### Added +* _go2null_: ✨ runs ssh-add automatically when shell starts +* _go2null_: ✨ migrate existing installs to the new location +### Changed +* _go2null_: __BREAKING__: 💥 install to XDG_DATA_DIR/lib Directory +### Fixed +* _go2null_: 🐛 fix detecting when sourced in ZSH ## [2.0.0] - 2022-04-01 ### Fixed @@ -15,7 +30,7 @@ and this project adheres to [Semantic Versioning]. ### Changed * _go2null_: __BREAKING__: install now defaults to _system_ only if running as `root`. * _go2null_: __BREAKING__: install now defaults to `~/.local/lib` per `systemd` standard. - + ## [1.3.1] - 2018-02-19 ### Added * _go2null_: Added support for [pearl] shell package manager. @@ -63,12 +78,13 @@ and this project adheres to [Semantic Versioning]. [Semantic Versioning]: http://semver.org [pearl]: https://github.com/pearl-core/pearl#installation -[Unreleased]: https://github.com/go2null/sshag/compare/2.0.0...HEAD -[2.0.0]: https://github.com/go2null/sshag/compare/1.3.0....2.0.0 -[1.3.1]: https://github.com/go2null/sshag/compare/1.3.0....1.3.1 -[1.3.0]: https://github.com/go2null/sshag/compare/1.2.1....1.3.0 -[1.2.1]: https://github.com/go2null/sshag/compare/1.2.0....1.2.1 -[1.2.0]: https://github.com/go2null/sshag/compare/1.1.0....1.2.0 -[1.1.0]: https://github.com/go2null/sshag/compare/1.0.0....1.1.0 -[1.0.0]: https://github.com/go2null/sshag/compare/0.0.0....1.0.0 +[Unreleased]: https://github.com/go2null/sshag/compare/3.0.0..HEAD +[3.0.0]: https://github.com/go2null/sshag/compare/2.0.0...3.0.0 +[2.0.0]: https://github.com/go2null/sshag/compare/1.3.0...2.0.0 +[1.3.1]: https://github.com/go2null/sshag/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/go2null/sshag/compare/1.2.1...1.3.0 +[1.2.1]: https://github.com/go2null/sshag/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/go2null/sshag/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/go2null/sshag/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/go2null/sshag/compare/0.0.0...1.0.0 [0.0.0]: https://github.com/go2null/sshag/releases/tag/0.0.0 From 671b66c71daed84bbbaf8cb10aa012be846cc17f Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 3 Apr 2025 13:34:42 -0400 Subject: [PATCH 29/45] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20removed=20example=20?= =?UTF-8?q?code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ba9549c..0886703 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ Enter passphrase for key '/home/austin/.ssh/id_ed25519': ^C $ sshag alotta@fagina.example.com Keys: 256 SHA256:2TWr3x/H6eGvE+vx9Ur8uFQWBIXTBH3jT12yHBB4TJY austin@powers (ED25519) -/tmp/ssh-5ock3tt0m3/agent.6969 ``` Invoked From 53ba4d71abebaa2ce8605ac9052d3714f7f740bb Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 3 Apr 2025 14:11:18 -0400 Subject: [PATCH 30/45] =?UTF-8?q?=F0=9F=92=84=20removed=20extra=20whitespa?= =?UTF-8?q?ce=20from=20keys=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 +++++++- sshag.sh | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb59404..17289dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,16 @@ and this project adheres to [Semantic Versioning]. ## [Unreleased] ### Added ### Changed +* _go2null_: 💄 removed extra whitespace from keys list ### Deprecated ### Removed ### Fixed ### Security +## [3.0.1] - 2025-04-03 +### Fixed +* _go2null_: ✏️ removed example code + ## [3.0.0] - 2025-04-03 ### Added * _go2null_: ✨ runs ssh-add automatically when shell starts @@ -78,7 +83,8 @@ and this project adheres to [Semantic Versioning]. [Semantic Versioning]: http://semver.org [pearl]: https://github.com/pearl-core/pearl#installation -[Unreleased]: https://github.com/go2null/sshag/compare/3.0.0..HEAD +[Unreleased]: https://github.com/go2null/sshag/compare/3.0.1...HEAD +[3.0.1]: https://github.com/go2null/sshag/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/go2null/sshag/compare/2.0.0...3.0.0 [2.0.0]: https://github.com/go2null/sshag/compare/1.3.0...2.0.0 [1.3.1]: https://github.com/go2null/sshag/compare/1.3.0...1.3.1 diff --git a/sshag.sh b/sshag.sh index e9edcf2..7cacc01 100755 --- a/sshag.sh +++ b/sshag.sh @@ -137,7 +137,7 @@ sshag_print_or_add_keys() { if keys="$(ssh-add -l 2>/dev/null)"; then # Display keys currently loaded in the agent print_info "Keys:" - print_info "$(printf '\t%s' "$keys")" + print_info "$(printf '* %s' "$keys")" else ssh-add fi From 628fc1ed4444ce7b10d64118dd97443a59bdc6fb Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Fri, 4 Apr 2025 16:15:29 -0400 Subject: [PATCH 31/45] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20pearl:=20update=20to?= =?UTF-8?q?=20new=20standard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/pearl-core/pearl?tab=readme-ov-file#structure-of-a-pearl-package --- CHANGELOG.md | 9 +++++++-- README.md | 26 +++++++++++++++----------- pearl-config/config.sh | 3 +-- pearl-config/{install.sh => hooks.sh} | 2 +- 4 files changed, 24 insertions(+), 16 deletions(-) rename pearl-config/{install.sh => hooks.sh} (63%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17289dc..b24b57c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,16 @@ and this project adheres to [Semantic Versioning]. ## [Unreleased] ### Added ### Changed -* _go2null_: 💄 removed extra whitespace from keys list ### Deprecated ### Removed ### Fixed ### Security +## [3.0.2] - 2025-04-04 +### Changed +* _go2null_: 💄 removed extra whitespace from keys list +* _go2null_: ⬆️ pearl: update to new standard + ## [3.0.1] - 2025-04-03 ### Fixed * _go2null_: ✏️ removed example code @@ -83,7 +87,8 @@ and this project adheres to [Semantic Versioning]. [Semantic Versioning]: http://semver.org [pearl]: https://github.com/pearl-core/pearl#installation -[Unreleased]: https://github.com/go2null/sshag/compare/3.0.1...HEAD +[Unreleased]: https://github.com/go2null/sshag/compare/3.0.2...HEAD +[3.0.2]: https://github.com/go2null/sshag/compare/3.0.1...3.0.2 [3.0.1]: https://github.com/go2null/sshag/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/go2null/sshag/compare/2.0.0...3.0.0 [2.0.0]: https://github.com/go2null/sshag/compare/1.3.0...2.0.0 diff --git a/README.md b/README.md index 0886703..b579d7b 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,20 @@ the output will always consist of just the socket location. Use the [pearl] package manager. ```sh # install -printf '%s=%s\n' \ - "PEARL_PACKAGES['sshag']" \ - "'https://github.com/go2null/sshag.git'" \ - >> "$HOME/.config/pearl/pearl.conf" -printf '%s=%s\n' \ - "PEARL_PACKAGES_DESCR['sshag']" \ - "'Hook up with an operating or new SSH agent'" \ - >> "$HOME/.config/pearl/pearl.conf" +pearl_conf="${XDG_CONFIG_HOME:-$HOME/.config}/pearl/pearl.conf" +if grep '^}$' >/dev/null 2>&1 "$pearl_conf"; then + sed -i.bak 's/^}$//' "$pearl_conf" +else + printf '%s\n' 'PEARL_PACKAGES = {' > "$pearl_conf" +fi +printf '"%s": {\n' 'sshag' >>"$pearl_conf" +printf '"%s": "%s"\n' \ + 'url' 'https://github.com/go2null/sshag.git' \ + >>"$pearl_conf" +printf '"%s": "%s"\n' \ + 'description' 'Hook up with an operating or new SSH agent' \ + >>"$pearl_conf" +printf '{\n' >>"$pearl_conf" pearl install sshag # update @@ -44,9 +50,7 @@ pearl update sshag ```sh # install wget https://raw.githubusercontent.com/go2null/sshag/stable/sshag.sh -. sshag.sh -rm sshag.sh -sshag install +sh sshag.sh install # update sshag update diff --git a/pearl-config/config.sh b/pearl-config/config.sh index a5c0b5e..86829ce 100644 --- a/pearl-config/config.sh +++ b/pearl-config/config.sh @@ -1,5 +1,4 @@ # shellcheck shell=sh # load `sshag` into current environment -. "$PEARL_PKGDIR/sshag.sh" # configure -sshag >/dev/null # invoke +. "$PEARL_PKGDIR/sshag.sh" diff --git a/pearl-config/install.sh b/pearl-config/hooks.sh similarity index 63% rename from pearl-config/install.sh rename to pearl-config/hooks.sh index dbee36c..ba58005 100644 --- a/pearl-config/install.sh +++ b/pearl-config/hooks.sh @@ -1,7 +1,7 @@ # shellcheck shell=sh post_install() { - . "$PEARL_PKGDIR/pearl-config/config.sh" + . "$PEARL_PKGDIR/pearl-config/config.sh" } post_update() { From fb988d0ec268f2d159c55706b941fdf1a76ae35c Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Thu, 17 Apr 2025 12:14:49 -0400 Subject: [PATCH 32/45] =?UTF-8?q?=F0=9F=94=A8=20devex:=20set=20git=20EOL?= =?UTF-8?q?=20and=20EditorConfig=20defaults?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 36 ++++++++++++++++++++++++++++++++++++ .gitattributes | 11 +++++++++++ CHANGELOG.md | 7 ++++++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00261e3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# EditorConfig is awesome: https://editorconfig.org + +root = true +# top-most EditorConfig file +# root: special property that should be specified at the top of the file outside of any sections. +# Set to "true" to stop .editorconfig files search on current file. + +[*] +# Defaults + +indent_style= tab +# set to "tab" or "space" to use hard tabs or soft tabs respectively. + +indent_size = 2 +# a whole number defining the number of columns used for each indentation level and the width of soft tabs (when supported). +# When set to "tab", the value of tab_width (if specified) will be used. + +# tab_width = 2 +# a whole number defining the number of columns used to represent a tab character. +# This defaults to the value of indent_size and doesn't usually need to be specified. + +end_of_line = lf +# set to "lf", "cr", or "crlf" to control how line breaks are represented. + +charset = utf-8 +# set to "latin1", "utf-8", "utf-8-bom", "utf-16be" or "utf-16le" to control the character set. + +insert_final_newline = true +# set to "true" to ensure file ends with a newline when saving and "false" to ensure it doesn't. + +trim_trailing_whitespace = true +# set to "true" to remove any whitespace characters preceding newline characters and "false" to ensure it doesn't. + +max_line_length = 80 +# Forces hard line wrapping after the amount of characters specified. +# "unset" to turn off this feature (use the editor settings). diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..39cdd10 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Use LF on checkout across all platforms (Linux, MacOSX, Windows) +# as most text files work well with LF on Windows. +# Exceptions can be added below. +* text eol=lf + +# Files that should have their EOL on checkout changed to CRLF. + +# Files that should retain their EOL on checkout but may be diff'ed. + +# Files that are truly binary - no EOL change on checkout and no diff. +*.png binary diff --git a/CHANGELOG.md b/CHANGELOG.md index b24b57c..20c1431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning]. ### Fixed ### Security +## [3.0.3] - 2025-04-23 +### Added +* _go2null_: 🔨 devex: set git EOL and EditorConfig defaults + ## [3.0.2] - 2025-04-04 ### Changed * _go2null_: 💄 removed extra whitespace from keys list @@ -87,7 +91,8 @@ and this project adheres to [Semantic Versioning]. [Semantic Versioning]: http://semver.org [pearl]: https://github.com/pearl-core/pearl#installation -[Unreleased]: https://github.com/go2null/sshag/compare/3.0.2...HEAD +[Unreleased]: https://github.com/go2null/sshag/compare/3.0.3...HEAD +[3.0.3]: https://github.com/go2null/sshag/compare/3.0.2...3.0.3 [3.0.2]: https://github.com/go2null/sshag/compare/3.0.1...3.0.2 [3.0.1]: https://github.com/go2null/sshag/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/go2null/sshag/compare/2.0.0...3.0.0 From a53c3a9be6b5ca59db3dfc3961d85cccdd564f2d Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Sat, 3 Jan 2026 18:48:41 -0500 Subject: [PATCH 33/45] =?UTF-8?q?=F0=9F=90=9B=20fix=20logic=20bug=20-=20ex?= =?UTF-8?q?it=20with=20success=20status=20if=20already=20sourced?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sshag.sh b/sshag.sh index 7cacc01..17fc3b3 100755 --- a/sshag.sh +++ b/sshag.sh @@ -21,7 +21,7 @@ sshag_is_sourced() { # Only allow to source file once. # This simplifies the installation by adding to all the dot profiles # and only source once. -sshag_function_is_defined && sshag_is_sourced && return 1 +sshag_function_is_defined && sshag_is_sourced && return 0 # USAGE # sshag install [TARGET_DIR] - install/update From fa4dc988674683d871c801614b961432ccc3e62b Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Sat, 3 Jan 2026 19:04:00 -0500 Subject: [PATCH 34/45] =?UTF-8?q?=F0=9F=A5=85=20use=20explicit=20return=20?= =?UTF-8?q?codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 59 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/sshag.sh b/sshag.sh index 17fc3b3..88ef0c7 100755 --- a/sshag.sh +++ b/sshag.sh @@ -5,17 +5,17 @@ # Project at: https://github.com/go2null/sshag sshag_function_is_defined() { - type sshag >/dev/null 2>&1 + type sshag >/dev/null 2>&1 && return 0 || return 1 } sshag_is_sourced() { # zsh appends `:file` to `$ZSH_EVAL_CONTEXT` when sourced - if [ -n "$ZSH_VERSION" ]; then + if [ -n "${ZSH_VERSION:-}" ]; then [ "${ZSH_EVAL_CONTEXT#*:file}" != "$ZSH_EVAL_CONTEXT" ] && return 0 return 1 fi - [ "${0#*sshag}" = "$0" ] + [ "${0#*sshag}" = "$0" ] && return 0 || return 1 } # Only allow to source file once. @@ -31,8 +31,8 @@ sshag_function_is_defined && sshag_is_sourced && return 0 # sshag AGENT_SOCKET - use specified agent # sshag USER@HOST [SSH_OPTIONS_AND_ARGS] - start agent and ssh to USER@HOST sshag() { - unset agent_socket - unset user_host + agent_socket='' + user_host='' while [ $# -gt 0 ]; do case "$1" in @@ -53,7 +53,9 @@ sshag() { done sshag_require_ssh - sshag_agent_get_socket "$agent_socket" || sshag_agent_new_socket + if [ -n "$agent_socket" ]; then + sshag_agent_get_socket "$agent_socket" || sshag_agent_new_socket + fi if [ -n "$user_host" ]; then sshag_ssh "$user_host" "$@" @@ -77,18 +79,17 @@ sshag_require_ssh() { # $1 - optional. Agent Socket sshag_agent_get_socket() { # Attempt to use socket passed in - sshag_agent_vet_socket "$1" && return + sshag_agent_vet_socket "$1" && return 0 # Attempt to use the ssh-agent in the current environment - sshag_agent_vet_socket "$SSH_AUTH_SOCK" && return + sshag_agent_vet_socket "$SSH_AUTH_SOCK" && return 0 # If there is no agent in the environment, - # search for possible agents to reuse - # before starting a fresh ssh-agent process. + # search for any agent to reuse before starting a fresh ssh-agent process. # ssh agent sockets can be attached to an ssh daemon process - # or an ssh-agent process. + # or an ssh-agent process. for agent_socket in $(sshag_agent_find_sockets); do - sshag_agent_vet_socket "$agent_socket" && return + sshag_agent_vet_socket "$agent_socket" && return 0 done return 1 @@ -185,12 +186,11 @@ sshag_ssh() ( sshag_ssh_config_has_add_keys() { grep '^[[:blank:]]*AddKeysToAgent' \ "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null 2>&1 - return $? } # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, -# but it is not set in the ~/.ssh/config +# but it is not set in the ~/.ssh/config # $1 - required. user@host sshag_ssh_add_key_to_agent() { sshag_ssh_is_identity_loaded "$1" && return @@ -205,7 +205,6 @@ sshag_ssh_add_key_to_agent() { # $1 - required. user@host sshag_ssh_is_identity_loaded() { echo 'exit' | ssh -o BatchMode=yes -- "$1" 2>/dev/null - return $? } # $1 - required. user@host @@ -263,17 +262,17 @@ sshag_install_path() { [ -z "$dir" ] && print_fatal " Invalid directory $1." else if [ "$USER" = 'root' ]; then - dir="${XDG_DATA_DIRS%%:*}" # use first entry - dir="${dir:-/usr/local/share}/lib" # default XDG value + dir="${XDG_DATA_DIRS%%:*}" # use first entry + dir="${dir:-/usr/local/share}/lib" # default XDG value - sshag_install_migrate '/usr/local/lib' "$dir" # v1.3.0 path + sshag_install_migrate '/usr/local/lib' "$dir" # v1.3.0 path else - : "${XDG_DATA_HOME:=$HOME/.local/share}" # set XDG + : "${XDG_DATA_HOME:=$HOME/.local/share}" # set XDG dir="$XDG_DATA_HOME/lib" - sshag_install_migrate "$XDG_DATA_HOME/../lib" "$dir" # v1.3.0 path - sshag_install_migrate "$HOME/.local/lib" "$dir" # v2.0.0 path + sshag_install_migrate "$XDG_DATA_HOME/../lib" "$dir" # v1.3.0 path + sshag_install_migrate "$HOME/.local/lib" "$dir" # v2.0.0 path fi fi @@ -326,7 +325,7 @@ sshag_install_profile() { if grep "^[ \t]*$1" "$2" >/dev/null; then print_info " SKIPPED $2, already added." - return + return 0 fi print_line "$1" >> "$2" @@ -374,11 +373,11 @@ $HOME/.zshrc" # $1 - required. config file sshag_remove_profile() { - [ -e "$1" ] || return - grep 'sshag.sh' "$1" 1>/dev/null 2>&1 || return + [ -e "$1" ] || return 0 + grep 'sshag.sh' "$1" 1>/dev/null 2>&1 || return 0 print_info " $1" - [ ! -w "$1" ] && print_warning " SKIPPED, cannot edit file." && return + [ ! -w "$1" ] && print_warning " SKIPPED, cannot edit file." && return 0 sed -i.bak '/.*sshag.sh.*/ d' "$1" \ || print_warning " FAILED to remove from file." @@ -392,13 +391,15 @@ print_stderr() { print_line "$@" >&2; } # Do not send messages to 'stdout' # - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell -print_error() { print_stderr "ERROR: $*"; return 1; } +print_error() { print_stderr "ERROR: $*"; return 0; } print_fatal() { print_stderr "FATAL: $*"; exit 1; } -print_info() { print_stderr "INFO: $*"; return 1; } -print_warning() { print_stderr "WARNING: $*"; return 1; } +print_info() { print_stderr "INFO: $*"; return 0; } +print_warning() { print_stderr "WARNING: $*"; return 0; } require_command() { - [ ! -x "$(command -v "$1")" ] && print_fatal "$1 is not available! aborting!" + if [ ! -x "$(command -v "$1")" ]; then + print_fatal "$1 is not available! aborting!" + fi } # == HOOK == From a4c999e2afc3920d8a467e54f268659e438fbee7 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 17 Mar 2026 23:45:03 -0400 Subject: [PATCH 35/45] =?UTF-8?q?=F0=9F=90=9B=20bug=20fix=20for=20when=20s?= =?UTF-8?q?ourced=20during=20shell=20login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During shell login, it passed in the file that sources the script and the name of the shell * This gets set to user_host and triggers sshag_ssh which errors out --- sshag.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sshag.sh b/sshag.sh index 88ef0c7..8245606 100755 --- a/sshag.sh +++ b/sshag.sh @@ -11,7 +11,7 @@ sshag_function_is_defined() { sshag_is_sourced() { # zsh appends `:file` to `$ZSH_EVAL_CONTEXT` when sourced if [ -n "${ZSH_VERSION:-}" ]; then - [ "${ZSH_EVAL_CONTEXT#*:file}" != "$ZSH_EVAL_CONTEXT" ] && return 0 + [ "${ZSH_EVAL_CONTEXT#*:file}" = "$ZSH_EVAL_CONTEXT" ] || return 0 return 1 fi @@ -57,14 +57,12 @@ sshag() { sshag_agent_get_socket "$agent_socket" || sshag_agent_new_socket fi - if [ -n "$user_host" ]; then + if sshag_is_sourced; then + sshag_print_or_add_keys + elif [ -n "$user_host" ]; then sshag_ssh "$user_host" "$@" else - if sshag_is_sourced; then - sshag_print_or_add_keys - else - sshag_agent_print_notice - fi + sshag_agent_print_notice fi } From fa87e23e174db01ff0f1cf5c78e12678c351336f Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Sat, 21 Mar 2026 18:25:43 -0400 Subject: [PATCH 36/45] =?UTF-8?q?=F0=9F=92=A5=20install=20to=20UAPI=20Grou?= =?UTF-8?q?p=20LIB=20dir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sshag.sh b/sshag.sh index 8245606..220f714 100755 --- a/sshag.sh +++ b/sshag.sh @@ -147,7 +147,7 @@ sshag_print_or_add_keys() { # Load first key for specified user@hostname and start `ssh`. # $1 - required. user@host # $@ - optional. ssh options -sshag_ssh() ( +sshag_ssh() { # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent # Or if the local ssh client support AddKeysToAgent, # but it is not set in the ~/.ssh/config @@ -178,7 +178,7 @@ sshag_ssh() ( # `$ssh_opts` may be unset, quoting it will pass an empty string to `ssh` # shellcheck disable=SC2086,SC2029 ssh "$@" $ssh_opts "$user_host" -) +} # Checks if ~/.ssh/config has AddKeysToAgent sshag_ssh_config_has_add_keys() { @@ -220,7 +220,7 @@ sshag_ssh_get_identity() { # $1 - required. action - install, update, or remove # $2 - optional. install directory -sshag_install() ( +sshag_install() { require_command 'git' dir="$(sshag_install_path "$2")" @@ -249,9 +249,11 @@ sshag_install() ( sshag_config=". '$dir/sshag/sshag.sh'" sshag_install_profiles "$sshag_config" sshag_install_manual "$sshag_config" -) +} # $1 - optional. install parent directory +# Use LIB directory specified by UAPI (SystemD's FileSystem Hierarchy successor) +# https://uapi-group.org/specifications/specs/linux_file_system_hierarchy/#localbin sshag_install_path() { unset dir @@ -260,17 +262,17 @@ sshag_install_path() { [ -z "$dir" ] && print_fatal " Invalid directory $1." else if [ "$USER" = 'root' ]; then - dir="${XDG_DATA_DIRS%%:*}" # use first entry - dir="${dir:-/usr/local/share}/lib" # default XDG value + dir='/usr/local/lib' - sshag_install_migrate '/usr/local/lib' "$dir" # v1.3.0 path + sshag_install_migrate "${XDG_DATA_DIRS%%:*}/lib" "$dir" # v3.0.0 path + sshag_install_migrate '/usr/local/share/lib' "$dir" # v3.0.0 path else - : "${XDG_DATA_HOME:=$HOME/.local/share}" # set XDG - - dir="$XDG_DATA_HOME/lib" + : "${XDG_LIB_HOME:="$(realpath "${XDG_DATA_HOME:-"$HOME/.local/share"}/../lib")"}" + dir="$XDG_LIB_HOME" sshag_install_migrate "$XDG_DATA_HOME/../lib" "$dir" # v1.3.0 path sshag_install_migrate "$HOME/.local/lib" "$dir" # v2.0.0 path + sshag_install_migrate "$XDG_DATA_HOME/lib" "$dir" # v3.0.0 path fi fi @@ -283,6 +285,8 @@ sshag_install_path() { sshag_install_migrate() { [ -d "$1/sshag" ] || return 0 + [ "$(realpath "$1")" = "$(realpath "$2")" ] && return 0 || : + print_info ' Migrating previous installation' print_info " from $1/sshag" print_info " to $2" @@ -292,12 +296,12 @@ sshag_install_migrate() { } # $1 - required. install directory -sshag_install_download() { +sshag_install_download() ( cd "$1" || print_fatal " Cannot access $1." git clone 'https://github.com/go2null/sshag.git' \ || print_fatal " 'git clone' failed with above error." -} +) # add to shell startup files # $1 - required. sshag config line From 865af4bbe9203773752677e927337005d85dc9f3 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 07:59:37 -0400 Subject: [PATCH 37/45] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20simplify=20if=5Fs?= =?UTF-8?q?ourced=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/sshag.sh b/sshag.sh index 220f714..4308b5a 100755 --- a/sshag.sh +++ b/sshag.sh @@ -4,24 +4,30 @@ # http://superuser.com/questions/141044/sharing-the-same-ssh-agent-among-multiple-login-sessions#answer-141241 # Project at: https://github.com/go2null/sshag + +# == LOAD ONCE == # + sshag_function_is_defined() { type sshag >/dev/null 2>&1 && return 0 || return 1 } +# $0 is set to filename (and any leading path) if invoked as a script. +# so if $0 does not end with the filename, then it is sourced. +# When sourced, +# POSIX - $0 is undefined +# bash - set to `*bash` +# zsh - in the main script scope - same as when called as a script. +# - within a function - name of the function, as here. sshag_is_sourced() { - # zsh appends `:file` to `$ZSH_EVAL_CONTEXT` when sourced - if [ -n "${ZSH_VERSION:-}" ]; then - [ "${ZSH_EVAL_CONTEXT#*:file}" = "$ZSH_EVAL_CONTEXT" ] || return 0 - return 1 - fi - - [ "${0#*sshag}" = "$0" ] && return 0 || return 1 + [ "${0%sshag.sh}" = "$0" ] && return 0 || return 1 } +# == MAIN == # + # Only allow to source file once. # This simplifies the installation by adding to all the dot profiles # and only source once. -sshag_function_is_defined && sshag_is_sourced && return 0 +sshag_function_is_defined && sshag_is_sourced && return 0 || : # USAGE # sshag install [TARGET_DIR] - install/update From e4260bad4bf63a49aa6e49581a8173bf8691c12c Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 19:43:17 -0400 Subject: [PATCH 38/45] =?UTF-8?q?=F0=9F=A5=85=20make=20sshag=5Fagent=5Fget?= =?UTF-8?q?=5Fsocket=20always=20return=20a=20socket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sshag.sh b/sshag.sh index 4308b5a..1f0254a 100755 --- a/sshag.sh +++ b/sshag.sh @@ -33,7 +33,7 @@ sshag_function_is_defined && sshag_is_sourced && return 0 || : # sshag install [TARGET_DIR] - install/update # sshag update [TARGET_DIR] - update # sshag uninstall [TARGET_DIR] - uninstall -# sshag - start/use agent +# sshag - start new/use existing agent # sshag AGENT_SOCKET - use specified agent # sshag USER@HOST [SSH_OPTIONS_AND_ARGS] - start agent and ssh to USER@HOST sshag() { @@ -60,7 +60,7 @@ sshag() { sshag_require_ssh if [ -n "$agent_socket" ]; then - sshag_agent_get_socket "$agent_socket" || sshag_agent_new_socket + sshag_agent_get_socket "$agent_socket" fi if sshag_is_sourced; then @@ -83,35 +83,38 @@ sshag_require_ssh() { # $1 - optional. Agent Socket sshag_agent_get_socket() { # Attempt to use socket passed in - sshag_agent_vet_socket "$1" && return 0 + sshag_agent_vet_socket "$1" && return 0 || : # Attempt to use the ssh-agent in the current environment - sshag_agent_vet_socket "$SSH_AUTH_SOCK" && return 0 + sshag_agent_vet_socket "$SSH_AUTH_SOCK" && return 0 || : # If there is no agent in the environment, # search for any agent to reuse before starting a fresh ssh-agent process. # ssh agent sockets can be attached to an ssh daemon process # or an ssh-agent process. for agent_socket in $(sshag_agent_find_sockets); do - sshag_agent_vet_socket "$agent_socket" && return 0 + sshag_agent_vet_socket "$agent_socket" && return 0 || : done - return 1 + # Start a new agent + sshag_agent_new_socket } # $1 - optional. Agent Socket sshag_agent_vet_socket() { - [ -z "$1" ] && return 1 + [ -n "$1" ] || return 1 if [ -S "$1" ]; then export SSH_AUTH_SOCK="$1" ssh-add -l >/dev/null 2>&1 if [ $? -eq 2 ]; then rm -f "$SSH_AUTH_SOCK" - print_warning "Socket $SSH_AUTH_SOCK is dead! Deleted!" + print_warning "Socket '$SSH_AUTH_SOCK' is dead! Deleted!" + return 1 fi else - print_warning "$SSH_AUTH_SOCK is not a socket!" + print_warning "'$SSH_AUTH_SOCK' is not a socket!" + return 1 fi } From c7686c40537d556d7c2394e554e47478cc7b849c Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 19:54:02 -0400 Subject: [PATCH 39/45] =?UTF-8?q?=F0=9F=8E=A8=20order=20sshag=5Fagent=5F*?= =?UTF-8?q?=20functions=20by=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sshag.sh b/sshag.sh index 1f0254a..a346264 100755 --- a/sshag.sh +++ b/sshag.sh @@ -22,6 +22,7 @@ sshag_is_sourced() { [ "${0%sshag.sh}" = "$0" ] && return 0 || return 1 } + # == MAIN == # # Only allow to source file once. @@ -64,7 +65,7 @@ sshag() { fi if sshag_is_sourced; then - sshag_print_or_add_keys + sshag_agent_print_or_add_keys elif [ -n "$user_host" ]; then sshag_ssh "$user_host" "$@" else @@ -78,6 +79,7 @@ sshag_require_ssh() { done } + # == Get/Start SSH-AGENT == # $1 - optional. Agent Socket @@ -129,6 +131,17 @@ sshag_agent_new_socket() { eval "$(ssh-agent)" } +# Ensure keys are loaded +sshag_agent_print_or_add_keys() { + if keys="$(ssh-add -l 2>/dev/null)"; then + # Display keys currently loaded in the agent + print_info "Keys:" + print_info "$(printf '* %s' "$keys")" + else + ssh-add + fi +} + sshag_agent_print_notice() { print_info "$(cat <<- NOTICE @@ -140,16 +153,6 @@ sshag_agent_print_notice() { )" } -# Ensure keys are loaded -sshag_print_or_add_keys() { - if keys="$(ssh-add -l 2>/dev/null)"; then - # Display keys currently loaded in the agent - print_info "Keys:" - print_info "$(printf '* %s' "$keys")" - else - ssh-add - fi -} # == SSH wrapper == From a32cb269cda6b186b7408e8bb6848656dbf11d96 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 19:55:57 -0400 Subject: [PATCH 40/45] =?UTF-8?q?=F0=9F=90=9B=20fix:=20ensure=20ssh=20agen?= =?UTF-8?q?t=20is=20loaded=20before=20using=20ssh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sshag.sh b/sshag.sh index a346264..9cfa109 100755 --- a/sshag.sh +++ b/sshag.sh @@ -80,7 +80,7 @@ sshag_require_ssh() { } -# == Get/Start SSH-AGENT == +# == Get/Start SSH-AGENT == # # $1 - optional. Agent Socket sshag_agent_get_socket() { @@ -154,7 +154,7 @@ sshag_agent_print_notice() { } -# == SSH wrapper == +# == SSH wrapper == # # Load first key for specified user@hostname and start `ssh`. # $1 - required. user@host @@ -171,6 +171,9 @@ sshag_ssh() { # (OpenSSH before v7.2 barfs on params it doesn't know about so can't use # it in a common ssh_config where some machines have pre v7.2 OpenSSH.) + # ensure ssh-agent is active + sshag + unset ssh_opts user_host="$1" From e643931caff5942fc512e3ba71de8097650c62db Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 20:42:17 -0400 Subject: [PATCH 41/45] =?UTF-8?q?=F0=9F=A5=85=20add=20error=20trapping=20t?= =?UTF-8?q?o=20sshag=5Fssh=5F*?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/sshag.sh b/sshag.sh index 9cfa109..7d2507d 100755 --- a/sshag.sh +++ b/sshag.sh @@ -187,7 +187,7 @@ sshag_ssh() { ssh_opts='-o AddKeysToAgent=yes' else # This is needed for OpenSSH pre v7.2, before AddKeysToAgent was added - sshag_ssh_add_key_to_agent "$user_host" + sshag_ssh_add_key_to_agent "$user_host" || : # let ssh emit its own error fi # `$ssh_opts` may be unset, quoting it will pass an empty string to `ssh` @@ -197,8 +197,9 @@ sshag_ssh() { # Checks if ~/.ssh/config has AddKeysToAgent sshag_ssh_config_has_add_keys() { - grep '^[[:blank:]]*AddKeysToAgent' \ - "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null 2>&1 + grep '^[[:blank:]]*AddKeysToAgent' \ + "$HOME/.ssh/config" "/etc/ssh/ssh_config" >/dev/null 2>&1 \ + && return 0 || return 1 } # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent @@ -206,29 +207,34 @@ sshag_ssh_config_has_add_keys() { # but it is not set in the ~/.ssh/config # $1 - required. user@host sshag_ssh_add_key_to_agent() { - sshag_ssh_is_identity_loaded "$1" && return + sshag_ssh_is_identity_loaded "$1" && return 0 || : # load identity if one is defined for the user@hostname. - sshag_identity="$(sshag_ssh_get_identity "$1")" - if [ -n "$sshag_identity" ] && ! ssh-add "$sshag_identity"; then - print_error "Unable to load identity $sshag_identity!" + if sshag_identity="$(sshag_ssh_get_identity "$1")"; then + ssh-add "$sshag_identity" && return 0 || : fi + + print_error "Unable to load identity '$sshag_identity'!" + return 1 } # $1 - required. user@host sshag_ssh_is_identity_loaded() { - echo 'exit' | ssh -o BatchMode=yes -- "$1" 2>/dev/null + echo 'exit' | ssh -o BatchMode=yes -- "$1" 2>/dev/null && return 0 || return 1 } # $1 - required. user@host sshag_ssh_get_identity() { sshag_identity="$(ssh -v -o BatchMode=yes "$1" 2>&1 \ - | awk ' /identity file/ { print $4 } ' \ + | awk ' /identity file/ { print $4 } ' \ | head -n 1)" - [ -n "$sshag_identity" ] \ - && sshag_identity="$(realpath -m "$sshag_identity")" \ - && printf '%s' "$sshag_identity" + if [ -n "$sshag_identity" ]; then + sshag_identity="$(realpath -m "$sshag_identity")" + printf '%s' "$sshag_identity" + else + return 1 + fi } # == INSTALL == From 754a7013e34af98137748b747b9035934e0aceb3 Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 20:45:55 -0400 Subject: [PATCH 42/45] =?UTF-8?q?=F0=9F=93=9D=20use=20SSH,=20not=20ssh,=20?= =?UTF-8?q?to=20refer=20to=20the=20agent/binary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sshag.sh b/sshag.sh index 7d2507d..f34bf56 100755 --- a/sshag.sh +++ b/sshag.sh @@ -47,7 +47,7 @@ sshag() { update) shift; sshag_install 'update' "$@"; return $? ;; uninstall) shift; sshag_install 'remove' "$@"; return $? ;; remove) shift; sshag_install 'remove' "$@"; return $? ;; - -*) break ;; # ssh options + -*) break ;; # SSH options *) if [ -e "$1" ] ; then agent_socket="$1" @@ -92,7 +92,7 @@ sshag_agent_get_socket() { # If there is no agent in the environment, # search for any agent to reuse before starting a fresh ssh-agent process. - # ssh agent sockets can be attached to an ssh daemon process + # SSH agent sockets can be attached to an SSH daemon process # or an ssh-agent process. for agent_socket in $(sshag_agent_find_sockets); do sshag_agent_vet_socket "$agent_socket" && return 0 || : @@ -156,12 +156,12 @@ sshag_agent_print_notice() { # == SSH wrapper == # -# Load first key for specified user@hostname and start `ssh`. +# Load first key for specified user@hostname and start SSH. # $1 - required. user@host -# $@ - optional. ssh options +# $@ - optional. SSH options sshag_ssh() { # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent - # Or if the local ssh client support AddKeysToAgent, + # Or if the local SSH client support AddKeysToAgent, # but it is not set in the ~/.ssh/config # OpenSSH v7.2 added support for AddKeysToAgent. @@ -183,14 +183,14 @@ sshag_ssh() { # Honor AddKeysToAgent settings : # do nothing elif ssh -o AddKeysToAgent 2>&1 | grep 'missing argument' >/dev/null; then - # If this ssh supports AddKeyToAgent, then use it + # If this SSH supports AddKeyToAgent, then use it ssh_opts='-o AddKeysToAgent=yes' else # This is needed for OpenSSH pre v7.2, before AddKeysToAgent was added - sshag_ssh_add_key_to_agent "$user_host" || : # let ssh emit its own error + sshag_ssh_add_key_to_agent "$user_host" || : # let SSH emit its own error fi - # `$ssh_opts` may be unset, quoting it will pass an empty string to `ssh` + # `$ssh_opts` may be unset, quoting it will pass an empty string to SSH # shellcheck disable=SC2086,SC2029 ssh "$@" $ssh_opts "$user_host" } @@ -203,7 +203,7 @@ sshag_ssh_config_has_add_keys() { } # This is needed for OpenSSH before v7.2 which added support AddKeysToAgent -# Or if the local ssh client support AddKeysToAgent, +# Or if the local SSH client support AddKeysToAgent, # but it is not set in the ~/.ssh/config # $1 - required. user@host sshag_ssh_add_key_to_agent() { From 8fa6c8b537e73a629a9ba1cb2b70ab7713e4beff Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 21:09:11 -0400 Subject: [PATCH 43/45] =?UTF-8?q?=F0=9F=92=A5=20install:=20use=20UAPI=20LI?= =?UTF-8?q?B=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/sshag.sh b/sshag.sh index f34bf56..e05337a 100755 --- a/sshag.sh +++ b/sshag.sh @@ -237,6 +237,7 @@ sshag_ssh_get_identity() { fi } + # == INSTALL == # $1 - required. action - install, update, or remove @@ -246,8 +247,9 @@ sshag_install() { dir="$(sshag_install_path "$2")" dir="${dir%/sshag}" # strip 'sshag' from path, as necessary + dir="$dir/sshag" - if [ -d "$dir/sshag" ]; then + if [ -d "$dir" ]; then case "$1" in install|update) sshag_update "$dir" @@ -260,21 +262,22 @@ sshag_install() { esac fi - [ "$1" = 'remove' ] \ - && print_fatal "Cannot detect where 'sshag' is installed" + if [ "$1" = 'remove' ]; then + print_fatal "Cannot detect where 'sshag' is installed" + fi print_info "Installing to $dir." sshag_install_download "$dir" print_info "Adding to startup files" - sshag_config=". '$dir/sshag/sshag.sh'" + sshag_config=". '$dir/sshag.sh'" sshag_install_profiles "$sshag_config" sshag_install_manual "$sshag_config" } # $1 - optional. install parent directory # Use LIB directory specified by UAPI (SystemD's FileSystem Hierarchy successor) -# https://uapi-group.org/specifications/specs/linux_file_system_hierarchy/#localbin +# https://uapi-group.org/specifications/specs/linux_file_system_hierarchy/#locallib sshag_install_path() { unset dir @@ -297,7 +300,7 @@ sshag_install_path() { fi fi - [ -d "$dir" ] || mkdir -p "$dir" || print_fatal " Cannot create directory $dir." + [ -d "$dir" ] || mkdir -p "$dir" || print_fatal " Cannot create directory '$dir'." printf '%s' "$dir" } @@ -317,12 +320,10 @@ sshag_install_migrate() { } # $1 - required. install directory -sshag_install_download() ( - cd "$1" || print_fatal " Cannot access $1." - - git clone 'https://github.com/go2null/sshag.git' \ - || print_fatal " 'git clone' failed with above error." -) +sshag_install_download() { + git clone 'https://github.com/go2null/sshag.git' "$1" \ + || print_fatal " 'git clone' failed with above error." +} # add to shell startup files # $1 - required. sshag config line @@ -363,15 +364,15 @@ sshag_install_manual() { # $1 - required. install directory sshag_update() { - print_info "Updating 'sshag' at $1." - cd "$1/sshag" || print_fatal " Cannot accees $1/sshag." + print_info "Updating 'sshag' at '$1'." + cd "$1" || print_fatal " Cannot access '$1'." git pull } # $1 - required. install directory sshag_remove() { - print_info "Removing 'sshag' at $1." - rm -rf "$1/sshag" + print_info "Removing 'sshag' at '$1'." + rm -rf "$1" print_info "Removing from startup files" sshag_remove_profiles @@ -379,7 +380,7 @@ sshag_remove() { sshag_remove_profiles() { file='/etc/profile.d/sshag.sh' - [ -w "$file" ] && print_info " REMOVED $file." && rm "$file" + [ -w "$file" ] && rm "$file" && print_info " REMOVED '$file'." files="/etc/profile $HOME/.profile @@ -403,10 +404,11 @@ sshag_remove_profile() { [ ! -w "$1" ] && print_warning " SKIPPED, cannot edit file." && return 0 sed -i.bak '/.*sshag.sh.*/ d' "$1" \ - || print_warning " FAILED to remove from file." + || print_warning " FAILED to remove from file." } -# == HELPERS == + +# == HELPERS == # print_line() { printf '%s\n' "$*"; } From 52e9a98a921d58ced32925172e49ec02909dfc6f Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 21:18:19 -0400 Subject: [PATCH 44/45] =?UTF-8?q?=F0=9F=8E=A8=20simplify=20sshag=5Fssh=20c?= =?UTF-8?q?heck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sshag.sh | 65 +++++++++++++++++++++++--------------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/sshag.sh b/sshag.sh index e05337a..7820acd 100755 --- a/sshag.sh +++ b/sshag.sh @@ -38,47 +38,24 @@ sshag_function_is_defined && sshag_is_sourced && return 0 || : # sshag AGENT_SOCKET - use specified agent # sshag USER@HOST [SSH_OPTIONS_AND_ARGS] - start agent and ssh to USER@HOST sshag() { - agent_socket='' - user_host='' - - while [ $# -gt 0 ]; do + if [ $# -gt 0 ]; then case "$1" in - install) shift; sshag_install 'install' "$@"; return $? ;; - update) shift; sshag_install 'update' "$@"; return $? ;; - uninstall) shift; sshag_install 'remove' "$@"; return $? ;; - remove) shift; sshag_install 'remove' "$@"; return $? ;; - -*) break ;; # SSH options - *) - if [ -e "$1" ] ; then - agent_socket="$1" - else - user_host="$1" - fi - ;; + install) shift; sshag_install 'install' "$@"; return $? ;; + update) shift; sshag_install 'update' "$@"; return $? ;; + uninstall) shift; sshag_install 'remove' "$@"; return $? ;; + remove) shift; sshag_install 'remove' "$@"; return $? ;; + *) [ -e "$1" ] || { sshag_ssh "$@"; return $?; } ;; esac - shift - done - - sshag_require_ssh - if [ -n "$agent_socket" ]; then - sshag_agent_get_socket "$agent_socket" fi if sshag_is_sourced; then - sshag_agent_print_or_add_keys - elif [ -n "$user_host" ]; then - sshag_ssh "$user_host" "$@" + sshag_agent_get_socket "$1" + sshag_print_or_add_keys else sshag_agent_print_notice fi } -sshag_require_ssh() { - for app in ssh ssh-add ssh-agent; do - require_command "$app" - done -} - # == Get/Start SSH-AGENT == # @@ -242,8 +219,9 @@ sshag_ssh_get_identity() { # $1 - required. action - install, update, or remove # $2 - optional. install directory -sshag_install() { - require_command 'git' +sshag_install() ( + check_commands 'ssh' 'ssh-add' 'ssh-agent' + check_commands -require 'git' dir="$(sshag_install_path "$2")" dir="${dir%/sshag}" # strip 'sshag' from path, as necessary @@ -410,22 +388,33 @@ sshag_remove_profile() { # == HELPERS == # +# $1 - -require. OPTIONAL +check_commands() { + [ "$1" = '-require' ] && { require=1; shift; } || require='' + + for cmd in "$@"; do + if [ ! -x "$(command -v "$cmd")" ]; then + if [ -n "$require" ]; then + print_fatal "'$cmd' is not available! Aborting!" + else + print_warning "'$cmd' is not available! Please install it." + fi + fi + done +} + print_line() { printf '%s\n' "$*"; } print_stderr() { print_line "$@" >&2; } # Do not send messages to 'stdout' # - it is reserved for outputting $SSH_AUTH_SOCH when invoked in a subshell +print_debug() { print_stderr "DEBUG: $*"; return 0; } print_error() { print_stderr "ERROR: $*"; return 0; } print_fatal() { print_stderr "FATAL: $*"; exit 1; } print_info() { print_stderr "INFO: $*"; return 0; } print_warning() { print_stderr "WARNING: $*"; return 0; } -require_command() { - if [ ! -x "$(command -v "$1")" ]; then - print_fatal "$1 is not available! aborting!" - fi -} # == HOOK == From a46fdfc17b0a3c4e9337c0a6c0680f644da0f3df Mon Sep 17 00:00:00 2001 From: go2null <1t1is2@gmail.com> Date: Tue, 21 Apr 2026 21:35:50 -0400 Subject: [PATCH 45/45] =?UTF-8?q?=F0=9F=93=9D=20update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c1431..8e9fea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,17 @@ and this project adheres to [Semantic Versioning]. ## [Unreleased] ### Added ### Changed +* 2026-04-21 8fa6c8b 💥 __BREAKING__ install: use [UAPI](https://uapi-group.org/specifications/specs/linux_file_system_hierarchy/#locallib) +LIB directory [go2null] + standard. ### Deprecated ### Removed ### Fixed +* 2026-04-21 a32cb26 🐛 fix: ensure ssh agent is loaded before using ssh [go2null] +* 2026-01-03 a53c3a9 🐛 fix logic bug - exit with success status if already sourced [go2null] ### Security +* 2026-04-21 e643931 🥅 add error trapping to sshag_ssh_* [go2null] +* 2026-01-03 fa4dc98 🥅 use explicit return codes [go2null] ## [3.0.3] - 2025-04-23 ### Added