#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS runs

function display_help() {
    uecho "$scriptname: Déploiement distant avec runs

USAGE
    $scriptname [-h hosts] [-T tmproot] rscriptname name=value...
    $scriptname [-h hosts] [-T tmproot] @recipe name=value...
    $scriptname [-h hosts] [-T tmproot] -f rscript name=value...
    $scriptname [-h hosts] [-T tmproot] -r recipe name=value...

Lancer ce script sans argument (hors options) est équivalent à le lancer avec
l'argument @default

OPTIONS
    -C  Ne pas faire le déploiement. Configurer uniquement la connexion par clé
        sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir
        se connecter par mot de passe pour configurer la connexion par clé.
        Si l'on veut configurer la connexion par clé pour le user root, mais que
        ce n'est pas possible de se connecter par mot de passe avec le user root
        sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant,
        il est possible de faire la configuration avec '--configure root'. La
        commande serait alors
            $scriptname -h user@host --configure root
    -T tmproot
        Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple
        /var/tmp. Cette option est utile pour les vservers, qui ont par défaut
        un /tmp minuscule de 16 Mo.
    -S ssh
        Spécifier le programme à utiliser pour la connection par ssh.
    -h host
    -h @hostsfile
        Spécifier un ou plusieurs hôtes sur lequels faire le déploiement. Pour
        spécifier plusieurs hôtes, il est possible d'utiliser plusieurs fois
        l'option -h, ou spécifier en une seule fois plusieurs hôtes en les
        séparant par un espace ou le caractère ':', e.g. 'host1 host2' ou
        'host1:host2'. Si la spécification contient les caractères { et },
        l'expansion est effectuée, e.g
            -h 'root@{host1,host2}.univ.run'
        Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur
        root. Il est possible de spécifier un autre utilisateur avec la syntaxe
        user@host, e.g -h user@host
        La forme @hostsfile permet de lire la liste des hôtes depuis le fichier
        hostsfile, à raison d'un hôte par ligne.
        Si cette option n'est pas spécifiée, et que le répertoire courant est
        dans un des répertoires de \$RUNSHOSTSPATH, sélectionner l'hôte
        correspondant. Sinon, l'utilisateur doit saisir l'hôte distant de façon
        interactive.
    -f RSCRIPT
        Lancer le script individuel spécifié au lieu de chercher dans les
        répertoires \$RUNS{SCRIPTS,HOSTS}PATH
    -r RECIPE
        Lancer les scripts spécifiés dans le fichier de recettes individuel
        spécifié.
    -z  Forcer la réinstallation des scripts qui se basent sur shouldrun/setdone
    -o OUTPUT
        Générer l'archive à lancer sur l'hôte distant au lieu de faire le
        déploiement. Si plusieurs hôtes sont spécifiés, OUTPUT est considéré
        comme un nom de base auquel est ajouté le nom de l'hôte sur lequel
        l'archive doit être déployée.
    --init
    --no-init
        Forcer (resp. empêcher) la création des répertoires d'hôte correspondant
        aux hôtes spécifiés. Par défaut, la création des répertoires d'hôte est
        effectuée uniquement si ce script est lancé sans argument.
    --sysinfos
        Après un déploiement réussi sur l'hôte distant, inscrire si ce n'est
        déjà fait le résultat de la commande usysinfos dans le fichier
        sysinfos.conf du répertoire d'hôte.
        Cette option est automatiquement activée si ce script est lancé sans
        argument (hors options)."
}

set_defaults runs

action=deploy
confuser=
tmproot=
hosts=()
force_make_archive=
rscripts=()
recipes=()
runsreset=
output=
init=auto
init_sysinfos=
sysinfos_data=
runsscriptspath=
runsmodulespath=
runshostspath=
parse_opts "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    -C action=configure \
    --configure: '$set@ confuser;action=configure' \
    -T:,--tmproot: tmproot= \
    -S: SSH= \
    --force-make-archive force_make_archive=1 \
    -h:,-H:,--host: hosts \
    -f: rscripts \
    -r: recipes \
    -z runsreset=1 \
    -o: output= \
    --init init=1 \
    --no-init init= \
    --sysinfos init_sysinfos=1 \
    --runsscriptspath: runsscriptspath= \
    --runsmodulespath: runsmodulespath= \
    --runshostspath: runshostspath= \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

[ -n "$runsscriptspath" ] && RUNSSCRIPTSPATH="$runsscriptspath"
[ -n "$runsmodulespath" ] && RUNSMODULESPATH="$runsmodulespath"
[ -n "$runshostspath" ] && RUNSHOSTSPATH="$runshostspath"

__PARSED_HOSTS=()
__PARSED_FILES=()
function parse_hostsfile() {
    # Lire chacun des fichiers $* et initialiser __PARSED_HOSTS avec la liste
    # des hôtes mentionnés dans les fichiers.
    local inputfile basedir inputs input
    for inputfile in "$@"; do
        inputfile="$(abspath "$inputfile")"
        array_contains __PARSED_FILES "$inputfile" && {
            ewarn "$(ppath "$inputfile"): inclusion récursive"
            continue
        }
        array_add __PARSED_FILES "$inputfile"
        basedir="$(dirname "$inputfile")"

        array_from_lines inputs "$(<"$inputfile" filter_conf)" || {
            ewarn "$inputfile: fichier ingnoré"
            continue
        }
        for input in "${inputs[@]}"; do
            if [ "${input#@}" != "$input" ]; then
                # fichier inclus
                parse_hostsfile "$(abspath "${input#@}" "$basedir")"
            else
                array_addu __PARSED_HOSTS "$input"
            fi
        done
    done
}
function __expand_braces() {
    if [[ "$1" == *{* ]] && [[ "$1" == *}* ]]; then
        eval "echo $1"
    else
        echo "$1"
    fi
}
function __dot_is_localhost() { [ "$1" == "." ] && echo "localhost" || echo "$1"; }
function fix_hosts() {
    # Si hosts contient des éléments multiple, comme a:b, séparer ces
    # éléments. i.e (a b:c) --> (a b c)
    # Supporter la syntaxe @hostsfile qui permet de charger la liste des hôtes
    # depuis un fichier.
    # Remplacer aussi les '.' par 'localhost'
    array_map hosts __expand_braces
    array_fix_paths hosts ":"
    array_fix_paths hosts " "

    local -a _hosts _tmphosts host
    for host in "${hosts[@]}"; do
        host="${host%/}"
        if [ "${host#@}" != "$host" ]; then
            __PARSED_HOSTS=()
            parse_hostsfile "${host#@}"
            array_fix_paths __PARSED_HOSTS
            array_extendu _hosts __PARSED_HOSTS
        else
            array_addu _hosts "$host"
        fi
    done
    array_copy hosts _hosts
    array_map hosts __dot_is_localhost
}

################################################################################
# Configuration de l'accès par clé aux hôtes

if [ "$action" == "configure" ]; then
    [ -n "${hosts[*]}" ] || hosts=("$@")
    [ -n "${hosts[*]}" ] || die "Vous devez spécifier la liste des hôtes à configurer"
    fix_hosts

    runs_initdomains
    for userhost in "${hosts[@]}"; do
        splituserhost "$userhost" user host
        [ -n "$user" ] || user=root

        if [ "$host" != "localhost" ]; then
            splithost "$host" hostname domain
            if [ -z "$domain" ]; then
                host="$(runs_find_host "$host")"
                splithost "$host" hostname domain
            fi
            [ -n "$domain" ] || host="$(runs_add_domain "$host")"
        fi

        "$scriptdir/ruinst" -C ${confuser:+--configure "$confuser"} \
            ${tmproot:+--tmproot "$tmproot"} ${SSH:+-S "$SSH"} \
            ${force_make_archive:+--force-make-archive} \
            -h "$user@$host" || continue
        etitle "Configuration initiale pour runs" \
            "$scriptdir/runs" \
            ${runsscriptspath:+--runsscriptspath "$runsscriptspath"} \
            ${runsmodulespath:+--runsmodulespath "$runsmodulespath"} \
            ${runshostspath:+--runshostspath "$runshostspath"} \
            --init -h "$host"
    done
    exit 0
fi

################################################################################
# Déploiement

SSH="${SSH:-ssh}"
make_archive=

## Hôtes sur lesquels faire le déploiement
if array_isempty hosts; then
    array_split hostsdirs "$RUNSHOSTSPATH" :
    cwd="$(pwd)"
    for hostsdir in "${hostsdirs[@]}"; do
        hostsdir="$(abspath "$hostsdir")"
        if [ "${cwd#$hostsdir/}" != "$cwd" ]; then
            host="${cwd#$hostsdir/}"
            host="${host%%/*}"
            enote "Sélection automatique de l'hôte $host"
            hosts=("$host")
            break
        fi
    done
fi
if array_isempty hosts; then
    read_value "Entrez une liste d'hôtes séparés par ':'" hosts "localhost"
fi
fix_hosts
array_isempty hosts && die "Vous devez spécifier l'hôte distant avec l'option -h"

## Déploiement

# si on ne précise pas ce qu'il faut déployer, il faudra utiliser une valeur par
# défaut: @default pour root, @userdefault pour les autres utilisateurs
runs_defaults=
if [ -z "$*" -a -z "${rscripts[*]}" -a -z "${recipes[*]}" ]; then
    runs_defaults=1
    [ "$init" == auto ] && init=1
    init_sysinfos=1
fi
[ "$init" == auto ] && init=

ac_set_tmpdir exportdir
exportdir="$exportdir/runsexport"

runs_initdomains
for userhost in "${hosts[@]}"; do
    continue=

    etitle "$userhost"

    # à cause des 'continue' ci-dessous, il est possible que le nettoyage ne
    # soit pas encore fait. Le faire ici
    if [ -d "$exportdir" -o -n "$archive" ]; then
        etitle "Nettoyage de l'export"
        rm -rf "$exportdir"
        [ -n "$archive" ] && rm -f "$archive"
        archive=
        eend
    fi

    splituserhost "$userhost" user host
    prevhost="$host"
    if [ "$host" == "localhost" ]; then
        [ -n "$user" ] || user="$USER"
    else
        [ -n "$user" ] || user=root

        splithost "$host" hostname domain
        if [ -z "$domain" ]; then
            host="$(runs_find_host "$host")"
            splithost "$host" hostname domain
        fi
        [ -n "$domain" ] || host="$(runs_add_domain "$host")"
    fi
    if [ "$host" != "$prevhost" ]; then
        enote "Le nom d'hôte utilisé est $host"
    fi

    if [ "$host" != "localhost" ]; then
        if [ -n "$init" ]; then
            action=--init
        else
            action=--verify
        fi

        estep "Vérification de la configuration de l'hôte pour runs"
        "$scriptdir/runs" \
            ${runsscriptspath:+--runsscriptspath "$runsscriptspath"} \
            ${runsmodulespath:+--runsmodulespath "$runsmodulespath"} \
            ${runshostspath:+--runshostspath "$runshostspath"} \
            $action -h "$host" || { eend; continue; }
    fi

    if [ -n "$runs_defaults" ]; then
        # Si on n'a pas précisé ce qu'il faut déployer, calculer une valeur par
        # défaut
        if [ "$user" == "root" ]; then
            set -- @default
        else
            set -- @userdefault
        fi
    fi

    # Création de l'export
    etitle "Préparation de l'export"
    args=(--export --runsexportdir "$exportdir"
        ${runsscriptspath:+--runsscriptspath "$runsscriptspath"}
        ${runsmodulespath:+--runsmodulespath "$runsmodulespath"}
        ${runshostspath:+--runshostspath "$runshostspath"}
    )
    [ "$host" != "localhost" ] && args=("${args[@]}" -h "$host")
    [ -n "$runsreset" ] && args=("${args[@]}" -z)
    for rscript in "${rscripts[@]}"; do
        args=("${args[@]}" -f "$rscript")
    done
    for recipe in "${recipes[@]}"; do
        args=("${args[@]}" -r "$recipe")
    done
    "$scriptdir/runs" "${args[@]}" -- "$@" || { eend; eend; continue; }
    eend

    # Lancement des scripts locaux
    etitle "Lancement des scripts locaux"
    (cd "$exportdir"; ./runs-local) || { eend; eend; continue; }
    eend

    # Création de l'archive
    make_archive=
    if [ -n "$force_make_archive" ]; then
        make_archive=1
    elif [ "$host" != "localhost" ]; then
        make_archive=1
    elif [ -n "$output" ]; then
        make_archive=1
    fi

    archive=
    archivename=
    if [ -n "$make_archive" ]; then
        if [ -n "$output" ]; then
            if [ "${#hosts[*]}" -gt 1 ]; then
                archive="$(dirname "$output")"
                archivename="$(basename "$output")"
                splitname "$archivename" basename ext
                archive="$archive/$basename-$host${ext:+.$ext}"
            else
                archive="$output"
            fi
        else
            ac_set_tmpfile archive
        fi
        archivename="$(basename "$archive")"

        [ -z "$output" ] && tmp_archive=1 || tmp_archive=
        etitle "Création de l'archive pour le déploiement" \
            "$scriptdir/mkusfx" --bare ${tmp_archive:+--tmp-archive} -o "$archive" \
            "$exportdir" ./runs-remote || continue
    fi

    if [ -z "$output" ]; then
        # Déploiement
        if [ "$host" == "localhost" ]; then
            etitle "Déploiement sur l'hôte local"
            if [ -n "$force_make_archive" ]; then
                "$archive" ${tmproot:+--tmproot "$tmproot"}
            else
                (cd "$exportdir"; ./runs-remote)
            fi
            eend
            # Réinitialiser sysinfos_data, la valeur de l'hôte courant sera
            # utilisé par runs
            sysinfos_data=
        else
            etitle "Déploiement sur $user@$host"
            estep "Copie de l'archive"
            if scp -S "$SSH" "$archive" "$user@$host:"; then
                estep "Lancement du script de déploiement"
                "$SSH" -qt "$user@$host" "\
__estack=$(quoted_arg "$__estack")
__tlevel=$(quoted_arg "$__tlevel")
export __estack __tlevel
${UTOOLS_LANG:+UTOOLS_LANG='$UTOOLS_LANG'; export UTOOLS_LANG
}$(quoted_args "./$archivename" ${tmproot:+--tmproot "$tmproot"})"
                if [ -n "$init_sysinfos" ]; then
                    estep "Calcul des informations de l'hôte distant"
                    sysinfos_script='
source /etc/ulib 2>/dev/null && urequire DEFAULTS || exit 1
echo "\
sysname=(${MYSYSNAME[*]})
sysdist=(${MYSYSDIST[*]})
sysver=(${MYSYSVER[*]})
bits=$MYBITS"'
                    sysinfos_data="$("$SSH" -qt "$user@$host" "$sysinfos_script" | _nl2lf)"
                fi
            fi
            eend
        fi

        if [ -n "$init_sysinfos" -a -n "$sysinfos_data" ]; then
            args=(-y --init --sysinfos "$sysinfos_data")
            [ "$host" != "localhost" ] && args=("${args[@]}" -h "$host")
            etitle "Configuration de sysinfos.conf" \
                "$scriptdir/runs" "${args[@]}"
        fi

        etitle "Nettoyage de l'export"
        rm -rf "$exportdir"
        [ -n "$archive" ] && rm -f "$archive"
        archive=
        eend
    fi

    eend
done