#!/bin/bash
# gitysnc.sh, Copyright (c) 2015 Rubin
# based on linesync.sh (c) 2002 Arjen Wolfs
#
# The code contained is in this file is licenced under the terms
# and conditions as specified in the GNU General Public License.
#
# This program should be run from crontab, i.e something like:
# 0 0 * * * /home/irc/bin/gitsync.sh -s /home/irc/.ssh/id_rsa /home/irc/lib/ircd.conf /home/irc/lib/ircd.pid
#

usage() {
    echo "Help: "
    echo "  $0 [-h|-i repository] <-p ircd.pem|-s id_rsa> <ircd.conf> <ircd.pid>"
    echo ""
    echo " -h                - this help"
    echo " -p ircd.pem       - convert this ircd.pem certificate to an ssh key and use that instead of the default ~/.ssh/id_rsa"
    echo " -s id_rsa         - Full path to your ssh private key to use for git access (defaults to ~/.ssh/id_rsa)"
    echo " -i repository-url - Perform initial setup, needed only once to set up. Provide git URL as argument"
    echo " -c tagname        - Load a certificate from this git tagged object (eg, servername-cert)"
    echo " -C certfile       - Write the certificate to this file (default: fullchain.pem in ircd.conf directory)"
    echo " <ircd.conf>       - Full path to your ircd.conf file"
    echo " <ircd.pid>        - Full path to your ircd.pid file"
    echo ""
}

#Handle argument parsing
while getopts "hi:p:s:c:C:" opt; do
    case $opt in
     h)
        usage
        exit
        ;;
     i)
         dosetup="yes"
         repository="$OPTARG"

         ;;
     p)
        ircdkey="yes"
        kpath="$OPTARG"
        ;;
     s)
        skey="$OPTARG"
        ;;
     c)
        certtag="$OPTARG"
        ;;
     C)
        certfile="$OPTARG"
        ;;
    \?)
        echo "Unknown option: -$OPTARG" >&2
        exit 1
        ;;
    :)
        echo "Option -$OPTARG requires an argument." >&2
        exit 1
    esac
done
shift $((OPTIND-1))

# This checks for the presence of an executable file in $PATH
locate_program() {
        if [ ! -x "`which $1 2>&1`" ]; then
                echo "You don't seem to have $1. Sorry."
                exit 1
        fi
}

# This checks for the presence of any file
check_file() {
        if [ ! -f "$1" ]; then
                echo "There doesn't appear to be a $1. Sorry."
                exit 1
        fi
}

# Try to find programs we will need
locate_program openssl 
locate_program git
locate_program egrep
locate_program diff
locate_program chmod
locate_program readlink

# try to find GNU awk
awk_cmd=`which gawk`
if [ $? -ne 0 ]; then
        awk_cmd=""
fi

if [ -z "$awk_cmd" ]; then
	locate_program awk
	is_gawk=`echo | awk --version | head -1 | egrep '^GNU.+$'`
	if [ -z "$is_gawk" ]; then
		echo "Your version of awk is not GNU awk. Sorry."
        echo "Try: # sudo apt-get install gawk"
		exit 1
	fi
	awk_cmd="awk"	
fi

# Check for required command line parameters
if [ -z "$1" ] || [ -z "$2" ] ; then
    echo "Error: No ircd.conf or ircd.pid specified"
    usage
    exit 1
fi

if [ -n "$skey" ] && [ -n "$ircdkey" ]; then
    echo "Error: You cannot use -s and -p together. Pick one method to authenicate."
    usage
    exit;
fi

if [ -z "$skey" ] && [ -z "$ircdkey" ]; then
    echo "Error: You must provide -s or -p. Pick one method to authenicate."
    usage
    exit;
fi


if [ -z "$skey" ]; then
    skey="$HOME/.ssh/id_rsa"
fi

# check and set up stuff
diff_cmd="diff"
cpath="$1"
ppath="$2"
check_file "$cpath"
dpath=`dirname "$cpath"`
dpath=`readlink -f "$dpath"`
lpath="$dpath/linesync"
#check_file $lpath
save_dir="$PWD"; cd "$dpath"
tpath="$PWD"; cd "$save_dir"
tmp_path="$dpath/tmp"
ipath="$tmp_path/ssh.pem"
mkdir "$tmp_path" > /dev/null 2>&1

# Not all versions of date support %s, work around it
TS=`date +%Y%m%d%H%M%S`
TMPFILE="$tmp_path/linesync.$TS"
TMPCERT="$tmp_path/cert.$TS"

echo '#!/bin/bash' > "$tmp_path/git.sh"

#If they specified -p, we generate the git ssh key from ircd.pem
if [ -n "$ircdkey" ]; then

    check_file "$kpath"
    # first get the private key by just grabbing the lines that match...
    awk '/BEGIN .*PRIVATE KEY/,/END .*PRIVATE KEY/' "$kpath" > "$ipath"
    # Then we'll get the public key more properly..
    if ! openssl x509 -in "$kpath" -pubkey -noout >> "$ipath"; then
        echo "Error: I could not use $kpath as a key for some reason. Stopping!"
        exit
    fi

    chmod 600 "$tmp_path/ssh.pem"

    #Override git's ssh command so we can force our custom identity and no password
    echo -e "ssh -oPasswordAuthentication=no -i \"$ipath\" \"\$1\" \"\$2\"\n" >> "$tmp_path/git.sh"
else 
    #Override git's ssh command so we can force our custom identity and no password
    echo -e "ssh -oPasswordAuthentication=no -i \"$skey\" \"\$1\" \"\$2\"\n" >> "$tmp_path/git.sh"
fi

chmod a+x "$tmp_path/git.sh"
export GIT_SSH="$tmp_path/git.sh"


if [ "$dosetup" = "yes" ]; then
    echo "Doing initial setup with repository $repository" >&2

    # Check if destination already has a git repo
    if [ -d "$lpath/.git" ]; then
        echo "Doing setup.. but $lpath already contains a git repository."
        echo "Remove it first if you want to re-clone: rm -rf $lpath/*"
        exit 2
    fi
    # Check if directory exists and is not empty (allow empty dirs for bind mounts)
    if [ -d "$lpath" ] && [ -n "$(ls -A "$lpath" 2>/dev/null)" ]; then
        echo "Doing setup.. but destination directory $lpath already exists and is not empty."
        echo "Move it out of the way and try again"
        exit 2
    fi
    echo "Note: your public key (linesync admin will have added this to keydir):"

    if [ -n "$ircdkey" ]; then
        ssh-keygen -i -m PKCS8 -f "$tmp_path/ssh.pem"
        #cat $ipath
    else 
        cat "$skey".pub
    fi

    prevdir=`pwd`
    cd "$dpath"
    git clone "$repository" "$lpath"
    if [ -d "$lpath"/.git ]; then
        echo "Initial setup success"
        exit 0
    else
        echo "Problem with initial setup. See above"
        exit 5
    fi
fi

#Check for the git repository
if [ ! -d "$lpath" ]; then
    echo "Cannot find a git repository at $lpath."
    echo "check ircd.conf path argument, or re-run with -i <repository> to perform initial setup"
    usage
    exit 6
fi

if [ ! -d "$lpath"/.git ]; then
    echo "Error: $lpath is not a git repository. ?!"
    usage
    exit 10
fi

#update the repository from upstream
prevdir=`pwd`
cd "$lpath"
git reset -q --hard origin/master
git fetch --tags -f --quiet
git merge -q origin/master
if [ ! -z "$certtag" ]; then
    #Store the cert in a temp file
    git show "$certtag" > "$TMPCERT"
fi
cd "$prevdir"

#Copy the data to the temp file
cp "$lpath/linesync.data" "$TMPFILE"

if [ ! -s "$TMPFILE" ]; then
        echo "Unable find retrieve $lpath/linesync.data, Sorry."
	rm "$TMPFILE" > /dev/null 2>&1
        exit 1
fi

# check our ircd.conf
ircd_setup=`egrep '^# (BEGIN|END) LINESYNC$' "$cpath"|wc -l`
if [ $ircd_setup != 2 ]; then
	cp "$cpath" "$cpath.orig"
	echo "Performing initial merge on $cpath, original file saved as $cpath.orig."
	
        echo "# Do NOT remove the following line, linesync.sh depends on it!" >> $cpath
        echo "# BEGIN LINESYNC" >> $cpath
        echo "# END LINESYNC" >> $cpath
        echo "# Do not remove the previous line, linesync.sh depends on it!" >> $cpath

	# Do an initial merge to remove duplicates
	inpath="$tmp_path/linesync.tmp.$TS"
	$awk_cmd '
	{
                if (!loaded_template) {
                        command="cat " tempfile; tlines=0;
                        while ((command | getline avar) > 0) { template[tlines]=avar; tlines++ }
                        close(command)
                        loaded_template++
                }
		dup_line=0
                for (i=0; i<tlines; i++) {
                        if (tolower($0)==tolower(template[i])) { dup_line++; break }
                }
		if (!dup_line) print $0
        } ' "tempfile='$TMPFILE'" < "$cpath" > "$inpath"
else
	inpath="$cpath"
fi

# Replace the marked block in ircd.conf with the new version

$awk_cmd ' 
$0=="# BEGIN LINESYNC" { chop++; print; next }
$0=="# END LINESYNC" {
        command="cat " syncfile
        while ((command | getline avar) > 0) { print avar }
        close(command)
        chop--
}
{ if (!chop) print $0 }
' "syncfile='$TMPFILE'" < "$inpath" > "$tmp_path/linesync.new.$TS"

# run a diff between current and new confs to see if we updated anything
# no point sending the ircd a -HUP if this is not needed, especially on a
# busy network, such as Undernet.
diff=`"$diff_cmd" "$cpath" "$tmp_path/linesync.new.$TS"`
if [ ! -z "$diff" ]; then
	# Changes were detected

	# Back up the current ircd.conf and replace it with the new one
	cp "$cpath"  "$dpath/ircd.conf.bk"
	cp "$tmp_path/linesync.new.$TS" "$cpath"

	# Rehash ircd (without caring wether or not it succeeds)
	kill -HUP `cat "$ppath" 2>/dev/null` > /dev/null 2>&1

    #todo: kill iauthd?
fi

if [ ! -z "$certtag" ]; then
    # Use provided certfile path or default to fullchain.pem
    : "${certfile:=$dpath/fullchain.pem}"

    if [ ! -f "$certfile" ]; then
        cdiff="yes"
    else
        cdiff=`"$diff_cmd" "$certfile" "$TMPCERT"`
    fi
    if [ ! -z "$cdiff" ]; then
        #Changes detected
        if [ -f "$certfile" ]; then
            cp "$certfile" "${certfile}.backup"
        fi
        cp "$TMPCERT" "$certfile"

	# Rehash pem files (without caring wether or not it succeeds)
        kill -USR1 `cat "$ppath" 2>/dev/null` > /dev/null 2>&1
    fi
fi


# (Try to) clean up
if [ -n "$tmp_path" ] && [ -d "$tmp_path" ]; then
   rm -rf "$tmp_path" #> /dev/null 2>&1
fi

# That's it...
