#!/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 pyulib/pyulib

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

USAGE
    $scriptname [-h host] [-T tmproot] <file|archive|dir> [-- options de uinst]

note: à cause d'une limitation de makeself, les options de uinst ne devraient
pas contenir d'espaces ni de caractères spéciaux. L'échappement de ces
caractères n'est pas garanti.

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
        Si l'hôte distant n'a pas sudo ou si sudo n'est pas configuré, il faut
        rajouter l'option --uses-su, e.g:
            $scriptname -h user@host --configure root --uses-su
    -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 ssh
        Spécifier le programme à utiliser pour la connection par ssh.
    -h hosts
    -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."
}

set_defaults pubkeys

action=deploy
confuser=
uses_su=
tmproot=
hosts=()
SSH=
force_make_archive=
parse_opts "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    -C action=configure \
    --configure: '$set@ confuser;action=configure' \
    --uses-su uses_su=1 \
    -T:,--tmproot: tmproot= \
    -S:,--ssh: SSH= \
    --force-make-archive force_make_archive=1 \
    -h:,-H:,--host: hosts \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

: "${SSH:=ssh}"

__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

function prepare_pubkey_access() {
    # générer un script d'installation de clé avec la bonne clé
    ac_set_tmpfile IPKS
    awkrun PUBKEYS="$PUBKEYS" '{sub(/@@PUBKEYS@@/, PUBKEYS); } {print}' \
        <"$ULIBDIR/support/install-pubkeys.sh" >"$IPKS"
    chmod +x "$IPKS"
}
function check_pubkey_access() {
    local user= host="$1" confuser="$2" ipks="$3" uses_su="$4"
    if [[ "$host" == *@* ]]; then
        user="${host%%@*}"
        host="${host#*@}"
    else
        user="$USER"
    fi
    [ -n "$confuser" ] || confuser="$user"

    [ "$user" != "$confuser" -a -z "$ipks" ] &&
    die "Il faut préparer le script d'installation de la clé avec prepare_pubkey_access()"

    if [ "$user" != "$confuser" ]; then
        enote "Vous pouvez être amené à entrer plusieurs fois le mot de passe de $user sur $host"
        scp -S "$SSH" -o StrictHostKeyChecking=no -o ConnectTimeout=2 -q "$ipks" "$user@$host:" &&
        "$SSH" -o ConnectTimeout=2 -qt "$user@$host" "./$(basename "$ipks") '$confuser' ${uses_su:+su}" ||
        eerror "Impossible de copier la clé publique sur l'hôte distant"
    else
        "$SSH" -o StrictHostKeyChecking=no -o ConnectTimeout=2 -q "$user@$host" "PUBKEYS=$(quoted_sarg "$PUBKEYS")"'
cd ~; mkdir -p .ssh; chmod 700 .ssh
cd .ssh; touch authorized_keys; chmod 600 authorized_keys
echo "$PUBKEYS" | while read pubkey; do
    grep -q "$pubkey" authorized_keys ||
    echo "$pubkey" >>authorized_keys
done' ||
        eerror "Impossible de copier la clé publique sur l'hôte distant"
    fi
}

if [ "$action" == "configure" ]; then
    if [ -z "$PUBKEYS" ]; then
        sshdir=~/.ssh
        for pubkey in id_rsa.pub id_dsa.pub; do
            [ -f "$sshdir/$pubkey" ] && PUBKEYS="${PUBKEYS:+$PUBKEYS
}$(<"$sshdir/$pubkey")"
        done
    fi
    [ -n "$PUBKEYS" ] || die "La connexion sur les hôtes distants ne peut se faire que par mot de passe parce que vous n'avez pas de clé publique configurée. Modifiez le fichier ~/etc/default.pubkeys si ce message est erroné."

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

    IPKS=
    [ -n "$confuser" ] && prepare_pubkey_access

    etitle -s "Configuration de l'accès par clé ssh"
    for host in "${hosts[@]}"; do
        etitle "$host" check_pubkey_access "$host" "$confuser" "$IPKS" "$uses_su"
    done
    eend
    exit 0
fi

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

make_archive=

## Répertoire à déployer
src="$1"
if [ -z "$src" ] || [[ "$src" == -* ]]; then
    # pas d'argument, ou c'est une option (qui fait donc partie des arguments de uinst)
    read_value "Entrez le répertoire à déployer" src "."
else
    # Enlever le répertoire à déployer. Le reste est pour uinst
    shift
fi

## Hôtes sur lesquels faire le déploiement
array_isempty hosts && read_value "Entrez une liste d'hôtes séparés par ':'" hosts "localhost"
fix_hosts

## Création de l'archive?
if [ -n "$force_make_archive" ]; then
    make_archive=1
else
    for host in "${hosts[@]}"; do
        if [ "$host" != "localhost" ]; then
            make_archive=1
            break
        fi
    done
fi

archive=
archivename=
if [ -n "$make_archive" ]; then
    ac_set_tmpfile archive
    archivename="$(basename "$archive")"

    etitle "Création de l'archive pour le déploiement" \
        "$scriptdir/mkusfx" --tmp-archive --self-contained -o "$archive" "$src" || die
fi

## Déploiement
for host in "${hosts[@]}"; do
    if [ "$host" == "localhost" ]; then
        etitle -s "Déploiement sur l'hôte local"
        if [ -n "$force_make_archive" ]; then
            "$archive" ${tmproot:+--tmproot "$tmproot"} -- "$@"
        else
            "$scriptdir/uinst" "$src" "$@"
        fi
        eend
    else
        splituserhost "$host" user host
        [ -n "$user" ] || user=root

        etitle -s "Déploiement sur $user@$host"
        estep "Copie de l'archive"
        scp -S "$SSH" "$archive" "$user@$host:" || die
        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"} -- MYHOST="$host" "$@")"
        eend
    fi
done
