#!/bin/bash # vim: set syntax=sh: set -e # sshok is a simple tool for managing a user's SSH configuration, allowing for a simpler and saner # way of storing large SSH configuration sets. # # Just put any groups of settings inside $SSH_CONFIG_DIR (defaults to $HOME/.ssh/config) and use # this script instead of `ssh` to perform any SSH task. # # This script will take any files inside $SSH_CONFIG_DIR and concatenate them into $SSH_CONFIG_FILE # so that the SSH configuration can be split into different files for easier management. This # script can be thought of as an SSH wrapper, which first determines whether the configuration file # needs to be updated, updates it (according to what has been determined) and then runs the # original ssh command with any argument provided to this script. # # Every time this script determines it needs to update the $SSH_CONFIG_FILE, it will backup the # current configuration first and then overwrite the file. Backups have two levels: a first one, # thought for quick restores which only keeps the last configuration in a file stored alongside # the $SSH_CONFIG_FILE and suffixed with ".bak", and a second one which is thought for keeping a # larger log of the different versions of the configuration file, which will back up every version # of the $SSH_CONFIG_FILE to $BACKUPS_DIR, suffixing each version with the timestamp of the moment # when the backup was made. This means that if anything should go wrong, a previous version of the # configuration could be restored. # # This file is intended to work as a self-documented script, so please do read the comments in it # as they aim at guiding anyone looking at it. # The following variables indicate where are located the source files and the target configuration # file, and can be overridden by setting their values in environment variables before running # this script. This is a pretty simple way of allowing the customization of the scripts' default # behavior. # # For instance, the script could be run like follows to store the resulting configuration file in # an alternative location if your SSH installation differs from the standard one, or if you would # like to test the outcome of running this script, or even if you would like to generate an SSH # config file to share with your SO: # # $ SSH_CONFIG_FILE=/tmp/my_ssh_config sshok SSH_DIR=${SSH_DIR:-$HOME/.ssh} SSH_CONFIG_DIR=${SSH_CONFIG_DIR:-$SSH_DIR/config.d} SSH_CONFIG_FILE=${SSH_CONFIG_FILE:-$SSH_DIR/config} BACKUPS_DIR=${BACKUPS_DIR:-/tmp/sshok} UNWANTED_FILES_PATTERN="# vim:" # Sometimes you need to share configuration files with different, unrelated groups thus creating # the need to more than a single config directory. For instance, working on 2 different projects # with people unrelated between them -- imagine two directories, "job1" and "job2", one for each # project: # SSH_CONFIG_DIRS="$HOME/job1 $HOME/job2 ${SSH_CONFIG_DIR}" # The default value for SSH_CONFIG_DIRS is $SSH_CONFIG_DIR. SSH_CONFIG_DIRS="${SSH_CONFIG_DIRS:-$SSH_CONFIG_DIR}" # Get the filename with the most recently mtime from the path passed in the first argument. $1 is # expected to exist. newest_file_in() { if [ -e $1 ]; then stat=$(determine_stat_variation $1) if [ $stat = "-c" ]; then find -L $SSH_CONFIG_DIRS -type f -print0 | xargs -0 stat -c '%Y %n' | sort -rn | head -1 | cut -d' ' -f2 else find -L $SSH_CONFIG_DIRS -type f -print0 | xargs -0 stat -f '%m %N' | sort -rn | head -1 | cut -d' ' -f2 fi fi } # Determine which arguments stat can handle. This is due to a difference in the expected args for # that command between Linux and OS X. determine_stat_variation() { set +e stat -c %Y $1 >/dev/null 2>&1 if [ $? -eq 0 ]; then # Linux' stat echo "-c" else # OS X/BSD's stat echo "-f" fi set -e } # Backs up the file passed in the first argument to a file named {name}.{current_timestmap} inside # the path passed in as second argument, and to {name}.bak (the latter will overwrite any existing # backup). backup_config() { if [ -f $1 ]; then cp $1 "$BACKUPS_DIR/$(basename $1).$(date +%s)" cp -f $1{,.bak} fi } # Print to stdout the configuration resulting from the concatenation of every file in the path # passed in the first argument. generate_config() { echo -e "#\n# This file was autogenerated from $1 on $(date +%FT%T)\n#\n" for dir in $SSH_CONFIG_DIRS; do for file in $dir/*; do echo "# From $file" cat $file | grep -v "$UNWANTED_FILES_PATTERN" done done } # First, make sure the $SSH_CONFIG_DIR directory exists and if it doesn't copy the current # $SSH_CONFIG_FILE to it. By doing that we won't overwrite the existing configuration by accident # on the first run. if [ ! -d $SSH_CONFIG_DIR ]; then mkdir -p $SSH_CONFIG_DIR if [ -f $SSH_CONFIG_FILE ]; then cp $SSH_CONFIG_FILE $SSH_CONFIG_DIR/original else echo -e "#\n# This file was autogenerated from $1 on $(date +%FT%T)\n#\n" > $SSH_CONFIG_FILE fi fi if [ ! -d $BACKUPS_DIR ]; then mkdir -p $BACKUPS_DIR fi # Only if the autogenerated config file is older than the source ones inside the $SSH_CONFIG_DIR, # regenerate the configuration on $SSH_CONFIG_FILE. if [ ! -e $SSH_CONFIG_FILE ] || [ $(newest_file_in $SSH_CONFIG_DIR) -nt $SSH_CONFIG_FILE ]; then # Backup current config... backup_config $SSH_CONFIG_FILE $BACKUPS_DIR # ...and then generate the new one generate_config $SSH_CONFIG_DIRS > $SSH_CONFIG_FILE fi # Finally, run ssh with any arguments passed to this script ssh $@