#! /bin/bash
#######################################################################
# Copyright 2012 VMware, Inc.  All rights reserved.                   #
#######################################################################

#Script used to install VSA Cluster Service

# Includes
THIS_DIR=`dirname $0`
source $THIS_DIR/common.sh

# Constants
MIN_FREE_SPACE_REQUIRED_MB=600
MIN_FREE_SPACE_RECOMMENDED_MB=2000

# Functions

# Arguments: $VCSUSER $PASS
# Creates an account that never expires. The account be assigned to default group.
create_useraccount() {

    myuser=$1
    mypass=$2

    print_verbose "Perform user account creation $@"

    if [ "X$myuser" == "X" ]
    then
        print_msg "Must specify user name for creation"
        return 1
    fi

    id $myuser > /dev/null 2>&1
    if [ $? != 0 ]
    then
        print_msg "Creating account $myuser"
        REMOVEUSER=1
        uerr=`useradd -m $1 2>&1`
        if [ $? != 0 ]
        then
            print_msg "Failed to create user $myuser"
            print_msg "$uerr"
            return 1
        fi
    else
        # No need to set the password if the account is already created
        print_msg "Skipping $myuser account creation."
    fi

# We don't need to create password. Not creating password will result in disabling
# login to the account, which is what we desire. The User is free to pass password
# to the command, if needed. We will not set password if password is not specified.
    if [ "X$mypass" != "X" ]
    then
        uerr=`echo $myuser:$mypass | chpasswd 2>&1`
        if [ $? != 0 ]
        then
            print_msg "Failed to change password for $myuser"
            print_msg "$uerr"
            return 1
        fi
    fi

    print_msg "Disabling password expiry for $myuser"
    uerr=`chage -I -1 -E -1 $myuser 2>&1`
    if [ $? != 0 ]
    then
        print_msg "Failed to disable password expiry for $myuser"
        print_msg "$uerr"
        return 1
    fi
    print_verbose "Done create_useraccount"
    return 0
}

# Arguments: $INSTALLDIR
# Return codes
# 1 - general error
# 2 - the directory already exist
setup_dir() {
    print_verbose "Setting up directories and permissions $@"
    installdir=$1

    if [ "X$installdir" == "X" ]
    then
        print_msg "Must specify installation directory"
        return 1
    fi

    # Verify that the directory is empty
    if [ -n "$(ls -A "$installdir" 2>/dev/null)" ]
    then
        print_err "Directory \"$installdir\" already exists and it is not empty."
        return 2
    fi

    # Create the installation directory
    mkdir -p $installdir
    if [ $? != 0 ]
    then
        print_err "Unable to create directory \"$installdir\""
        return 1
    fi
    return 0
}

# Arguments: $INSTALLDIR
copy_files() {
    print_verbose "Copying VCS files $@"
    cdir=$1

    if [ "X$cdir" == "X" ]
    then
        print_msg "Must specify installation directory"
        return 1
    fi
    uerr=`cp -r $ROOTDIR/* $cdir 2>&1`
    cerr=$?
    if [ $cerr != 0 ]
    then
        print_msg "Failed to copy files from $ROOTDIR to $cdir"
        print_msg "$uerr"
        return 1
    fi
    print_verbose "Done copying VCS files"
    return 0
}

# Arguments
# $1 - Target installation directory
set_execution_permissions() {
    pdir=$1

    print_msg "Changing file execution permissions in $pdir"

    # Change file execution permissions
    uerr=`chmod u+x "$pdir/bin/setup" "$pdir/setup/uninstall.sh" 2>&1`
    if [ $? != 0 ]
    then
        print_err "Failed to change execution permissions."
        print_err "$uerr"
        return 1
    fi

    uerr=`chmod ugo-x "$pdir/setup/install.sh" 2>&1`
    if [ $? != 0 ]
    then
        print_err "Failed to change execution permissions."
        print_err "$uerr"
        return 1
    fi

    return 0
}

# Arguments $VCSUSER $INSTALLDIR
set_permissions() {
    print_verbose "Setting up directories and permissions $@"
    puser=$1
    pdir=$2
    if [ "X$pdir" == "X" ]
    then
        print_msg "Must specify installation directory"
        return 1
    fi
    if [ "X$puser" == "X" ]
    then
        print_msg "Must specify user name"
        return 1
    fi
    print_msg "Changing ownership for $pdir"
    uerr=`chown -R $puser $pdir 2>&1`
    if [ $? != 0 ]
    then
        print_err "Failed to change user owner of directory $pdir"
        print_err "$uerr"
        return 1
    fi
    print_msg "Removing privileges for group and others for $pdir"
    uerr=`chmod o-rwxs,g-rwxs -R $pdir 2>&1`
    if [ $? != 0 ]
    then
        print_err "Failed to change permissions for $pdir"
        print_err "$uerr"
        return 1
    fi

    print_verbose "Done set_permission"
    return 0
}

# Check if ports are in use.
# Arguments $1 - a comma separated list of ports.
check_port_usage() {
    ports=$1

    error=0
    for port in ${ports//,/ }
    do
        # Parsing a line similar to:
        # tcp        0      0 0.0.0.0:4330            0.0.0.0:*               LISTEN
        netstat -l --numeric-ports --numeric-hosts -n | tr -s ' ' | cut -d ' ' -f 4 | grep ":$port\$" > /dev/null
        if [ $? == 0 ]
        then
            print_err "Port $port is in use."
            error=1
        fi
    done

    return $error
}

# Creates firewall exceptions for ports specified in the ini file.
# Arguments $1 - a comma separated list of ports.
create_firewall_exceptions() {
    ports=$1

    linux_distribution=$(get_linux_distribution)

    # If it is a supported Linux distribution
    if [ "$linux_distribution" == "RedHat" ]
    then
        for port in ${ports//,/ }
        do
            iptables -I INPUT -p tcp --dport $port -j ACCEPT
            if [ $? != 0 ]
            then
                print_err "Unable to create firewall exception for port $port."
                return 1
            fi
        done
    else
        add_warning "You must change firewall rules to allow incoming TCP/IP connections on\n\t\
                     ports ${ports}."
    fi

    # Make the firewall changes persistent
    myerr=0
    if [ "$linux_distribution" == "RedHat" ]
    then
        /sbin/service iptables save
        myerr=$?
    fi

    if [ $myerr != 0 ]
    then
        print_err "Unable to make firewall changes permanent."
        return 1
    fi

    return 0
}

# Updates RUN_AS_USER parameter inside 'wrapper-script' file.
# Argument $1 - user name
# Argument $2 - location of the file to modify
update_run_as_user() {
    vcsuser=$1
    targetfile=$2
    sed "s/^#\?RUN_AS_USER=.*/RUN_AS_USER=$vcsuser/g" <"$targetfile" >"${targetfile}.tmp"
    if [ $? == 0 ]
    then
        mv "${targetfile}.tmp" "$targetfile"
    else
        return 1
    fi

    return 0
}

# Roll backs the changes in case of installation failure
# Arguments
# $1 = User name
# $2 = Installation target directory

rollback_install() {
    username=$1
    targetDir=$2

    print_msg "Rolling back the installation."

    # Check if uninstall file exists in the target directory and is executable
    # If it does, execute it
    if [ -x "$targetDir/setup/uninstall.sh" ]
    then
        print_verbose "Calling uninstall script."
        if [ "$REMOVEUSER" == "1" ]
        then
            "$targetDir/setup/uninstall.sh" -s
            return $?
        else
            "$targetDir/setup/uninstall.sh" -s -k
            return $?
        fi
    else
        # Delete Directory
        if [ -d "$targetDir" ]
        then
            remove_directory "$targetDir" 1
        fi

        # Delete User Account
        if [ "$REMOVEUSER" == "1" ]
        then
            remove_useraccount "$username" 1
        fi
    fi
    return 0
}
# Arguments: $VCSUSER $PASS $INSTALLDIR
main() {
    muser=$1
    mpass=$2
    mdir=$3

    if [ "X$muser" == "X" ]
    then
        print_msg "Must specify user name"
        return 1
    fi

    # Verify whether the local host name can be resolved to an IP address
    ping -c 1 -W 2 `uname -n` 2>&1 >/dev/null
    myerr=$?
    if [ $myerr != 0 ]
    then
        msgtext="The name of the local host cannot be resolved to an IP address. Please add \"127.0.0.1 `uname -n`\" to the /etc/hosts file and retry the installation."
        print_msg "$msgtext"
        return 1
    fi

    # Setup user account
    create_useraccount "$@"
    myerr=$?

    if [ $myerr != 0 ]
    then
        print_msg "User account creation failed."
        rollback_install "$muser" ""
        return 1
    fi

    # Figure out installation path
    id $muser > /dev/null 2>&1
    if [ $? != 0 ]
    then
        print_msg "User account $muser must be created before setting up directories"
        return 1
    fi

    if [ "X$mdir" == "X" ]
    then
        mdir=`eval echo ~$muser`
        mdir="$mdir/$VCSDIRNAME"
    fi

    if [ "X$mdir" == "X" ]
    then
        print_msg "Failed to find installation directory for $muser"
        rollback_install "$muser" ""
        return 1
    fi

    # Create installation dir
    setup_dir "$mdir"
    myerr=$?

    if [ $myerr == 2 ] # The directory already exist and is not empty
    then
        print_msg "The installation folder must not exist or must not contain any files."
        rollback_install "$muser" ""
        return 1
    elif [ $myerr != 0 ]
    then
        print_msg "Failed to create directory \"$mdir\"."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Verify that there is enough free space on the disk.
    local availableSpaceMB=$( df -Pk "$mdir" | awk 'NR==2 {print $4}' )
    (( availableSpaceMB=availableSpaceMB / 1024 ))

    print_msg "Available free space on the disk is $availableSpaceMB MB."

    if (( availableSpaceMB < MIN_FREE_SPACE_REQUIRED_MB )); then
        print_err "Insufficient free space on the disk. Required $MIN_FREE_SPACE_REQUIRED_MB MB."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    if (( availableSpaceMB < MIN_FREE_SPACE_RECOMMENDED_MB )); then
        add_warning "It is recommended to have $MIN_FREE_SPACE_RECOMMENDED_MB MB of free space dedicated to the cluster service\n\t\
          before the installation. You had $availableSpaceMB MB. Please, free additional $((MIN_FREE_SPACE_RECOMMENDED_MB - availableSpaceMB)) MB of space."
    fi

    # Copy VCS files to the installation dir
    copy_files "$mdir"
    if [ $? != 0 ]
    then
        print_msg "Failed to copy files to directory \"$mdir\"."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Change execution permissions
    set_execution_permissions "$mdir"
    if [ $? != 0 ]
    then
        print_msg "Failed to change execution permissions on files."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Update wrapper RUN_AS_USER property
    update_run_as_user "$muser" "$mdir/bin/wrapper-script"
    if [ $? != 0 ]
    then
        print_msg "Unable to modify $mdir/bin/wrapper-script file."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Setup VMVCS
    "$mdir/bin/setup"
    if [ $? != 0 ]
    then
        print_msg "The execution of \"setup\" script has failed."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Set permissions appropriately
    set_permissions "$muser" "$mdir"
    if [ $? != 0 ]
    then
        print_msg "Failed to change permissions on files inside \"$mdir\" directory."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Get list of ports
    ports=$(get_list_of_ports "$mdir/setup/ports.ini")
    if [ $? != 0 ]
    then
        print_msg "Unable to obtain the list of ports used by VCS."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Check if ports are in use
    check_port_usage "$ports"
    if [ $? != 0 ]
    then
        print_msg "Make sure that all ports required by the Cluster Service are available."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Create firewall exceptions
    create_firewall_exceptions "$ports"
    if [ $? != 0 ]
    then
        print_msg "Failed to create all necessary firewall exceptions."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Install VMVCS service deamon process.
    "$mdir/bin/vmvcs" install
    if [ $? != 0 ]
    then
        print_msg "Unable to install Cluster Service as a Linux deamon process."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Start VMVCS service
    "$mdir/bin/vmvcs" start
    if [ $? != 0 ]
    then
        print_msg "Unable to start Cluster Service."
        rollback_install "$muser" "$mdir"
        return 1
    fi

    # Verify that the service is really running.
	"$mdir/bin/vmvcs" status | grep -i "is running" > /dev/null
	if [ $? != 0 ]
    then
        print_err "The service failed to start."
        rollback_install "$muser" "$mdir"
        return 1
	fi

	print_warnings
	echo
	print_msg "The installation completed successfully."

   return 0
}

################### ENTRY POINT #######################

# Get the fully qualified directory name where this script is located
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
# Directory where this script is located (.../VSAClusterService-x.x/setup)
SRCDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
ROOTDIR=`dirname "$SRCDIR"`

if [[ "X$ROOTDIR" == "X" || "X$SRCDIR" == "X" ]]
then
    print_msg "Unable to identify VCS installation source"
    return 1
fi

ver=`cat $SRCDIR/../conf/vcs.version`
print_msg "Installing VMWare VSA cluster service $ver"

# Check if all required commands exists
CHKCMDS="sudo xargs gettext iptables sed useradd passwd chage chpasswd userdel netstat tr cut"
which $CHKCMDS > /dev/null
if [ $? != 0 ]
then
     print_msg "Missing required commands $CHKCMDS from $PATH"
     exit 1
fi

# Check if supported architecutre
arch=`uname -m`
if [ "$arch" != "x86_64" ]
then
    print_msg "Unsupported architecture $arch. VCS can be installed only on 64-bit distributions."
    exit 1
fi

# ---Defaults: should not be changed
VCSUSER="vmwarevcsadmin"
if [ "X$VMWAREVCSADMIN" != "X" ]
then
    VCSUSER=$VMWAREVCSADMIN
fi

# Done defaults---

PASS=""
INSTALLDIR=""
VERBOSE=0
DEBUG=0
VCSDIRNAME="VSAClusterService-5.1.3"

# If installation fails, then we should remove user home dir and data.
# This varialbe should be 0, in case the User wants to preserver home dir.
REMOVEUSER=0

TEMP=`getopt -o hp:d:vD --long help,pass:,dir:,verbose,debug -n "${0##*/}" -- "$@"`
if [ $? != 0 ] ; then echo "Error parsing command line options..." >&2 ; exit 1 ; fi

# Note the quotes around "$TEMP": they are essential!
eval set -- "$TEMP"

while true ; do
        case "$1" in
                -h|--help)
                        echo "Usage: ${0##*/} [options]

                        An account $VCSUSER with the specified password is created if needed.
                        VCS will be installed in the home directory of $VCSUSER.
                        Access to the user's home directory will be restricted only to $VCSUSER and root.
                        This script must be run as root.

                        -h              | --help                        Print this help.
                        -p password     | --pass password               Password for $VCSUSER account. If this parameter is not specified, then
                                                                        password will not be set for the account. As a result, login to this account
                                                                        will be disabled. Set the password if login (e.g., su, ssh) is desired.
                                                                        As a best practice, we recommended disabling login to account $VCSUSER.
                        -d install-dir  | --dir <install-dir>           Default value  is ~$VCSUSER. Path to directory where VCS will be installed.
                        -v      | --verbose                             Print verbose information.
                        -D      | --debug                               Print all commands and their arguments as they are executed (set -x)."
                        exit 1
                        ;;
                -p|--pass)
                        PASS="$2"
                        shift 2
                        ;;
                -d|--dir)
                        INSTALLDIR="$2"
                        shift 2
                        ;;
                -v|--verbose)
                        VERBOSE=1
                        shift
                        ;;
                -D|--debug)
                        DEBUG=1
                        shift
                        ;;
                --) shift ; break ;;
                *) echo "Internal error!" ; exit 1 ;;
        esac
done

# Check if root
if [ `id | sed 's/^uid=//;s/(.*$//'` != "0" ] ; then
    print_err "Must be root to install VCS"
    exit 1
fi

if [ $DEBUG == 1 ]
then
    set -x
fi

# Check Linux distribution
LINUX_DIST=$(get_linux_distribution)
print_verbose "Distribution: ${LINUX_DIST}."

print_verbose "Arguments: user=$VCSUSER pass=$PASS dir=$INSTALLDIR"

main "$VCSUSER" "$PASS" "$INSTALLDIR"
exit $?