Skip to content

Instantly share code, notes, and snippets.

@comete-upn
Last active September 28, 2019 06:46
Show Gist options
  • Save comete-upn/f0c8e64d44dc05eba0a4 to your computer and use it in GitHub Desktop.
Save comete-upn/f0c8e64d44dc05eba0a4 to your computer and use it in GitHub Desktop.
Simple utilitaire bash pour mise à jour de Moodle.
#!/bin/bash
##
# Interactive Moodle update script. This script works only if you use git as
# source for Moodle core files and modules.
#
# The script doesn't write to a log file. For best results execute it with tee :
#
# moodleup 2>&1 | tee moodleup.log
#
# This way you can see the results on the console and written in a log file.
#
# @package local_commit
# @copyright Université Paris Ouest Nanterre <[email protected]>
# @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
##
# get terminal columns used in Echo for pretty output
COLS="${COLUMNS:-$(tput cols)}"
PHP=$(which php)
##
# Pretty output
#
# Print text with applied style. List of styles :
# - header
# - ask
# - run
# - info
# - warn
#
# @param style string the style to apply
# @param text string the text to output
# @example
Echo() {
case "$1" in
"header")
# line of '-'
printf '%*s\n' "$COLS" '' | tr ' ' -
# centered text
printf "%*s\n" $(( (${#2} + $COLS) / 2)) "$2"
# line of '-'
printf '%*s\n' "$COLS" '' | tr ' ' -
;;
"ask")
printf "[?] %s" "$2" | fmt -s -w $COLS
;;
"run")
printf "[>] %s\n" "$2" | fmt -s -w $COLS
;;
"info")
printf "[*] %s\n" "$2" | fmt -s -w $COLS
;;
"warn")
printf "[!] %s\n" "$2" | fmt -s -w $COLS
;;
*)
echo -e "$2" | fmt -s -w $COLS
;;
esac
}
##
# General purpose function to prompt user for oui/non.
#
# Takes the question as argument. Returns 0 if O/o read from input
# and 1 otherwise.
##
Confirm() {
# print the question and read only 1 character
read -p "$(Echo ask "$1 [o/n]:") " -n 1
if [[ $REPLY =~ ^[Oo]$ ]]; then
echo " [Oui]"
return 0
else
echo " [Non]"
return 1
fi
}
##
# Get value from php variable and print it.
#
# Don't include the leading $ in tha variable name you search for.
# @param string the variable name to search for (don't put leading $)
# @param string the file to search
# @return string the value of the variable if found
# @example value=$(GetPhpValue CFG->dbname config.php)
##
GetPhpValue() {
echo $(sed -nre 's/.*\$'"$1"' *= ['\''"]?([^'\'']*)['\''"]? ?;.*/\1/p' "$2")
}
##
# Checks if git branch has new commits and they "can be pulled".
#
# This function return 0 if new commits are available and the local
# branch is not ahead or diverged.
#
# @param string folder
# @return integer returns 0 if updates are available and on common merge base
# @credit http://stackoverflow.com/a/3278427
##
Checkup() {
local cpwd hasupstream glocal gremote gbase
# save current path
cpwd=$(pwd -P)
# enter folder to check for updates
cd $1
git fetch
# git status --untracked-files=no
# print header lines for information
git status -uno | head -n 2
glocal=$(git rev-parse @{0})
gremote=$(git rev-parse @{u})
# check if there is upstream branch configured
if [ $? -ne 0 ]; then
hasupstream=1
else
hasupstream=0
# get last common commit (merge base)
gbase=$(git merge-base @ @{u})
fi
# return to current path
cd $cpwd
if [ $hasupstream -eq 1 ]; then
Echo info "Aucune branche amont(upstream) configurée. Le dépôt ne peut pas être mis à jour."
return 1
elif [ $glocal = $gremote ]; then
Echo info "Le dépôt est à jour."
return 1
elif [ $glocal = $gbase ]; then
Echo info "Le dépôt peut être mis à jour."
return 0
elif [ $gremote = $gbase ]; then
Echo info "La branche locale est en avance de la branche d'origine. Le dépôt ne peut pas être mis à jour."
return 1
else
Echo info "La branche locale divérge de la branche d'origine. Le dépôt ne peut pas être mis à jour."
return 1
fi
}
##
# Update local git repository. This is done in the following steps :
# 1. Check for modified files
# 2. Create patches
# 3. Checkout original files
# 4. Pull changes.
#
# @param string folder
##
Update() {
local path cpwd filestopatch file
for path in $1; do
# save current path
cpwd=$(pwd -P)
# enter folder to update
cd $path
filestopatch=$(git diff --name-only)
if [ -n "$filestopatch" ]; then
# add repository for patching later
modpatch+="$path "
for file in $filestopatch; do
Echo info "Création du fichier correctif ${file}.moodleup.patch."
git diff "$file" > "${file}.moodleup.patch"
git checkout -- "$file"
done
fi
# update
git pull
# return to current path
cd $cpwd
done
}
##
# Apply patches created by moodleup in the repository given as argument.
#
# @param string folder
##
Patch() {
local path cpwd file
for path in $1; do
# save current path
cpwd=$(pwd -P)
# enter folder to update
cd $path
for file in $(find -type f -name *.moodleup.patch); do
# check if patch applies without errors
git apply --check "$file"
if [ $? -eq 0 ]; then
# apply the patch
git apply "$file"
Echo info "Fichier correctif $file appliqué avec succès."
# delete the patch file
rm "$file"
else
Echo warn "Erreur lors de l'application du fichier correctif $file."
mv "$file" "${file/moodleup.patch/moodleup.failed.patch}"
fi
done
# return to current path
cd $cpwd
done
}
##
# Calculate elapsed time and display it.
#
# @param integer start time in seconds
# @param integer end time in seconds, if empty now will be taken
##
TimeElapsed() {
local end elapsed
if [ -n "$2" ]; then
end="$2"
else
end=$(date +%s)
fi
elapsed=$(($end-$1))
echo "$(($elapsed/60))min $(($elapsed%60))sec"
}
##
# Script starts here
##
Echo header "Script interactif de mise à jour de Moodle"
cat <<EOF
Ce script se déroule en trois étapes:
1. Vérification
2. Configuration des tâches à exécuter
3. Exécution de la mise à jour
Lors de la mise à jour des fichiers si vous avez apporté des modifications
locales des fichiers de correctifs seront crées et appliqués après la mise à
jour.
Votre confirmation sera demandé avant l'exécution.
Appuyez sur une touche pour commencer.
EOF
read
#
# Config
#
read -e -p "$(Echo ask "Chemin relatif ou absolu vers le dossier racine Moodle:") " MOODLE_ROOT
cd "$MOODLE_ROOT" > /dev/null 2>&1
if [ $? -eq 1 ]; then
Echo warn "Le chemin $MOODLE_ROOT n'existe pas."
exit 1
else
MOODLE_ROOT=$(pwd -P)
if [ -f "$MOODLE_ROOT/config.php" ]; then
Echo info "Je trouve config.php, à priori c'est un dossier racine Moodle."
else
Echo warn "Je ne trouve pas config.php, ce n'est pas un dossier racine Moodle."
exit 1
fi
fi
if [ ! -d "$MOODLE_ROOT/.git" ]; then
Echo warn "Ce script fonctionne que si Moodle est installé à partir de git."
exit 1
fi
Confirm "Le chemin vers votre Moodle est bien $MOODLE_ROOT?"
# If yes continue, exit otherwise
[ $? -eq 0 ] || exit 0
# From here on we are in Moodle root folder
cd $MOODLE_ROOT
dbname=$(GetPhpValue "CFG->dbname" config.php)
dbhost=$(GetPhpValue "CFG->dbhost" config.php)
dbuser=$(GetPhpValue "CFG->dbuser" config.php)
dbpass=$(GetPhpValue "CFG->dbpass" config.php)
version=$(GetPhpValue "version" version.php)
release=$(GetPhpValue "release" version.php)
lastcron=$(mysql -h "$dbhost" -u "$dbuser" -p"$dbpass" -BNe "SELECT max(lastcron) FROM modules" $dbname)
lastcron=$(date -d @$lastcron +"%Y-%m-%d %H:%M")
Echo header "Informations Moodle"
echo "Version : $version"
echo "Release : $release"
echo "Dossier : $MOODLE_ROOT"
echo "DB host : $dbhost"
echo "DB name : $dbname"
echo "DB user : $dbuser"
echo "Cron : $lastcron"
Echo header "Vérification des mises à jour noyau."
Checkup $MOODLE_ROOT
if [ $? -eq 0 ]; then
corepull=0
# Check for modified files
filestopatch=$(git diff --name-only)
if [ -n "$filestopatch" ]; then
corepatch=0
Echo info "Des fichiers ont été modifiés, voici la liste : "
echo $filestopatch
else
corepatch=1
fi
else
corepull=1
# if no updates for core ask to continue with module updates
Confirm "Continuer le script pour mettre à jour les modules complémentaires ?"
[ $? -eq 0 ] || exit 0
fi
# store modules that need updating
modpull=''
# store modules that need patching ( see Update function)
modpatch=''
Echo header "Mise à jour des modules complémentaires installés à partir de git."
# find all .git folders
for dir in $(find * -type d -name *.git); do
# remove the trailing .git
dir=${dir%.git}
Echo info "Vérification des mises à jour pour $dir."
Checkup $dir
if [ $? -eq 0 ]; then
# Ask user only if updates are availble
Confirm "Mettre à jour $dir?"
if [ $? -eq 0 ]; then
modpull+="$dir "
fi
fi
done
Echo header "Sauvegarder votre Moodle"
Confirm "Sauvegarder le dossier Moodle?"
backupfiles=$?
if [ $backupfiles -eq 0 ]; then
read -e -p "$(Echo ask "Dossier de sauvegarde:") " -i "${MOODLE_ROOT}_upgrade" backupdir
if [ "$MOODLE_ROOT" = "$backupdir" ]; then
Echo warn "Le dossier de sauvegarde ne peux pas être le même que votre dossier actuel de Moodle."
exit 1
fi
if [ -d "$backupdir" ]; then
Confirm "Le dossier de sauvegarde existe. Supprimer le dossier et sauvegarder dessus ?"
backupdirdelete=$?
if [ $backupdirdelete -eq 1 ]; then
Echo warn "Supprimmer le dossier de sauvegarde manuellement ou choisissez un autre dossier de sauvegarde."
exit 1
fi
else
backupdirdelete=1
fi
fi
Confirm "Sauvegarder la base de données ?"
backupdb=$?
if [ $backupdb -eq 0 ]; then
read -e -p "$(Echo ask "Hôte de la base de données de sauvegarde:") " -i "$dbhost" backupdbhost
read -e -p "$(Echo ask "Nom de la base de données de sauvegarde:") " -i "${dbname}_upgrade" backupdbname
if [ "$dbname" = "$backupdbname" ]; then
Echo warn "La base de données de sauvegarde ne peux pas être la même que votre base actuelle de Moodle."
exit 1
fi
read -e -p "$(Echo ask "Utilisateur (avec privilèges d'administrateur):") " -i "$dbuser" backupdbuser
read -e -s -p "$(Echo ask "Mot de passe:") " backupdbpass
echo
# check if database exists
r=$(mysql -h $backupdbhost -u $backupdbuser -p$backupdbpass -BNe "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$backupdbname'")
# if there is an error connecting it'll be printed
# so on error we can just exit here
[ $? -eq 0 ] || exit 1
if [ -n "$r" ]; then
Confirm "Base de données $backupdbname existe. Recréer la base et et sauvegarder dessus?"
backupdbdelete=$?
if [ $backupdbdelete -eq 1 ]; then
Echo warn "Supprimmer la base de sauvegarde manuellement ou choisissez une autre base de sauvegarde."
exit 1
fi
else
backupdbdelete=1
fi
Confirm "Sauvegarder le contenu de la table de log (logstore_standard_log)?"
backupdblog=$?
fi
Echo header "Tâches supplémentaires"
Confirm "Activer le mode maintenance?"
maintenance=$?
if [ $maintenance -eq 0 ]; then
Echo info "Verifier le message qui sera affiche en mode maintenance : /admin/settings.php?section=maintenancemode ."
fi
Confirm "Purger les caches après la mise à jour ?"
purgecache=$?
Confirm "Executer le cron après la mise à jour ? "
runcron=$?
##
# Summary of actions to take
##
Echo header "Récapitulatif des tâches à exécuter"
if [ $maintenance -eq 0 ]; then
echo "Activer le mode maintenance."
fi
if [ $backupfiles -eq 0 ]; then
if [ $backupdirdelete -eq 0 ]; then
echo "Suppression du dossier de sauvegarde $backupdir."
fi
echo "Sauvegarde du dossier Moodle dans le dossier $backupdir."
fi
if [ $backupdb -eq 0 ]; then
if [ $backupdbdelete -eq 0 ]; then
echo "Vider la base de données $backupdbname."
fi
echo "Sauvegarde de la base de données $dbname dans la base $backupdbname."
if [ $backupdblog -eq 1 ]; then
echo "La table log (logstore_standard_log) ne sera pas sauvegardé."
fi
fi
if [ $corepull -eq 0 ]; then
echo "Mise à jour des fichiers noyau."
fi
if [ -n "$modpull" ]; then
echo "Mise à jour des fichiers des modules complémentaires:"
echo "$modpull" | tr " " "\n"
fi
if [ $purgecache -eq 0 ]; then
echo "Vider les caches."
fi
if [ $runcron -eq 0 ]; then
echo "Exécuter le cron."
fi
if [ $maintenance -eq 0 ]; then
echo "Désactiver le mode maintenance."
fi
##
# Run
##
Confirm "Exécuter les tâches de mise à jour?"
[ $? -eq 0 ] || exit 0
updatestart=$(date +%s)
Echo header "Lancement de la mise à jour"
if [ $maintenance -eq 0 ]; then
Echo info "Activation du mode maintenance."
sudo -u www-data $PHP $MOODLE_ROOT/admin/cli/maintenance.php --enable
fi
if [ $backupfiles -eq 0 ]; then
taskstart=$(date +%s)
if [ $backupdirdelete -eq 0 ]; then
Echo info "Suppression du dossier de sauvegarde $backupdir."
rm -rf $backupdir
else
mkdir $backupdir
fi
Echo info "Sauvegarde du dossier Moodle dans le dossier $backupdir."
cp -a $MOODLE_ROOT $backupdir
Echo info "Temps de sauvegarde des fichiers $(TimeElapsed $taskstart)."
fi
if [ $backupdb -eq 0 ]; then
Echo info "Sauvegarde de la base de données."
taskstart=$(date +%s)
if [ $backupdbdelete -eq 0 ]; then
Echo info "Suppression la base de données de sauvegarde."
mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" -BNe "DROP DATABASE $backupdbname"
[ $? -eq 0 ] || exit 1
fi
charset=$(mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" -BNe "SELECT DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$dbname'")
collate=$(mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" -BNe "SELECT DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$dbname'")
Echo info "Création de la base de données de sauvegarde."
mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" -BNe "CREATE DATABASE $backupdbname CHARACTER SET $charset COLLATE $collate"
[ $? -eq 0 ] || exit 1
Echo info "Copie de la structure."
mysqldump -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" --no-data "$dbname" | mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" "$backupdbname"
[ $? -eq 0 ] || exit 1
if [ $backupdblog -eq 0 ]; then
Echo info "Copie des données."
mysqldump -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" "$dbname" | mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" "$backupdbname"
else
Echo info "Copie des données à l'exception de la table de log (logstore_standard_log)."
mysqldump -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" "$dbname" --ignore-table="$dbname".logstore_standard_log | mysql -h "$backupdbhost" -u "$backupdbuser" -p"$backupdbpass" "$backupdbname"
fi
[ $? -eq 0 ] || exit 1
Echo info "Temps de sauvegarde de la base de données $(TimeElapsed $taskstart)."
fi
if [ -n "$modpull" ] || [ -n "$corepull" ]; then
Echo header "Mise à jour des fichiers"
taskstart=$(date +%s)
if [ -n "$corepull" ]; then
Echo info "Mise à jour des fichiers noyau"
Update "$MOODLE_ROOT"
fi
if [ -n "$modpull" ]; then
Echo info "Mise à jour des fichiers des modules complémentaires"
Update "$modpull"
fi
Echo header "Mise à jour de la base de données."
# run Moodle database upgrade before or after patching ?
sudo -u www-data $PHP $MOODLE_ROOT/admin/cli/upgrade.php --non-interactive
Echo header "Application des correctifs"
if [ -n "$modpatch" ]; then
Patch "$modpatch"
fi
if [ -n "$corepatch" ]; then
Patch "$MOODLE_ROOT"
fi
failed=$(find -type f -name "*.moodleup.failed.patch")
if [ -n "$failed" ]; then
Echo info "L'application de certains fichiers correctif a échoué. Voici la liste : "
echo $failed
fi
Echo info "Temps de mise à jour des fichiers $(TimeElapsed $taskstart)"
fi
if [ $purgecache -eq 0 ]; then
Echo info "Vider les caches."
sudo -u www-data $PHP $MOODLE_ROOT/admin/cli/purge_caches.php
fi
if [ $maintenance -eq 0 ]; then
Echo info "Désactivation du mode maintenance."
sudo -u www-data $PHP $MOODLE_ROOT/admin/cli/maintenance.php --disable
fi
if [ $runcron -eq 0 ]; then
Echo info "Exécution du cron."
sudo -u www-data $PHP $MOODLE_ROOT/admin/cli/cron.php
fi
Echo info "Temps total de l'exécution de la mise à jour $(TimeElapsed $updatestart)."
Echo info "Mise à jour finie !"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment