diff --git a/rwyinst b/rwyinst new file mode 100755 index 0000000..072784d --- /dev/null +++ b/rwyinst @@ -0,0 +1,312 @@ +#!/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 + +function display_help() { + uecho "$scriptname: Déploiement distant avec wyinst + +USAGE + $scriptname [-h host] [-G tmproot] [-- options de wyinst] + +note: à cause d'une limitation de makeself, les options de wyinst ne devraient +pas contenir d'espaces ni de caractères spéciaux. L'échappement de ces +caractères n'est pas garanti. + +OPTIONS + -G, --tmproot 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, --host hosts + -h, --host @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. + --deploydb + --nd, --no-deploydb + Autoriser (respectivement interdire) l'utilisation de la configuration + locale de déploiement pour identifier la source et/ou la destination + s'ils ne sont pas spécifiés. Par défaut, la configuration locale de + déploiement est utilisée. + -p, --dp, --deploydb-profile PROFILENAME + Spécifier un ou plusieurs profils séparés par des virgules pour le + déploiement avec la configuration locale de déploiement. NONE est la + valeur par défaut et signifie de ne sélectionner que les définitions + sans profil. ALL signifie de ne pas tenir compte des profils dans les + définitions. + -A, --all-profiles + -P, --prod + -T, --test + Raccourcis respectivement pour -pALL, -pprod et -ptest + -c, --dc, --deploydb-config CONFNAME + Cette option permet de spécifier un fichier de configuration ou le nom + de la configuration locale de déploiement à utiliser pour effectuer la + requête. Par défaut, utiliser le nom 'wyinst.conf'" +} + +__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 +} + +action=deploy +tmproot= +SSH= +hosts=() +ddb_enable=1 +ddb_profile=NONE +ddb_conf=wyinst.conf +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -G:,--tmproot: tmproot= \ + -S:,--ssh: SSH= \ + -h:,-H:,--host: hosts \ + --deploydb ddb_enable=1 \ + --nd,--no-deploydb ddb_enable= \ + -p:,--dp:,--deploydb-profile ddb_profile= \ + -A,--all-profiles ddb_profile=ALL \ + -P,--prod ddb_profile=prod \ + -T,--test ddb_profile=test \ + -c:,--dc:,--deploydb-config ddb_conf= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +: "${SSH:=ssh}" + +## wyapp à déployer et hôtes sur lesquels faire le déploiement + +# quels informations avons-nous? +array_isempty hosts && has_hosts= || has_hosts=1 + +if [ $# -eq 0 ] || [[ "$1" == -* ]]; then + # pas d'argument, ou c'est une option (qui fait donc partie des arguments de wyinst) + has_wyapp= +else + wyapp="$1" + has_wyapp=1 + shift +fi + +# configuration locale de déploiement +if [ -n "$ddb_enable" ]; then + deploydb=( + "$scriptdir/lib/nulib/deploydb" + --missing-ok + ${ddb_conf:+-c "$ddb_conf"} + -m wyinst + --run -r wyinst.query_rwyinst "$script" + ) + cmds=() + if [ -n "$has_hosts" ]; then + fix_hosts + for host in "${hosts[@]}"; do + array_from_lines tmpcmds "$("${deploydb[@]}" "$wyapp" "$host" "$ddb_profile")" + array_extend cmds tmpcmds + done + elif [ -n "$has_wyapp" ]; then + array_from_lines tmpcmds "$("${deploydb[@]}" "$wyapp" "" "$ddb_profile")" + array_extend cmds tmpcmds + else + read_value "Entrez le répertoire à déployer" wyapp . O + has_wyapp=1 + array_from_lines tmpcmds "$("${deploydb[@]}" "$wyapp" "" "$ddb_profile")" + array_extend cmds tmpcmds + fi + if [ ${#cmds[*]} -gt 0 ]; then + [ $# -gt 0 ] && ewarn "Les options supplémentaires '$*' seront ignorées" + if check_interaction -c; then + if [ ${#cmds[*]} -eq 1 ]; then + einfo "La commande suivante va être lancée:" + eecho "\$ $script --no-deploydb \\" + for cmd in "${cmds[0]}"; do + eecho " ${cmd#$script --no-deploydb }" + done + else + einfo "Les commandes suivantes seront lancées:" + eecho "\$ $script --no-deploydb \\" + for cmd in "${cmds[@]}"; do + eecho " ... ${cmd#$script --no-deploydb }" + done + fi + read -p "Confirmez ou attendez 4 secondes [On] " -t 4 r + if [ $? -gt 128 ]; then + echo # cosmetic + elif [ $? -le 128 -a -n "$r" ]; then + is_yes "$r" || die + fi + fi + r=0 + for cmd in "${cmds[@]}"; do + einfo "$cmd" + eval "$cmd" || r=1 + done + exit $r + elif [ "$ddb_profile" != "ALL" ]; then + ewarn "Aucune configuration locale de déploiement n'a été trouvée pour le profil $ddb_profile" + fi +fi + +## Répertoires à déployer et hôtes sur lesquels faire le déploiement + +if [ -z "$has_wyapp" ]; then + # pas d'argument, ou c'est une option (qui fait donc partie des arguments de wyinst) + read_value "Entrez le répertoire à déployer" wyapp . O + has_wyapp=1 +fi +[ -n "$has_wyapp" ] && set -- "$wyapp" "$@" + +array_isempty hosts && read_value "Entrez une liste d'hôtes séparés par ':'" hosts "localhost" +fix_hosts + +## Création de l'archive +etitle "Création du répertoire de travail" +ac_set_tmpdir workdir +wyapps=() +while [ $# -gt 0 ]; do + if [ "$1" == "--" ]; then + # début des options de wyinst + shift + break + elif [[ "$1" == -* ]]; then + # début des options de wyinst + break + fi + + osrc="$1"; shift + setx src=abspath "$osrc" + setx srcname=basename -- "$src" + if [ -f "$src" ] && is_archive "$src"; then + setx waname=get_archive_appname "$src" + estep "$(ppath "$src") --> $waname" + wadir="$workdir/$waname" + mkdir "$wadir" + extract_archive "$src" "$wadir" || die + src="$wadir" + elif [ -d "$src" ]; then + estep "$(ppath2 "$src")" + cpdirnovcs "$src" "$workdir/$srcname" || die + elif [ -e "$src" ]; then + die "$osrc: fichier invalide" + else + die "$osrc: fichier ou répertoire introuvable" + fi + + array_add wyapps "$srcname" +done + +estep "Copie de l'environnement de déploiement" +mkdir "$workdir/lib" +ulibsync "$workdir/lib" +cp "$scriptdir/wyinst" "$workdir" +chmod +x "$workdir/wyinst" + +eend + +ac_set_tmpfile archive +archivename="$(basename "$archive")" + +etitle "Création de l'archive pour le déploiement" +"$scriptdir/mkusfx" --bare --tmp-archive -o "$archive" "$workdir" -- ./wyinst || die +eend + +## Déploiement + +# sur l'hôte distant, ne rendre non interactif qu'à partir de -yy +rinteraction=$__interaction +[ $rinteraction -lt 2 ] && rinteraction=$(($rinteraction + 1)) + +for host in "${hosts[@]}"; do + if [ "$host" == "localhost" ]; then + etitle "Déploiement sur l'hôte local" + "$archive" ${tmproot:+--tmproot "$tmproot"} -- "${wyapps[@]}" "$@" + 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" "\ +__interaction=$rinteraction +__estack=$(qval "$__estack") +__tlevel=$(qval "$__tlevel") +export __interaction __estack __tlevel +${UTOOLS_LANG:+UTOOLS_LANG='$UTOOLS_LANG'; export UTOOLS_LANG +}$(qvals "./$archivename" ${tmproot:+--tmproot "$tmproot"} -- "${wyapps[@]}" "$@")" + + eend + fi +done diff --git a/wyinst b/wyinst new file mode 100755 index 0000000..c284e90 --- /dev/null +++ b/wyinst @@ -0,0 +1,198 @@ +#!/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 debian service conf + +function display_help() { + uecho "$scriptname: installer une webpyapp et le service associé + +USAGE + $scriptname [options] [APPDIR] + +OPTIONS + -c, --config CONFIG + Spécifier un fichier de configuration à utiliser pour installer le + service. Par défaut, prendre APPDIR/config/server.conf + -n, --name NAME + Spécifier le nom du service + --port PORT + Spécifier le port sur lequel doit écouter le serveur + -p, --profile PROFILE + Spécifier le profil avec lequel l'application doit tourner + -P, --prod-profile + -T, --test-profile + Raccourcis pour -pprod et -ptest respectivement + -u, --owner USER[:GROUP] + Spécifier l'utilisateur qui doit faire tourner le serveur. La valeur par + défaut est $DEFAULT_OWNER + -d, --destdir DESTDIR + Spécifier le répertoire d'installation. Par défaut, l'installation se + fait dans le répertoire $DEFAULT_DESTDIR + -s, --start + Activer et démarrer le service après installation + -k, --no-restart + Ne pas redémarrer le service s'il était déjà en train de tourner" +} + +DEFAULT_DESTDIR=/opt +DEFAULT_OWNER=root +DEFAULT_PYTHON=python2.7 + +serverconf= +name= +port= +profile= +owner= +mode=u=rwX,go=rX +destdir= +overwrite_config= +enable= +start= +norestart= +args=( + --help '$exit_with display_help' + -c:,--config: serverconf= + -n:,--name: name= + --port: port= + -p:,--profile: profile= + -P,--prod-profile profile=prod + -T,--test-profile profile=test + -u:,--owner: owner= + -d:,--destdir: destdir= + --overwrite-config overwrite_config=1 + -s,--start '$enable=1; start=1' + -k,--no-restart norestart=1 +) +parse_args "$@"; set -- "${args[@]}" + +appdir="${1:-.}"; shift +[ -d "$appdir" ] || die "$appdir: répertoire inexistant" +[ -d "$appdir/config" -a -d "$appdir/python" ] || die "$appdir: ne semble pas être une webpyapp" +setx appdir=abspath "$appdir" + +[ -n "$serverconf" ] || serverconf="$appdir/config/server.conf" +[ -f "$serverconf" ] || die "$(ppath "$serverconf"): impossible de trouver le fichier de configuration" +NAME= +PORT= +PROFILE= +OWNER= +VIRTUAL_ENV= +PYTHON= +source "$serverconf" + +[ -n "$name" ] || name="$NAME" +[ -n "$name" ] || setx name=basename "$appdir" +[ -n "$port" ] || port="$PORT" +[ -n "$profile" ] || profile="$PROFILE" +[ -n "$owner" ] || owner="$OWNER" +[ -n "$destdir" ] || destdir="$DEFAULT_DESTDIR" +run_as_root -c "$serverconf" -n "$name" ${port:+--port "$port"} \ + ${profile:+-p "$profile"} \ + ${owner:+-u "$owner"} \ + -d "$destdir" ${overwrite_config:+--overwrite-config} \ + ${start:+-s} ${norestart:+-k} \ + "$appdir" "$@" + +: "${PYTHON:=$DEFAULT_PYTHON}" +if [ -n "$VIRTUAL_ENV" ]; then + etitle "Environnement virtuel" + packages=(python-virtualenv) + if ! pkg_check "${packages[@]}"; then + estep "Installation ${packages[*]}" + pkg_install "${packages[@]}" || die + fi + if [ ! -d "$VIRTUAL_ENV" ]; then + estep "Installation dans $VIRTUAL_ENV" + virtualenv -p "$PYTHON" "$VIRTUAL_ENV" || die + fi + eend +fi +[ -n "$VIRTUAL_ENV" ] && PYTHON="$VIRTUAL_ENV/bin/python" + +restart= +if [ -z "$norestart" ]; then + if service "$name" check; then + estep "Arrêt du service" + service "$name" stop + start=1 + restart=1 + fi +fi + +etitle "Copie des fichiers" +destdir="$destdir/$name" +mkdir -p "$destdir" || die +rsync -a --delete-after \ + --exclude /config/ --exclude /nulib --exclude /.devel \ + -f "P /var/**" \ + "$appdir/" "$destdir" +eend + +etitle "Vérification de la configuration" +estep "Dans $destdir/config:" +array_from_lines configs "$(list_files "$appdir/config")" +for config in "${configs[@]}"; do + if [ -n "$overwrite_config" -o ! -f "$destdir/config/$config" ]; then + estepi "$config: copie initiale" + mkdir -p "$destdir/config" + if [ "$config" == server.conf ]; then + # pour ce cas particulier, prendre le fichier $serverconf + cp "$serverconf" "$destdir/config/$config" + estep "Mise à jour de la configuration" + conf_enable "$destdir/config/$config" \ + NAME="$name" ${port:+PORT="$port"} \ + ${profile:+PROFILE="$profile"} \ + ${owner:+OWNER="$owner"} + else + cp "$appdir/config/$config" "$destdir/config/$config" + fi + else + estepw "$config: refus d'écraser la configuration existante" + fi +done +eend + +[ -n "$owner" ] || owner="$DEFAULT_OWNER" +splitpair "$owner" user group +sdowner="User=$user" +[ -n "$group" ] && sdowner="$sdowner\\ +Group=$group" + +etitle "Compilation des classes" +"$PYTHON" -m compileall "$destdir/config" +"$PYTHON" -m compileall "$destdir/python" +eend + +etitle "Correction des permissions" +chown -R "$user:$group" "$destdir" +chmod -R "$mode" "$destdir" +eend + +etitle "Configuration du service" +estep "Vérification du fichier unit" +if [ -d /etc/systemd/system ]; then + service="/etc/systemd/system/$name.service" + ac_set_tmpfile tmpfile + sed "\ +s|@@destdir@@|$destdir|g +/^User=@@owner@@\$/c\\ +$sdowner +s|@@name@@|$name|g" "$destdir/support/server.service" >"$tmpfile" + if testupdated "$tmpfile" "$service"; then + estep "$service" + cat "$tmpfile" >"$service" + systemctl daemon-reload + fi + ac_clean "$tmpfile" +fi +if [ -n "$enable" ]; then + estep "Activation du service" + service_enable "$name" +fi +if [ -n "$start" ]; then + if [ -n "$restart" ]; then estep "Redémarrage du service" + else estep "Démarrage du service" + fi + service "$name" start +fi +eend