#! /bin/bash
export MYNAME=$(basename $0)
export MYDIR=$(dirname $0)
export MYDIR=$(readlink -m "$MYDIR")
export VERSION="0.0.6"
export VERBOSE=""
export LOGFILE=""
export CONTAINER=""
export SERVER=""
export SSH="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet"

export STOP_OPTION=""
export GZIP_OPTION=""
export OPTION_DRY=""
export OPTION_ARCHIVE_DIRECTORY="$HOME/archives"

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

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

  $MYNAME - Creates archives of 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.
  --dry                  Do not do actual archiving.
  --server=SERVER        Archive containers found on the server(s).
  --stop                 Stop containers that are running.
  --gzip                 Compress the archive with gzip.

  --auto                 Execute automatic archiving of a given server.
  
EXAMPLES:
  pip-container-archive --server=core1 --gzip --verbose "www"
  pip-container-archive --auto --server=core1 --verbose

EOF
    exit 0
}

ARGS=$(\
    getopt \
        -o hvs:c:l \
        -l "help,verbose,version,log-file:,dry,server:,stop,\
auto,gzip" \
        -- "$@")

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
            ;;

        --dry)
            shift
            OPTION_DRY="true"
            ;;

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

        --stop)
            shift
            STOP_OPTION="--stop"
            ;;

        --auto)
            shift
            AUTO_OPTION="true"
            ;;

        --gzip)
            shift
            GZIP_OPTION="--gzip"
            ;;

        --)
            shift
            break
            ;;

        *)
            printError "Option '$1' not handled."
            break
            ;;
    esac
done

CONTAINER="$1"

#
# Checking the command line options.
#
if [ -z "$CONTAINER" -a -z "$VERSION_OPTION" -a -z "$AUTO_OPTION" ]; then
    printError "The first argument should be the name of the container."
    exit 6
fi

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

function archive_file_name()
{
    local container="$1"
    local tar_file_name="${container}-container-archive.tar"

    if [ "$GZIP_OPTION" ]; then
        tar_file_name+=".gz"
    fi

    echo "$tar_file_name"
}

function archive_log_file_name()
{
    local container="$1"
    local log_file_name="${container}-container-archive.log"

    echo "$log_file_name"
}

#
# Prints the names of the containers on the local computer.
#
function get_container_names()
{
    local pattern="$1"
    local container
    local n_containers=0

    cd /var/lib/lxc

    if [ -z "$pattern" ]; then
        pattern='*'
    fi

    for container in $pattern; do 
        if [ ! -d "$container" ]; then
            continue
        fi

        echo "$container"
        let n_containers+=1
    done

    printVerbose "Found $n_containers container(s)."
    return 0
}

function archive_one_container()
{
    local container="$1"
    local tar_file_name
    local tar_log_file_name
    local old_tar_file_name
    local state
    local stopped
    local retcode
    local tar_failed

    #
    # If the container is running and we are allowed to stop it we stop it.
    #
    state="$(sudo lxc-ls -f | tail -n +2 | grep "^$container " | awk '{print $2}')"
    if [ "$state" == "RUNNING" ]; then
        if [ -z "$STOP_OPTION" ]; then
            printWarning "Container '$container' is running (no --stop option)."
            printWarning "Archiving on the fly."
        else
            printVerbose "Stopping container '$container'."
            if [ -n "$OPTION_DRY" ]; then
                echo "DRY: Not stopping container '$container'."
            else
                sudo lxc-stop -n "$container"
                stopped=$?
            fi
        fi
    fi
        
    #
    # Archiving into the /var/lib/lxc/ directory.
    #
    cd /var/lib/lxc

    printVerbose "Archiving container '$container'."
    tar_file_name="$(archive_file_name "$container")"
    tar_basename="$(basename $tar_file_name)"
    tar_log_file_name=$(archive_log_file_name "$container")

    old_tar_file_name="${tar_file_name}.BAK"

    if [ -f "$tar_file_name" ]; then
        if [ -n "$OPTION_DRY" ]; then
            echo "DRY: Not renaming '$tar_file_name' -> '$old_tar_file_name'"
        else
            printVerbose "Renaming old archive file."
            mv "$tar_file_name" "$old_tar_file_name"
        fi
    fi
    
    if [ -f "$tar_file_name" ]; then
        if [ -z "$OPTION_DRY" ]; then
            printError "Failed to move '$tar_file_name' aside."
            return 1
        fi
    fi

    if [ -F "$tar_log_file_name" ]; then
        rm -f "$tar_log_file_name"
    fi

    printVerbose "Archiving into '$tar_file_name'."
    retcode=""
    if [ "$GZIP_OPTION" ]; then
        if [ -n "$OPTION_DRY" ]; then
            echo "DRY: tar --numeric-owner --warning=no-file-changed -czf $tar_file_name $container"
        else
            tar --numeric-owner -czf \
                "$tar_file_name" "$container" \
                >>"$tar_log_file_name" 2>>"$tar_log_file_name" 
            retcode=$?
        fi
    else
        if [ -n "$OPTION_DRY" ]; then
            echo "DRY: tar --numeric-owner --warning=no-file-changed -cf $tar_file_name $container"
        else
            tar --numeric-owner -cf \
                "$tar_file_name" "$container" \
                >>"$tar_log_file_name" 2>>"$tar_log_file_name"
            retcode=$?
        fi
    fi

    if [ -n "$retcode" ]; then
        echo "Return code from tar is $retcode." >>"$tar_log_file_name"
        printVerbose "Return code from tar is $retcode."

        if [ "$retcode" != "1" ] && [ "$retcode" != "0" ]; then
            tar_failed="true"
        fi

        # If the tar is not failed we remove the old archive file.
        if [ -z "$tar_faile" -a -z "$OPTION_DRY" -a -f "$old_tar_file_name" ];
        then
            printVerbose "Removing '$old_tar_file_name' file."
            rm -f "$old_tar_file_name"
        fi
    fi

    #
    # If we stopped the container, we start it again.
    #
    if [ "$stopped" ]; then
        printVerbose "Starting container '$container'."
        if [ -n "$OPTION_DRY" ]; then
            echo "DRY: sudo lxc-start -n $container"
        else
            sudo lxc-start -n "$container"
        fi
    fi

    printVerbose "Done archiving '$container'..."
    return 0
}

function check_one_container()
{
    local container="$1"
    local state
    local retval=0

    state="$(sudo lxc-ls -f | tail -n +2 | grep "^$container " | awk '{print $2}')"

    if [ "$state" == "RUNNING" ]; then
        if [ -z "$STOP_OPTION" ]; then
            retval=0
        fi
    elif [ "$state" == "STOPPED" ]; then
        printVerbose "Container '$container' is not running."
    else
        printError "Container '$container' has unknown ($state) state."
        retval=1
    fi

    return $retval
}

function archive_all_containers()
{
    local pattern="$1"
    local container
    local retval=0
    local containers="$(get_container_names $pattern)"

    for container in $containers; do
        if ! check_one_container "$container"; then
            retval=1
        fi
    done

    if [ $retval -ne 0 ]; then
        return $retval
    fi

    for container in $containers; do
        if ! archive_one_container "$container"; then
            retval=1
        fi
    done

    return $retval
}

#
# This is kinda normal operation...
#   pip-container-archive --server=core1 --gzip --verbose "www"
#
function create_archive_normal()
{
    #
    # We received the --server option and so we run the script on a remote
    # server.
    #
    printVerbose "Executing on server '$SERVER'."
    $SSH $SERVER -- \
        sudo $MYNAME \
            $GZIP_OPTION \
            $VERSION_OPTION \
            $VERBOSE_OPTION \
            $STOP_OPTION \
            "$EXTRA_OPTIONS"

    if [ -n "$SERVER" -a -z "$VERSION_OPTION" ]; then
        for container in $EXTRA_OPTIONS; do
            tar_file_name="/var/lib/lxc/$(archive_file_name "$container")"
            tar_basename="$(basename "$tar_file_name")"

            printVerbose "Downloading $tar_file_name"
            printVerbose "  to $tar_basename"
            $SSH $SERVER -- \
                "sudo cat $tar_file_name" >$tar_basename
        done
    fi
}

function create_backup_dir()
{
    local server=$1
    local n=5
    local nplusone=6

    if [ -z "$server" ]; then
        printError "create_backup_dir(): Expected server name."
        exit 1
    fi

    if [ -d "${server}.${nplusone}" ]; then
        rm -rf "${server}.${nplusone}"
    fi

    while true; do
        if [ $n -eq 0 ]; then
            break
        fi

        if [ -d "${server}.${n}" ]; then
            mv "${server}.${n}" "${server}.${nplusone}"
        fi

        let nplusone-=1
        let n-=1
    done

    if [ -d "$server" ]; then
        mv "${server}" "${server}.1"
    fi

    mkdir "$server"
}

function create_archive_auto_server()
{
    local server="$1"
    local server_status
    local server_new_status
    local target_dir
    local containers
    local container
    local n_containers

    # Checking arguments.
    printVerbose "Executing in auto mode..."
    if [ -z "$server" ]; then
        printError "create_archive_auto_server(): Expected server name."
        exit 1
    fi

    # Checking if the server is on, maybe starting it if not.
    server_status=$(pip-server-control --ping "$server")
    if [ "$server_status" == "off" ]; then
        printVerbose "Server '$server' is shut down, trying to start it."
        timeout --signal=9 15m pip-server-control --wake --wait "$server"

        server_new_status=$(pip-server-control --ping "$server")
        if [ "$server_new_status" == "off" ]; then
            printError "Could not wake up the server, giving up."
            return 1
        fi
    fi

    # Finding the target directory. Maybe creating one.
    target_dir="$OPTION_ARCHIVE_DIRECTORY"
    if [ ! -d "$target_dir" ]; then
        mkdir -p "$target_dir" 2>/dev/null

        if [ ! -d "$target_dir" ]; then
            printError "Directory '$target_dir' is not found..."
            return 1
        fi
    fi

    # Entering the target directory.
    printVerbose "Entering directory '$target_dir'..."
    pushd "$target_dir" 2>/dev/null >/dev/null
    if [ $? -ne 0 ]; then
        printVerbose "Failed to enter directory '$target_directory'."
        return 1
    fi

    # Finding out which containers we need to archive.
    containers=$(pip-server-control --list --print-containers "$server")
    n_containers=0
    for container in $containers; do
        let n_containers+=1
    done

    if [ $n_containers -gt 0 ]; then
        printVerbose "Will archive $n_containers container(s)..."
    else
        printError "No containers in the server's config file."
        return 1
    fi

    # Creating and entering the backup directory for the server.
    create_backup_dir "$server"
    
    printVerbose "Entering directory '$server'..."
    pushd "$server" 2>/dev/null >/dev/null
    if [ $? -ne 0 ]; then
        printVerbose "Failed to enter directory '$server'."
        return 1
    fi

    # Now we can archive the containers one by one.
    for container in $containers; do
        if [ -n "$OPTION_DRY" ]; then
            printVerbose "DRY: Not archiving $container in $PWD on $server."
        else
            printVerbose "Archiving $container in $PWD"
            pip-container-archive --server=$server --gzip --verbose "$container"
        fi
    done

    # If the server was off, we can stop it again.
    if [ "$server_status" == "off" ]; then
        if [ -n "$OPTION_DRY" ]; then
            printVerbose "DRY: Not stopping '$server'."
        else
            printVerbose "The server '$server' was off, stopping it again."
            pip-server-control --shut-down "$server"
        fi
    fi

    # Deleting the archive files that are made empty.
    for file in *; do
        if [ ! -f "$file" ]; then
            continue
        fi

        if [ ! -s "$file" ]; then
            rm -f "$file"
        fi
    done

    # Out from the "$server" directory.
    popd 2>/dev/null >/dev/null
    
    # If we did not create files (deleted the empty files) we can remove the
    # directory, if not this will silently fail.
    rmdir "$server" 2>/dev/null

    # Out from the target directory.
    popd 2>/dev/null >/dev/null
}

function create_archive_auto()
{
    local server

    for server in $(echo $SERVER | tr ',' ' '); do
        create_archive_auto_server "$server"
    done
}

#
# Doing the job.
#
if [ "$VERSION_OPTION" ]; then
    printVersionAndExit
    exit 0
fi

if [ -z "$SERVER" ]; then
    # This is for local containers.
    archive_all_containers "$EXTRA_OPTIONS"
elif [ -n "$AUTO_OPTION" ]; then
    create_archive_auto
else
    create_archive_normal
fi

