Skip to content

Instantly share code, notes, and snippets.

@colorwebdesigner
Last active February 12, 2022 09:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colorwebdesigner/446e2f04d0fb670325bbe52fcaeaba83 to your computer and use it in GitHub Desktop.
Save colorwebdesigner/446e2f04d0fb670325bbe52fcaeaba83 to your computer and use it in GitHub Desktop.
MODx Revolution advanced update and backup script
#!/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