#!/bin/bash -e
#
# sync-router	shell scripts to synchronize cisco configuration and DHCP static
#		lease files with with GIT repository. Operations performed:
#		  - update header of modified DHCP static lease files, upload
#		    them using using scp and add them to the next commit
#		  - restart Cisco DHCP service after updating static lease files
#		  - copy Cisco startup-config using scp and add to next commit
#		  - update IOS image files on the router when checked in to the
#		    repository, update startup-config file accordingly on router
#		  - commit changes to the git repository
#
# Version 1.2, latest version at: https://gitlab.lindenaar.net/scripts/cisco
#
# Copyright (c) 2016 Frederik Lindenaar
#
# This script is free software: you can redistribute and/or modify it under the
# terms of version 3 of the GNU General Public License as published by the Free
# Software Foundation, or (at your option) any later version of the license.
#
# This script is distributed in the hope that it will be useful but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, visit <http://www.gnu.org/licenses/> to download it.

### Configuration ###
router=`basename \`dirname $0 | pwd\``	  # name of router - based on directory
dhcpfiles=dhcp-\*			  # list/pattern of dhcp lease files
imagefiles=c??00-universalk9-mz.S[SP]A.\* # list/pattern of IOS image files
filestore=flash				  # location of dhcp/IOS files on cisco

### Implementation ###
echo updating with $router

# Support function to print an error message and quit with an error exit code(1)
# parameters:	$1 - message to be printed
fail() {
  echo FATAL: ${1?ERROR: no error message provided for fail()}, aborted
  exit 1
}

# Support function to download a file from the router
# parameters:	$1 - filename to be copied and $2 - (optional) target filename
# when no target filename is provided the source file will be downloaded to the
# current directory with the same name. Adds downloaded file to git changeset
router_file_download() {
  fromfile=${1?ERROR: at least one filename is required to copy from router}
  tofile=${2:-$fromfile}
  if [ "$fromfile" == startup-config ]; then
	filesrc=nvram
  else
	filesrc=$filestore
  fi
  echo downloading $tofile from router $filesrc
  if scp -q $router:$filesrc:$fromfile $tofile; then
	  git add $tofile
  else
	  fail "download of $tofile failed"
  fi
}

# Support function to upload a file to the router
# parameters:	$1 - filename to be copied and $2 - (optional) target filename
# when a target filename is provided the source file will be moved to the target
# file after uploading and the target file is added to the git changeset
router_file_upload() {
  fromfile=${1?ERROR: at least one filename is required to copy to router}
  tofile=${2:-$fromfile}
  if [ "$tofile" == startup-config ]; then
	filedst=nvram
  else
	filedst=$filestore
  fi
  echo uploading new/updated $tofile to router $filedst
  if scp -q $fromfile $router:$filedst:$tofile; then
	if [ "$fromfile" != "$tofile" ]; then
		mv $fromfile $tofile
		git add $tofile
	fi
  else
	fail "upload of $tofile failed"
  fi
}

# Support function to remove a file from the router
# parameters:	$1 - filename to be removed
router_file_remove() {
  delfile=${1?ERROR: need a filename to remove from router}
  echo removing $delfile as it is no longer in the repository
  if ! ssh -q $router "delete /force $filestore:$delfile"; then
	  fail "removal of $delfile failed"
  fi
}


# Fetch the start-up configuration from the router
router_file_download startup-config

# Process the DHCP static lease files that have changed
git status -s "$dhcpfiles" | while read status filename token newfilename
do
  case $status in
    \?\?)
	echo skipping $filename as it is not yet added to the repository
	;;
    D)
	router_file_remove $filename
	;;
    R)
	router_file_remove $filename
	router_file_upload $newfilename
	;;
    A |M|MM)
  	echo updating and uploading modified file $filename
	head -2 $filename | while read tag value
	  do
	    case $tag in
	      \*time\*)
		date +"$tag %h %d %Y %l:%M %p" > .$filename.$$.tmp
		;;
	      \*version\*)
		echo $tag $[ $value + 1 ] >> .$filename.$$.tmp
		;;
	      *)
		fail "found unknown entry \"$tag $value\" in $filename"
		;;
	      esac
	  done
	tail +3 $filename >> .$filename.$$.tmp
	router_file_upload .$filename.$$.tmp $filename
	;;
    *)
	fail "unsupported git status \"$status\" for $filename"
	;;
  esac
done

# Restart the DHCP service on the router if any of the dhcp files changed
if git status -s "$dhcpfiles" | egrep -q ^[MAD]; then
  echo restarting DHCP service
  cat << EOT | if ! ssh -q $router; then
configure terminal
no service dhcp
service dhcp
exit
exit
EOT
	fail "unable to restart DHCP service"
  fi
fi


# Process the IOS image files that have changed
git status -s "$imagefiles" | while read status filename token newfilename
do
  case $status in
    \?\?)
	echo skipping $filename as it is not yet added to the repository
	;;
    R)
	router_file_remove $filename
	router_file_upload $newfilename
	;;
    D)
	router_file_remove $filename
	;;
    A |M)
	router_file_upload $filename
	;;
    *)
	fail "unsupported git status \"$status\" for $filename"
	;;
  esac
done

# Update the boot images in the startup-config file if we're updating any images
if git status -s "$imagefiles" | egrep -q ^[MADR]; then
  fgrep -n "boot system $filestore" startup-config | cut -d: -f1 > .startup-config.$$.lines
  head -$[ `head -1 .startup-config.$$.lines` -1 ] startup-config > .startup-config.$$
  git ls-files $imagefiles | sort -r | sed "s/^/boot system $filestore /g" >> .startup-config.$$
  tail +$[ `tail -1 .startup-config.$$.lines` +1 ] startup-config >> .startup-config.$$
  rm .startup-config.$$.lines
  router_file_upload .startup-config.$$ startup-config
fi


# show what has changed in the startup config and commit to the repository
git diff --cached startup-config
git commit || git reset HEAD startup-config