#!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 # Script permettant de lancer une commande dans docker et/ou de bootstrapper # l'utilisation de nulib dans un projet PHP # Les fichiers suivants doivent être copiés à un endroit quelconque du projet: # - runphp (ce script, à générer avec update-runphp.sh) # - Dockerfile.runphp # Les fichiers suivants peuvent être intégrés dans le projet comme exemples: # - dot-build.env.dist (à renommer en .build.env.dist) # - dot-dkbuild.env.dist (indiquer qu'il faut le copier en ~/.dkbuild.env) # Par défaut, ce script assume que runphp est copié dans le répertoire sbin/ # du projet, et que le fichier composer.json et le répertoire vendor/ sont à la # racine du projet. Le cas échéant, modifier les valeurs ci-dessous (return 0 2>/dev/null) && _sourced=1 || _sourced= ############################################################################### # Modifier les valeurs suivantes si nécessaire #SOF:runphp.userconf:ne pas modifier cette ligne # répertoire du projet. ce chemin doit être absolu. s'il est relatif, il est # exprimé par rapport au répertoire de ce script PROJDIR= # composer: répertoire du projet composer (celui qui contient le fichier # composer.json), chemin de composer.phar et répertoire vendor. ces chemins # doivent être relatifs à $PROJDIR COMPOSERDIR= COMPOSERPHAR= VENDORDIR= # fichier de configuration pour le build BUILDENV0= BUILDENV= # Listes des images que le script build construit automatiquement BUILD_IMAGES=(php-apache mariadb10) BUILD_FLAVOUR= ## En ce qui concerne DIST et IMAGENAME, les valeurs dans BUILDENV prennent le ## dessus. si BUILDENV *n'est pas* utilisé, ces valeurs peuvent être spécifiées ## ici # version de debian à utiliser pour l'image # d12=php8.2, d11=php7.4, d10=php7.3 DIST= # Nom de base de l'image (sans le registry), e.g prefix/ IMAGENAME= ## Fichiers .dist ## Lors du build, les fichiers de la forme .name.dist sont copiés vers un ## fichier name sauf s'il existe déjà # Liste de fichiers (ou de répertoirs à considérer). Pour chaque répertoire, les # fichiers .*.dist dans l'arborescence du répertoire sont recherchés DISTFILES=() #EOF:runphp.userconf:ne pas modifier cette ligne ################################################################################ # Ne pas modifier à partir d'ici if [ -n "$_sourced" ]; then if [ "${0#-}" != "$0" ]; then # sourcé depuis la ligne de commande MYSELF="${BASH_SOURCE[1]}" else # sourcé depuis un script MYSELF="${BASH_SOURCE[0]}" fi MYDIR="$(cd "$(dirname -- "$MYSELF")"; pwd)" MYNAME="$(basename -- "$MYSELF")" else MYDIR="$(cd "$(dirname -- "$0")"; pwd)" MYNAME="$(basename -- "$0")" fi if [ -f "$MYDIR/runphp.userconf.local" ]; then source "$MYDIR/runphp.userconf.local" fi DEFAULT_DIST=d12 if [ -n "$RUNPHP_STANDALONE" ]; then PROJDIR="$RUNPHP_PROJDIR" COMPOSERDIR=. COMPOSERPHAR= VENDORDIR=vendor BUILDENV0= BUILDENV= DIST="${RUNPHP_DIST:-$DEFAULT_DIST}" IMAGENAME=nulib/ PRIVAREG=docker.io REGISTRY="$RUNPHP_REGISTRY" [ -n "$RUNPHP_BUILD_FLAVOUR" ] && BUILD_FLAVOUR="$RUNPHP_BUILD_FLAVOUR" else [ -n "$PROJDIR" ] || PROJDIR="$(dirname -- "$MYDIR")" [ "${PROJDIR#/}" != "$PROJDIR" ] || PROJDIR="$(cd "$MYDIR/$PROJDIR"; pwd)" [ -n "$COMPOSERDIR" ] || COMPOSERDIR=. [ -n "$COMPOSERPHAR" ] || COMPOSERPHAR=sbin/composer.phar [ -n "$VENDORDIR" ] || VENDORDIR=vendor [ -n "$BUILDENV0" ] || BUILDENV0=.build.env.dist [ -n "$BUILDENV" ] || BUILDENV=build.env [ -n "$DIST" ] || DIST="$DEFAULT_DIST" [ -n "$IMAGENAME" ] || IMAGENAME=nulib/ [ "$COMPOSERPHAR" == none ] && COMPOSERPHAR= [ "$BUILDENV0" == none ] && BUILDENV0= [ "$BUILDENV" == none ] && BUILDENV= fi [ "$BUILD_FLAVOUR" == none ] && BUILD_FLAVOUR= function after_source_buildenv() { NDIST="${DIST#d}" } after_source_buildenv [ -n "$_sourced" ] && return 0 function eecho() { echo "$*" 1>&2; } function eerror() { eecho "ERROR: $*"; } function die() { [ $# -gt 0 ] && eerror "$*"; exit 1; } function is_defined() { [ -n "$(declare -p "$1" 2>/dev/null)" ]; } function in_path() { [ -n "$1" -a -x "$(which "$1" 2>/dev/null)" ]; } function composer() { if [ -n "$PROJDIR" -a -n "$COMPOSERDIR" ]; then cd "$PROJDIR/$COMPOSERDIR" || exit 1 fi if [ -n "$PROJDIR" -a -n "$COMPOSERPHAR" -a -x "$PROJDIR/$COMPOSERPHAR" ]; then "$PROJDIR/$COMPOSERPHAR" "$@" elif in_path composer; then command composer "$@" elif [ -x /usr/bin/composer ]; then /usr/bin/composer "$@" elif [ -x /usr/local/bin/composer ]; then /usr/local/bin/composer "$@" else die "impossible de trouver composer" fi if [ -n "$PROJDIR" -a -z "$RUNPHP_STANDALONE" -a -f composer.lock ]; then cp composer.lock "$PROJDIR/.composer.lock.runphp" fi } function host_parse_args() { # analyser les arguments et calculer les opérations à lancer: bootstrap, # composer install et/ou commande # faut-il lancer bootstrap? Bootstrap=auto # faut-il installer les dépendances? Composer= # commande à lancer Cmd=() if [ "$1" == --runphp-bootstrap -o "$1" == --bs ]; then Bootstrap=1 shift elif [ "$1" == --runphp-exec ]; then Bootstrap= shift elif [ "$1" == --runphp-install -o "$1" == --ci ]; then Composer=ci shift elif [ "$1" == --runphp-update -o "$1" == --cu ]; then Composer=cu shift fi Cmd=("$@") } function host_ensure_image() { # calculer le nom de l'image runphp local dfdir suffix dockerfiles dockerfile local privareg imagename if [ -z "$Image" ]; then [ -n "$RUNPHP_STANDALONE" ] && dfdir="$RUNPHP_STANDALONE/runphp" || dfdir="$MYDIR" dockerfiles=( "_local:$dfdir/Dockerfile.runphp.local" "${BUILD_FLAVOUR//+/_}:$dfdir/Dockerfile.runphp$BUILD_FLAVOUR" ":$dfdir/Dockerfile.runphp" ) for dockerfile in "${dockerfiles[@]}"; do suffix="${dockerfile%:*}" dockerfile="${dockerfile##*:}" [ -f "$dockerfile" ] && break done Dockerfile="$dockerfile" [[ "$IMAGENAME" == */ ]] && imagename=runphp || imagename="${IMAGENAME%/*}/runphp" privareg="$PRIVAREG" if [ "$imagename" == runphp ]; then [ -z "$privareg" -o "$privareg" == docker.io ] && privareg=docker.io/library else [ -z "$privareg" ] && privareg=docker.io fi Image="$privareg/$imagename$suffix:$DIST" fi } function host_check_image() { # vérifier que l'image runphp existe local image="$Image" for prefix in docker.io/library/ docker.io; do if [ "${image#$prefix}" != "$image" ]; then image="${image#$prefix}" break fi done [ -n "$(docker image ls --no-trunc --format '{{.Repository}}:{{.Tag}}' "$image" 2>/dev/null)" ] } function host_check_projdir() { # vérifier le projet pour voir s'il faut installer les dépendances [ -n "$RUNPHP_STANDALONE" ] && return local install if [ ! -f "$PROJDIR/$VENDORDIR/nulib/php/load.sh" ]; then install=1 elif [ ! -f "$PROJDIR/.composer.lock.runphp" ]; then install=1 elif ! diff -q "$PROJDIR/$COMPOSERDIR/composer.lock" "$PROJDIR/.composer.lock.runphp" >&/dev/null; then install=1 fi if [ -n "$install" ]; then [ -n "$Composer" ] || Composer=ci fi } function host_init_env() { ## Charger ~/.dkbuild.env APT_PROXY= APT_MIRROR= SEC_MIRROR= TIMEZONE= PRIVAREG= REGISTRY= PROFILE= HOST_MAPPINGS=() function default_profile() { PROFILE="$1" } function profile() { local profile for profile in "$@"; do [ "$profile" == "$PROFILE" ] && return 0 done return 1 } function setenv() { eval "export $1" } function default() { local command="$1"; shift local nv n v case "$command" in docker) for nv in "$@"; do [[ "$nv" == *=* ]] || continue n="${nv%%=*}" v="${nv#*=}" case "$n" in host-mappings) read -a ns <<<"$v" for v in "${ns[@]}"; do HOST_MAPPINGS+=("$v") done ;; esac done ;; esac } envs=() if [ -z "$RUNPHP_IGNORE_DEFAULTS" ]; then envs+=( /etc/default/dkbuild ~/etc/default.${HOSTNAME%%.*}/dkbuild ~/etc/default/dkbuild ) fi envs+=(~/.dkbuild.env) for env in "${envs[@]}"; do [ -f "$env" ] && source "$env" done [ -n "$APT_PROXY" ] || APT_PROXY= [ -n "$APT_MIRROR" ] || APT_MIRROR=default [ -n "$SEC_MIRROR" ] || SEC_MIRROR=default [ -n "$TIMEZONE" ] || TIMEZONE=Europe/Paris [ -n "$PRIVAREG" ] || PRIVAREG= [ -n "$REGISTRY" ] || REGISTRY=pubdocker.univ-reunion.fr/dist ## Charger la configuration # Recenser les valeur de proxy declare -A PROXY_VARS for var in {HTTPS,ALL,NO}_PROXY {http,https,all,no}_proxy; do is_defined "$var" && PROXY_VARS[${var,,}]="${!var}" done # Paramètres de montage if [ -n "$RUNPHP_NO_USE_RSLAVE" ]; then UseRslave= elif [ -n "$RUNPHP_USE_RSLAVE" ]; then UseRslave=1 elif [ -e /proc/sys/fs/binfmt_misc/WSLInterop ]; then # pas de mount propagation sous WSL UseRslave= else UseRslave=1 fi Image= if [ -n "$RUNPHP_FORCE_BUILDENVS" ]; then eval "Configs=($RUNPHP_FORCE_BUILDENVS)" elif [ -n "$BUILDENV" -a -f "$PROJDIR/$BUILDENV" ]; then Configs=("$PROJDIR/$BUILDENV") elif [ -n "$BUILDENV0" -a -f "$PROJDIR/$BUILDENV0" ]; then Configs=("$PROJDIR/$BUILDENV0") else Configs=() fi for config in "${Configs[@]}"; do source "$config" || exit 1 done after_source_buildenv Chdir= Verbose="$RUNPHP_VERBOSE" Exec= } function host_docker_build() { BUILD_ARGS=( DIST NDIST REGISTRY APT_PROXY APT_MIRROR SEC_MIRROR TIMEZONE ) SOPTS=+d:9876543210:c:UjDx:z:r:p LOPTS=help,dist:,d19,d18,d17,d16,d15,d14,d13,d12,d11,d10,config:,ue,unless-exists,pull,nc,no-cache,po,plain-output,apt-proxy:,timezone:,privareg:,push,ci,no-use-rslave args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" Dist= UnlessExists= Pull= NoCache= PlainOutput= while [ $# -gt 0 ]; do case "$1" in --) shift; break;; --help) eecho "\ runphp: construire l'image docker USAGE $MYNAME --bs [options...] OPTIONS -c, --config build.env -d, --dist DIST --ue, --unless-exists -U, --pull -j, --nc, --no-cache -D, --po, --plain-output -x, --apt-proxy APT_PROXY -z, --timezone TIMEZONE -r, --privareg PRIVAREG -p, --push paramètres pour la consruction de l'image --ci --cu lancer composer install (resp. update) s'il y a eu bootstrap --no-use-rslave paramètre montage des volumes" exit 0 ;; -c|--config) shift; Configs+=("$1");; -d|--dist) shift; Dist="$1";; -[0-9]) Dist="d1${1#-}";; --d*) Dist="${1#--}";; --ue|--unless-exists) UnlessExists=1;; -U|--pull) Pull=1;; -j|--nc|--no-cache) NoCache=1;; -D|--po|--plain-output) PlainOutput=1;; -x|--apt-proxy) shift; APT_PROXY="$1";; -z|--timezone) shift; TIMEZONE="$1";; -r|--privareg) shift; PRIVAREG="$1";; -p|--push) Push=1;; --ci) Composer=ci;; --cu) Composer=cu;; --no-use-rslave) UseRslave=;; *) die "$1: option non configurée";; esac shift done Cmd=("$@") for config in "${Configs[@]}"; do if [ "$config" == none ]; then Configs=() break fi done if [ ${#Configs[*]} -gt 0 ]; then for config in "${Configs[@]}"; do source "$config" || exit 1 done after_source_buildenv fi [ -n "$Dist" ] && DIST="$Dist" Image= host_ensure_image host_check_image && exists=1 || exists= if [ -z "$UnlessExists" -o -z "$exists" ]; then eecho "== bootstrapping $Image" args=( -f "$Dockerfile" ${Pull:+--pull} ${NoCache:+--no-cache} ${BuildPlain:+--progress plain} -t "$Image" ) for arg in "${BUILD_ARGS[@]}"; do args+=(--build-arg "$arg=${!arg}") done for arg in "${!PROXY_VARS[@]}"; do args+=(--build-arg "$arg=${PROXY_VARS[$arg]}") done for host in "${HOST_MAPPINGS[@]}"; do args+=(--add-host "$host") done mkdir -p /tmp/runphp-build docker build "${args[@]}" /tmp/runphp-build || exit 1 if [ -n "$Push" -a -n "$PRIVAREG" ]; then eecho "== Pushing $Image" docker push "$Image" || exit 1 fi else # s'il n'y a pas eu bootstrap, alors ne pas lancer la commande composer Composer= fi } function host_docker_run() { # lancer une commande avec docker SOPTS=+w: LOPTS=help,chdir:,no-use-rslave args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" while [ $# -gt 0 ]; do case "$1" in --) shift; break;; --help) eecho "\ runphp: lancer une commande dans un environnement PHP déterminé USAGE $MYNAME --bs ... $MYNAME ci|cu|composer $MYNAME [options] command [args...] COMMANDES SPECIALES --bs faire un bootstrap de l'image runphp COMMANDES COMPOSER ci cu installer/mettre à jour les dépendances du projet avec composer composer [args...] lancer composer avec les arguments spécifiés. pour les commandes ci-dessus, l'option --chdir est ignorée: le répertoire courant est forcé au répertoire du projet composer OPTIONS -w, --chdir CHDIR aller dans le répertoire spécifié avant de lancer la commande --no-use-rslave paramètre montage des volumes" exit 0 ;; -w|--chdir) shift; Chdir="$1";; --no-use-rslave) UseRslave=;; *) die "$1: option non configurée";; esac shift done args=( run -it --rm --name "runphp-$(basename -- "$1")-$$" -e RUNPHP_MODE=docker ) for arg in "${!PROXY_VARS[@]}"; do args+=(-e "$arg=${PROXY_VARS[$arg]}") done if [ -n "$RUNPHP_STANDALONE" ]; then args+=( -e "RUNPHP_STANDALONE=$RUNPHP_STANDALONE" -e "RUNPHP_PROJDIR=$PROJDIR" ) fi for host in "${HOST_MAPPINGS[@]}"; do args+=(--add-host "$host") done # monter le répertoire qui contient $PROJDIR mount_composer= mount_runphp=1 if [ -z "$PROJDIR" -o "${PROJDIR#$HOME/}" != "$PROJDIR" -o "$PROJDIR" == "$HOME" ]; then # bind mount $HOME args+=(-v "$HOME:$HOME${UseRslave:+:rslave}") [ -n "$RUNPHP_STANDALONE" ] && [ "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ] && mount_runphp= elif [ -n "$PROJDIR" ]; then # bind mount uniquement le répertoire du projet args+=(-v "$PROJDIR:$PROJDIR${UseRslave:+:rslave}") mount_composer=1 [ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_runphp= fi if [ -n "$mount_composer" -a -d "$HOME/.composer" ]; then # monter la configuration de composer args+=(-v "$HOME/.composer:$HOME/.composer") fi if [ -n "$RUNPHP_STANDALONE" -a -n "$mount_runphp" ]; then args+=(-v "$RUNPHP_STANDALONE:$RUNPHP_STANDALONE") fi args+=(-w "$(pwd)") # lancer avec l'utilisateur courant if [ $(id -u) -ne 0 ]; then # si c'est un utilisateur lambda, il faut monter les informations # nécessaires. composer est déjà monté via $HOME args+=( -e DEVUSER_USERENT="$(getent passwd "$(id -un)")" -e DEVUSER_GROUPENT="$(getent group "$(id -gn)")" ) fi args+=( "$Image" exec "$0" ${Chdir:+-w "$Chdir"} ) [ -n "$Verbose" ] && eecho "\$ docker ${args[*]} $*" ${Exec:+exec} docker "${args[@]}" "$@" } function container_exec() { # lancer la commande $@. cette fonction doit être lancée dans le container # docker if [ -n "$DEVUSER_USERENT" ]; then user="${DEVUSER_USERENT%%:*}" export DEVUSER_USERENT= export DEVUSER_GROUPENT= if in_path su-exec; then exec su-exec "$user" "$0" "$@" else exec runuser -u "$user" -- "$0" "$@" fi fi SOPTS=+w: LOPTS=chdir: args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" chdir= action= while [ $# -gt 0 ]; do case "$1" in --) shift; break;; -w|--chdir) shift; chdir="$1";; *) die "$1: option non configurée";; esac shift done if [ $# -eq 0 ]; then die "no command specified" elif [ "$1" == ci ]; then eecho "== installing composer dependencies" shift composer i "$@" elif [ "$1" == cu ]; then eecho "== upgrading composer dependencies" shift composer u "$@" elif [ "$1" == composer ]; then "$@" else if [ -n "$chdir" ]; then cd "$chdir" || exit 1 fi exec "$@" fi } ################################################################################ if [ "$RUNPHP_MODE" != docker ]; then # Lancement depuis l'extérieur du container host_parse_args "$@" host_init_env Image= if [ "$Bootstrap" == auto ]; then host_ensure_image host_check_image && Bootstrap= || Bootstrap=1 fi if [ -n "$Bootstrap" ]; then host_docker_build "${Cmd[@]}" || exit $? else host_ensure_image fi host_check_projdir if [ -n "$Composer" ]; then host_docker_run "$Composer" || exit $? fi if [ ${#Cmd[*]} -gt 0 ]; then Exec=1 host_docker_run "${Cmd[@]}" fi else # Lancement depuis l'intérieur du container container_exec "$@" fi