#! /bin/bash
# vim: fdm=marker et

. shell-args

# function show_help {{{
show_help()
{
    cat <<EOF

mkve-bundle - create a bundle for mkve

Usage: $PROG [options] [<template>]

Options:
  --hypervisor=HYPERVISOR   hypervisor (type of virtualization: openvz);
  --output=PATH             path to output a bundle;
  --hooks=PATH              path to hooks/ directory;
  --logfile=PATH            logfile to log to;
  --force-no-sign           don't sign a bundle;
  -q, --quiet               try to be more quiet;
  -v, --verbose             try to be more verbose;
  -V, --version             print program version and exit;
  -h, --help                show help text and exit.

See mkve-bundle(1) for more verbose help.
EOF

    exit 0
}
# }}}

# predefined variables {{{

hypervisor=
template=
output=
hooks=/usr/share/mkve/hooks
logfile=
no_suffix=

force_no_sign=

# set other default values
[ -r "$HOME/.mkve/bundle-config" ] &&
    . "$HOME/.mkve/bundle-config"

# but you can't change the value of these values
MKVE_TEMPLATES=/usr/share/mkve/templates
# }}}
# function cleanup (run it on EXIT) {{{
cleanup()
{
    if [ -d "$workdir" ]; then
        verbose "cleanup $workdir" 2>>"$logfile"
        rm -rf "$workdir" ||
        verbose "can't remove $workdir" 2>>"$logfile"
    fi
}

trap cleanup EXIT
# }}}

# function check_variables {{{

# function check_mandatory {{{

# This function checks that mandatory arguments are given
check_mandatory()
{
    [ -n "$hypervisor" ] ||
        fatal "hypervisor isn't specified"

    [ -n "$template" ] ||
        fatal "template isn't specified"

    [ -n "$hooks" ] ||
        fatal "hooks directory isn't specified"
}
# }}}
# function check_hypervisor {{{

# This function checks that it's argument is a valid hypervisor
check_hypervisor()
{
    egrep -q "^$hypervisor$" "$template/hypervisors" ||
        fatal "unsupported hypervisor: $hypervisor"

    verbose "using hypervisor '$hypervisor'"
}
# }}}
# function check_template {{{

# This function checks, that it's argument represents a valid template.
# The format of template directory described at
#     (rus) http://www.altlinux.org/CoreSystem/Virtualization

check_template()
{
    if [ ! -d "$template" ]; then
        [ -d "$MKVE_TEMPLATES/$template" ] &&
            template="$MKVE_TEMPLATES/$template" ||
            fatal "$MKVE_TEMPLATES/$template: no such template"
    fi

    [ -r "$template/packages-lists/$hypervisor" ] ||
        fatal "template contains no packages"

    verbose "hyperv=$hypervisor"
    [ -r "$template/versions/$hypervisor" ] &&
        template_version=$( . "$template/versions/$hypervisor"; echo "$template_version") ||
        fatal "template version undefined"

    [ -n "$template_version" ] ||
        fatal "failed to determine template version"

    verbose "using template '$template'"
    verbose "using template_version '$template_version'"
}
# }}}
# function check_output {{{
check_output()
{
    if [ -z "$output" ]; then
        verbose "output name is not specified; I'll use default"
        output="$(pwd)/default.bun"
    elif [ $output = ${output##/} ]; then
        verbose "I found, that output name is relative"
        output="$(pwd)/$output"
    fi

    if [ -z "$no_suffix" ]; then
        [ "${output%%.bun}" = "$output" ] &&
            output="$output.bun"
    fi

    verbose "using output '$output'"
}
# }}}
# function check_hooks {{{

# this function checks, that $hooks directory is exist
check_hooks()
{
    [ -d "$hooks" ] ||
        fatal "$hooks is not a directory"
    verbose "using hooks directory '$hooks'"
}
# }}}
# function check_log {{{
check_log()
{
    if [ -z "$logfile" ]; then
        logfile="$TMPDIR/mkve-bundle.log"
    fi
}
# }}}

check_variables()
{
    check_mandatory
    check_template
    check_hypervisor
    check_output
    check_hooks
}
# }}}
# function tarify_packages {{{

# function get_hooks {{{
get_hooks()
{
    local h
    local looks="$template/hooks"

    for h in $(cat "$template/hooks-lists/$hypervisor" | egrep -v '^(#|$)'); do
        if   [ -x "$looks/$h" ]; then printf ' --hook %s/%s' "$looks" "$h"
        elif [ -x "$hooks/$h" ]; then printf ' --hook %s/%s' "$hooks" "$h"
        else fatal "can't find hook $h"; fi
    done
}
# }}}
# function get_packages {{{
get_packages()
{
    local p

    for p in $(cat "$template/packages-lists/$hypervisor" | egrep -v '^(#|$)'); do
        printf ' %s' "$p"
    done
}
# }}}

tarify_packages()
{
    local cache_cmd

    cache_cmd="mkve-cache --create-fake-devices --output $workdir/files.tar $(get_hooks) $(get_packages)"

    verbose "running $cache_cmd"
    eval "$cache_cmd" || fatal "tarify_packages failed"
}
# }}}
# function append_hypervisor_specific_data {{{
append_hypervisor_specific_data()
{
    local confdir="$template/conf/$hypervisor"

    if [ -d "$confdir" ]; then
        pushd "$confdir"
        verbose "tarifying $hypervisor-specific configs: $(ls)"
        tar -cf "$workdir/conf.tar" * ||
        fatal "can't tarify $template/conf/$hypervisor"
        popd
    fi
}
# }}}
# function append_license {{{
append_license()
{
    local license="$template/license/$hypervisor"

    if [ -r "$license" ]; then
        cp "$license" "$workdir/license.txt" ||
        fatal "can't copy $license to bundle"
    fi
}
# }}}
# function create_meta_file {{{

# function meta_section {{{
meta_section()
{
    printf '[%s]\n' "$1" >>"$metafile" ||
        fatal "can't write to $metafile"
}
# }}}
# function meta_write {{{
meta_write()
{
    [ -n "$1" ] ||
        fatal "meta_write: key is not specified"
    [ -n "$2" ] ||
        fatal "meta_write: value is not specified"

    printf '%s=%s\n' "$1" "$2" >>"$metafile" ||
        fatal "can't write to $metafile"
}
# }}}

create_meta_file()
{
    local metafile="$workdir/info"
    local vendor_file="$template/vendor/$hypervisor"

    verbose "creating metadata: $metafile"
    rm -f "$metafile"; touch "$metafile"

    meta_section "main"
    meta_write "template" "$(basename "$(realpath "$template")")"
    meta_write "template_version" "$template_version"
    meta_write "hypervisor" "$hypervisor"
    meta_write "arch" "$(arch)"
    [ -r "$vendor_file" ] &&
        meta_write "vendor" "$( . "$vendor_file"; echo $vendor )"

    meta_section "image"
    meta_write "image_path" "files.tar"

    if [ -d "$template/conf/$hypervisor" ]; then
        meta_section "$hypervisor"
        meta_write "config" "conf.tar"
    fi
}
# }}}
# function write_output {{{
write_output()
{
    verbose "creating a bundle: $output"
    cd "$workdir" && tar -cf "$output" info $(ls -Sr1 * | egrep -v '^info$') ||
    fatal "can't create $output"
}
# }}}

# program:

# parse command line and validate all the options {{{
TEMP=$(getopt -o ${getopt_common_opts} -n "$PROG" --long logfile:,hypervisor:,output:,hooks:,force-no-sign,no-suffix,${getopt_common_longopts} -- "$@")
eval set -- "${TEMP}"
while true; do
    case "$1" in
        --hypervisor)
            shift; hypervisor="$1"
            ;;
        --output)
            shift; output="$1"
            ;;
        --hooks)
            shift; hooks="$1"
            ;;
        --logfile)
            shift; logfile="$1"
            ;;
        --force-no-sign)
            force_no_sign='yes'
            ;;
        --no-suffix)
            no_suffix='yes'
            ;;
        --) shift; break
            ;;
        *)
            parse_common_option "$1"
            ;;
    esac
    shift
done

[ -n "$1" ] && template="$1"
workdir=$(mktemp -d) || fatal "mktemp failed"
# }}}

main()
{
    check_variables
    tarify_packages
    append_hypervisor_specific_data
    append_license
    create_meta_file
    write_output
}

check_log
if [ -z "$verbose" ]; then
    main >"$logfile" 2>&1 &&
    [ -n "$quiet" ] || message "created a bundle: $output"
else
    main 2>&1 | tee "$logfile"
fi
