diff --git a/php/src/app/cli/include-launcher.php b/php/src/app/cli/include-launcher.php new file mode 100644 index 0000000..0958cb7 --- /dev/null +++ b/php/src/app/cli/include-launcher.php @@ -0,0 +1,29 @@ + $name, + ]); + require $app; +} diff --git a/php/src/tools/BgLauncherApp.php b/php/src/tools/BgLauncherApp.php new file mode 100644 index 0000000..1e41b79 --- /dev/null +++ b/php/src/tools/BgLauncherApp.php @@ -0,0 +1,124 @@ + "lancer un script en tâche de fond", + "usage" => "ApplicationClass args...", + + "sections" => [ + parent::VERBOSITY_SECTION, + ], + + ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, + "help" => "Afficher des informations sur la tâche", + ], + ["-s", "--start", "name" => "action", "value" => self::ACTION_START, + "help" => "Démarrer la tâche", + ], + ["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP, + "help" => "Arrêter la tâche", + ], + ]; + + protected int $action = self::ACTION_START; + + protected ?array $args = null; + + static function show_infos(RunFile $runfile, ?int $level=null): void { + msg::print($runfile->getDesc(), $level); + msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1); + } + + function main() { + $args = $this->args; + + $appClass = $args[0] ?? null; + if ($appClass === null) { + self::die("Vous devez spécifier la classe de l'application"); + } + $appClass = $args[0] = str_replace("/", "\\", $appClass); + if (!class_exists($appClass)) { + self::die("$appClass: classe non trouvée"); + } + + $useRunfile = constant("$appClass::USE_RUNFILE"); + if (!$useRunfile) { + self::die("Cette application ne supporte le lancement en tâche de fond"); + } + + $runfile = app::with($appClass)->getRunfile(); + switch ($this->action) { + case self::ACTION_START: + $argc = count($args); + $appClass::_manage_runfile($argc, $args, $runfile); + if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED); + array_splice($args, 0, 0, [ + PHP_BINARY, + path::abspath(NULIB_APP_app_launcher), + ]); + app::params_putenv(); + self::_start($args, $runfile); + break; + case self::ACTION_STOP: + self::_stop($runfile); + self::show_infos($runfile, -1); + break; + case self::ACTION_INFOS: + self::show_infos($runfile); + break; + } + } + + public static function _start(array $args, Runfile $runfile): void { + $pid = pcntl_fork(); + if ($pid == -1) { + # parent, impossible de forker + throw new ExitError(app::EC_FORK_PARENT, "Unable to fork"); + } elseif (!$pid) { + # child, fork ok + $runfile->wfPrepare($pid); + $outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out"; + $exitcode = app::EC_FORK_CHILD; + try { + # rediriger STDIN, STDOUT et STDERR + fclose(fopen($outfile, "wb")); // vider le fichier + fclose(STDIN); $in = fopen("/dev/null", "rb"); + fclose(STDOUT); $out = fopen($outfile, "ab"); + fclose(STDERR); $err = fopen($outfile, "ab"); + # puis lancer la commande + $cmd = new Cmd($args); + $cmd->addSource("/g/init.env"); + $cmd->addRedir("both", $outfile, true); + $cmd->fork_exec($exitcode, false); + sh::_waitpid(-$pid, $exitcode); + } finally { + $runfile->wfReaped($exitcode); + } + } + } + + public static function _stop(Runfile $runfile): bool { + $data = $runfile->read(); + $pid = $runfile->_getCid($data); + msg::action("stop $pid"); + if ($runfile->wfKill($reason)) { + msg::asuccess(); + return true; + } else { + msg::afailure($reason); + return false; + } + } +} diff --git a/php/src/tools/SteamTrainApp.php b/php/src/tools/SteamTrainApp.php new file mode 100644 index 0000000..e4c5d44 --- /dev/null +++ b/php/src/tools/SteamTrainApp.php @@ -0,0 +1,53 @@ + self::TITLE, + "description" => << 1, + "help" => "spécifier le nombre d'étapes", + ], + ["-f", "--force-enabled", "value" => true, + "help" => "lancer la commande même si les tâches planifiées sont désactivées", + ], + ["-n", "--no-install-signal-handler", "value" => false, + "help" => "ne pas installer le gestionnaire de signaux", + ], + ]; + + protected $count = 100; + + protected bool $forceEnabled = false; + + protected bool $installSignalHandler = true; + + function main() { + app::check_bgapplication_enabled($this->forceEnabled); + if ($this->installSignalHandler) app::install_signal_handler(); + $count = intval($this->count); + msg::info("Starting train for ".words::q($count, "step#s")); + app::action("Running train...", $count); + for ($i = 1; $i <= $count; $i++) { + msg::print("Tchou-tchou! x $i"); + app::step(); + sleep(1); + } + msg::info("Stopping train at ".new DateTime()); + } +} diff --git a/php/support/copy-templates b/php/support/copy-templates new file mode 100755 index 0000000..7e2e118 --- /dev/null +++ b/php/support/copy-templates @@ -0,0 +1,39 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source /etc/nulib.sh || exit 1 + +declare -A DESTDIRS=( + [template-_bg_launcher.php]=sbin + [template-.launcher.php]=_cli + [template-_wrapper.sh]=_cli +) +declare -A MODES=( + [template-_bg_launcher.php]=+x + [template-.launcher.php]= + [template-_wrapper.sh]=+x +) + +projdir= +args=( + "copier les templates dans le projet en cours" + #"usage" + -d:,--projdir: . +) +parse_args "$@"; set -- "${args[@]}" + +if [ -n "$projdir" ]; then + cd "$projdir" || die +fi + +[ -f composer.json ] || die "$(basename "$(dirname "$(pwd)")"): n'est pas un projet composer" + +setx -a templates=ls_files "$MYDIR" "template-*" +for template in "${templates[@]}"; do + destdir="${DESTDIRS[$template]}" + [ -n "$destdir" ] || die "$template: la destination n'est pas configurée" + mode="${MODES[$template]}" + destname="${template#template-}" + + tail -n+4 "$MYDIR/$template" >"$destdir/$destname" + [ -n "$mode" ] && chmod "$mode" "$destdir/$destname" +done diff --git a/php/support/template-.launcher.php b/php/support/template-.launcher.php new file mode 100644 index 0000000..eeb2b41 --- /dev/null +++ b/php/support/template-.launcher.php @@ -0,0 +1,12 @@ +# TODO Faire une copie de ce script dans un répertoire de l'application web +# (dans le répertoire _cli/ par défaut) et modifier les paramètres si nécessaire +#------------------------------------------------------------------------------- + __DIR__ . '/..', + "appcode" => \app\config\bootstrap::APPCODE, +]; +require __DIR__.'/../vendor/nulib/php/php/src/app/cli/include-launcher.php'; diff --git a/php/support/template-_bg_launcher.php b/php/support/template-_bg_launcher.php new file mode 100644 index 0000000..88147f5 --- /dev/null +++ b/php/support/template-_bg_launcher.php @@ -0,0 +1,18 @@ +# TODO Faire une copie de ce script dans un répertoire de l'application web +# (dans le répertoire sbin/ par défaut) et modifier les paramètres si nécessaire +#------------------------------------------------------------------------------- + __DIR__.'/..', + "appcode" => \app\config\bootstrap::APPCODE, +]); +BgLauncherApp::run(); diff --git a/php/support/template-_wrapper.sh b/php/support/template-_wrapper.sh new file mode 100644 index 0000000..22c34f6 --- /dev/null +++ b/php/support/template-_wrapper.sh @@ -0,0 +1,132 @@ +# TODO Faire une copie de ce script dans un répertoire de l'application web +# (dans le répertoire _cli/ par défaut) et modifier les paramétres si nécessaire +#------------------------------------------------------------------------------- +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# S'assurer que le script PHP est lancé avec l'utilisateur www-data +# Tous les chemins suivants sont relatifs au répertoire qui contient ce script + +# Chemin relatif de la racine du projet +PROJPATH=.. + +# Chemin relatif vers le lanceur PHP +LAUNCHERPATH=.launcher.php + +# Chemin relatif des scripts PHP wrappés +WRAPPEDPATH= + +# Nom du service dans docker-compose.yml utilisé pour lancer les commandes +COMPOSE_SERVICE=web + +############################################################################### + +case "${RUNPHP_MODE:-auto}" in +auto) + RUNPHP_MODE= + [ -f /.dockerenv ] && RUNPHP_MODE=docker + [ -z "$RUNPHP_MODE" ] && + [ -f /proc/self/mountinfo ] && + grep -q ' /docker/' /proc/self/mountinfo && + RUNPHP_MODE=docker + [ -z "$RUNPHP_MODE" ] && + [ -f /proc/1/cgroup ] && + grep -q ':/docker/' /proc/1/cgroup && + RUNPHP_MODE=docker + [ -n "$RUNPHP_MODE" ] || RUNPHP_MODE=host + ;; +docker) RUNPHP_MODE=docker;; +host) RUNPHP_MODE=host;; +direct) RUNPHP_MODE=direct;; +*) RUNPHP_MODE=host;; +esac + +MYDIR="$(dirname -- "$0")" +MYNAME="$(basename -- "$0")" +if [ ! -L "$0" ]; then + echo "\ +$0 +Ce script doit être lancé en tant que lien symbolique avec un nom de la forme +'monscript.php' et lance le script PHP du même nom situé dans le même répertoire +avec l'utilisateur www-data" + + if [ "$RUNPHP_MODE" == host -o "$RUNPHP_MODE" == direct ]; then + echo "\ +---------------------------------------- +Vérification des liens..." + cd "$MYDIR" + for i in *.php*; do + [ -f "$i" ] || continue + name="bin/${i%.*}.php" + dest="../_cli/_wrapper.sh" + link="../bin/${i%.*}.php" + if [ -L "$link" ]; then + echo "* $name OK" + elif [ -e "$link" ]; then + echo "* $name KO (not a link)" + else + echo "* $name NEW" + ln -s "$dest" "$link" || exit 1 + fi + done + fi + exit 0 +fi + +MYTRUESELF="$(readlink -f "$0")" +MYTRUEDIR="$(dirname -- "$MYTRUESELF")" +PROJDIR="$(cd "$MYTRUEDIR${PROJPATH:+/$PROJPATH}"; pwd)" + +if [ "$RUNPHP_MODE" == host ]; then + args=( + docker compose run + ${RUNPHP_BUILD:+--build} + --rm + ) + cwd="$(pwd)" + mounted= + if [ "$PROJDIR" == "$HOME" -o "${PROJDIR#$HOME/}" != "$PROJDIR" ]; then + # monter HOME + args+=(-v "$HOME:$HOME") + [ "${cwd#$HOME/}" != "$cwd" ] && mounted=1 + else + # monter uniquement le répertoire du projet + args+=(-v "$PROJDIR:$PROJDIR") + [ "${cwd#$PROJDIR/}" != "$cwd" ] && mounted=1 + fi + if [ -z "$mounted" ]; then + echo "Impossible de mapper le répertoire courant avec les montages du container" + exit 1 + fi + args+=( + --workdir "$cwd" + "$COMPOSE_SERVICE" + exec "$MYNAME" + "$@" + ) + cd "$PROJDIR" + exec "${args[@]}" +fi + +launcher="$MYTRUEDIR/$LAUNCHERPATH" +class="$MYTRUEDIR${WRAPPEDPATH:+/$WRAPPEDPATH}/${MYNAME%.php}.phpc" +script="$MYTRUEDIR${WRAPPEDPATH:+/$WRAPPEDPATH}/${MYNAME%.php}.php" + +[ -f /g/init.env ] && source /g/init.env + +www_data="${DEVUSER_USERENT%%:*}" +[ -n "$www_data" ] || www_data=www-data + +cmd=(php "$launcher") +[ -n "$MEMPROF_PROFILE" ] && cmd+=(-dextension=memprof.so) +if [ -f "$class" ]; then + cmd+=("$(<"$class")") +else + cmd+=("$script") +fi +cmd+=("$@") + +if [ "$(id -u)" -eq 0 ]; then + su-exec "$www_data" "${cmd[@]}" +else + exec "${cmd[@]}" +fi