Skip to content

Instantly share code, notes, and snippets.

@dgeo
Last active July 31, 2024 15:38
Show Gist options
  • Save dgeo/2190aea81f7bfcb15e3af6be440935b8 to your computer and use it in GitHub Desktop.
Save dgeo/2190aea81f7bfcb15e3af6be440935b8 to your computer and use it in GitHub Desktop.
nextcloud upgrade script
#!/bin/sh -e
#
# Updater script for nextcloud + apps
#
# Designed for "maintenance" user different from "run" user
# eg: here php runs with _nextcloud:nextcloud and maintenance is done by nextcloud:nextcloud
# this way, an exploit cannot rewrite the code in place (datadir does not allow php execution)
#
# Needs curl, xpath (perl module XML::XPath), jq
#
# To start/stop nginx and create zfs snapshots, you need a sudo config like:
# Cmnd_Alias NCUPGRADE = /sbin/zfs snapshot -r zroot/iocage/jails/claude/data@*,/usr/sbin/service nginx *
# nextcloud ALL=(root) NOPASSWD:NCUPGRADE
#
NCDIR=/home/nextcloud/nextcloud
APPSDIR=$NCDIR/apps-local
NCUSER=nextcloud
# start/stop nginx (need sudo)
NGINX=YES
# create zfs snapshot (need sudo)
ZFS=YES
[ "$(id -un)" != "$NCUSER" ] && exec su -l $NCUSER -c "$0 $*"
occ() {
umask 002; php -dapc.enable_cli=1 $NCDIR/occ $*
}
# nginx_service [stop|start]
nginx_service() {
if [ "$NGINX" = "YES" ]; then
sudo /usr/sbin/service nginx $1
fi
}
# do_zfs_snapshot version
do_zfs_snapshot() {
if [ "$ZFS" = "YES" ]; then
ZCODE=$(df -h -t zfs $NCDIR | tail -1 | cut -w -f1)
ZDATA=$(df -h -t zfs $(occ config:system:get datadirectory) | tail -1 | cut -w -f1)
if [ -n "$ZCODE" ]; then
zfs list -Honame $ZCODE@nc_$1 2>/dev/null && sudo /sbin/zfs destroy -r $ZCODE@nc_$1
sudo /sbin/zfs snapshot -r $ZCODE@nc_$1
fi
if [ -n "$ZDATA" ] && ! echo "$ZDATA" | grep -q "${ZCODE:-NEVERMATCHDATADIR}"; then
zfs list -Honame $ZDATA@nc_$1 2>/dev/null && sudo /sbin/zfs destroy -r $ZDATA@nc_$1
sudo /sbin/zfs snapshot -r $ZDATA@nc_$1
fi
fi
}
grep -q 'config_is_read_only.*rue' ${NCDIR}/config/config.php
ROCONFIG=$?
[ -e "${NCDIR}/config/config.php.noro.php" ] && ROCONFIG=0
# get running version
eval $(php -r 'include("'${NCDIR}'/version.php"); print("curvers=$OC_VersionString\ncvers=".join("x",$OC_Version)."\nbuild=\"".urlencode($OC_Build)."\"\nfullvers=".join(".",$OC_Version)."\nphpversion=".PHP_MAJOR_VERSION."x".PHP_MINOR_VERSION."x".PHP_RELEASE_VERSION);')
if [ -z "$fullvers" ] || [ -z "$curvers" ] || [ -z "$phpversion" ] || [ -z "$build" ] || [ -z "$cvers" ]; then
echo "Impossible de trouver la version de nextcloud dans ${NCDIR}/version.php :("
exit 1
fi
# get next version for updater
if newvers=$(curl -s "https://updates.nextcloud.com/updater_server/?version=${cvers}xxxproductionxx${build}x${phpversion}" | xpath -q -e '/nextcloud/version/text()' 2>/dev/null); then
if [ -z "$newvers" ]; then
echo "Impossible de trouver une nouvelle version"
elif [ "${newvers%%.*}" != "${curvers%%.*}" ]; then
echo "MAJ majeure: $curvers -> $newvers"
if [ $# -ne 1 ] && [ "$1" != "OKMAJ" ]; then
echo "$0 OKMAJ pour l'installer"
NOUP=1
fi
fi
else
echo "pas de nouvelle version pour nextcloud (${curvers})"
fi
# OK, there is an upgrade to do… go !
if [ -n "$newvers" ] && [ "${newvers}" != "${fullvers}" ] && [ -z "$NOUP" ]; then
nginx_service stop
do_zfs_snapshot ${fullvers}
[ $ROCONFIG = 0 ] && sed -i .noro.php '/config_is_read_only/d' "${NCDIR}/config/config.php"
php -dapc.enable_cli=1 ${NCDIR}/updater/updater.phar --no-backup -v --no-interaction
# re-get new running version
eval $(php -r 'include("'${NCDIR}'/version.php"); print("curvers=$OC_VersionString\ncvers=".join("x",$OC_Version)."\nbuild=\"".urlencode($OC_Build)."\"\nfullvers=".join(".",$OC_Version)."\nphpversion=".PHP_MAJOR_VERSION."x".PHP_MINOR_VERSION."x".PHP_RELEASE_VERSION);')
fi
# apps upgrade
v=${fullvers%%.*}
cd $APPSDIR
curl -s -o /tmp/apps.json https://apps.nextcloud.com/api/v1/platform/${curvers}/apps.json
for app in *; do
curvers=$(xpath -q -e '/info/version/text()' $app/appinfo/info.xml)
version=$(jq -r '.[] | select (.id=="'$app'") | {releases}.[] | .[].version' < /tmp/apps.json | sort -t. -nk1 -nk2 -nk3 | tail -1)
url=$(jq -r '.[] | select (.id=="'$app'") | {releases}.[].[] | select (.version=="'$version'") | .download' < /tmp/apps.json)
UP=""
if [ -z "$version" ]; then
echo "$app-$curvers : no version found"
continue
fi
if [ "$version" = "$curvers" ]; then
echo "$app-$curvers a jour ($version)"
continue
else
echo "$app-$curvers: version $version disponible"
UP=YES
fi
if [ -d $app/.git ]; then
# if git-managed, upgrade to stableXX branch or master
git -C $app fetch origin
branch=$(git -C $app branch -r | grep 'stable'$v'$' || echo origin/master)
if [ "$branch" = "origin/master" ]; then
tag=$(git -C $app tag | grep "^v" | sort -nk1 -k2 -k3 -t. | grep $version)
if [ -n "$tag" ]; then
branch=$tag
echo "found tag $tag for $app-$curvers"
fi
fi
git -C $app checkout $branch
[ -f $app/package.json ] && ( cd $app; npm install --no-dev )
[ -f $app/composer.json ] && ( cd $app; composer install --no-dev )
else
# if no git, get tar.gz
if [ -n "$url" ]; then
fetch -o- $url | tar xzf -
fi
fi
if [ "YES" = "$UP" ]; then
[ $ROCONFIG = 0 ] && [ ! -f "${NCDIR}/config/config.php.noro.php" ] && sed -i .noro.php '/config_is_read_only/d' "${NCDIR}/config/config.php"
occ upgrade -n
fi
# delete old files
occ integrity:check-app --output=json $app | jq -r '.EXTRA_FILE | select(any(.expected == "")) | keys | map("'$app/'"+.) | .[]' 2>/dev/null | xargs -t rm
done
# make config read-write
[ $ROCONFIG = 0 ] && sed -i .noro.php '/config_is_read_only/d' "${NCDIR}/config/config.php"
occ upgrade -n
# re-make config read-only
[ $ROCONFIG = 0 ] && occ config:system:set --value=true config_is_read_only && rm -f "${NCDIR}/config/config.php.noro.php"
# start nginx if it was stopped
if [ "$NGINX" = "YES" ]; then
nginx_service start
fi
@dgeo
Copy link
Author

dgeo commented Jul 31, 2024

use sort -t. -nk1 -nk2 -nk3 to get latest version of application (lite semver sorting)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment