##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Fonctions pour la commande runs
##@cooked nocomments
##@require base
uprovide runs
urequire base

################################################################################
# Configuration

function __runs_initt() {
    if is_yes "$verbose"; then
        rscriptt="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
desc \"CHANGEME: Description du script $rscriptname\"
#var name=value arr+=value arr-=value
#var arr value0 value1...

script:

# pour un script d'installation qui ne doit tourner qu'une seule fois:
#shouldrun || exit
#...
#setdone"
        runsconft="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient les paramètres qui sont partagés par tous les scripts de $host
#var name=value arr+=value arr-=value
#var arr value0 value1..."
    else
        rscriptt="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
desc \"\"

script:"
        runsconft="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient les paramètres qui sont partagés par tous les scripts de $host"
    fi
    sysinfost="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient les informations sur le type de système installé sur $host
# Il est possible de recopier la valeur affichée par la commande usysinfos"
    if [ -n "$sysinfos_data" ]; then
        sysinfost="$sysinfost
$sysinfos_data"
    else
        sysinfost="$sysinfost
#sysname=()
#sysdist=()
#sysver=()
#bits="
    fi
    defaultt="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient la liste des script à lancer avec le compte root pour
# configurer $host
# Chaque ligne contient le nom du script suivi des arguments éventuels
#initenv
#dump-users
#base
#@services
#@config"
    configt="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient la recette pour configurer le serveur: configuration des
# mappings, configuration du serveur de courrier, de la sauvegarde, etc... Il
# faut relancer cette recette à chaque fois que la configuration change.
#mailrelay rootmail=
#hostmappings mappings
#backupclient bckhost="
    servicest="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient la recette pour installer les services du serveur. Cette
# installation ne se fait en principe qu'une seule fois."
    userdefaultt="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Ce fichier contient la liste des script à lancer avec le compte utilisateur
# pour configurer $host
# Chaque ligne contient le nom du script suivi des arguments éventuels"
}

function runs_initdir() {
    # Initialiser le répertoire d'hôte. $1 est un nom d'hôte pleinement
    # qualifié, et les fichiers sont créés dans le premier répertoire de
    # RUNSHOSTSDIRS qui convient: si un fichier .udir existe avec un tableau
    # runs_domains qui contient le domaine de l'hôte spécifié, alors c'est ce
    # répertoire qui est sélectionné. Sinon, on prend le premier répertoire de
    # RUNSHOSTSDIRS.
    # $2 spécifie si le fichier doit être créé avec de l'aide (yes) ou avec le
    # script minimum (no)
    # $3 est le contenu à placer dans le fichier sysinfos.conf, s'il n'a pas
    # déjà été provisionné.
    # Il faut lancer __runs_setpath avant d'utiliser cette fonction et
    # RUNSHOSTDIRS ne doit pas être vide
    [ -n "${RUNSHOSTSDIRS[*]}" ] || return 1

    local runshostdir
    local edit_default
    local host hostname domain
    splithost "$1" hostname domain
    local verbose="${2:-yes}"
    local sysinfos_data="$3"
    if [ -n "$domain" ]; then
        host="$hostname.$domain"

        local dir found
        # d'abord chercher si le répertoire existe déjà
        for runshostdir in "${RUNSHOSTSDIRS[@]}"; do
            found=
            for dir in "$runshostdir/$domain/$hostname" "$runshostdir/$host"; do
                if [ -d "$dir" ]; then
                    found=1
                    break
                fi
            done
        done
        # sinon chercher un répertoire qui convient
        if [ -z "$found" ]; then
            for runshostdir in "${RUNSHOSTSDIRS[@]}"; do
                if [ -f "$runshostdir/.udir" ]; then
                    (source "$runshostdir/.udir"; array_contains runs_domains "$domain") && {
                        found=1
                        break
                    }
                fi
            done
        fi
        if [ -n "$found" ]; then
            found=
            for dir in "$runshostdir/$host" "$runshostdir/$domain/$hostname"; do
                if [ -d "$dir" ]; then
                    runshostdir="$dir"
                    found=1
                    break
                fi
            done
            [ -n "$found" ] || runshostdir="$runshostdir/$host"
        else
            runshostdir="${RUNSHOSTSDIRS[0]}/$host"
        fi
    else
        host="$hostname"
        runshostdir="${RUNSHOSTSDIRS[0]}/$host"
    fi

    local rscriptt runsconft sysinfost defaultt configt servicest
    __runs_initt

    mkdir -p "$runshostdir"
    if [ ! -f "$runshostdir/runs.conf" ]; then
        estep "Création de $(ppath "$runshostdir/runs.conf")"
        echo "$runsconft" >"$runshostdir/runs.conf"
    fi
    local create_sysinfos
    if [ -f "$runshostdir/sysinfos.conf" ]; then
        # si le fichier sysinfos.conf ne contient que des commentaires,
        # considérer qu'il n'existe pas.
        local -a sysinfoslines
        array_from_lines sysinfoslines "$(<"$runshostdir/sysinfos.conf" filter_conf)"
        [ "${#sysinfoslines[*]}" -eq 0 ] && create_sysinfos=1
    else
        create_sysinfos=1
    fi
    if [ -n "$create_sysinfos" ]; then
        estep "Création de $(ppath "$runshostdir/sysinfos.conf")"
        echo "$sysinfost" >"$runshostdir/sysinfos.conf"
    fi
    if [ ! -f "$runshostdir/default" ]; then
        estep "Création de $(ppath "$runshostdir/default")"
        echo "$defaultt" >"$runshostdir/default"
        edit_default=1
    fi
    if [ ! -f "$runshostdir/config" ]; then
        estep "Création de $(ppath "$runshostdir/config")"
        echo "$configt" >"$runshostdir/config"
    fi
    if [ ! -f "$runshostdir/services" ]; then
        estep "Création de $(ppath "$runshostdir/services")"
        echo "$servicest" >"$runshostdir/services"
    fi
    if [ ! -f "$runshostdir/userdefault" ]; then
        estep "Création de $(ppath "$runshostdir/userdefault")"
        echo "$userdefaultt" >"$runshostdir/userdefault"
    fi

    [ -n "$edit_default" ] && "${EDITOR:-vi}" "$runshostdir/default"

    return 0
}

function __runs_create_rscript() {
    local rscript="$1"
    local rscriptname="$(basename "$rscript")"
    local overwrite="$2"
    local template
    if [ "$rscriptname" == "runs.conf" ]; then
        template="$runsconft"
    elif [ "$rscriptname" == "sysinfos.conf" ]; then
        template="$sysinfost"
    elif [ "$rscriptname" == "default" ]; then
        template="$defaultt"
    elif [ "$rscriptname" == "userdefault" ]; then
        template="$userdefaultt"
    else
        withext "$rscript" || rscript="$rscript.rs"
        template="$rscriptt"
    fi

    if [ -f "$rscript" ] && is_no "$overwrite"; then
        eerror "$(ppath "$rscript"): fichier déjà existant"
        return 1
    fi

    mkdirof "$rscript"
    estep "Création de $(ppath "$rscript")"
    echo "$template" >"$rscript"

    array_add new_rscripts "$(abspath "$rscript")"

    return 0
}

function runs_create_rscript() {
    # Créer un modèle de script. Si $2 est spécifié, c'est un nom d'hôte
    # pleinement qualifié. Le répertoire d'hôte correspondant *doit* exister.
    # $3 spécifie si le fichier doit être créé avec de l'aide (yes) ou avec le
    # script minimum (no)
    # Si $2!="", il faut lancer __runs_setpath avant d'utiliser cette fonction
    # et RUNSHOSTDIRS ne doit pas être vide
    # Le chemin du nouveau script est ajouté au tableau new_rscripts
    local rscript="$1"
    local host="$2"
    local verbose="${3:-yes}"
    local overwrite="${4:-no}"

    local rscriptt runsconft sysinfost defaultt userdefaultt
    __runs_initt

    if [ -n "$host" ]; then
        if withpath "$rscript"; then
            # si c'est un chemin relatif à '.', '..', ou un chemin absolu, le
            # prendre tel quel
            __runs_create_rscript "$rscript" "$overwrite" || return 1
        else
            # trouver le répertoire d'hôte
            [ -n "${RUNSHOSTSDIRS[*]}" ] || return 1

            local found runsdir hostname domain
            splithost "$host" hostname domain
            for runsdir in "${RUNSHOSTSDIRS[@]}"; do
                found=
                for dir in "$runsdir/$host" "$runsdir/$domain/$hostname"; do
                    if [ -d "$dir" ]; then
                        runsdir="$dir"
                        found=1
                        break
                    fi
                done
                [ -n "$found" ] && break
            done
            [ -n "$found" ] || return 1

            # puis exprimer le chemin spécifié par rapport à runsdir
            __runs_create_rscript "$runsdir/$rscript" "$overwrite" || return 1
        fi

    else
        __runs_create_rscript "$rscript" "$overwrite" || return 1
    fi
}

function runs_unsupported_system() {
    # Afficher un message d'erreur indiquant que le système actuel n'est pas
    # supporté, et quitter le script
    local msg="Ce script n'est pas supporté sur $MYSYSNAME${MYSYSDIST:+/$MYSYSDIST}${MYSYSVER:+/$MYSYSVER}"
    [ -n "$*" ] && msg="$msg
Il faut au moins l'un des systèmes suivants: $*"
    die "$msg"
}

function runs_require_sysinfos() {
    # Vérifier le système actuel avec check_sysinfos(), et afficher un message
    # d'erreur avec runs_unsupported_system() s'il ne correspond pas à la
    # requête
    check_sysinfos "$@" && return 0
    local infos info
    # Construire une description du type de système attendu en fonction des
    # arguments. Actuellement, on se contente de copier la ligne de commande, en
    # sautant les options.
    # XXX améliorer l'algorithme pour décrire précisément le système attendu
    for info in "$@"; do
        if ! [[ "$info" == -* ]]; then
            infos=("${infos[@]}" "$info")
        fi
    done
    runs_unsupported_system "${infos[@]}"
}

################################################################################
# Lancement de scripts nommés

function __runs_setpath() {
    # Initialiser les tableaux $1(=RUNSSCRIPTSDIRS), $2(=RUNSMODULESDIRS),
    # $3(=RUNSHOSTSDIRS) avec les chemins de RUNSSCRIPTSPATH, RUNSMODULESPATH,
    # RUNSHOSTSPATH
    function __runs_addsuffix() { echo "$1${2:+/$2}"; }
    local -a __runsdirs __tmpa
    local __runsscriptsdirs="${1:-RUNSSCRIPTSDIRS}"
    local __runsmodulesdirs="${2:-RUNSMODULESDIRS}"
    local __runshostsdirs="${3:-RUNSHOSTSDIRS}"

    array_from_path "$__runsscriptsdirs" "$RUNSSCRIPTSPATH"
    array_map "$__runsscriptsdirs" abspath

    array_from_path "$__runsmodulesdirs" "$RUNSMODULESPATH"
    array_map "$__runsmodulesdirs" abspath

    array_from_path "$__runshostsdirs" "$RUNSHOSTSPATH"
    array_map "$__runshostsdirs" abspath
}

function __runs_find_host() {
    # Si $1 est un nom d'hôte pleinement qualifié, retourner cette valeur
    # Sinon, chercher en utilisant RUNSDOMAINS un nom d'hôte qui aurait déjà été
    # configuré
    local host="$1" hostname domain
    splithost "$host" hostname domain
    if [ -n "$domain" ]; then
        echo "$host"
        return 0
    fi

    local runsdomain runsdir
    for runsdomain in "${RUNSDOMAINS[@]}"; do
        for runsdir in "${RUNSHOSTSDIRS[@]}"; do
            if [ -d "$runsdir/$hostname.$runsdomain" ]; then
                echo "$hostname.$runsdomain"
                return 0
            elif [ -d "$runsdir/$runsdomain/$hostname" ]; then
                echo "$hostname.$runsdomain"
                return 0
            fi
        done
    done
    echo "$host"
    return 1
}
function runs_find_host() {
    local RUNSSCRIPTSDIRS RUNSMODULESDIRS RUNSHOSTSDIRS; __runs_setpath
    __runs_find_host "$@"
}

function runs_add_domain() {
    # Si $1 est nom d'hôte pleinement qualifié, retourner cette valeur
    # Sinon, lui rajouter le domaine RUNSDOMAIN
    local host="$1" hostname domain
    splithost "$host" hostname domain
    if [ -n "$domain" -o -z "$RUNSDOMAIN" ]; then
        echo "$host"
        return 0
    fi
    echo "$host.$RUNSDOMAIN"
    return 1
}

function runs_find_hostfile() {
    # Trouver et afficher le fichier d'hôte $1 dans les répertoires du tableau
    # $3(=RUNSHOSTSDIRS), pour l'hôte $2(=$RUNSHOST). Retourner 0 en cas de
    # succès, 1 en cas d'échec.
    # Si host=$2 est une valeur non vide, la recherche est effectuée dans
    # {$RUNSHOSTSDIRS}/$host et {$RUNSHOSTSDIRS}/$domain/$hostname. Sinon,
    # retourner 1, car il faut spécifier un nom d'hôte.
    local __hostfile="$1" __runshost="${2:-$RUNSHOST}" __runshostsdirs="${3:-RUNSHOSTSDIRS}[@]"
    local __hostname __domain
    local __runsdir __runsfile

    [ -n "$__runshost" ] || return 1
    splithost "$__runshost" __hostname __domain
    for __runsdir in "${!__runshostsdirs}"; do
        for __runsfile in \
            "$__runsdir/$__runshost/$__hostfile" \
            "$__runsdir/$__domain/$__hostname/$__hostfile"; do
            if [ -e "$__runsfile" ]; then
                echo "$__runsfile"
                return 0
            fi
        done
    done
    return 1
}

function runs_find_datafile() {
    # Trouver et afficher le fichier de données $1 dans le répertoire $3 s'il
    # est non vide puis dans les répertoires des tableaux $4(=RUNSSCRIPTSDIRS),
    # $5(=RUNSMODULESDIRS) et $6(=RUNSHOSTSDIRS), pour l'hôte
    # $2(=$RUNSHOST). Retourner 0 en cas de succès, 1 en cas d'échec.
    # - D'abord, si $1 *n'est pas* de la forme "./path" ou "../path", chercher
    # dans $3.
    # - Puis si l'hôte est spécifié, chercher dans {$RUNSHOSTSDIRS}/$host et
    # {$RUNSHOSTSDIRS}/$domain/$hostname.
    # - Puis chercher dans {$RUNSSCRIPTSDIRS} puis {$RUNSMODULESDIRS}.
    # - Puis, si $1 est de la forme "./path" ou "../path", chercher dans $3.
    # - Sinon, retourner 1
    local __datafile="$1" __runshost="${2:-$RUNSHOST}"
    local __runsscriptdir="$3"
    local __runsscriptsdirs="${4:-RUNSSCRIPTSDIRS}[@]"
    local __runsmodulesdirs="${5:-RUNSMODULESDIRS}[@]"
    local __runshostsdirs="${6:-RUNSHOSTSDIRS}[@]"
    local __domain __hostname
    local __runsdir __runsfile
    local __withpath

    withpath "$__datafile" && __withpath=1

    # D'abord chercher dans le répertoire du script
    if [ -n "$__withpath" -a -n "$__runsscriptdir" ]; then
        __runsfile="$__runsscriptdir/$__datafile"
        if [ -e "$__runsfile" ]; then
            echo "$__runsfile"
            return 0
        fi
    fi
    # puis chercher dans les répertoire d'hôtes
    if [ -n "$__runshost" ]; then
        splithost "$__runshost" __hostname __domain
        for __runsdir in "${!__runshostsdirs}"; do
            for __runsfile in \
                "$__runsdir/$__runshost/$__datafile" \
                "$__runsdir/$__domain/$__hostname/$__datafile"; do
                if [ -e "$__runsfile" ]; then
                    echo "$__runsfile"
                    return 0
                fi
            done
        done
    fi
    # puis chercher dans les répertoires des scripts et des modules
    for __runsdir in "${!__runsscriptsdirs}" "${!__runsmodulesdirs}"; do
        __runsfile="$__runsdir/$__datafile"
        if [ -e "$__runsfile" ]; then
            echo "$__runsfile"
            return 0
        fi
    done
    # puis chercher dans le répertoire du script
    if [ -z "$__withpath" -a -n "$__runsscriptdir" ]; then
        __runsfile="$__runsscriptdir/$__datafile"
        if [ -e "$__runsfile" ]; then
            echo "$__runsfile"
            return 0
        fi
    fi
    # fichier non trouvé
    return 1
}

function runs_initvars() {
    # Initialiser les variables RUNSDIR, RUNSSCRIPT, RUNSDIRPATH,
    # RUNSSCRIPTPATH, RUNSSCRIPTDIR et RUNSSCRIPTNAME pour le script $1.
    # Les valeurs sont initialisées comme suit:
    # RUNSSCRIPT="$(abspath "$1")"
    # RUNSDIR="$2" (le répertoire de $RUNS*PATH dans lequel a été trouvé le
    # script)
    # Si $3!="", RUNSDIRPATH="$3" et RUNSSCRIPTPATH="$4"
    # Sinon, RUNSDIRPATH="$RUNSSCRIPTDIR" et RUNSSCRIPTPATH="$RUNSSCRIPTNAME"
    RUNSSCRIPT="$(abspath "$1")"
    RUNSSCRIPTDIR="$(dirname "$RUNSSCRIPT")"
    RUNSSCRIPTNAME="$(basename "$RUNSSCRIPT")"
    RUNSDIR="$2"
    if [ -n "$3" ]; then
        RUNSDIRPATH="$3"
        RUNSSCRIPTPATH="$4"
    else
        RUNSDIRPATH="$RUNSSCRIPTDIR"
        RUNSSCRIPTPATH="$RUNSSCRIPTNAME"
    fi
}

function runs_find_scriptfile() {
    # Trouver sans l'afficher le script $1 dans les répertoires des tableaux
    # $3(=RUNSSCRIPTSDIRS), $4(=RUNSMODULESDIRS) et $5(=RUNSHOSTSDIRS), en
    # considérant que le script sera lancé sur l'hôte $2(=$RUNSHOST), et
    # initialiser les variables RUNSDIR, RUNSSCRIPT, RUNSSCRIPTDIR,
    # RUNSSCRIPTNAME, RUNSDIRPATH et RUNSSCRIPTPATH. Retourner 0 en cas de
    # succès, 1 en cas d'échec.
    # RUNSDIR est le répertoire dans lequel a été trouvé le script (parmi les
    # valeurs fournies dans les tableaux RUNSSCRIPTSDIRS, RUNSMODULESDIRS,
    # RUNSHOSTSDIRS), RUNSDIRPATH est le répertoire à partir duquel est exprimé
    # le chemin du script (i.e RUNSDIRPATH + RUNSSCRIPTPATH == RUNSSCRIPT),
    # RUNSSCRIPT contient le chemin absolu vers le script, RUNSSCRIPTPATH
    # contient le chemin du script dans RUNSDIRPATH, RUNSSCRIPTDIR le répertoire
    # du script, et RUNSSCRIPTNAME le nom du script.
    # D'abord, si l'hôte est spécifié, chercher dans {$RUNSHOSTSDIRS}/$host et
    # {$RUNSHOSTSDIRS}/$domain/$hostname. Puis chercher dans {$RUNSSCRIPTSDIRS}
    # puis {$RUNSMODULESDIRS}. Sinon, retourner 1
    local __scriptfile="$1" __runshost="${2:-$RUNSHOST}"
    local __runsscriptsdirs="${3:-RUNSSCRIPTSDIRS}[@]"
    local __runsmodulesdirs="${4:-RUNSMODULESDIRS}[@]"
    local __runshostsdirs="${5:-RUNSHOSTSDIRS}[@]"
    local __domain __hostname
    local __runsdir __runsfile

    # chercher le cas échéant dans les répertoire d'hôtes
    if [ -n "$__runshost" ]; then
        splithost "$__runshost" __hostname __domain
        for __runsdir in "${!__runshostsdirs}"; do
            for __runsfile in \
                "$__runsdir/$__runshost/$__scriptfile" \
                "$__runsdir/$__domain/$__hostname/$__scriptfile"; do
                if [ ! -f "$__runsfile" -a -f "$__runsfile.rs" ]; then
                    __runsfile="$__runsfile.rs"
                fi
                if [ -e "$__runsfile" ]; then
                    runs_initvars "$__runsfile" "$__runsdir" "$__runsdirpath" "$__scriptfile"
                    return 0
                fi
            done
        done
    fi
    # puis chercher dans les répertoires des scripts et des modules
    for __runsdir in "${!__runsscriptsdirs}" "${!__runsmodulesdirs}"; do
        __runsfile="$__runsdir/$__scriptfile"
        if [ ! -f "$__runsfile" -a -f "$__runsfile.rs" ]; then
            __runsfile="$__runsfile.rs"
        fi
        if [ -e "$__runsfile" ]; then
            runs_initvars "$__runsfile" "$__runsdir" "$__runsdir" "$__scriptfile"
            return 0
        fi
    done
    return 1
}

function runs_find_scriptfile_reverse() {
    # Soit le fichier de script $1, exprimée de façon absolue, trouver le
    # fichier parmi les tableaux $3(=RUNSSCRIPTSDIRS), $4(=RUNSMODULESDIRS)
    # et $5(=RUNSHOSTSDIRS), en considérant que le script sera lancé sur l'hôte
    # $2(=$RUNSHOST), puis initialiser les variables RUNSDIR, RUNSSCRIPT,
    # RUNSSCRIPTDIR, RUNSSCRIPTNAME, RUNSDIRPATH et RUNSSCRIPTPATH. Retourner 0
    # en cas de succès, 1 en cas d'échec.
    local __runsscript="$1" __runshost="${2:-$RUNSHOST}"
    local __runsscriptsdirs="${3:-RUNSSCRIPTSDIRS}[@]"
    local __runsmodulesdirs="${4:-RUNSMODULESDIRS}[@]"
    local __runshostsdirs="${5:-RUNSHOSTSDIRS}[@]"
    local __domain __hostname
    local __runsdir __runsfile __prefix

    # chercher le cas échéant dans les répertoire d'hôtes
    if [ -n "$__runshost" ]; then
        splithost "$__runshost" __hostname __domain
        for __runsdir in "${!__runshostsdirs}"; do
            __prefix="$__runsdir/$__runshost/"
            if [ "${__runsscript#$__prefix}" != "$__runsscript" ]; then
                runs_initvars "$__runsscript" "$__runsdir" "$__prefix" "${__runsscript#$__prefix/}"
                return 0
            fi
            __prefix="$__runsdir/$__domain/$__hostname/"
            if [ "${__runsscript#$__prefix}" != "$__runsscript" ]; then
                runs_initvars "$__runsscript" "$__runsdir" "$__prefix" "${__runsscript#$__prefix/}"
                return 0
            fi
        done
    fi
    # puis chercher dans les répertoires des scripts et des modules
    for __runsdir in "${!__runsscriptsdirs}"; do
        __prefix="$__runsdir/"
        if [ "${__runsscript#$__prefix}" != "$__runsscript" ]; then
            runs_initvars "$__runsscript" "$__runsdir" "$__prefix" "${__runsscript#$__prefix/}"
            return 0
        fi
    done
    for __runsdir in "${!__runsmodulesdirs}"; do
        __prefix="$__runsdir/"
        if [ "${__runsscript#$__prefix}" != "$__runsscript" ]; then
            runs_initvars "$__runsscript" "$__runsdir" "$__prefix" "${__runsscript#$__prefix/}"
            return 0
        fi
    done
    return 1
}

function runs_rscript() {
    # Lancer le fichier $1 comme un script avec les arguments $2..$*. Retourner
    # la valeur de retour du script.
    local state
    local RUNSDIR RUNSSCRIPT RUNSSCRIPTDIR RUNSSCRIPTNAME RUNSDIRPATH RUNSSCRIPTPATH
    local RUNSSCRIPTSDIRS RUNSMODULESDIRS RUNSHOSTSDIRS; __runs_setpath

    local rscript="$1"; shift
    if [ ! -f "$rscript" ]; then
        eerror "$rscript: fichier introuvable."
        return 123
    fi

    # trouver runs*dir correspondant à $rscript
    rscript="$(abspath "$rscript")"
    runs_find_scriptfile_reverse "$rscript" "$RUNSHOST" || runs_initvars "$rscript"

    # puis lancer le script
    RUNSSTORY=("${RUNSSTORY[@]}" "$RUNSSCRIPTPATH")
    state=0
    "$RUNSACTION" "$@" || {
        state=$?
        eerror "$RUNSSCRIPTPATH: Une erreur s'est produite."
    }
    return $state
}

function __runs_iscont() {
    # tester si $1 est une ligne de continuation dans une recette
    [ "${1# }" != "$1" ] && return 0
    [ "${1#$TAB}" != "$1" ] && return 0
    return 1
}

function runs_recipe() {
    # Lancer les scripts de la recette contenue dans le fichier $1. Arrêter au
    # premier script qui est en erreur
    local recipe rcmds rcmd i count rscripts rscript

    recipe="$(abspath "$1")"
    if [ -d "$recipe" ]; then
        if is_root; then
            recipe="$recipe/default"
        else
            recipe="$recipe/userdefault"
        fi
    fi
    if [ ! -f "$recipe" ]; then
        eerror "$(ppath "$recipe"): fichier introuvable"
        return 1
    fi
    if array_contains RUNSRECIPES "$recipe"; then
        eerror "$(ppath "$recipe"): inclusion récursive"
        return 1
    fi
    RUNSRECIPES=("${RUNSRECIPES[@]}" "$recipe")
    # lire les ingredients de la recette
    recipe="$(<"$recipe" filter_comment -m)"
    array_from_lines rcmds "$recipe"
    # fusionner les lignes d'ingrédients
    let i=0
    let count=${#rcmds[@]}
    rscript=
    rscripts=()
    while [ $i -lt $count ]; do
        rcmd="${rcmds[$i]}"
        if ! __runs_iscont "$rcmd"; then
            # il faut recommencer une nouvelle commande
            # mais d'abord, traiter l'ancienne commande si nécessaire
            [ -n "$rscript" ] && rscripts=("${rscripts[@]}" "$rscript")
            rscript="$rcmd"
        elif [ -z "$rscript" ]; then
            # nous avons une ligne de continuation mal placée
            ewarn "$rcmd: Ligne de continuation mal placée, elle sera ignorée"
        else
            # nous avons une ligne de continuation
            rscript="$rscript \; $rcmd"
        fi
        let i=$i+1
        [ $i -eq $count -a -n "$rscript" ] && rscripts=("${rscripts[@]}" "$rscript")
    done
    # lancer les ingrédients et s'arrêter à la premier erreur
    for rscript in "${rscripts[@]}"; do
        eval "set -- $rscript"
        if [ "${1#@}" != "$1" ]; then
            # Le caractère @ identifie une recette au lieu d'un script individuel
            local RUNSDIR RUNSSCRIPT RUNSSCRIPTDIR RUNSSCRIPTNAME RUNSDIRPATH RUNSSCRIPTPATH
            local RUNSSCRIPTSDIRS RUNSMODULESDIRS RUNSHOSTSDIRS; __runs_setpath
            recipe="${1#@}"
            if ! runs_find_scriptfile "$recipe"; then
                eerror "$recipe: fichier introuvable. Vérifiez les valeurs suivantes:
RUNSSCRIPTSPATH=$RUNSSCRIPTSPATH
RUNSMODULESPATH=$RUNSMODULESPATH
RUNSHOSTSPATH=$RUNSHOSTSPATH
RUNSHOST=$RUNSHOST"
                return 123
            fi
            etitle "$1" runs_recipe "$RUNSSCRIPT" || return
        else
            etitle "$1" runs_rscriptpath "$@" || return
        fi
    done
    return 0
}

function runs_rscriptpath() {
    # Lancer le script $1 avec les arguments $2..$*. Le script est cherché dans
    # les répertoires de RUNSSCRIPTSPATH. Retourner 123 si le script n'est pas
    # trouvé, sinon retourner la valeur de retour du script.
    local state
    local RUNSDIR RUNSSCRIPT RUNSSCRIPTDIR RUNSSCRIPTNAME RUNSDIRPATH RUNSSCRIPTPATH
    local RUNSSCRIPTSDIRS RUNSMODULESDIRS RUNSHOSTSDIRS; __runs_setpath
    if ! runs_find_scriptfile "$1"; then
        eerror "$1: fichier introuvable. Vérifiez les valeurs suivantes:
RUNSSCRIPTSPATH=$RUNSSCRIPTSPATH
RUNSMODULESPATH=$RUNSMODULESPATH
RUNSHOSTSPATH=$RUNSHOSTSPATH
RUNSHOST=$RUNSHOST"
        return 123
    fi
    shift

    RUNSSTORY=("${RUNSSTORY[@]}" "$RUNSSCRIPTPATH")
    state=0
    "$RUNSACTION" "$@" || {
        state=$?
        eerror "$RUNSSCRIPTPATH: Une erreur s'est produite."
    }
    return $state
}

function runs_recipepath() {
    # Lancer la recette $1. Le fichier de recette est cherché dans les
    # répertoires de RUNSSCRIPTSPATH. Retourner 123 si le fichier de recette n'a
    # pas été trouvé, sinon retourner la valeur de retour de runs_recipe()
    local RUNSDIR RUNSSCRIPT RUNSSCRIPTDIR RUNSSCRIPTNAME RUNSDIRPATH RUNSSCRIPTPATH
    local RUNSSCRIPTSDIRS RUNSMODULESDIRS RUNSHOSTSDIRS; __runs_setpath
    if ! runs_find_scriptfile "$1"; then
        eerror "$1: fichier introuvable. Vérifiez les valeurs suivantes:
RUNSSCRIPTSPATH=$RUNSSCRIPTSPATH
RUNSMODULESPATH=$RUNSMODULESPATH
RUNSHOSTSPATH=$RUNSHOSTSPATH
RUNSHOST=$RUNSHOST"
        return 123
    fi

    runs_recipe "$RUNSSCRIPT"
}

################################################################################
# Moteur de script

function runs_init() {
    RUNSSRCDIR="${1:-$scriptdir}" # répertoire d'où sont copiés les scripts
    RUNSACTION=runs_action_run
    # informations sur le système
    RUNSHOST="$MYHOST"
    RUNSACTUALSYSNAME=("${MYSYSNAME[@]}")
    RUNSACTUALSYSDIST=("${MYSYSDIST[@]}")
    RUNSACTUALSYSVER=("${MYSYSVER[@]}")
    RUNSACTUALBITS="$MYBITS"
    RUNSEXPORTDIR= # avec --export, répertoire de base de l'export
    RUNSROOTDIR= # avec --export, répertoire correspondant à la racine
    RUNSWORKDIR=
    RUNSUPDATERECIPE=1 # faut-il rajouter la ligne de recette lors d'un export
    RUNSCONFFILES= # fichier de configuration qui ont été chargés
    # liste des varibles définies
    RUNSVARS=()
    # parmis les variables de RUNSVARS, liste de celles qui sont des tableaux
    RUNSARRAYS=()
    # parmis les variables de RUNSVARS, liste de celles qui sont des indirections
    RUNSVARSINDIRECT=()
    # liste des références définies
    RUNSREFS=()
    # recettes qui ont déjà été lancées, pour éviter les inclusions récursives
    RUNSRECIPES=()
    # scripts qui ont déjà été lancés, pour la commande after
    RUNSSTORY=()
    # flags valides et ceux qui ont été activés
    RUNSVALIDCONFS=(root local proxy nolang)
    RUNSCONFS=()
    # Faut-il réinitialiser les scripts qui se basent sur shouldrun/setdone
    RUNSRESET=
}

function runs_initdomains() {
    # Si ce n'est pas déjà le cas, initialiser RUNSDOMAINS en fonction de
    # /etc/resolv.conf
    [ -n "${RUNSDOMAINS[*]}" ] && return
    local domains="$(awk '$1 == "search" { $1 = ""; print }' /etc/resolv.conf)"
    eval "RUNSDOMAINS=($domains)"
}

function runs_inithost() {
    [ -n "$1" ] && RUNSHOST="$1"
}

function runs_initsysinfos() {
    local RUNSSCRIPTSDIRS RUNSMODULESDIRS RUNSHOSTSDIRS; __runs_setpath
    local sysname="$1" sysdist="$2" sysver="$3" bits="$4"

    # on a spécifié le nombre de bits
    [ -n "$bits" ] && MYBITS="$bits"

    if [ -n "$sysname" -o -n "$sysdist" -o -n "$sysver" ]; then
        # on a spécifié un système en ligne de commande
        ensure_sysinfos sysname sysdist sysver
    else
        # Si le fichier sysinfos.conf existe dans le répertoire d'hôte, y
        # prendre les valeurs
        local sysinfos
        if sysinfos="$(runs_find_hostfile sysinfos.conf)"; then
            sysname=()
            sysdist=()
            sysver=()
            bits=
            source "$sysinfos"
            [ -n "$bits" ] && MYBITS="$bits"
            if [ -n "$sysname" -o -n "$sysdist" -o -n "$sysver" ]; then
                ensure_sysinfos sysname sysdist sysver
            else
                # aucune modification n'a été faite par le fichier de
                # configuration. ne pas modifier les valeurs courantes
                return
            fi
        else
            # aucun fichier de configuration. ne pas modifier les valeurs
            # courantes
            return
        fi
    fi
    MYSYSNAME=("${sysname[@]}")
    MYSYSDIST=("${sysdist[@]}")
    MYSYSVER=("${sysver[@]}")
    MYBITS="$bits"
}

function runs_initworkdir() {
    local runsexportdir="$1" runsworkdir="$2" scriptdir="${3:-$scriptdir}"
    if [ -z "$runsexportdir" -a "$RUNSACTION" == runs_action_export ]; then
        # en mode export, il faut définir runsexportdir
        ac_set_tmpdir runsexportdir
    fi
    if [ -n "$runsexportdir" ]; then
        if [ -n "$runsworkdir" ]; then
            ewarn "Avec --export, la valeur de --runsworkdir est ignorée"
        fi
        RUNSEXPORTDIR="$(abspath "$runsexportdir")"
        RUNSROOTDIR="$RUNSEXPORTDIR/root"
        RUNSWORKDIR="$RUNSEXPORTDIR/work"
        mkdir -p "$RUNSEXPORTDIR"
        mkdir -p "$RUNSROOTDIR"
        mkdir -p "$RUNSWORKDIR"

        if [ "$RUNSACTION" == "runs_action_export" ]; then
            # préparer les fichiers pour l'export
            >"$RUNSEXPORTDIR/varsfile"
            >"$RUNSEXPORTDIR/localuser"
            >"$RUNSEXPORTDIR/localroot"
            >"$RUNSEXPORTDIR/remoteuser"
            >"$RUNSEXPORTDIR/remoteroot"

            # synchronisation ulib
            # comme on copie runs et uinst, il faut copier ulib et pyulib comme pour nutools: dans le répertoire lib/
            urequire ulib pyulib/pyulib
            mkdir -p "$RUNSEXPORTDIR/lib"
            ulibsync "$RUNSEXPORTDIR/lib"
            pyulibsync "$RUNSEXPORTDIR/lib"

            # copie runs, uinst
            cp "$RUNSSRCDIR/runs" "$RUNSEXPORTDIR"
            cp "$RUNSSRCDIR/uinst" "$RUNSEXPORTDIR"

            # faire les scripts
            args_def="args=($(quoted_args ./runs --runsscriptspath "$RUNSSCRIPTSPATH" --runsmodulespath "$RUNSMODULESPATH" --runshostspath "$RUNSHOSTSPATH" --runsexportdir . --runsvarsfile varsfile -h "$RUNSHOST" ${RUNSRESET:+ -z}))"
            echo '#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
cd "$(dirname "$0")"
'"$args_def"'
[ -s localuser ] && { "${args[@]}" -r localuser || exit 1; }
[ -s localroot ] && { "${args[@]}" -r localroot -s || exit 1; }
exit 0
' >"$RUNSEXPORTDIR/runs-local"
            echo '#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
cd "$(dirname "$0")"
'"$args_def"'
if [ -s remote ]; then
    [ -f remote-needs-root ] && args=("${args[@]}" -s)
    "${args[@]}" -r remote || exit 1
fi
exit 0
' >"$RUNSEXPORTDIR/runs-remote"
            chmod +x "$RUNSEXPORTDIR/runs-local" "$RUNSEXPORTDIR/runs-remote"
        else
            # corriger RUNS*PATH à partir de RUNSROOTDIR
            function __runs_add_runsroot() { echo "$RUNSROOTDIR$1"; }
            local runspath runsdirs
            for runspath in RUNSSCRIPTSPATH RUNSMODULESPATH RUNSHOSTSPATH; do
                array_from_path runsdirs "${!runspath}"
                array_map runsdirs abspath
                array_map runsdirs __runs_add_runsroot
                set_var "$runspath" "$(array_join runsdirs :)"
            done
            # XXX quelles autres variables doivent être corrigées?
        fi
    elif [ -n "$runsworkdir" ]; then
        RUNSWORKDIR="$runsworkdir"
    else
        ac_set_tmpdir RUNSWORKDIR
    fi
}

function runs_after_export() {
    # après l'export, initialiser varsfile avec les valeurs qu'il faut garder
    # entre le déploiement local et le déploiement distant.
    if [ "$RUNSACTION" == runs_action_export ]; then
        set_array_cmd RUNSSTORY >>"$RUNSEXPORTDIR/varsfile"
    fi
}

function __runs_is_runsscript() {
    if [ -f "$1" ]; then
        quietgrep "^script:" "$1"
    elif [ -d "$1" ]; then
        [ -f "$1/.udir" ]
    else
        return 1
    fi
}

function runs_check_runsscript() {
    if [ -f "$1" ] && ! __runs_is_runsscript "$1"; then
        eerror "$1: ce n'est pas un fichier de script valide: il manque la ligne 'script:'"
        return 1
    elif [ -d "$1" ]; then
        return 0
    fi
    return 0
}

function __runs_check_varname() {
    if [ "${1#RUNS}" != "$1" -o "${1#_}" != "$1" ]; then
        ewarn "Variable invalide: $1. Cette valeur a été ignorée"
        return 1
    fi
    return 0
}
function __runs_check_notref() {
    if array_contains RUNSREFS "$1"; then
        ewarn "Vous ne pouvez pas modifier la référence $1. La valeur sera ignorée"
        return 1
    fi
    return 0
}
function __runs_splitref() {
    if [[ "$1" == *=* ]]; then
        set_var "${2:-__name}" "${1%%=*}"
        set_var "${3:-__value}" "${1#*=}"
    else
        set_var "${2:-__name}" "$1"
        set_var "${3:-__value}" "$1"
    fi
}

function runs_var() {
    # Initialiser les variables selon les directives données en ligne de
    # commande.
    # Les arguments peuvent être une suite de définitions de la forme
    # 'scalar=value', 'scalar!=name', 'array+=value', 'array-=value' ou
    # 'array@=name'.
    # Sinon, le *dernier* argument peut-être de l'une des formes suivantes:
    # 'array value0 [value1...]' pour initialiser un tableau,
    # 'array+ value0 [value1...]' pour ajouter des valeurs à un tableau,
    # 'array- value0 [value1...]' pour enlever des valeurs à un tableau.
    # Les formes 'scalar!=value' et 'array@=value' sont des indirections et
    # permettent d'initialiser la variable avec la valeur d'une autre
    # variable. L'avantage est que la résolution de la valeur est faite
    # uniquement lors de l'appel de cette fonction, ce qui est utile avec des
    # fonction comme 'after -r'
    local __name __value
    # variables scalaires ou manipulation de tableaux
    while [ -n "$1" ]; do
        if [[ "$1" == *+=* ]]; then
            __name="${1%%+=*}"
            __value="${1#*+=}"
            if __runs_check_varname "$__name" && __runs_check_notref "$__name"; then
                array_set RUNSVARS "$__name"
                array_set RUNSARRAYS "$__name"
                array_del RUNSVARSINDIRECT "$__name"
                array_add "$__name" "$__value"
            fi
        elif [[ "$1" == *-=* ]]; then
            __name="${1%%-=*}"
            __value="${1#*-=}"
            if __runs_check_varname "$__name" && __runs_check_notref "$__name"; then
                array_set RUNSVARS "$__name"
                array_set RUNSARRAYS "$__name"
                array_del RUNSVARSINDIRECT "$__name"
                array_del "$__name" "$__value"
            fi
        elif [[ "$1" == *!=* ]]; then
            __name="${1%%!=*}"
            __value="${1#*!=}"
            if __runs_check_varname "$__name" && __runs_check_notref "$__name"; then
                array_set RUNSVARS "$__name"
                array_del RUNSARRAYS "$__name"
                array_set RUNSVARSINDIRECT "$__name"
                set_var "$__name" "$__value"
            fi
        elif [[ "$1" == *@=* ]]; then
            __name="${1%%@=*}"
            __value="${1#*@=}"
            if __runs_check_varname "$__name" && __runs_check_notref "$__name"; then
                array_set RUNSVARS "$__name"
                array_set RUNSARRAYS "$__name"
                array_set RUNSVARSINDIRECT "$__name"
                set_var "$__name" "$__value"
            fi
        elif [[ "$1" == *=* ]]; then
            __name="${1%%=*}"
            __value="${1#*=}"
            if __runs_check_varname "$__name" && __runs_check_notref "$__name"; then
                array_set RUNSVARS "$__name"
                array_del RUNSARRAYS "$__name"
                array_del RUNSVARSINDIRECT "$__name"
                set_var "$__name" "$__value"
            fi
        else
            break
        fi
        [ -n "$RUNSVARDESC" ] && set_var "RUNSVARDESC_$__name" "$RUNSVARDESC"
        shift
    done
    # variables tableau
    if [ -n "$1" ]; then
        __name="$1"; shift
        if __runs_check_varname "$__name" && __runs_check_notref "$__name"; then
            if [ "${__name%+}" != "$__name" ]; then
                __name="${__name%+}"
                for __value in "$@"; do
                    array_add "$__name" "$__value"
                done
            elif [ "${__name%-}" != "$__name" ]; then
                __name="${__name%-}"
                for __value in "$@"; do
                    array_del "$__name" "$__value"
                done
            else
                set_array "$__name" @ "$@"
            fi
            # Placer $__name dans les tableau après qu'il aie été corrigé
            # si par exemple il se terminer par '+' ou '-'
            array_set RUNSVARS "$__name"
            array_set RUNSARRAYS "$__name"
            array_del RUNSVARSINDIRECT "$__name"
        fi
    fi
}

function runs_conf() {
    # Activer les flags $*
    for __conf in "$@"; do
        if array_contains RUNSVALIDCONFS "$__conf"; then
            array_set RUNSCONFS "$__conf"
        else
            ewarn "Flag inconnu: $__conf. Il sera ignoré"
        fi
    done
}

function runs_indref() {
    # fonction de convenance pour créer des références $3..* avec le fichier de
    # configuration $1 et les variables $2
    local __sconf="$1" __vars="$2"; shift; shift
    ref -s "$__sconf" -v "$__vars" "$@"
}
function runs_refcerts() {
    # fonction de convenance pour créer une référence à un répertoire contenant
    # des certificats mentionnés dans le fichier de configuration $1. Si les
    # références $2..* ne sont pas mentionnées, la variable certsdir dans le
    # fichier de configuration est utilisée.
    local __sconf="$1"; shift
    runs_indref "$__sconf" ca,cert,key -V certsdir "$@"
}
function runs_refapacheconfig() {
    # fonction de convenance pour créer les références à un répertoire de
    # configuration pour apache.
    # USAGE: refapacheconfig autoconfdir=path/to/autoconfdir [certsdir=[path/to/certsdir]]
    # - autoconfdir= est requis et permet de définir à la fois la variable qui
    # contiendra la référence ainsi que le répertoire à référencer.
    # - certsdir= est optionel. Si cet argument est spécifié sous la forme
    # certsdir=path/to/certsdir, il permet de définir à la fois la variable qui
    # contiendra la référence ainsi que le répertoire à référencer. Si
    # l'argument est spécifié sous la forme certsdir=, il permet de définir la
    # variable qui contiendra la référence. C'est cette variable qui sera lue
    # dans les fichiers de configuration. Si l'argument n'est pas spécifié, on
    # considère que l'argument 'certsdir=' a été utilisé.
    :
    #local -a __args
    #parse_opts + \
    #    @ __args -- "$@" && set -- "${__args[@]}" || die "$__args"
}

function runs_set_lang() {
    # Charger la valeur de LANG depuis l'environnement. La variable LANG est
    # initialisée
    if ! array_contains RUNSCONFS nolang; then
        if check_sysinfos -d debianlike; then
            eval "$(LANG=
                source_ifexists /etc/default/locale
                set_var_cmd LANG "$LANG"
            )"
        fi
        export LANG
    fi
}

function runs_set_proxy() {
    # Charger la valeur du proxy depuis l'environnement. Les variables
    # http_proxy, ftp_proxy et no_proxy sont initialisées
    if array_contains RUNSCONFS proxy; then
        if check_sysinfos -d debianlike; then
            eval "$(http_proxy=; ftp_proxy=; no_proxy=
                source_ifexists /etc/environment
                set_var_cmd http_proxy "$http_proxy"
                set_var_cmd ftp_proxy "$ftp_proxy"
                set_var_cmd no_proxy "$no_proxy"
            )"
        fi
        export http_proxy ftp_proxy no_proxy
    fi
}

function runs_check_confs() {
    # Vérifier l'état courant par rapport aux flags
    if array_contains RUNSCONFS root; then
        is_root || die "Ce script requière d'être lancé en root (essayer avec l'option -s)"
    fi
}

function runs_after() {
    # Vérifier que ce script est lancé après le scriptpath $1, par rapport à
    # RUNSSTORY
    local -a __args
    local runslevel=0
    parse_opts + \
        -r,--run runslevel \
        @ __args -- "$@" && set -- "${__args[@]}" || die "$__args"

    # Le traitement est fait en fonction de runslevel
    if [ $runslevel -eq 0 ]; then
        # sans l'option -r, on ne fait que vérifier que les scriptpaths
        # correspondant aux arguments ont déjà été lancés. On peut spécifier
        # autant de scriptpaths que nécessaire, puisque que des arguments ne
        # sont pas nécessaires
        local scriptpath
        for scriptpath in "$@"; do
            if ! array_contains RUNSSTORY "$scriptpath"; then
                eerror "Ce script requière d'être lancé après $scriptpath"
                return 1
            fi
        done
        return 0
    fi

    # Protéger la liste actuelle des variables en en faisant une copie
    # locale. En effet, cette liste sera supprimée à la fin de l'invocation
    # récursive avec -r. Protéger également les valeurs de variables en en
    # faisant une copie locale, puis fusionner avec les valeurs saisies sur la
    # ligne de commande.
    # Attention! si on veut utiliser dans les paramètre du script dépendant une
    # valeur d'une variable déjà définie, il faut utiliser une indirection
    # var!=othervar, parce que la valeur définitive est calculée dans cette
    # fonction, et non avant son appel!
    local __var __tmp
    for __var in "${RUNSVARS[@]}"; do
        if array_contains RUNSARRAYS "$__var"; then
            eval "array_copy __tmp $__var; local -a $__var; array_copy $__var __tmp"
        else
            eval "set_var __tmp \"\${!__var}\"; local $__var; $__var=\"\$__tmp\""
        fi
    done
    runs_clvars "${RUNSCLVARS[@]}"
    runs_indvars
    # Etendre tout de suite les indirections var!=othervar et array@=otherarray,
    # parce que la valeur qui doit être prise en compte est la valeur actuelle.
    local -a __args
    local __arg __name __value
    __args=("$1"); shift
    for __arg in "$@"; do
        if [[ "$__arg" == *!=* ]]; then
            __name="${__arg%%!=*}"
            __value="${__arg#*!=}"
            __args=("${__args[@]}" "$__name=$(quoted_arg "${!__value}")")
        elif [[ "$__arg" == *@=* ]]; then
            __name="${__arg%%@=*}"
            __value="${__arg#*@=}[@]"
            __args=("${__args[@]}" \; "$__name")
            for __var in "${!__value}"; do
                __args=("${__args[@]}" "$__var")
            done
            __args=("${__args[@]}" \;)
        else
            __args=("${__args[@]}" "$__arg")
        fi
    done
    set -- "${__args[@]}"

    if [ $runslevel -eq 1 ]; then
        # Avec une seule option -r, si le scriptpath a déjà été lancé, on ne
        # fait rien. Sinon, le lancer maintenant avec les arguments $2..$*
        # Ne pas mettre à jour la ligne de recette lors d'un export pour éviter
        # de lancer le script deux fois
        local RUNSUPDATERECIPE=
        if ! array_contains RUNSSTORY "$1"; then
            etitle "Lancement du script dépendant $1" \
                runs_rscriptpath "$@"
        fi
        return 0
    else
        # Avec deux options -r et plus, forcer le lancer de scriptpath avec les
        # arguments $2..$*, même s'il a déjà été lancé auparavant.
        # Ne pas mettre à jour la ligne de recette lors d'un export pour éviter
        # de lancer le script deux fois
        local RUNSUPDATERECIPE=
        etitle "Lancement du script dépendant $1" \
            runs_rscriptpath "$@"
    fi
}

function runs_clvars() {
    # Traiter les spécifications de variables données en ligne de commande ou
    # dans un fichier de recettes
    local __vars
    __vars=()
    while [ -n "$1" ]; do
        if [ "$1" == ";" ]; then
            runs_var "${__vars[@]}"
            __vars=()
        else
            __vars=("${__vars[@]}" "$1")
        fi
        shift
    done
    [ -n "${__vars[*]}" ] && runs_var "${__vars[@]}"
}

function runs_indvars() {
    # Résoudre les valeurs effectives des variables qui sont des indirections
    local __var __ind
    for __var in "${RUNSVARSINDIRECT[@]}"; do
        if array_contains RUNSARRAYS "$__var"; then
            # tableau
            array_copy "$__var" "${!__var}"
        else
            # variable scalaire
            __ind="${!__var}"
            set_var "$__var" "${!__ind}"
        fi
    done
    RUNSVARSINDIRECT=()
}

function runs_clvars_cmd() {
    # écrire la ligne de recette correspondant au script $1 et aux variables
    # $2..$*
    local __vars __prefix
    __vars=()
    while [ -n "$1" ]; do
        if [ "$1" == ";" ]; then
            echo "$__prefix$(quoted_args "${__vars[@]}")"
            __vars=()
            __prefix="    "
        else
            __vars=("${__vars[@]}" "$1")
        fi
        shift
    done
    [ -n "${__vars[*]}" ] && echo "$__prefix$(quoted_args "${__vars[@]}")"
}

function runs_loadconfs() {
    local __conffile __path __done
    RUNSCONFFILES=()
    if [ -n "$RUNSDIR" ]; then
        __path="$RUNSSCRIPT"
        while [ -n "$__path" ]; do
            __path="$(dirname "$__path")"
            __conffile="$__path/runs.conf"
            if [ -f "$__conffile" ]; then
                RUNSCONFFILES=("$__conffile" "${RUNSCONFFILES[@]}")
            fi
            [ "$__path" == "$RUNSDIR" ] && __path=
        done
    fi

    for __conffile in "${RUNSCONFFILES[@]}"; do
        source "$__conffile"
    done
}

function runs_clearvars() {
    local __var
    for __var in "${RUNSVARS[@]}"; do
        eval "unset $__var"
    done
}

function runs_action_desc() {
    local -a RUNSVARS RUNSARRAYS RUNSVARSINDIRECT
    local -a RUNSCLVARS RUNSCONFS RUNSCONFFILES
    local RUNSVARDESC __state

    function desc() { eecho "$(ppath "$RUNSSCRIPT"): $*"; exit 0; }
    function unsupported_system() { runs_unsupported_system "$@"; }
    function require_sysinfos() { runs_require_sysinfos "$@"; }
    function vardesc() { RUNSVARDESC="$*"; }
    function var() { runs_var "$@"; RUNSVARDESC=; }
    function conf() { runs_conf "$@"; }
    function after() {
        local scriptpath
        for scriptpath in "$@"; do
            einfo "Ce script doit être lancé après $scriptpath"
        done
    }
    function ref() {
        local -a __args
        parse_opts + \
            -s:,--sconf: '$:' \
            -V:,--refvar: '$:' \
            -v:,--vars: '$:' \
            -l:,--lconf: '$:' \
            @ __args -- "$@" && set -- "${__args[@]}" || die "$__args"
        runs_var "$@"
        RUNSVARDESC=
        local __name __value; __runs_splitref "$1" __name __value
        array_add RUNSREFS "$__name"
    }
    function indref() { runs_indref "$@"; }
    function refcerts() { runs_refcerts "$@"; }
    function refapacheconfig() { runs_refapacheconfig "$@"; }
    function out() { runs_var "$@"; }
    function script:() {
        eecho "(pas de description)"
        exit 0
    }

    runs_check_runsscript "$RUNSSCRIPT" || die
    runs_loadconfs
    (source "$RUNSSCRIPT"); __state=$?
    runs_clearvars
    return $__state
}

function runs_action_dump() {
    local -a RUNSVARS RUNSARRAYS RUNSVARSINDIRECT
    local -a RUNSCLVARS RUNSCONFS RUNSCONFFILES
    local RUNSVARDESC __state

    RUNSCLVARS=("$@")

    function desc() { eecho "$(ppath "$RUNSSCRIPT"): $*"; }
    function unsupported_system() { runs_unsupported_system "$@"; }
    function require_sysinfos() { runs_require_sysinfos "$@"; }
    function vardesc() { RUNSVARDESC="$*"; }
    function var() { runs_var "$@"; RUNSVARDESC=; }
    function conf() { runs_conf "$@"; }
    function after() { :; }
    function ref() {
        local -a __args
        parse_opts + \
            -s:,--sconf: '$:' \
            -V:,--refvar: '$:' \
            -v:,--vars: '$:' \
            -l:,--lconf: '$:' \
            @ __args -- "$@" && set -- "${__args[@]}" || die "$__args"
        runs_var "$@"
        RUNSVARDESC=
        local __name __value; __runs_splitref "$1" __name __value
        array_add RUNSREFS "$__name"
    }
    function indref() { runs_indref "$@"; }
    function refcerts() { runs_refcerts "$@"; }
    function refapacheconfig() { runs_refapacheconfig "$@"; }
    function out() { runs_var "$@"; }
    function script:() {
        runs_set_lang
        runs_set_proxy
        runs_clvars "${RUNSCLVARS[@]}"
        runs_indvars

        local __var __desc __values __count
        for __var in "${RUNSVARS[@]}"; do
            __desc="RUNSVARDESC_$__var"
            [ -n "${!__desc}" ] && eecho "${COULEUR_BLEUE}$__var${COULEUR_NORMALE}: ${!__desc}"

            __values="$__var[@]"
            eval "__count=\"\${#$__var[@]}\""
            if [ "$__count" -eq 1 ]; then
                # variable scalaire
                eecho "$__var=$(quoted_args "${!__values}")"
            else
                # variable tableau
                eecho "$__var=($(quoted_args "${!__values}"))"
            fi
        done
        exit 0
    }

    runs_check_runsscript "$RUNSSCRIPT" || die
    runs_loadconfs
    (source "$RUNSSCRIPT"); __state=$?
    runs_clearvars
    return $__state
}

function runs_action_run() {
    local -a RUNSVARS RUNSARRAYS RUNSVARSINDIRECT
    local -a RUNSCLVARS RUNSCONFS RUNSCONFFILES
    local RUNSVARDESC __state

    RUNSCLVARS=("$@")

    function desc() { :; }
    function unsupported_system() { runs_unsupported_system "$@"; }
    function require_sysinfos() { runs_require_sysinfos "$@"; }
    function vardesc() { RUNSVARDESC="$*"; }
    function var() { runs_var "$@"; RUNSVARDESC=; }
    function conf() { runs_conf "$@"; }
    function after() { runs_after "$@" || exit 1; }
    function ref() {
        local -a __args
        local __ref __name __value __found __required __rdir __rfile __rsconf
        local __shellconfs __shellconf __refvarname=refdir __refvar
        local __tmpconf __wildconf
        # ignorer les options -v, -l et leur argument
        parse_opts + \
            -r,--required,-m,--mandatory __required=1 \
            -f,--required-file '$__required=1; __rfile=1' \
            -d,--required-dir '$__required=1; __rdir=1' \
            -s:,--sconf: __shellconf= \
            --rs,--rsconf,--required-sconf __rsconf=1 \
            -V:,--refvar: __refvarname= \
            -v:,--vars: '$:' \
            -l:,--lconf: '$:' \
            @ __args -- "$@" && set -- "${__args[@]}" || die "$__args"

        if [ -n "$__shellconf" ]; then
            splitwcs "$__shellconf" __tmpconf __wildconf
            [ -n "$__wildconf" ] && __shellconf="$__tmpconf"
            if [ -e "$__shellconf" -a "${__shellconf#/}" != "$__shellconf" ]; then
                # un chemin absolu _existant_, le garder tel quel
                :
            elif withpath "$__shellconf" && [ -e "$RUNSSCRIPTDIR/$__shellconf" ]; then
                # d'abord dans le répertoire du script
                __shellconf="$RUNSSCRIPTDIR/$__shellconf"
            elif __tmpconf="$(runs_find_datafile "$__shellconf" "" "$RUNSSCRIPTDIR")"; then
                # puis chercher dans RUNS*DIRS
                __shellconf="$__tmpconf"
            fi
            if [ ! -e "$__shellconf" ]; then
                if [ -n "$__rsconf" ]; then
                    die "$__shellconf: fichier introuvable"
                else
                    ewarn "$__shellconf: fichier introuvable"
                fi
            fi
            [ -n "$__wildconf" ] && __shellconf="$__shellconf/$__wildconf"

            if [ -z "$*" ]; then
                # Si aucun argument n'est spécifié, essayer de prendre la
                # configuration dans le fichier $__shellconf

                __runs_check_varname "$__refvarname" || die
                set_var "$__refvarname" ""

                splitwcs "$__shellconf" __tmpconf __wildconf
                if [ -n "$__wildconf" ]; then
                    array_lsall __shellconfs "$__tmpconf" "$__wildconf"
                else
                    __shellconfs=("$__shellconf")
                fi
                __args=()
                for __tmpconf in "${__shellconfs[@]}"; do
                    [ -f "$__tmpconf" ] || {
                        eerror "$__tmpconf: fichier introuvable. Il a été ignoré"
                        continue
                    }
                    __refvar="$(source "$__tmpconf"; echo "${!__refvarname}")"
                    __args=("${__args[@]}" "$__refvarname=$__refvar")
                done
                set -- "${__args[@]}"
            fi
        fi

        for __ref in "$@"; do
            __runs_splitref "$__ref" __name __value
            if __runs_check_varname "$__name"; then
                __found=
                if [ "${__value#/}" != "$__value" ]; then
                    # un chemin absolu. corriger éventuellement avec RUNSROOTDIR
                    # s'il existe, le garder tel quel
                    if [ -n "$RUNSROOTDIR" ]; then
                        # corriger le chemin à partir de RUNSROOTDIR
                        __value="$RUNSROOTDIR$__value"
                    fi
                    [ -e "$__value" ] && __found=1
                fi
                if [ -z "$__found" ]; then
                    if withpath "$__value" && [ -e "$RUNSSCRIPTDIR/$__value" ]; then
                        # d'abord dans le répertoire du script
                        __value="$RUNSSCRIPTDIR/$__value"
                    elif [ -e "$RUNSWORKDIR/$__value" ]; then
                        # ensuite dans le répertoire partagé
                        __value="$RUNSWORKDIR/$__value"
                    else
                        # puis chercher dans RUNS*DIRS
                        local __file
                        if __file="$(runs_find_datafile "$__value" "" "$RUNSSCRIPTDIR")"; then
                            __value="$__file"
                        elif [ -z "$__required" ]; then
                            # par défaut, prendre dans le répertoire partagé
                            __value="$RUNSWORKDIR/$__value"
                        else
                            die "Le fichier $__value ($__name) est requis"
                        fi
                    fi
                fi
                [ -z "$__rfile" -o -f "$__value" ] || die "Le fichier $__value ($__name) est requis"
                [ -z "$__rdir" -o -d "$__value" ] || die "Le répertoire $__value ($__name) est requis"

                runs_var "$__name=$__value"
                RUNSVARDESC=
                array_add RUNSREFS "$__name"
            fi
        done
    }
    function indref() { runs_indref "$@"; }
    function refcerts() { runs_refcerts "$@"; }
    function refapacheconfig() { runs_refapacheconfig "$@"; }
    function out() {
        for __out in "$@"; do
            if [[ "$__out" == *=* ]]; then
                __name="${__out%%=*}"
                __value="${__out#*=}"
            else
                __name="$__out"
                __value="$__out"
            fi
            if __runs_check_varname "$__name"; then
                __value="$RUNSWORKDIR/$__value"
                runs_var "$__name=$__value"

                mkdirof "$__value"
                >"$__value" # s'assurer que le fichier est vide
            fi
        done
    }
    function script:() {
        runs_set_lang
        runs_set_proxy
        runs_clvars "${RUNSCLVARS[@]}"
        runs_indvars
        runs_check_confs
    }

    runs_check_runsscript "$RUNSSCRIPT" || die
    runs_loadconfs
    if [ -d "$RUNSSCRIPT" ]; then
        # installer le répertoire avec uinst
        # les variables sont passées en ligne de commande, et les tableau sont
        # transformés en chaine avec chaque élément séparés par ':'
        (
            runs_clvars "${RUNSCLVARS[@]}"
            runs_indvars
            for __name in "${RUNSVARS[@]}"; do
                __value="$(array_join "$__name" ":")"
                __vars=("${__vars[@]}" "$(set_var_cmd "$__name" "$__value")")
            done

            urequire uinst udir prefixes uinc
            # Il faut définir UINST, chemin vers le script uinst.
            UINST="$RUNSSRCDIR/uinst"
            uinst $(get_interaction_option) $(get_verbosity_option) \
                "$RUNSSCRIPT" "${__vars[@]}"
        ); __state=$?
    else
        # lancer le script avec le protocole runs
        (source "$RUNSSCRIPT"); __state=$?
    fi
    runs_clearvars
    return $__state
}

function runs_action_export() {
    local -a RUNSVARS RUNSARRAYS RUNSVARSINDIRECT
    local -a RUNSCLVARS RUNSCONFS RUNSCONFFILES
    local RUNSVARDESC __state

    RUNSCLVARS=("$@")

    function desc() { :; }
    function unsupported_system() { runs_unsupported_system "$@"; }
    function require_sysinfos() { runs_require_sysinfos "$@"; }
    function vardesc() { RUNSVARDESC="$*"; }
    function var() { runs_var "$@"; RUNSVARDESC=; }
    function conf() { runs_conf "$@"; }
    function after() { runs_after "$@" || exit 1; }
    function ref() {
        local -a __args
        local __ref __name __value __copy __required __rdir __rfile __rsconf
        local __shellconfs __shellconf __refvarname=refdir __refvar __shellvars
        local __lineconfs __lineconf
        local __tmpconf __wildconf
        parse_opts + \
            -r,--required,-m,--mandatory __required=1 \
            -f,--required-file '$__required=1; __rfile=1' \
            -d,--required-dir '$__required=1; __rdir=1' \
            -s:,--sconf: __shellconf= \
            --rs,--rsconf,--required-sconf __rsconf=1 \
            -V:,--refvar: __refvarname= \
            -v:,--vars: __shellvars= \
            -l:,--lconf: __lineconf= \
            @ __args -- "$@" && set -- "${__args[@]}" || die "$__args"

        if [ -n "$__shellconf" ]; then
            splitwcs "$__shellconf" __tmpconf __wildconf
            [ -n "$__wildconf" ] && __shellconf="$__tmpconf"
            if [ -e "$__shellconf" -a "${__shellconf#/}" != "$__shellconf" ]; then
                # un chemin absolu _existant_, le garder tel quel
                :
            elif withpath "$__shellconf" && [ -e "$RUNSSCRIPTDIR/$__shellconf" ]; then
                # d'abord dans le répertoire du script
                __shellconf="$RUNSSCRIPTDIR/$__shellconf"
            elif __tmpconf="$(runs_find_datafile "$__shellconf" "" "$RUNSSCRIPTDIR")"; then
                # puis chercher dans RUNS*DIRS
                __shellconf="$__tmpconf"
            fi
            if [ ! -e "$__shellconf" ]; then
                if [ -n "$__rsconf" ]; then
                    die "$__shellconf: fichier introuvable"
                else
                    ewarn "$__shellconf: fichier introuvable"
                fi
            fi
            [ -n "$__wildconf" ] && __shellconf="$__shellconf/$__wildconf"
            [ -n "$__lineconf" ] && ewarn "L'option -s étant spécifiée, l'option -l a été ignorée"

            if [ -z "$*" ]; then
                # Si aucun argument n'est spécifié, essayer de prendre la
                # configuration dans le fichier $__shellconf

                __runs_check_varname "$__refvarname" || die
                set_var "$__refvarname" ""

                splitwcs "$__shellconf" __tmpconf __wildconf
                if [ -n "$__wildconf" ]; then
                    array_lsall __shellconfs "$__tmpconf" "$__wildconf"
                else
                    __shellconfs=("$__shellconf")
                fi
                __args=()
                for __tmpconf in "${__shellconfs[@]}"; do
                    [ -f "$__tmpconf" ] || {
                        eerror "$__tmpconf: fichier introuvable. Il a été ignoré"
                        continue
                    }
                    __refvar="$(source "$__tmpconf"; echo "${!__refvarname}")"
                    __args=("${__args[@]}" "$__refvarname=$__refvar")
                done
                set -- "${__args[@]}"
            fi

        elif [ -n "$__lineconf" ]; then
            splitwcs "$__lineconf" __tmpconf __wildconf
            [ -n "$__wildconf" ] && __lineconf="$__tmpconf"
            if [ -e "$__lineconf" -a "${__lineconf#/}" != "$__lineconf" ]; then
                # un chemin absolu _existant_, le garder tel quel
                :
            elif withpath "$__lineconf" && [ -e "$RUNSSCRIPTDIR/$__lineconf" ]; then
                # d'abord dans le répertoire du script
                __lineconf="$RUNSSCRIPTDIR/$__lineconf"
            elif __tmpconf="$(runs_find_datafile "$__lineconf" "" "$RUNSSCRIPTDIR")"; then
                # puis chercher dans RUNS*DIRS
                __lineconf="$__tmpconf"
            fi
            [ -e "$__lineconf" ] || die "$__lineconf: fichier introuvable"
            [ -n "$__wildconf" ] && __lineconf="$__lineconf/$__wildconf"
        fi
        if [ -n "$__rfile" ]; then
            [ -n "$__shellconf" -o -n "$__lineconf" ] && die "-f est incompatible avec -s et -l"
        fi

        for __ref in "$@"; do
            if [ -n "$__shellconf" -a -z "$__shellvars" ]; then
                ewarn "L'option -v n'a pas été spécifiée pour $__ref"
            fi
            __copy=1
            __runs_splitref "$__ref" __name __value
            if __runs_check_varname "$__name"; then
                if [ -e "$__value" -a "${__value#/}" != "$__value" ]; then
                    # un chemin absolu _existant_, le garder tel quel
                    :
                elif withpath "$__value" && [ -e "$RUNSSCRIPTDIR/$__value" ]; then
                    # d'abord dans le répertoire du script
                    __value="$RUNSSCRIPTDIR/$__value"
                elif [ -e "$RUNSWORKDIR/$__value" ]; then
                    # puis dans le répertoire partagé
                    __value="$RUNSWORKDIR/$__value"
                    __copy=
                else
                    # puis chercher dans RUNS*DIRS
                    local __file
                    if __file="$(runs_find_datafile "$__value" "" "$RUNSSCRIPTDIR")"; then
                        __value="$__file"
                    elif [ -z "$__required" ]; then
                        # par défaut, prendre dans le répertoire partagé
                        __value="$RUNSWORKDIR/$__value"
                        __copy=
                    else
                        die "Le fichier $__value ($__name) est requis"
                    fi
                fi
                [ -z "$__rfile" -o -f "$__value" ] || die "Le fichier $__value ($__name) est requis"
                [ -z "$__rdir" -o -d "$__value" ] || die "Le répertoire $__value ($__name) est requis"

                runs_var "$__name=$__value"
                RUNSVARDESC=
                array_add RUNSREFS "$__name"

                if [ -n "$__copy" ]; then
                    if [ -n "$__shellconf" ]; then
                        # copier les fichiers mentionnés dans le fichier de
                        # configuration, d'après les valeurs de __shellvars
                        estep "Vérification du répertoire $(ppath "$__value")"
                        mkdirof "$RUNSROOTDIR$__value"
                        if [ -n "$__shellvars" ]; then
                            (
                                __CPNOVCS_RSYNC_ARGS=(--copy-unsafe-links)
                                array_split __shellvars "$__shellvars" ,
                                splitwcs "$__shellconf" __tmpconf __wildconf
                                if [ -n "$__wildconf" ]; then
                                    array_lsall __shellconfs "$__tmpconf" "$__wildconf"
                                else
                                    __shellconfs=("$__shellconf")
                                fi
                                for __shellconf in "${__shellconfs[@]}"; do
                                    estep "Dans $(ppath "$__shellconf")"
                                    for __shellvar in "${__shellvars[@]}"; do
                                        # s'assurer que la variables sont vides
                                        # avant de sourcer $__shellconf
                                        set_var "$__shellvar"
                                    done
                                    source "$__shellconf"
                                    for __shellvar in "${__shellvars[@]}"; do
                                        [ -n "${!__shellvar}" ] || continue
                                        estep "... Copie de ${!__shellvar}"
                                        cpnovcs "$__value/${!__shellvar}" "$(dirname "$RUNSROOTDIR$__value/${!__shellvar}")"
                                    done
                                done
                            )
                        fi
                    elif [ -n "$__lineconf" ]; then
                        # copier les fichiers mentionnés dans le fichier de
                        # configuration, à raison de un par ligne
                        estep "Vérification du répertoire $(ppath "$__value")"
                        mkdirof "$RUNSROOTDIR$__value"
                        (
                            __CPNOVCS_RSYNC_ARGS=(--copy-unsafe-links)
                            splitwcs "$__lineconf" __tmpconf __wildconf
                            if [ -n "$__wildconf" ]; then
                                array_lsall __lineconfs "$__tmpconf" "$__wildconf"
                            else
                                __lineconfs=("$__lineconf")
                            fi
                            for __lineconf in "${__lineconfs[@]}"; do
                                estep "Dans $(ppath "$__lineconf")"
                                array_from_lines __relpaths "$(<"$__lineconf" filter_conf)"
                                for __relpath in "${__relpaths[@]}"; do
                                    estep "... Copie de $__relpath"
                                    cpnovcs "$__value/$__relpath" "$(dirname "$RUNSROOTDIR$__value/$__relpath")"
                                done
                            done
                        )
                    elif [ ! -e "$RUNSROOTDIR$__value" ]; then
                        # copie standard
                        estep "Copie de $(ppath "$__value")"
                        mkdirof "$RUNSROOTDIR$__value"
                        cpnovcs "$__value" "$(dirname "$RUNSROOTDIR$__value")"
                    fi
                fi
            fi
        done
    }
    function indref() { runs_indref "$@"; }
    function refcerts() { runs_refcerts "$@"; }
    function refapacheconfig() { runs_refapacheconfig "$@"; }
    function out() {
        for __out in "$@"; do
            if [[ "$__out" == *=* ]]; then
                __name="${__out%%=*}"
                __value="${__out#*=}"
            else
                __name="$__out"
                __value="$__out"
            fi
            if __runs_check_varname "$__name"; then
                __value="$RUNSWORKDIR/$__value"
                runs_var "$__name=$__value"

                mkdirof "$__value"
                >"$__value" # s'assurer que le fichier est vide
            fi
        done
    }
    function script:() {
        runs_set_lang
        runs_set_proxy
        runs_clvars "${RUNSCLVARS[@]}"
        runs_indvars

        local __recipe
        if array_contains RUNSCONFS local; then
            if array_contains RUNSCONFS root; then
                __recipe="$RUNSEXPORTDIR/localroot"
            else
                __recipe="$RUNSEXPORTDIR/localuser"
            fi
        else
            __recipe="$RUNSEXPORTDIR/remote"
            if array_contains RUNSCONFS root; then
                touch "$RUNSEXPORTDIR/remote-needs-root"
            fi
        fi
        if [ -n "$RUNSUPDATERECIPE" ]; then
            estep "Ajout de la ligne de recette"
            runs_clvars_cmd "$RUNSSCRIPTPATH" "${RUNSCLVARS[@]}" >>"$__recipe"
        fi

        if [ ! -e "$RUNSROOTDIR$RUNSSCRIPT" ]; then
            estep "Copie du script"
            mkdirof "$RUNSROOTDIR$RUNSSCRIPT"
            cpnovcs "$RUNSSCRIPT" "$(dirname "$RUNSROOTDIR$RUNSSCRIPT")"
        fi
        exit 0
    }

    runs_check_runsscript "$RUNSSCRIPT" || die
    runs_loadconfs

    local __conffile
    for __conffile in "${RUNSCONFFILES[@]}"; do
        if [ ! -e "$RUNSROOTDIR$__conffile" ]; then
            estep "Copie de $(ppath "$__conffile")"
            mkdirof "$RUNSROOTDIR$__conffile"
            cpnovcs "$__conffile" "$(dirname "$RUNSROOTDIR$__conffile")"
        fi
    done

    if [ -d "$RUNSSCRIPT" ]; then
        # le répertoire sera installé avec uinst
        if [ -n "$RUNSUPDATERECIPE" ]; then
            estep "Ajout de la ligne de recette"
            runs_clvars_cmd "$RUNSSCRIPTPATH" "${RUNSCLVARS[@]}" >>"$RUNSEXPORTDIR/remote"
        fi

        if [ ! -e "$RUNSROOTDIR$RUNSSCRIPT" ]; then
            estep "Copie des fichiers"
            mkdir -p "$RUNSROOTDIR$RUNSSCRIPT" || return 1
            cpdirnovcs "$RUNSSCRIPT" "$RUNSROOTDIR$RUNSSCRIPT" || return 1
        fi
    else
        # le script sera lancé avec le protocole runs
        (source "$RUNSSCRIPT") || return 1
    fi
    runs_clearvars
    return 0
}

################################################################################
# Les fonctions de cette section permettent de maintenir des informations sur
# des opérations _effectuées avec l'utilisateur root_. Cela permet à un script de
# savoir s'il a déjà été lancé. Le code typique sera celui-ci:
#   shouldrun || exit
#   ...
#   setdone
# Les informations sont stockées sur la machine cible dans /var/lib/runs.state

if is_root; then
    RUNSSTATE_FILE=/var/lib/runs.state
else
    RUNSSTATE_FILE="$HOME/etc/runs.state"
fi
RUNSSTATE_LASTKEY=
RUNSSTATE_LASTVALUE=
# Vérifier que la clé $1 existe dans $RUNSSTATE_FILE, et si $2!="", qu'elle
# existe avec la valeur $2. Si la clé existe, retourner faux (il ne faut pas
# lancer le script).
# Si $RUNSRESET est vrai, toujours retourner vrai. Ceci permet de forcer
# l'installation pour un script
# La clé est toujours automatiquement préfixée de $3(=$RUNSSCRIPTPATH)
function shouldrun() {
    [ -f "$RUNSSTATE_FILE" ] || return 0
    is_yes "$RUNSRESET" && return 0
    local key="${3:-$RUNSSCRIPTPATH}${1:+-$1}"
    if ! quietgrep "^$key:$2" "$RUNSSTATE_FILE"; then
        RUNSSTATE_LASTKEY="$1"
        RUNSSTATE_LASTVALUE="$2"
        return 0
    else
        return 1
    fi
}
# Pour le script de chemin scriptpath $1, vérifier que la clé $2 existe dans
# $RUNSSTATE_FILE, et si $3!="", qu'elle existe avec la valeur $3.
# Cette fonction permet de ne lancer un traitement que si un autre script a été
# lancé auparavant avec succès
function checkdone() {
    [ -n "$1" ] || return 1
    [ -f "$RUNSSTATE_FILE" ] || return 1
    local key="$1${1:+-$2}" value="$3"
    quietgrep "^$key:$value" "$RUNSSTATE_FILE"
}
# Comme checkdone(), mais si la clé n'existe pas, afficher un message d'erreur
# et quitter le script
function requiredone() {
    if ! checkdone "$@"; then
        msg="Ce script requière que le script dépendant $1"
        if [ -n "$2" ]; then
            msg="${msg} soit dans l'état $2=${3:-done}"
        else
            msg="${msg} aie été installé avec succès"
        fi
        die "$msg.
Pour résoudre ce problème, vous pouvez essayer de relancer le script dépendant"
    fi
}
# Ajouter la clé $1 avec la valeur $2(=done) dans $RUNSSTATE_FILE si elle n'y
# existe pas déjà. Si $1 est vide, prendre par défaut la dernière clé utilisée
# avec shouldrun()
# La clé est toujours automatiquement préfixée de $3(=$RUNSSCRIPTPATH)
function setdone() {
    local key="$1" value="$2"
    [ -n "$key" ] || key="$RUNSSTATE_LASTKEY"
    [ -n "$value" ] || value="$RUNSSTATE_LASTVALUE"
    key="${3:-$RUNSSCRIPTPATH}${key:+-$key}"
    [ -n "$value" ] || value=done

    local line="$key:$value"
    mkdirof "$RUNSSTATE_FILE"
    [ -f "$RUNSSTATE_FILE" ] || touch "$RUNSSTATE_FILE"
    if ! quietgrep "^$line" "$RUNSSTATE_FILE"; then
        echo "$line" >>"$RUNSSTATE_FILE"
    fi
    RUNSSTATE_LASTKEY=
    RUNSSTATE_LASTVALUE=
}
# Supprimer toutes les valeurs de la clé $1 dans $RUNSSTATE_FILE
# La clé est toujours automatiquement préfixée de $2(=$RUNSSCRIPTPATH)
function resetdone() {
    local key="${2:-$RUNSSCRIPTPATH}${1:+-$1}"
    [ -n "$key" ] || return
    [ -f "$RUNSSTATE_FILE" ] || return
    sedi "/^${key//\//\\/}:/d" "$RUNSSTATE_FILE"
}