#!/usr/bin/env bash # # deploy(1) - Minimalistic shell script to deploy Git repositories. # Released under the MIT License. # # https://github.com/visionmedia/deploy # VERSION="0.6.0" CONFIG=./deploy.conf LOG=/tmp/pm2-deploy-${USER:-default}.log FORCE=0 REF= ENV= # # Read PIPED JSON # read conf # # Output usage information. # usage() { cat <<-EOF Usage: deploy [options] [command] Options: -C, --chdir change the working directory to -c, --config set config path. defaults to ./deploy.conf -V, --version output program version -h, --help output help information -f, --force skip local change checking Commands: setup run remote setup commands revert [n] revert to [n]th last deployment or 1 config [key] output config file or [key] curr[ent] output current release commit prev[ious] output previous release commit exec|run execute the given list list previous deploy commits ref [ref] deploy [ref] EOF } # # Abort with # abort() { echo echo " $@" 1>&2 echo exit 1 } # # Log . # log() { echo " ○ $@" } # # Get config value by . # config_get() { local key=$1 echo $(expr "$conf" : '.*"'$key'":"\([^"]*\)"') } # # Output version. # version() { echo $VERSION } # # Return the ssh command to run. # ssh_command() { local user="`config_get user`" if test -n "$user"; then local url="$user@`config_get host`" else local url="`config_get host`" fi local unexpanded_key="`config_get key`" local key="${unexpanded_key/#\~/$HOME}" local forward_agent="`config_get forward-agent`" local port="`config_get port`" local needs_tty="`config_get needs_tty`" local ssh_options="`config_get ssh_options`" test -n "$forward_agent" && local agent="-A" test -n "$key" && local identity="-i $key" test -n "$port" && local port="-p $port" test -n "$needs_tty" && local tty="-t" test -n "ssh_options" && local ssh_opts="$ssh_options" echo "ssh $ssh_opts $tty $agent $port $identity $url" } # # Run the given remote . # runRemote() { local shell="`ssh_command`" echo $shell "\"$@\"" >> $LOG $shell $@ } # # Run the given local . # runLocal() { echo "\"$@\"" >> $LOG /usr/bin/env bash -c "$*" } # # Run the given either locally or remotely # run() { local host="`config_get host`" if [[ $host == localhost ]] then runLocal $@ else runRemote $@ fi } # # Output config or [key]. # config() { echo $(expr "$conf" : '.*"$key":"\([^"]*\)"') } # # Execute hook relative to the path configured. # hook() { test -n "$1" || abort hook name required local hook=$1 local path=`config_get path` local cmd=`config_get $hook` if test -n "$cmd"; then log "executing $hook \`$cmd\`" run "cd $path/current; \ SHARED=\"$path/shared\" \ $cmd 2>&1 | tee -a $LOG; \ exit \${PIPESTATUS[0]}" test $? -eq 0 else log hook $hook fi } # # Pre Setup hook runs on the host before the actual setup runs # multiple commands or a script # hook_pre_setup() { local cmd=`config_get pre-setup` if test -n "$cmd"; then local is_script=($cmd) if [ -f "${is_script[0]}" ]; then log "executing pre-setup script \`$cmd\`" local shell="`ssh_command`" runLocal "$shell 'bash -s' -- < $cmd" else log "executing pre-setup \`$cmd\`" run "$cmd" fi test $? -eq 0 else log hook pre-setup fi } # # Run setup. # setup() { local path=`config_get path` local repo=`config_get repo` local ref=`config_get ref` local fetch=`config_get fetch` local branch=${ref#*/} hook_pre_setup || abort pre-setup hook failed run "mkdir -p $path/{shared/{logs,pids},source}" test $? -eq 0 || abort setup paths failed log running setup log cloning $repo if test "$fetch" != "fast"; then log "full fetch" run "git clone --branch $branch $repo $path/source" else log "fast fetch" run "git clone --depth=5 --branch $branch $repo $path/source" fi test $? -eq 0 || abort failed to clone run "ln -sfn $path/source $path/current" test $? -eq 0 || abort symlink failed hook post-setup || abort post-setup hook failed log setup complete } # # Deploy [ref]. # deploy() { local ref=$1 local branch=$2 if test -z "$branch"; then branch=${ref#*/} fi local path=`config_get path` local fetch=`config_get fetch` log "deploying ${ref}" # 1- Execute local commands log executing pre-deploy-local local pdl=`config_get pre-deploy-local` runLocal $pdl || abort pre-deploy-local hook failed # 2- Execute pre deploy commands on remote server hook pre-deploy || abort pre-deploy hook failed # 3- Fetch updates log fetching updates if test "$fetch" != "fast"; then log "full fetch" run "cd $path/source && git fetch --all --tags" else log "fast fetch" run "cd $path/source && git fetch --depth=5 --all --tags" fi test $? -eq 0 || abort fetch failed # 4- If no reference retrieve shorthand name for the remote repo if test -z "$ref"; then log fetching latest tag ref=`run "cd $path/source && git for-each-ref \ --sort=-*authordate \ --format='%(refname)' \ --count=1 | cut -d '/' -f 3"` test $? -eq 0 || abort failed to determine latest tag fi # 5- Reset to ref log resetting HEAD to $ref run "cd $path/source && git reset --hard $ref" test $? -eq 0 || abort git reset failed # 6- Link current run "ln -sfn $path/source $path/current" test $? -eq 0 || abort symlink failed # deploy log run "cd $path/source && \ echo \`git rev-parse HEAD\` \ >> $path/.deploys" test $? -eq 0 || abort deploy log append failed hook post-deploy || abort post-deploy hook failed # done log successfully deployed $ref } # # Get current commit. # current_commit() { local path=`config_get path` run "cd $path/source && \ git rev-parse --short HEAD" } # # Get th deploy commit. # nth_deploy_commit() { local n=$1 local path=`config_get path` run "cat $path/.deploys | tail -n $n | head -n 1 | cut -d ' ' -f 1" } # # List deploys. # list_deploys() { local path=`config_get path` run "tac $path/.deploys | awk '{printf \"%d\t%s\n\", NR-1, \$0}'" } # # Revert to the th last deployment. # revert_to() { local n=$1 log "reverting $n deploy(s)" local commit=`nth_deploy_commit $((n + 1))` deploy "$commit" } # # Ensure all changes are committed and pushed before deploying. # check_for_local_changes() { local path=`config_get path` if [ $FORCE -eq 1 ] then return fi git --no-pager diff --exit-code --quiet || abort "commit or stash your changes before deploying" git --no-pager diff --exit-code --quiet --cached || abort "commit your staged changes before deploying" [ -z "`git rev-list @{upstream}.. -n 1`" ] || abort "push your changes before deploying" } # parse argv while test $# -ne 0; do arg=$1; shift case $arg in -h|--help) usage; exit ;; -V|--version) version; exit ;; -C|--chdir) log cd $1; cd $1; shift ;; -F|--force) FORCE=1 ;; run|exec) run "cd `config_get path`/current && $@"; exit ;; console) console; exit ;; curr|current) current_commit; exit ;; prev|previous) nth_deploy_commit 2; exit ;; revert) revert_to ${1-1}; exit ;; setup) setup $@; exit ;; list) list_deploys; exit ;; config) config $@; exit ;; ref) REF=$1; BRANCH=$2 ;; esac done check_for_local_changes echo # deploy deploy "${REF:-`config_get ref`}" "${BRANCH}"