#! /bin/bash
MYNAME=$(basename $0)
MYDIR=$(dirname $0)
MYDIR=$(readlink -m "$MYDIR")
VERSION="0.0.6"
VERBOSE=""
LOGFILE="$HOME/${MYNAME}.log"

SERVER=""
PARENT_NAME=""
VENDOR_NAME=""
RELEASE_NAME=""

OWNER=${USER}
if [ "${OWNER}" = "root" ]; then
    OWNER=${SUDO_USER}
fi
OWNER_OPTION=" --owner=\"$OWNER\""

SSH="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet"
OPTION_NO_SSH_TEST=""
LIST_IMAGES_OPTION=""
APT_UPDATED=""

VERBOSE="true"
VERBOSE_OPTION="--verbose"

#
# If this is empty, the default will be used.
# WORKING pool.sks-keyservers.net
# FAILING pgp.mit.edu
#
# https://discuss.linuxcontainers.org/t/3-0-unable-to-fetch-gpg-key-from-keyserver/2015/12
#
KEYSERVER="pgp.mit.edu"
KEYSERVER="hkp://keyserver.ubuntu.com"

if [ -f "$MYDIR/utilityfunctions.sh" ]; then
    source $MYDIR/utilityfunctions.sh
else
    echo "File '$MYDIR/utilityfunctions.sh' was not found." >&2
    exit 5
fi

#
# Prints the software version and exits.
#
function printVersionAndExit()
{
    echo "$MYNAME Version $VERSION on $(hostname)" >&2
}

#
# https://unix.stackexchange.com/questions/30286/can-i-configure-my-linux-system-for-more-aggressive-file-system-caching
# http://www.networkinghowtos.com/howto/list-all-hard-disks-connected-to-a-ubuntu-system/
#
# Use this much percent of ram for cache
# echo 99 > /proc/sys/vm/dirty_ratio
#
# Use this much percent of ram before slowing down writer process.
# echo 50 > /proc/sys/vm/dirty_background_ratio
#
# It is ok to wait 1h before writing.
# echo 360000 > /proc/sys/vm/dirty_expire_centisecs
# echo 360000 > /proc/sys/vm/dirty_writeback_centisecs

# /usr/share/lxc/templates/lxc-download --list

function printHelpAndExit()
{
cat <<EOF
Usage:
  $MYNAME [OPTION]... [CONTAINER_NAME]...

  $MYNAME - Creates and starts containers.

  -h, --help           Print this help and exit.
  -v, --version        Print version information and exit.
  --verbose            Print more messages.
  --log-file=FILE      Store all the messages in the given file too.

  --list-images        Lists the images available for download.

  --server=SERVER      Create the container on remote server.
  --owner=USERNAME     The name of the user to be created.
  --no-ssh-test        Do not test the ssh connection to the container.
  
  --template=CONTAINER Use container as a template for the new container.
  --vendor=VENDOR      Download and install the given distribution.
  --release=RELEASE    Download and install the given release.

EXAMPLES:
  pip-container-create --server=host01
  pip-container-create --verbose --server=core1 --vendor=ubuntu --release=xenial

EOF
    exit 0
}



ARGS=$(\
    getopt \
        -o hvs:c:l \
        -l "help,verbose,version,log-file:,\
list-images,\
server:,owner:,no-ssh-test,tmp,\
template:,vendor:,release:" \
        -- "$@")

if [ $? -ne 0 ]; then
    exit 6
fi

eval set -- "$ARGS"
while true; do
    case "$1" in
        -h|--help)
            shift
            printHelpAndExit
            ;;

        --verbose)
            shift
            VERBOSE="true"
            VERBOSE_OPTION="--verbose"
            ;;

        -v|--version)
            shift
            VERSION_OPTION="--version"
            ;;

        --log-file)
            shift
            LOGFILE=$(readlink -m "$1")
            shift
            ;;

        --list-images)
            shift
            LIST_IMAGES_OPTION="--list-images"
            ;;

        --server)
            shift
            SERVER="$1"
            shift
            ;;

        --owner)
            shift
            OWNER="$1"
            OWNER_OPTION=" --owner=\"$OWNER\""
            shift
            ;;

        --no-ssh-test)
            shift
            OPTION_NO_SSH_TEST="true"
            ;;

        --template)
            shift
            PARENT_NAME="$1"
            PARENT_NAME_OPTION="--template='$1'"
            shift
            ;;

        --vendor)
            shift
            VENDOR_NAME="$1"
            VENDOR_NAME_OPTION="--vendor='$1'"
            shift
            ;;

        --release)
            shift
            RELEASE_NAME="$1"
            RELEASE_NAME_OPTION="--release='$1'"
            shift
            ;;

        --)
            shift
            break
            ;;

        *)
            break
            ;;
    esac
done

#
# $*
# Container names are either coming from the command line or are automatically
# created.
#
function container_names()
{
    local number=1
    local node_name

    #
    # If there are container names in the command line we use those.
    #
    if [ "$1" ]; then
        echo $*
        return 0
    fi

    #
    # Finding a new name for the new node.
    #
    while true; do
        node_name=$(printf "node%03d" "$number")
        if sudo [ ! -d "/var/lib/lxc/$node_name" ]; then
            echo "$node_name"
            return 0
        fi

        if [ "$number" -gt 100 ]; then
            printError "Could not find name for a new node."
            return 1
        fi

        let number+=1
    done
}

#
# This function will choose a server by checking how many containers are there
# on the individual servers.
#
function choose_server()
{
    local server
    local this_number
    local min_number
    local min_server
    local retval=0

    for server in $(echo $SERVER | tr ',' ' '); do
        this_number=$(\
            ssh -o UserKnownHostsFile=/dev/null \
                -o StrictHostKeyChecking=no \
                -o LogLevel=quiet \
                $server -- sudo lxc-ls  --running -f | wc -l)

        # The header line. :(
        let this_number-=1
        printVerbose "$this_number $server"

        if [ -z "$min_server" ]; then
            printVerbose "First $server"
            min_server="$server"
            min_number="$this_number"
        elif [ $this_number -lt $min_number ]; then
            printVerbose "Smaller $server"
            min_server="$server"
            min_number="$this_number"
        fi
    done

    if [ "$min_server" ]; then
        printVerbose "*** min_server: $min_server"
        echo "$min_server"
    fi
}

#
# Prints how many remote servers are provided in the --server command line
# option argument. If there are more than one server we chose one as a load
# balancer would do.
#
function number_of_servers()
{
    local server
    local retval=0

    for server in $(echo $SERVER | tr ',' ' '); do
        let retval+=1
    done

    echo $retval
}

#
# $1: the server name
#
function is_server_running_ssh()
{
    local serverName="$1"
    local owner="$2"
    local isOK

    isOk=$(sudo -u $owner -- ssh -o ConnectTimeout=1 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet "$serverName" 2>/dev/null -- echo OK)
    if [ "$isOk" = "OK" ]; then
        return 0
    fi

    return 1
}

#
# $2: the server name
#
# Waits until the server is accepting SSH connections. There is also a timeout
# implemented here.
#
function wait_for_server_ssh()
{
    local serverName="$1"
    local owner="$2"
    local nTry=0

    if [ "$OPTION_NO_SSH_TEST" ]; then
        return 0
    fi

    while true; do
        if is_server_running_ssh "$serverName" $owner; then
            printVerbose "Server '$serverName' is started, SSH ready."
            return 0
        fi

        # 3 minutes
        if [ "$nTry" -gt 180 ]; then
            printVerbose "Server '$serverName' did not came alive."
            return 1
        fi

        printVerbose "Server '$owner@$serverName' is not yet SSH ready."
        sleep 1
        let nTry+=1
    done

    return 0
}

#
# $1: The name of the container.
# $2: The name of the package.
#
function install_package_in_container()
{
    local container_name="$1"
    local package_name="$2"
    local container_root="/var/lib/lxc/$container_name"
    local container_rootfs="$container_root/rootfs"
    local retval

    if sudo [ ! -d "$container_rootfs" ]; then
        printError "Container root '$container_rootfs' missing."
        printError "Can not install packages."
        return 1
    fi 

    if sudo [ -f "$container_rootfs/usr/bin/apt-get" ]; then
        if [ -z "$APT_UPDATED" ]; then
            printVerbose "Updating apt package list."
            sudo lxc-attach -n "$container_name" -- \
                apt-get -m -qq -y update \
                >>"$LOGFILE" 2>>"$LOGFILE"

            retval=$?
            if [ "$retval" -ne 0 ]; then
                printVerbose "Failed to update APT package list."

                printVerbose "/etc/resolv.conf:"
                sudo lxc-attach -n "$container_name" -- \
                    cat /etc/resolv.conf \
                    >>"$LOGFILE" 2>>"$LOGFILE"

                return $retval
            fi

            APT_UPDATED="true"
        fi

        printVerbose "Installing package '$package_name' with apt-get."
        sudo lxc-attach -n "$container_name" -- \
            apt-get -y --force-yes install "$package_name" \
            >>"$LOGFILE" 2>>"$LOGFILE"
        
        retval=$?

        if [ "$retval" -ne "0" ]; then
            printVerbose "Installing $package_name failed..."
            echo "/etc/resolv.conf:" \
                >>"$LOGFILE" 2>>"$LOGFILE"

            sudo lxc-attach -n "$container_name" -- \
                cat /etc/resolv.conf \
                >>"$LOGFILE" 2>>"$LOGFILE"
            
            echo "/etc/network/interfaces:" \
                >>"$LOGFILE" 2>>"$LOGFILE"

            sudo lxc-attach -n "$container_name" -- \
                cat /etc/network/interfaces \
                >>"$LOGFILE" 2>>"$LOGFILE"
        fi
    elif sudo [ -f "$container_rootfs/usr/bin/yum" ]; then
        printVerbose "Installing package '$package_name' with yum."
        sudo lxc-attach -n "$container_name" -- \
            yum install -y "$package_name" \
            >>"$LOGFILE" 2>>"$LOGFILE"

        if [ "$package_name" = "openssh-server" ]; then
            sudo lxc-attach -n "$container_name" -- \
                service sshd start \
                >>"$LOGFILE" 2>>"$LOGFILE"
        fi
    else
        printError "No package manager found in '$container_rootfs'."
        printError "Can not install packages."
    fi

    return $retval
}

#
# $1: the name of the new container.
# $2: the name of the parent container if it is provided in the command line
# $3: the vendor if any
# $4: the release name if any
#
function create_container()
{
    local new_name="$1"
    local parent_name="$2"
    local vendor_name="$3"
    local release_name="$4"
    local container_root
    local container_rootfs
    local ip
    local retval
    local temp_file
    local counter

    #
    # Checking the command line options.
    #
    if [ -z "$new_name" ]; then
        printError "No name for the new container, giving up."
        return 1
    fi

    if [ "$parent_name" -a "$vendor_name" ]; then
        printError "Template name and vendor name are mutually exclusive."
        return 1
    fi

    if [ -z "$parent_name" -a -z "$vendor_name" ]; then
        if sudo [ -d "/var/lib/lxc/ubuntu" ]; then
            printVerbose "Chose 'ubuntu' as template."
            parent_name="ubuntu"
        else
            printVerbose "Chose ubuntu xenial."
            vendor_name="ubuntu"
            release_name="xenial"
        fi
    fi
    
    if [ "$vendor_name" = "ubuntu" ]; then
        if [ -z "$release_name" ]; then
            printVerbose "Chose 'xenial' release."
            release_name="xenial"
        fi
    fi

    if [ "$vendor_name" ]; then
        if [ -z "$release_name" ]; then
            printError "Release name reuired for '$vendor_name'."
            return 1
        fi
    fi

    if [ "$parent_name" ]; then
        if sudo [ ! -d "/var/lib/lxc/$parent_name" ]; then
            printError "Template container '$parent_name' does not exist."
            return 1
        fi
    fi
    

    #
    # Checking if the software is installed.
    #
    if [ -z $(which "lxc-create") ]; then
        printVerbose "The lxc-create is not installed, attempting to install."
        apt install -y lxc1
    fi
    
    if [ -z $(which "lxc-create") ]; then
        printError "Could not install 'lxc-create' from package lxc1."
        exit 2
    fi

    #
    # Checking if the container already exists.
    #
    if sudo [ -d "/var/lib/lxc/$new_name" ]; then
        printError "The container '$new_name' already exists."
        return 1
    fi

    temp_file=$(mktemp)

    # 
    # Creating the container. We either copy an existing container that we use
    # as template or we download a distribution. 
    #
    if [ "$parent_name" ]; then
        printVerbose "Creating container $new_name from template $parent_name."
        sudo lxc-copy -n "$parent_name" --newname="$new_name"
    else 
        #
        # The network configuration is in 
        # /etc/lxc/default.conf
        #
        printVerbose "Creating cointainer with $vendor_name $release_name."
        printVerbose "DOWNLOAD_KEYSERVER=$KEYSERVER sudo lxc-create \
            -t download --name=$new_name \
                -- \
                --dist $vendor_name \
                --release $release_name \
                --arch amd64 --no-validate"
        DOWNLOAD_KEYSERVER="$KEYSERVER" sudo lxc-create \
            -t download --name="$new_name" \
                -- \
                --dist "$vendor_name" \
                --release "$release_name" \
                --arch amd64 --no-validate\
                >>$temp_file 2>>$temp_file ||
        DOWNLOAD_KEYSERVER="$KEYSERVER" sudo lxc-create \
            -t download --name="$new_name" \
                -- \
                --dist "$vendor_name" \
                --release "$release_name" \
                --arch amd64 --no-validate\
                >>$temp_file 2>>$temp_file
    fi
   
    container_root="/var/lib/lxc/$new_name"
    container_rootfs="$container_root/rootfs"

    if sudo [ ! -d "$container_root" ]; then
        printError "Failed to create '$new_name' container."
        if sudo [ -f "$temp_file" ]; then
            cat "$temp_file" >&2
            rm -f "$temp_file"
        fi

        return 1
    fi

    # 
    # Starting the container. 
    #
    sudo lxc-start -n "$new_name" >>"$temp_file" 2>>"$temp_file"
    
    #
    # Waiting for IP.
    #
    counter=0
    while [ -z $ip ]; do
        ip=$(sudo lxc-info -n "$new_name" -i | awk '{print $2}')

        if [ "$ip" = "-" ]; then
            ip=""
        fi

        if [ -z "$ip" ]; then
            if [ "$counter" = 0 ]; then
                printVerbose "Waiting for $new_name to get an IP ($counter)."
            fi
            sleep 1
        fi

        let counter+=1
        if [ "$counter" -gt 60 ]; then
            printError "Container did not get an IP."
            cat "$temp_file" >&2
            rm -f "$temp_file"
            return 1
        fi
    done

    if sudo [ -f "$temp_file" ]; then
        rm -f "$temp_file"
    fi
   
    #
    # Checking and installing packages.
    #
    if [ ! -f "$container_rootfs/usr/bin/sudo" ]; then
        printVerbose "Container has no sudo, attempting to install."
        install_package_in_container "$new_name" "sudo"

        retval=$?
        if [ "$retval" -ne 0 ]; then
            #return "$retval"
            printWarning "Failed to install."
        fi
    fi
    
    if sudo [ ! -f "$container_rootfs/usr/sbin/sshd" ]; then
        printVerbose "Container has no sshd, attempting to install."
        install_package_in_container "$new_name" "openssh-server"

        retval=$?
        if [ "$retval" -ne 0 ]; then
            printWarning "Failed to install."
            #return "$retval"
        fi
    fi
    
    #
    # Seems to make sense to install this.
    #
    install_package_in_container "$new_name" "dnsutils"

    printVerbose "Removing 'ubuntu' user."
    sudo lxc-attach -n "$new_name" -- \
        /usr/sbin/userdel --remove "ubuntu" \
        2>/dev/null

    #
    # Creating the new user.
    #
    printVerbose "Creating new user 'pipas'."

    sudo lxc-attach -n "$new_name" -- \
        /usr/sbin/useradd --create-home --shell=/bin/bash "$OWNER" \
        2>/dev/null

    # Well, if the user is already created this might happen.
    #if [ $? -ne 0 ]; then
    #    printError "Could not create user $OWNER"
    #fi

    #
    # Copying the SSH keys.
    #
    printVerbose "user     : $USER"
    printVerbose "OWNER    : $OWNER"
    printVerbose "new_name : $new_name"
    
    #sudo ls -lha /home/$OWNER
    printVerbose \
        "/home/$OWNER/.ssh -> /var/lib/lxc/$new_name/rootfs/home/$OWNER"

    sudo cp -r \
        "/home/$OWNER/.ssh" \
        "/var/lib/lxc/$new_name/rootfs/home/$OWNER/.ssh"

    sudo lxc-attach -n "$new_name" -- \
        chown -R ${OWNER}.${OWNER} "/home/${OWNER}/.ssh" \
        2>/dev/null

    #ls -lha /var/lib/lxc/$new_name/rootfs/home/$OWNER

    #cp -r "/home/$OWNER/bin/" "/var/lib/lxc/$new_name/rootfs/home/$OWNER/"
    #sudo lxc-execute -n "$new_name" -- \
    #    chown -R ${OWNER}.${OWNER} "/home/${OWNER}/bin" \
    #    2>/dev/null

    #
    # Adding to the sudoers.
    #
    # On centos 7 we also need : lxc-attach -n centos -- yum install sudo
    printVerbose "Adding '$OWNER' to sudoers."
    echo "" | sudo tee -a "/var/lib/lxc/$new_name/rootfs/etc/sudoers" > /dev/null
    echo "${OWNER} ALL=(ALL) NOPASSWD:ALL" | \
        sudo tee -a "/var/lib/lxc/$new_name/rootfs/etc/sudoers" > /dev/null

    #
    # Installing some packages.
    #
    #sudo lxc-execute -n "$new_name" -- apt-get -y --force-yes update
    #sudo lxc-execute -n "$new_name" -- apt-get -y --force-yes upgrade
    #sudo lxc-execute -n "$new_name" -- apt-get -y --force-yes install xauth vim-gtk3

    #
    # Starting the new container, showing the list.
    #
    #sudo lxc-start -n "$new_name"


    echo $ip
    wait_for_server_ssh $ip $OWNER

    return 0
}

function list_images_long()
{
    local old_ifs="$IFS"
    local line
    local distribution
    local version
    local old_distribution
    local old_version

    IFS=$'\n'
    for line in $(DOWNLOAD_KEYSERVER=$KEYSERVER \
        /usr/share/lxc/templates/lxc-download --list | \
        grep -v "Setting up"  | \
        grep -v "Downloading" | \
        grep -v "^DIST" | \
        grep -v "^$" | \
        grep '^[a-zA-Z]')
    do
        distribution=$(echo "$line" | awk '{print $1}')
        version=$(echo "$line" | awk '{print $2}')

        if [ "$old_version" = "$version" ]; then
            continue
        else
            old_version="$version"
        fi

        #echo "-> $line"
        if [ -z "$old_distribution" ]; then
            printf "${DIST_COLOR}%-10s${TERM_NORMAL} " "$distribution"
            old_distribution="$distribution"
        elif [ "$distribution" != "$old_distribution" ]; then
            printf "\n"
            printf "${DIST_COLOR}%-10s${TERM_NORMAL} " "$distribution"
            old_distribution="$distribution"
        fi

        printf "${VERSION_COLOR}%-9s${TERM_NORMAL} " "$version"
    done
    IFS="$old_ifs"

    printf "\n"
}

#
# Checking command line arguments.
#
EXTRA_OPTIONS=$*

if [ "$OWNER" = "root" -a -n "$LIST_IMAGES_OPTION" ]; then
    printError "The owner can't be root."
    printError "Use the --owner command line option to set the owner account."
    exit 1
fi

#
# Doing the job. If this is a local call we create the container here, if it is
# a remote operation we ssh to the remote server and execute this same script 
# there.
#
if [ -z "$SERVER" ]; then
    # 
    # No server is provided, we execute locally.
    #
    if [ "$VERSION_OPTION" ]; then
        printVersionAndExit
        exit 0
    elif [ -n "$LIST_IMAGES_OPTION" ]; then
        list_images_long
    else
        #
        # The log file.
        #
        touch "$LOGFILE"

        if [ ! -f "$LOGFILE" ]; then
            LOGFILE="/dev/null"
        fi

        printVerbose "Logfile is '$LOGFILE'."

        #
        # Actually creating the containers here.
        #
        for container in $(container_names $*); do
            printVerbose create_container "$container" "$PARENT_NAME" "$VENDOR_NAME" "$RELEASE_NAME"
            create_container \
                "$container" "$PARENT_NAME" "$VENDOR_NAME" "$RELEASE_NAME"
        done

    fi
else
    #
    # We received the --server option and so we run the script on a remote
    # server.
    #
    if [ $(number_of_servers) -gt 1 ]; then
        chosen_server=$(choose_server)
        printVerbose "Multiple servers... chose '$chosen_server'..."
    else
        chosen_server="$SERVER"
    fi

    if [ "$chosen_server" ]; then
        printVerbose "Executing on server '$chosen_server'."
        if [ -n "$LIST_IMAGES_OPTION" ]; then
            $SSH \
                $chosen_server -- \
                sudo $MYNAME \
                    $OWNER_OPTION \
                    --no-ssh-test \
                    $VERSION_OPTION $VERBOSE_OPTION \
                    $PARENT_NAME_OPTION \
                    $VENDOR_NAME_OPTION \
                    $RELEASE_NAME_OPTION \
                    $LIST_IMAGES_OPTION \
                    $EXTRA_OPTIONS
            
            exit $?
        fi

        container_ip=$($SSH \
            $chosen_server -- \
            sudo $MYNAME \
                $OWNER_OPTION \
                --no-ssh-test \
                $VERSION_OPTION $VERBOSE_OPTION \
                $PARENT_NAME_OPTION \
                $VENDOR_NAME_OPTION \
                $RELEASE_NAME_OPTION \
                $LIST_IMAGES_OPTION \
                $EXTRA_OPTIONS)

        printVerbose "Finished executing on server '$chosen_server'."
        printVerbose "IP address is '$container_ip'..."

        if [ -z "$container_ip" ]; then
            printError "No IP address..."
            exit 5
        fi

        wait_for_server_ssh "$container_ip" "$OWNER"

        echo "$container_ip"
        $SSH $chosen_server -- pip-host-control
    else
        printError "No server found."
    fi
fi

