diff --git a/bin/revertfog.sh b/bin/revertfog.sh index 4ccb5089ab..5f676d5a8f 100755 --- a/bin/revertfog.sh +++ b/bin/revertfog.sh @@ -291,9 +291,11 @@ _candidateDumps() { # --------------------------------------------------------------------------- # Same problem as the dump and the same answer. configureHttpd() REMOVES an # existing fog_web_.BACKUP before writing a new one, so a second -# installfog.sh run after a failed upgrade overwrites the 1.5 tree with the -# 1.6 one it just laid down -- and the directory name, which carries the -# version being installed, looks identical either way. +# installfog.sh run after a failed upgrade used to overwrite the 1.5 tree with +# the 1.6 one it just laid down -- and the directory name, which carries the +# version being installed, looks identical either way. The installer now +# renames a backup of another release after its own version first (GH-2000), +# but a server upgraded before that change may still hold only the 1.6 copy. # # 1.5 keeps its classes under lib/fog/ and has no src/; 1.6 is PSR-4 under # src/ (ADR 0013) and has no lib/fog/. One file each way settles it. diff --git a/docs/release/1.6.0-release-notes.md b/docs/release/1.6.0-release-notes.md index df62f578de..44ff2a0479 100644 --- a/docs/release/1.6.0-release-notes.md +++ b/docs/release/1.6.0-release-notes.md @@ -117,16 +117,19 @@ persisted to .fogsettings (lib/common/functions.sh:6539). --> - **Web tree:** `${DB_backup_path}/fog_web_.BACKUP` -Both are named after the version being upgraded *from*, not the one you're -upgrading to. Check `${DB_backup_path}` (default `/home/`, or whatever you +Both are named after the version being *installed*, not the one you are +upgrading from. The dump also carries a timestamp, so every run keeps its own. +If you run the installer a second time, the web backup from the first run holds +your 1.5 tree. The second run renames it after its own version +(`fog_web_<1.5 version>.BACKUP`) instead of replacing it. Check `${DB_backup_path}` (default `/home/`, or whatever you passed to `-B` / recorded in `.fogsettings`) after the upgrade to confirm both exist before you consider the run safe to walk away from. ### 3. If something goes wrong: reverting `bin/revertfog.sh` restores a FOG install from the backups above. It must be -run as root, from the `bin/` directory of a **1.5** checkout — the one you -are going back to: +run as root, from the `bin/` directory of the **1.6** checkout you upgraded +with. A 1.5 checkout does not contain it: ``` Usage: ./revertfog.sh [-h?y] [--list] [--dump ] [--kernel-generation ] [--dry-run] diff --git a/lib/common/functions.sh b/lib/common/functions.sh index 0b8588c751..d1950e250e 100755 --- a/lib/common/functions.sh +++ b/lib/common/functions.sh @@ -12840,6 +12840,34 @@ _warnUnrecognizedPlugins() { echo " and re-run, or install them from that directory via the UI." echo } +# Keep a web backup that holds a DIFFERENT release, instead of deleting it. +# +# The backup is named after the version being installed, and configureHttpd() +# removes any existing one of that name before copying the current tree in. On +# the first 1.5 -> 1.6 run that directory holds the 1.5 tree. A second run of +# the same release then finds the 1.6 tree it laid down, and replacing the +# backup deleted the only copy of 1.5 -- the tree bin/revertfog.sh needs +# (GH-2000). +# +# So a backup whose own FOG_VERSION is not $version is renamed after its own +# version, with a timestamp if that name is taken. bin/revertfog.sh finds it by +# its fog_web_*.BACKUP glob. A backup of the same release is still replaced, +# exactly as before. The files are the version file of each layout: the +# generated 1.6 one, its tracked fallback, and 1.5's lib/fog/. +_setAsideOtherVersionWebBackup() { + local backup="${DB_backup_path}/fog_web_${version}.BACKUP" + [[ -d $backup ]] || return 0 + local f="" held="" + for f in commons/version.php src/Base/System.php lib/fog/system.class.php; do + [[ -f $backup/$f ]] || continue + held="$(awk -F\' /"define\('FOG_VERSION'[,](.*)"/'{print $4}' "$backup/$f" | tr -d '[[:space:]]')" + [[ -n $held ]] && break + done + [[ -z $held || $held == "$version" ]] && return 0 + local dest="${DB_backup_path}/fog_web_${held}.BACKUP" + [[ -e $dest ]] && dest="${DB_backup_path}/fog_web_${held}_$(date +%Y%m%d_%H%M%S).BACKUP" + mv "$backup" "$dest" >>$error_log 2>&1 +} configureHttpd() { normalizeWebroot # Both customizations trees, before anything below needs either of them. @@ -13002,6 +13030,7 @@ configureHttpd() { # the time that runs the evidence has been deleted. The list itself is # $retiredplugins, defined at the top level beside those two functions. retiredpluginsstripped="" + _setAsideOtherVersionWebBackup if [[ -d ${DB_backup_path}/fog_web_${version}.BACKUP ]]; then rm -rf ${DB_backup_path}/fog_web_${version}.BACKUP >>$error_log 2>&1 fi diff --git a/tests/web-backup-keeps-other-release.test.sh b/tests/web-backup-keeps-other-release.test.sh new file mode 100644 index 0000000000..a013a30b42 --- /dev/null +++ b/tests/web-backup-keeps-other-release.test.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# +# A second install run keeps the web backup of the release it upgraded from. +# +# tests/web-backup-keeps-other-release.test.sh +# +# configureHttpd() names the web backup fog_web_${version}.BACKUP, after the +# version being INSTALLED, and removes any existing one before it copies the +# current tree in. A 1.5 -> 1.6 upgrade that is run twice therefore replaced the +# 1.5 tree with the 1.6 one, and bin/revertfog.sh had no web tree left to +# restore (GH-2000). _setAsideOtherVersionWebBackup() renames a backup of a +# different release out of the way instead. +# +# The function is EXECUTED against fixtures, not read. +# +# No root, no network, no FOG install. +# +# Usage: bash tests/web-backup-keeps-other-release.test.sh +# Exit status 0 = pass, 1 = fail. + +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +functions="$root/lib/common/functions.sh" + +pass=0 +fail=0 + +check() { + if [[ $2 -eq 0 ]]; then + pass=$((pass + 1)) + else + fail=$((fail + 1)) + printf ' FAIL %s\n' "$1" + fi +} + +snippet=$(awk ' + /^_setAsideOtherVersionWebBackup\(\) \{/ { grab = 1 } + grab { print } + grab && /^\}$/ { exit } +' "$functions") +if [[ -z $snippet ]]; then + echo "FAIL: could not find _setAsideOtherVersionWebBackup in lib/common/functions.sh" >&2 + exit 1 +fi +eval "$snippet" + +# The backup step must call it before it removes the old backup. +grep -Pzo '_setAsideOtherVersionWebBackup\n if \[\[ -d \$\{DB_backup_path\}/fog_web_\$\{version\}\.BACKUP \]\]; then\n rm -rf' "$functions" >/dev/null +check "configureHttpd() calls it before removing fog_web_\${version}.BACKUP" $? + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +error_log="$work/error.log" +version="1.6.0-RC-19" + +# Write a backup holding release $2 in the layout $3 (15, 16, 16fallback). +mkbackup() { + local dir="$DB_backup_path/$1" + case $3 in + 15) mkdir -p "$dir/lib/fog"; printf "define('FOG_VERSION', '%s');\n" "$2" > "$dir/lib/fog/system.class.php" ;; + 16) mkdir -p "$dir/commons" "$dir/src/Base" + printf "define('FOG_VERSION', '%s');\n" "$2" > "$dir/commons/version.php" + printf "define('FOG_VERSION', 'fallback');\n" > "$dir/src/Base/System.php" ;; + 16fallback) mkdir -p "$dir/src/Base"; printf "define('FOG_VERSION', '%s');\n" "$2" > "$dir/src/Base/System.php" ;; + esac + touch "$dir/marker-$2" +} +reset() { rm -rf "$work/backup"; DB_backup_path="$work/backup"; mkdir -p "$DB_backup_path"; } + +# --- the GH-2000 rerun: the backup holds 1.5 --------------------------------- +reset +mkbackup "fog_web_${version}.BACKUP" "1.5.10.2518" 15 +_setAsideOtherVersionWebBackup +[[ ! -e $DB_backup_path/fog_web_${version}.BACKUP ]] +check "a 1.5 tree under the 1.6 name is moved off that name" $? +[[ -f $DB_backup_path/fog_web_1.5.10.2518.BACKUP/marker-1.5.10.2518 ]] +check "and kept, renamed after its own version" $? + +# --- that name is already taken: keep both ----------------------------------- +reset +mkbackup "fog_web_1.5.10.2518.BACKUP" "1.5.10.2518" 15 +mkbackup "fog_web_${version}.BACKUP" "1.5.10.2518" 15 +_setAsideOtherVersionWebBackup +n=$(find "$DB_backup_path" -maxdepth 1 -name 'fog_web_1.5.10.2518_*.BACKUP' | wc -l) +[[ $n -eq 1 && -d $DB_backup_path/fog_web_1.5.10.2518.BACKUP ]] +check "an existing backup of that release is not overwritten" $? + +# --- an older 1.6 build, read from the generated version file ---------------- +reset +mkbackup "fog_web_${version}.BACKUP" "1.6.0-RC-18" 16 +_setAsideOtherVersionWebBackup +[[ -d $DB_backup_path/fog_web_1.6.0-RC-18.BACKUP && ! -e $DB_backup_path/fog_web_${version}.BACKUP ]] +check "a 1.6 tree's generated version.php wins over the fallback" $? + +# --- the same release: left for the caller to replace, as before ------------- +reset +mkbackup "fog_web_${version}.BACKUP" "$version" 16fallback +_setAsideOtherVersionWebBackup +[[ -f $DB_backup_path/fog_web_${version}.BACKUP/marker-$version ]] +check "a backup of the release being installed stays where it is" $? +[[ $(find "$DB_backup_path" -mindepth 1 -maxdepth 1 | wc -l) -eq 1 ]] +check "and nothing else is created" $? + +# --- no backup, or one with no readable version ------------------------------ +reset +_setAsideOtherVersionWebBackup +check "no backup: returns 0" $? +mkdir -p "$DB_backup_path/fog_web_${version}.BACKUP" +_setAsideOtherVersionWebBackup +[[ -d $DB_backup_path/fog_web_${version}.BACKUP ]] +check "an unreadable version leaves the backup to the old path" $? + +echo "web backup keeps other release: $pass passed, $fail failed" +[[ $fail -eq 0 ]]