Last active
February 12, 2022 09:56
-
-
Save colorwebdesigner/446e2f04d0fb670325bbe52fcaeaba83 to your computer and use it in GitHub Desktop.
MODx Revolution advanced update and backup script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# modx_update | |
# | |
# MODx Revo advanced update script | |
# 1) Put this file in your website folder | |
# 2) Make it executable | |
# 3) Run | |
# -------------------------------------------------------- | |
# Script will automaticaly find all configs, core folder | |
# (even if you change default name and location), make | |
# backup of your files, mysql dump and update your modx | |
# to latest advanced version. | |
# -------------------------------------------------------- | |
# author: Colorwebdesigner | |
# github: https://github.com/colorwebdesigner | |
# date: 12-12-2019 | |
# license: GNU | |
# -------------------------------------------------------- | |
# Arguments: | |
# | |
# [ --url ] - | |
# [ --rootpath ] - | |
# [ --domain ] - | |
# [ --servpath ] - | |
# [ --users ] - | |
# [ --help, -h ] - | |
# | |
# ======================================================== | |
# DEBUGGER On/Off | |
# set -x | |
# VARIABLES | |
# ------------------------- | |
ROOTPATH=${PWD} | |
TMPPATH=${ROOTPATH}/modx-tmp | |
# FUNCTIONS | |
# ------------------------- | |
function checkDBHost () { | |
DBPORT=3306 | |
local status=7 | |
local regex="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" | |
[[ $1 =~ ";port=" ]] && status=1 | |
[[ $1 =~ $regex ]] && status=2 | |
[[ $1 == 'localhost' ]] && status=3 | |
function ipValid () { | |
OIFS=$IFS && IFS='.' | |
local ip=($1) | |
for i in ${ip[*]}; do | |
if [[ $i -gt 255 ]]; then | |
status=8 | |
IFS=$OIFS | |
return 1 | |
fi | |
status=0 | |
done | |
IFS=$OIFS | |
DBHOST=$1 | |
return 0 | |
} | |
function parsePort () { | |
local string="$1" | |
local host=${string%';port='*} | |
local port=${string##*';port='} | |
[[ "$host" =~ $regex ]] && ipValid "$host" && \ | |
DBHOST=${host} | |
[[ -n "$port" && -z "${port//[0-9]}" ]] && \ | |
DBPORT=${port} || status=9 | |
return 0 | |
} | |
case $status in | |
1) parsePort $1;; | |
2) ipValid $1;; | |
3) DBHOST=$1; status=0;; | |
*) unset DBHOST DBPORT;; | |
esac | |
return $status | |
} | |
function collectModxPaths () { | |
# Find MODx base path. | |
# Search configuration files and collect an array. | |
local result=($(find ${1} -type f -name config.core.php)) | |
# Check array length and return error if empty | |
[ ${#result[@]} -eq 0 ] && return 1 | |
# Normaly there must be 3 files and the first one | |
# is in root of basepath. | |
BASEPATH=${result[0]%/*} | |
# Parse configuration file to get current | |
# MODx core path and assign it to variable | |
COREPATH=$(cat ${BASEPATH}/config.core.php | grep MODX_CORE_PATH | sed "s/^.*', '//" | sed "s/\/');//" | tr -d '"\t\r\n') | |
# Check and return error if empty | |
[ ! $COREPATH ] && return 2 | |
# Processing configuration file to get current | |
# MODx configuration ID and assign it to variable | |
CONFIGID=$(cat ${BASEPATH}/config.core.php | grep MODX_CONFIG_KEY | sed "s/^.*', '//" | sed "s/');//" | tr -d '"\t\r\n') | |
# Check and return error if empty | |
[ ! $CONFIGID ] && return 3 | |
# Processing core configuration file to get database name | |
DBNAME=$(cat "${COREPATH}/config/${CONFIGID}.inc.php" | grep '$dbase = ' | sed "s/^.*= '//" | sed "s/';//" | tr -d '"\t\r\n' ) | |
[ -z $DBNAME ] && return 4 | |
# Processing core configuration file to get database user | |
DBUSER=$(cat "${COREPATH}/config/${CONFIGID}.inc.php" | grep '$database_user = ' | sed "s/^.*= '//" | sed "s/';//" | tr -d '"\t\r\n' ) | |
[ -z $DBNAME ] && return 4 | |
# Processing core configuration file to get database password | |
DBPASS=$(cat "${COREPATH}/config/${CONFIGID}.inc.php" | grep '$database_password = ' | sed "s/^.*= '//" | sed "s/';//" | tr -d '"\t\r\n' ) | |
[ -z $DBPASS ] && return 5 | |
# Parse core configuration file to get table prefix | |
DBPREF=$(cat "${COREPATH}/config/${CONFIGID}.inc.php" | grep '$table_prefix = ' | sed "s/^.*= '//" | sed "s/';//" | tr -d '"\t\r\n' ) | |
#[ -z $DBPREF ] && return 6 | |
# Processing core configuration file to get database name / username | |
DBHOST=$(cat "${COREPATH}/config/${CONFIGID}.inc.php" | grep '$database_server = ' | sed "s/^.*= '//" | sed "s/';//" | tr -d '"\t\r\n' ) | |
checkDBHost "${DBHOST}" | |
[ -z $DBHOST ] && return 7 | |
return 0 | |
} | |
# Function for printing step title in console | |
function stepTitle () { | |
printf " --> ${c[y]}${1} ${c[n]}" | |
tput sc && printf "\n" | |
return 0 | |
} | |
# Function for processing result of each step | |
# and print human readable output to console | |
function resultProcessing () { | |
local report=${2:-done} | |
if [ $1 -eq 0 ]; then | |
tput rc && tput ed && printf "${c[g]}${report}${c[n]}\n" | |
else | |
tput rc && printf "${c[r]}error ${1}${c[n]}\n" && exit 1 | |
fi | |
unset status | |
return 0 | |
} | |
# Function for compare MODx versions | |
# and cancel update if current version is up to date | |
function updateStatus () { | |
if [[ ${1//./} -eq ${2//./} ]]; then | |
tput rc && printf "${c[g]}MODx is up to date${c[n]}\n" | |
[ -d ${TMPPATH} ] && rm -rf ${TMPPATH} | |
exit 0 | |
else | |
tput rc && tput ed && printf "${c[c]}${1} -> ${2}${c[n]}\n" | |
fi | |
} | |
# Function for parsing core/docs/version.inc.php | |
# and get current MODx version | |
function getCurrentModxVersion () { | |
local path=${1}/docs/version.inc.php | |
local version=$(cat $path | grep "Current version" | sed 's/[^0-9]*//g') | |
local major_version=$(cat $path | grep "Current major version" | sed 's/[^0-9]*//g') | |
local minor_version=$(cat $path | grep "Current minor version" | sed 's/[^0-9]*//g') | |
[[ ! $version || ! $major_version || ! $minor_version ]] && return 1 | |
CURRENT_MODX_VERSION="${version}.${major_version}.${minor_version}" | |
return 0 | |
} | |
# Function for parsing official MODx site | |
# to get latest MODx version number | |
function getLatestModxVersion () { | |
local url='https://modx.com/download' | |
local id='Current Version' | |
local res=$(curl -s ${url} | grep "${id}" | sed 's|<[^>]*>||g' | sed 's/[^0-9.]*//g' | tr -d '\040\011\012\015') | |
[ -z $res ] && return 8 | |
LATEST_MODX_VERSION="${res}" | |
return 0 | |
} | |
# Function for make .cnf file for | |
# mysqldump with user and password | |
function makeMysqldumpConfig () { | |
printf "%s\n" "[mysqldump]" > ${1}/${2} | |
printf "%s\n" "host=${DBHOST}" >> ${1}/${2} | |
printf "%s\n" "port=${DBPORT}" >> ${1}/${2} | |
printf "%s\n" "user=${DBUSER}" >> ${1}/${2} | |
printf "%s" "password=${DBPASS}" >> ${1}/${2} | |
chmod 700 ${1}/${2} && return 0 | |
return 1 | |
} | |
# Function to make current Data Base backup | |
function backupDataBase () { | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# Important! | |
# Clear string from special charachters | |
# to prevent stupid errors from mysqldump | |
local src=$(echo "${1}" | tr -d '\t\r\n') | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
local cnf=${TMPPATH}/${3} | |
local dest=${2}/backup/${today}/modx-${today}.sql | |
mysqldump --defaults-extra-file="${cnf}" ${src} > ${dest}; status=$? | |
[ $status -eq 0 ] && chmod 600 ${dest} | |
rm ${cnf} | |
return $status | |
} | |
# Function for download latest MODx zip | |
function downloadModx () { | |
local src="https://modx.com/download/direct?id=modx-${1}-pl-advanced.zip" | |
local dest=${2}/modx-${1}-pl-advanced.zip | |
wget -qO "${dest}" "${src}" | |
return $? | |
} | |
# Function to create MODx configuration file | |
# - setup/includes/config.core.php | |
function configBuilder () { | |
# Find path to unziped modx directory | |
local newmodx=$(find ${ROOTPATH} -type d -name "modx-${LATEST_MODX_VERSION}*") | |
[ -z ${newmodx} ] && return 1 | |
# Move setup and core folders one level up and delete old parent | |
mv ${newmodx}/* ${TMPPATH} && rm -rf ${newmodx} | |
# Set path to setup config.core.php | |
local setupconfig=${TMPPATH}/setup/includes/config.core.php | |
[ -f ${setupconfig} ] || return 2 | |
# Set path to setup/config template file | |
local configtpl=${TMPPATH}/setup/config.dist.upgrade.xml | |
[ -f ${configtpl} ] || return 3 | |
# Set path to main setup/config file | |
local config=${TMPPATH}/setup/config.xml | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# Filtering password to escape special characters. | |
# This is the eval core of errors when you pass variable | |
# to sed or mysqldump etc. | |
# | |
# local pass="${DBPASS//\&/\\&}" | |
# pass="${pass//\$/\\$}" | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# Build setup/includes/config.core.php | |
printf "%s\n" "<?php" > $setupconfig | |
printf "%s" "if (!defined('MODX_CORE_PATH')) " >> $setupconfig | |
printf "%s\n" "define('MODX_CORE_PATH', '${COREPATH}/');" >> $setupconfig | |
printf "%s" "if (!defined('MODX_CONFIG_KEY')) " >> $setupconfig | |
printf "%s\n" "define('MODX_CONFIG_KEY', '${CONFIGID}');" >> $setupconfig | |
printf "%s\n" "define ('MODX_SETUP_KEY', '@advanced@');" >> $setupconfig | |
# Build setup/config.xml | |
cp ${configtpl} ${config} | |
sed -i -e 's|language>en|language>ru|g' ${config} | |
sed -i -e "s|core_path>/www/modx/core/|core_path>${COREPATH}/|g" ${config} | |
return 0 | |
} | |
# MAIN FUNCTION | |
# ------------------------- | |
modx_update () { | |
local status | |
local today=$(date +"%d-%m-%Y") | |
# Preparing temporary folder to store | |
# files needed by update process | |
[ -d ${TMPPATH} ] && rm -rf ${TMPPATH} | |
mkdir ${TMPPATH} && chmod -R 700 ${TMPPATH} | |
# Catch and set arguments | |
# ------------------------- | |
for (( i=1; i<=$#; i++ )); do | |
case "${!i}" in | |
-* ) local val="$((i+1))" | |
[[ -n "${!val}" && "${!val}" != "-"* ]] && val="${!val}" || val=true | |
case "${!i}" in | |
--nobackup ) [ "$val" != true ] && URL="$val";; | |
--rootpath ) [ "$val" != true ] && ROOTPATH="$val";; | |
--domain ) [ "$val" != true ] && DOMAIN="$val";; | |
--servpath ) [ "$val" != true ] && SERVPATH="$val";; | |
--users ) [ "$val" != true ] && USERS="$val";; | |
* ) echo "unknown argument ${!i}";; | |
esac;; | |
* ) continue;; | |
esac | |
done | |
# DO THE JOB | |
# ------------------------- | |
# [1] Collect MODx paths for current installation | |
# ------------------------- | |
stepTitle 'Collect MODx paths:' | |
collectModxPaths; status=$? | |
resultProcessing $status | |
# exit 0 | |
# [2] Get current MODx version | |
# ------------------------- | |
stepTitle 'Get current MODx version:' | |
getCurrentModxVersion ${COREPATH}; status=$? | |
resultProcessing $status ${CURRENT_MODX_VERSION} | |
# [3] Get latest MODx version | |
# ------------------------- | |
stepTitle 'Get latest MODx version:' | |
getLatestModxVersion; status=$? | |
resultProcessing $status ${LATEST_MODX_VERSION} | |
# [4] Compare MODx versions and cancel if no updates needed | |
# ------------------------- | |
stepTitle 'Update status:' | |
updateStatus ${CURRENT_MODX_VERSION} ${LATEST_MODX_VERSION} | |
# [5] Make backup folders | |
# ------------------------- | |
stepTitle 'Make backup folder:' | |
mkdir -p ${ROOTPATH}/backup/${today} && chmod -R 700 ${ROOTPATH}/backup; status=$? | |
resultProcessing $status | |
# [6] Backup current basepath and core folders | |
# ------------------------- | |
stepTitle 'Backup current files:' | |
zip -qr ${ROOTPATH}/backup/${today}/modx-${today} ${BASEPATH}/ ${COREPATH}/; status=$? | |
resultProcessing $status | |
# [7] Backup current database | |
# ------------------------- | |
stepTitle "Backup current database:" | |
makeMysqldumpConfig ${TMPPATH} ".${CONFIGID}.cnf" && \ | |
backupDataBase ${DBNAME} ${ROOTPATH} ".${CONFIGID}.cnf"; status=$? | |
resultProcessing $status | |
# [8] Download latest MODx archive | |
# ------------------------- | |
stepTitle 'Download latest MODx:' | |
downloadModx ${LATEST_MODX_VERSION} ${TMPPATH}; status=$? | |
resultProcessing $status | |
# [9] Extract files from archive | |
# ------------------------- | |
stepTitle 'Extracting files:' | |
unzip -qo ${TMPPATH}/modx-${LATEST_MODX_VERSION}-pl-advanced.zip -d ${TMPPATH}; status=$? | |
resultProcessing $status | |
# [10] Generating config | |
# ------------------------- | |
stepTitle 'Cooking configuration files:' | |
configBuilder; status=$? | |
resultProcessing $status | |
# [11] Copy setup folder to basepath | |
# ------------------------- | |
stepTitle 'Copy MODx setup to basepath:' | |
rsync -a ${TMPPATH}/setup ${BASEPATH}/; status=$? | |
resultProcessing $status | |
# [12] Clear current core/cache folder | |
# ------------------------- | |
stepTitle 'Clear core/cache:' | |
rm -rf ${COREPATH}/cache/*; status=$? | |
resultProcessing $status | |
# [13] Merge new core to existing core | |
# ------------------------- | |
stepTitle 'Update MODx core:' | |
rsync -raz ${TMPPATH}/core/ ${COREPATH}/; status=$? | |
resultProcessing $status | |
# [14] Start MODx update with php-cli | |
# ------------------------- | |
stepTitle "Update MODx to ${LATEST_MODX_VERSION}:" | |
php ${BASEPATH}/setup/index.php --installmode=upgrade; status=$? | |
resultProcessing $status | |
# [15] Remove installation files | |
# ------------------------- | |
stepTitle "Remove installation files:" | |
rm -rf ${TMPPATH} ${BASEPATH}/setup; status=$? | |
resultProcessing $status | |
# Exit function | |
return 0 | |
} | |
# Declare array with color values to colorise output | |
declare -A c=( ['c']='\e[36m' ['y']='\e[33m' ['r']='\e[31m' ['g']='\e[32m' ['n']='\e[0m' ) | |
# Sample usage | |
modx_update $@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment