Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 267 lines (225 sloc) 5.74 KB
#!/usr/bin/env bash
#
# Minimalistic SSH deployment.
#
# Author: Artem Sapegin, sapegin.me
# License: MIT
#
# Inspired by:
# https://github.com/visionmedia/deploy
# http://gleero.com/all/remote-ssh/
#
PROGRAM=$(basename "$0")
VERSION="0.3.0"
CONFIG_DIR=$(pwd)
CONFIG_NAME=.shipit
CONFIG=
TARGET=deploy
SSH_HOST=
SSH_PATH=
VERBOSE=
# Common stuff
RED="$(tput setaf 1)"
GREEN="$(tput setaf 2)"
WHITE="$(tput setaf 7)"
CYAN="$(tput setaf 6)"
UNDERLINE="$(tput sgr 0 1)"
BOLD="$(tput bold)"
NOCOLOR="$(tput sgr0)"
function header() { echo -e "$UNDERLINE$CYAN$1$NOCOLOR\n"; }
function error() { echo -e "$UNDERLINE$RED$1$NOCOLOR\n"; }
# Print usage information
usage() {
version
echo
echo "Usage: $PROGRAM [option] <command>"
echo
echo "Commands:"
echo
echo " <target> Execute <target> target on remote host"
echo " list ls Print list of available targets"
echo " console shell, ssh Open an SSH session on the remote host"
echo " exec <cmd> run Execute <cmd> on the remote host"
echo " copy <file> cp Copy <files> to the remote host"
echo " --version -V Print program version"
echo " --help -h Print help (this screen)"
echo
echo "Options:"
echo
echo " -c, --config Config file name (default: .shipit)"
echo " -v, --verbose Enable verbose mode for SSH"
echo
}
# Print error message and exit
abort() {
echo
error "$@"
exit 1
}
# Print version number
version() {
echo
echo "$PROGRAM v$VERSION"
}
# Enable verbose mode
set_verbose() {
VERBOSE="1"
}
# Set config file name
set_config_file() {
test -z "$1" && abort 'File name is required for --config/-c option.'
CONFIG_NAME="$1"
}
# Print squirrel
squirrel() {
echo "$WHITE"
echo " __ (\\_ ⎧ ⎻⎻⎻⎻⎻⎻⎻⎻⎫"
echo " (_ \\ ( $GREEN$BOLD'$NOCOLOR$WHITE> 〈 SHIP IT!⎟"
echo " ) \\/_)= ⎩ ⎽⎽⎽⎽⎽⎽⎽⎽⎭"
echo " (_(_ )_"
echo "$NOCOLOR"
}
# Traverse upwards until we find the config file
find_config() {
while [ ! -f "$CONFIG_DIR/$CONFIG_NAME" ]; do
if [ "$CONFIG_DIR" == "/" ]; then
break;
fi
CONFIG_DIR=$(dirname "$CONFIG_DIR")
done
if [ -f "$CONFIG_DIR/$CONFIG_NAME" ]; then
CONFIG="$CONFIG_DIR/$CONFIG_NAME"
fi
}
# Read and validate config file
read_config() {
local config
find_config
# Check config file existance
test -f "$CONFIG" || abort "Config file $CONFIG_NAME not found."
# Read and eval first section
config=$(awk '/^/ {print; exit}' RS= $CONFIG)
eval $config
# Check required params
test -z "$host" && incomplete_config 'host'
test -z "$path" && incomplete_config 'path'
test -z "$host" || test -z "$path" && exit 1
# Expose params
SSH_HOST=$host
SSH_PATH=$path
}
# Print message about missing config param
incomplete_config() {
error "Incomplete config: '$1' not defined."
}
# Deploy specified target
deploy() {
local local_script
local remote_script
read_config
# Check target existance
target_exists $TARGET || abort "Target $TARGET not found in config file."
# Ship it!
header "Shipping $TARGET..."
# Read local script
local_script=$(awk "/^\[$TARGET:local\]/ {print; exit}" RS= $CONFIG | sed 1d)
if [ -n "$local_script" ]; then
header "Running local script..."
run_local "$local_script"
if [ $? -ne 0 ]; then
abort "Deploy wasn’t successful: local script failed."
fi
fi
# Read remote script
remote_script=$(awk "/^\[$TARGET\]/ {print; exit}" RS= $CONFIG | sed 1d)
if [ -n "$remote_script" ]; then
header "Running remote script at $SSH_HOST..."
run_remote "$remote_script"
if [ $? -ne 0 ]; then
abort "Deploy wasn’t successful: remote script failed."
fi
fi
# Shipped?
if [ $? -eq 0 ]; then
squirrel
fi
}
# Check if config <section> exists
target_exists() {
grep "^\[$1\(:local\)\?\]" $CONFIG &> /dev/null
}
# Run script or command via SSH with cd to remote path
run_remote() {
# 1. Connect to remote host (-A enables forwarding of the authentication agent connection).
# 2. Configure shell to exit on first erorr (set -e).
# 3. Check destination directory.
# 4. Open destination directory.
# 5. Run script.
local script="""
set -e
test -d \"$SSH_PATH\" || { echo \"${RED}Remote directory $SSH_PATH doesn’t exist.$NOCOLOR\"; exit 1; }
cd \"$SSH_PATH\"
$1
"""
local verbose=
if [ -n "$VERBOSE" ]; then
verbose="-v"
fi
ssh $verbose -A $2 "$SSH_HOST" "$script"
}
# Run local script
# Stop script execution and return 0 on first error
run_local() {
trap "{ return 0; trap - ERR; }" ERR
eval "$1"
trap - ERR
}
# Print available targets
list_targets() {
read_config
header "Available shipit targets:"
awk '/^\[/ {gsub(":local", "", $1); print "• " $1}' $CONFIG | tr -d '][' | sort | uniq
}
# Open remote terminal
open_console() {
read_config
header "Opening terminal on $SSH_HOST..."
run_remote "\$SHELL --login" "-t"
}
# Execute command via SSH
exec_command() {
read_config
header "Executing $* on $SSH_HOST..."
run_remote "$*"
}
# Copy local file to remote host
copy_to_remote() {
read_config
test -f "$1" || abort "Local file $1 not found."
header "Copying $1 to $SSH_HOST..."
scp "$1" "$SSH_HOST:$SSH_PATH/$1"
}
########################################################################################################################
# Parse CLI arguments
# Options
for arg in "$@"; do
case "$arg" in
-v|--verbose) set_verbose; shift ;;
-c|--config) set_config_file "$2"; shift; shift ;;
esac
done
# Commands
arg=$1; shift
if [ -n "$arg" ]; then
case $arg in
-h|--help) usage; exit ;;
-V|--version) version; exit ;;
list|ls) list_targets; exit ;;
console|shell|ssh) open_console; exit ;;
exec|run) exec_command "$@"; exit ;;
copy|cp) copy_to_remote "$@"; exit ;;
*) TARGET="$arg" ;;
esac
fi
# Run specified target
deploy