269 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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=$(qval "$__estack")
 | |
| __tlevel=$(qval "$__tlevel")
 | |
| export __estack __tlevel
 | |
| ${UTOOLS_LANG:+UTOOLS_LANG='$UTOOLS_LANG'; export UTOOLS_LANG
 | |
| }$(qvals "./$archivename" ${tmproot:+--tmproot "$tmproot"} -- MYHOST="$host" "$@")"
 | |
|         eend
 | |
|     fi
 | |
| done
 |