Et oui, je suis toujours vivant ! J’ai été pas mal absorbé par le boulot ces derniers temps … et j’ai du laisser ce blog un peu en friche. Cela dit, je pense que cela n’aura pas gêné grand monde !
Trêve de plaisanterie, revenons au sujet de ce billet.
Il y a quelques temps, sur la liste de diffusion MacEntreprise j’étais tombé sur une discussion où Chris George cherchait un outil capable de lancer plusieurs scripts au boot d’un Mac, sans avoir besoin de mettre en place pour chacun d’entre eux un LaunchDaemon.
C’est une très bonne question, … qui du coup m’a remis le doigt sur un souci potentiel. En effet, j’utilise sur le parc de Mac que je gère des scripts lancés au boot … sauf que pas fénaiantise, alors que j’aurais du me poser la même question que Chris, j’ai concaténé mes scripts les uns à la suite des autres pour n’avoir qu’un seul LaunchDaemon et un seul script à déployer. Je sais c’est mal … car si la fonction 2 de mon script crashe … toutes les fonctions qui suivent ne seront pas lancées.
Du coup, cette question m’a incité à revoir mon approche pour quelque chose d’un peu plus robuste, comme ce que cherchait à mettre en place Chris George dans cette discussion. Cela n’avait pas été demandé, mais j’ai cherché à disposer d’un outil qui soit à la fois utilisable sous Linux et sous OS X.
Plusieurs solutions lui avaient été proposées en réponse. Passons les en revue avant de voir celle que je vous propose.
ScriptRunner.sh de Bryan Pietrzak
Il s’agit là d’une version basique mais néanmoins très fonctionnelle. Ce script Bash liste les item du dossier /Library/User Login Scripts
et les lance un à un.
Cet outil propose de journaliser la sortie dans un fichier de log.
Côté points négatifs, cet outil, très simple, cherche à lancer tout ce qui se trouve dans le dossier donné, sans distinction. Par ailleurs, il ne sait pas faire de distinction entre des scripts devant être lancés de manière systématique et des scripts devant être lancés une seule fois.
#!/bin/bash # save this file at /Library/yourorg/ScriptRunner.sh script_path="${0}" source_path=$(dirname "${script_path}") chmod 777 "/var/log/yourorg-scriptrunner.log" if [ -d "${source_path}/User Login Scripts" ]; then cd "${source_path}/User Login Scripts" for item in *; do echo "### $(date) BEGIN ${item}" "./${item}" echo "### $(date) END ${item}" done fi chmod 777 "/var/log/yourorg-scriptrunner.log" exit 0
scriptRunner.py de Nate Walck
scriptRunner.py est un script Python de Nate Walck (il y a une version en Ruby pour ceux que cela amuse) qui est le plus proche de l’approche que j’aurais adoptée.
Cet outil sait travailler à la fois avec des scripts devant être lancés de manière systématique et des scripts ne devant être lancés une seule fois.
Par contre, revers de la médaille, il stocke les informations sur les scripts qui ont déjà été lancés dans des fichiers plist
grâce à la commande defaults read/write
ce qui exclu que cet outil soit facilement fonctionnel sous Linux.
#!/usr/bin/python # scriptRunner will run all scripts within a folder that you specify. # Useful for LaunchAgents and running multiple scripts at user login. # Thanks to Greg Neagle for an example of how to do this import optparse import os import subprocess import plistlib import datetime import sys import stat def main(): """Main""" p = optparse.OptionParser() p.set_usage("""Usage: %prog [options]""") p.add_option('--once', '-o', dest='runOnce', help="""Directory of scripts to run only once.""") p.add_option('--every', '-e', dest='runEvery', help="""Directory of scripts to run every time.""") options, arguments = p.parse_args() # Check to see if passed options are a directory or not for path in (options.runOnce, options.runEvery): if path is not None: if not os.path.isdir(path): sys.exit(path + " is not a directory") runOncePlist = os.path.expanduser("~/Library/Preferences/") +\ "com.company.scriptrunner.plist" try: runOncePlistValues = plistlib.readPlist(runOncePlist) except IOError: runOncePlistValues = {} if options.runEvery: for script in os.listdir(options.runEvery): st = os.stat(os.path.join(options.runEvery, script)) mode = st.st_mode if not mode & stat.S_IWOTH: try: subprocess.call(os.path.join(options.runEvery, script), stdin=None) except OSError: print "Something went wrong!" else: print script + " is not executable or has bad permissions" if options.runOnce: for script in os.listdir(options.runOnce): if script in runOncePlistValues: print os.path.join(options.runOnce, script) + " already run!" else: st = os.stat(os.path.join(options.runOnce, script)) mode = st.st_mode if not mode & stat.S_IWOTH: try: subprocess.call(os.path.join(options.runOnce, script), stdin=None) runOncePlistValues[script] = datetime.datetime.now() except OSError: print "Uh oh" else: print script + " is not executable or has bad permissions" plistlib.writePlist(runOncePlistValues, runOncePlist) if __name__ == '__main__': main()
outset de Joseph Chilcote
outset est un outil assez complet :
- il permet de lancer des scripts à “usage unique” et des scripts à lancer à chaque fois,
- il permet de lancer l’installation de packages au boot,
- il est prévu pour gérer de manière distincte des scripts qui doivent être lancés au boot et des scripts lancés à la connexion d’un utilisateur.
Pour cela les scripts et packages doivent être déposés dans les dossiers suivants :
- pour ce qui sera lancé au boot :
/usr/local/outset/firstboot-packages
/usr/local/outset/firstboot-scripts
/usr/local/outset/everyboot-scripts
- pour ce qui sera lancé lors de la connexion d’un utilisateur :
/usr/local/outset/login-every
/usr/local/outset/login-once
Les commandes associées sont :
sudo ./outset --boot
./outset --login
L’auteur fournit également les fichiers plist
pour créer les entrées LaunchDaemon et LaunchAgent et quelques explications pour ceux qui souhaiteraient utiliser cet outil avec AutoDMG.
Enfin, cet outil génère des logs :
/var/log/outset.log
pour les commandes lancées au boot,~/Library/Logs/outset.log
pour les commandes lancées à la connexion d’un utilisateur.
Pour découvrir cet outil, rendez-vous sur GitHub.
Ma proposition : scriptRunner.sh
J’ai écrit ce petit outil rapidement en tentant d’en faire un outil à la fois utilisable en environnement Mac OS X ou Linux. Disons que c’est un bon exercice pour s’entrainer en Bash !
Pour cela, j’utilise seulement un dossier tampon dans lequel je stocke les scripts destinés à n’être lancés qu’une seule fois après leur lancement. Cela permet aussi de forcer si besoin leur exécution à nouveau.
Vous pouvez télécharger le script sur GitHub.
Le script supporte les options suivantes :
-h
permet d’afficher l’aide.
-o <répertoire des scripts à lancer une fois>
permet de lancer les scripts à n’exécuter qu’une fois. Spécifiez le répertoire contenant les scripts en argument (par exemple : -o /usr/local/bin/bootScriptsOnce
) ou utilisez -o default
pour utiliser le répertoire par défaut nommé runOnce
et situé au même emplacement que scriptRunner.sh
.
-f
est un paramètre qui doit obligatoirement être utilisé en complément de -o
et qui permet de forcer le lancement de tous les scripts à ne lancer qu’une fois, y compris ceux qui ont été précédemment lancés.
-e <répertoire des scripts à lancer à chaque fois>
permet de lancer tous les scripts à exécuter périodiquement. Spécifiez le répertoire contenant les scripts en argument (par exemple : -e /usr/local/bin/bootScriptsEvery
) ou utilisez -e default
pour utiliser le répertoire par défaut nommé runEvery
et situé au même emplacement que scriptRunner.sh
.
-l
désactive la sortie standard au profit de l’écriture dans un fichier journal. Spécifiez en argument le chemin complet du fichier de log ou utilisez -l default
pour utiliser le fichier journal standard de scriptRunner.sh
, à savoir /var/log/scriptRunner.log
.
Pour une aide complète sur les options supportées, installez le script et lancez l’aide :
./scriptRunner.sh help
Bug report
Si vous voulez me faire remonter un bug : ouvrir un bug.
Installation
Pour installer cet outil, depuis votre terminal :
git clone https://github.com/yvangodard/scriptRunner.git ; sudo chmod -R 750 scriptRunner
Et voici le code en prime :
#! /bin/bash #-------------------------------------# # scriptRunner # #-------------------------------------# # # # scriptRunner will run all # # scripts within a folder that # # you specify # # # # Yvan Godard # # godardyvan@gmail.com # # # # Version 0.1 -- august, 8 2014 # # Tool licenced under # # Creative Commons 4.0 BY NC SA # # # # http://goo.gl/cBqSdY # # # #-------------------------------------# # Variables initialisation VERSION="scriptRunner v 0.1 by Yvan Godard - 2014 - http://goo.gl/cBqSdY" help="no" SCRIPT_DIR=$(dirname $0) SCRIPT_NAME=$(basename $0) LOG_FILE="/var/log/scriptRunner.log" LOG_ACTIVE=0 DIR_RUN_ONCE=${SCRIPT_DIR}/runOnce DIR_RUN_EVERY=${SCRIPT_DIR}/runEvery RUN_ONCE=0 RUN_EVERY=0 NUMBER_RUN_ONCE=0 NUMBER_RUN_EVERY=0 FORCE_ONCE=0 help () { echo -e "$VERSION\n" echo -e "This tool is designed to run all scripts within a folder that you specify." echo -e "It's useful for LaunchAgents and running multiple scripts at user login." echo -e "It works both with scripts to be executed each time and with those that may only be executed once." echo -e "This tool is licensed under the Creative Commons 4.0 BY NC SA licence." echo -e "\nDisclamer:" echo -e "This tool is provide without any support and guarantee." echo -e "\nSynopsis:" echo -e "./scriptRunner.sh [-h] | -o <directory once> -e <directory every time>" echo -e " [-l <log file>] [-f]" echo -e "\n -h: Prints this help then exit" echo -e "\nMandatory option (one or both of the following):" echo -e " -o <directory once>: The directory of scripts to run only one time." echo -e " Could be 'default' for '${DIR_RUN_ONCE}'" echo -e " -e <directory every time>: The directory of scripts to run each time." echo -e " Could be 'default' for '${DIR_RUN_EVERY}'" echo -e "\nOptional options:" echo -e " -l <log file>: Enables logging instead of standard output." echo -e " Specify an argument for the full path to the log file" echo -e " (e.g.: '${LOG_FILE}') or use 'default' (${LOG_FILE})" echo -e " -f This option forces to run any scripts in the directory" echo -e " containing scripts to run only once." echo -e " This option must be use with option '-o <directory once>'" exit 0 } error () { echo -e "\n*** Error ***" echo -e ${1} echo -e "\n"${VERSION} alldone 1 } alldone () { if [[ ${LOG_ACTIVE} = "1" ]] then exec 1>&6 6>&- fi exit ${1} } optsCount=0 while getopts "he:o:l:f" OPTION do case "$OPTION" in h) help="yes" ;; e) [ $OPTARG != "default" ] && DIR_RUN_EVERY=${OPTARG} RUN_EVERY=1 let optsCount=$optsCount+1 ;; o) [ $OPTARG != "default" ] && DIR_RUN_ONCE=${OPTARG} RUN_ONCE=1 let optsCount=$optsCount+1 ;; l) [ $OPTARG != "default" ] && LOG_FILE=${OPTARG} LOG_ACTIVE=1 ;; f) FORCE_ONCE=1 ;; esac done if [[ ! ${optsCount} -gt "0" ]] then help alldone 1 fi if [[ ${help} = "yes" ]] then help fi if [[ ${LOG_ACTIVE} = "1" ]] then if [[ ! -e "${LOG_FILE}" ]] then [[ ! -e $(dirname ${LOG_FILE}) ]] && mkdir -p $(dirname ${LOG_FILE}) touch ${LOG_FILE} [[ $? -ne 0 ]] && echo "Trying to use '-l' option but '${LOG_FILE} seems to be incorrect. Process will continue without writing log file." && LOG_ACTIVE=0 fi fi if [[ ${LOG_ACTIVE} = "1" ]] then # Redirect standard outpout to temp file exec 6>&1 exec >> ${LOG_FILE} fi # Start echo -e "\n****************************** `date` ******************************\n" echo -e "$0 started with options:" [[ ${RUN_ONCE} = "1" ]] && echo -e "\t-o ${DIR_RUN_ONCE}" [[ ${RUN_EVERY} = "1" ]] && echo -e "\t-e ${DIR_RUN_EVERY}" [[ ${FORCE_ONCE} = "1" ]] && echo -e "\t-f" [[ ${LOG_ACTIVE} = "1" ]] && echo -e "\t-l ${LOG_FILE}" # Preparing directory for sripts runned one time DIR_RUN_ONCE_RUNNED=${DIR_RUN_ONCE}AlreadyRunned [[ ! -d ${DIR_RUN_ONCE_RUNNED} ]] && mkdir -p ${DIR_RUN_ONCE_RUNNED} if [[ ${FORCE_ONCE} = "1" ]] then echo -e "\n#################################\nRESET SCRIPTS ALREADY RUNNED ONCE\n#################################" OLDIFS=$IFS IFS=$'\n' for FILE in $(find ${DIR_RUN_ONCE_RUNNED} -type f -maxdepth 1) do echo -e "\n### Moving '${FILE}' to '${DIR_RUN_ONCE}/'" mv "${FILE}" ${DIR_RUN_ONCE}/ [[ $? -ne 0 ]] && echo ">>> Problem when moving '${FILE}' to '${DIR_RUN_ONCE}/'" done IFS=$OLDIFS fi if [[ ${RUN_ONCE} = "1" ]] then echo -e "\n#########################\nSTARTING ONCE RUN SCRIPTS\n#########################" # Test if directory exists [[ ! -d ${DIR_RUN_ONCE} ]] && echo "Trying to use '-o' option but '{DIR_RUN_ONCE}' seems to be incorrect. Interruption of this part of the process." && RUN_ONCE=0 fi if [[ ${RUN_ONCE} = "1" ]] then # Test if directory contain one script NUMBER_RUN_ONCE=$(find ${DIR_RUN_ONCE} -type f -maxdepth 1 | wc -l) if [[ ${NUMBER_RUN_ONCE} -eq "0" ]] then echo "No script in directory '${DIR_RUN_ONCE}'. Interruption of this part of the process." elif [[ ${NUMBER_RUN_ONCE} -gt "0" ]] then # List all scripts in directory OLDIFS=$IFS IFS=$'\n' for FILE in $(find ${DIR_RUN_ONCE} -type f -maxdepth 1) do echo -e "\n### $(date) ### BEGIN ${FILE}" if [[ -x ${FILE} ]] then "${FILE}" if [[ $? -ne 0 ]] then echo ">>> Problem when trying to launch '${FILE}'" else mv "${FILE}" ${DIR_RUN_ONCE_RUNNED}/ [[ $? -ne 0 ]] && echo ">>> Problem when moving '${FILE}' to the directory of scripts already launched (${DIR_RUN_ONCE_RUNNED})." fi elif [[ ! -x ${FILE} ]] then echo -e ">>> Problem when trying to launch '${FILE}': is not executable or has bad permissions." fi echo "### $(date) ### END ${FILE}" done IFS=$OLDIFS fi fi if [[ ${RUN_EVERY} = "1" ]] then echo -e "\n###########################\nSTARTING EVERY TIME SCRIPTS\n###########################" # Test if directory exists [[ ! -d ${DIR_RUN_EVERY} ]] && echo "Trying to use '-e' option but '{DIR_RUN_EVERY}' seems to be incorrect. Interruption of this part of the process." && RUN_EVERY=0 fi if [[ ${RUN_EVERY} = "1" ]] then # Test if directory contain one script NUMBER_RUN_EVERY=$(find ${DIR_RUN_EVERY} -type f -maxdepth 1 | wc -l) if [[ ${NUMBER_RUN_EVERY} -eq "0" ]] then echo "No script in directory '${DIR_RUN_EVERY}'. Interruption of this part of the process." elif [[ ${NUMBER_RUN_EVERY} -gt "0" ]] then # List all scripts in directory OLDIFS=$IFS IFS=$'\n' for FILE in $(find ${DIR_RUN_EVERY} -type f -maxdepth 1) do echo -e "\n### $(date) ### BEGIN ${FILE}" if [[ -x ${FILE} ]] then "${FILE}" [[ $? -ne 0 ]] && echo ">>> Problem when trying to launch '${FILE}'" elif [[ ! -x ${FILE} ]] then echo -e ">>> Problem when trying to launch '${FILE}': is not executable or has bad permissions." fi echo "### $(date) ### END ${FILE}" done IFS=$OLDIFS fi fi echo -e "\n********************************* ${SCRIPT_NAME} finished *********************************" alldone 0
Evidemment, si vous avez mieux en magasin que cet outil, n’hésitez pas à partager !