fix: enhance Nextcloud upgrade playbook with improved runtime checks, maintenance mode handling, and application integrity validation

This commit is contained in:
martin.fencl
2026-08-06 21:11:11 +02:00
parent 64ef2f839a
commit 9d1f9b8107
+218 -40
View File
@@ -203,12 +203,27 @@
fi
done
current_step="checking Nextcloud status"
echo "Checking Nextcloud status"
current_step="checking Nextcloud runtime"
echo "Checking Nextcloud runtime"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
sh -c '
command -v php >/dev/null
test -s /var/www/html/occ
test -s /var/www/html/version.php
test -s /var/www/html/config/config.php
'
current_step="reading Nextcloud status"
echo "Reading Nextcloud status"
# The status command is informational during preflight. A previous failed
# upgrade may legitimately leave maintenance mode enabled or a database
# upgrade pending. Such a state must not prevent a recovery run.
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ status
php occ status || true
current_step="checking MariaDB"
echo "Checking MariaDB"
@@ -245,6 +260,25 @@
set -Eeuo pipefail
current_step="backup initialization"
maintenance_enabled_by_script="false"
cleanup() {
rc=$?
if [ "${rc}" -ne 0 ] && \
[ "${maintenance_enabled_by_script}" = "true" ]
then
echo "Backup failed; attempting to disable maintenance mode" >&2
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ maintenance:mode --off >/dev/null 2>&1 || true
fi
exit "${rc}"
}
trap cleanup EXIT
trap '
rc=$?
@@ -268,6 +302,8 @@
{{ nextcloud_container | quote }} \
php occ maintenance:mode --on
maintenance_enabled_by_script="true"
current_step="creating configuration archive"
echo "Backing up Nextcloud configuration and custom applications"
@@ -301,11 +337,19 @@
sh -c '
if command -v mariadb-dump >/dev/null 2>&1; then
exec mariadb-dump \
--single-transaction \
--quick \
--routines \
--triggers \
-u"$MYSQL_USER" \
-p"$MYSQL_PASSWORD" \
"$MYSQL_DATABASE"
else
exec mysqldump \
--single-transaction \
--quick \
--routines \
--triggers \
-u"$MYSQL_USER" \
-p"$MYSQL_PASSWORD" \
"$MYSQL_DATABASE"
@@ -327,6 +371,8 @@
{{ nextcloud_container | quote }} \
php occ maintenance:mode --off
maintenance_enabled_by_script="false"
current_step="verifying maintenance mode after backup"
echo "Verifying maintenance mode after backup"
@@ -413,71 +459,124 @@
if [ "${SECONDS}" -ge "${container_deadline}" ]; then
echo "Timeout waiting for container {{ nextcloud_container }}" >&2
{{ docker_cmd }} logs \
--tail 100 \
{{ nextcloud_container | quote }} >&2 || true
exit 124
fi
sleep 3
done
current_step="waiting for Nextcloud OCC"
echo "Waiting for Nextcloud OCC"
current_step="waiting for Nextcloud initialization"
echo "Waiting for Nextcloud container initialization"
occ_deadline=$((SECONDS + 180))
initialization_deadline=$((SECONDS + 300))
while true
do
if {{ docker_cmd }} exec -u www-data \
if {{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
php occ status >/dev/null 2>&1
sh -c '
set -eu
# The official image entrypoint eventually replaces PID 1 with
# apache2-foreground. Until that happens, application files may
# still be copied from /usr/src/nextcloud to /var/www/html.
pid1_command="$(
tr "\000" " " < /proc/1/cmdline
)"
case "${pid1_command}" in
*apache2-foreground*)
;;
*)
exit 1
;;
esac
command -v php >/dev/null
test -s /var/www/html/occ
test -s /var/www/html/version.php
test -s /var/www/html/config/config.php
test -d /var/www/html/apps
test -s /var/www/html/apps/activity/appinfo/info.xml
' >/dev/null 2>&1
then
break
fi
if [ "${SECONDS}" -ge "${occ_deadline}" ]; then
echo "Timeout waiting for Nextcloud OCC" >&2
if [ "${SECONDS}" -ge "${initialization_deadline}" ]; then
echo "Timeout waiting for Nextcloud initialization" >&2
{{ docker_cmd }} logs \
--tail 200 \
{{ nextcloud_container | quote }} >&2 || true
exit 124
fi
sleep 3
done
current_step="disabling stale maintenance mode"
echo "Ensuring maintenance mode is disabled before OCC upgrade"
current_step="validating Nextcloud PHP runtime"
echo "Validating Nextcloud PHP runtime"
maintenance_state="$(
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ config:system:get maintenance \
2>/dev/null || true
)"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
php -r '
require "/var/www/html/version.php";
if [ "${maintenance_state}" = "true" ] || \
[ "${maintenance_state}" = "1" ]; then
echo "Maintenance mode is enabled; disabling it"
if (empty($OC_Version)) {
fwrite(STDERR, "Nextcloud version is unavailable\n");
exit(1);
}
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ maintenance:mode --off
fi
echo "Nextcloud code version: ";
echo implode(".", $OC_Version), PHP_EOL;
'
current_step="verifying maintenance mode before OCC upgrade"
current_step="validating bundled application files"
echo "Validating bundled Nextcloud application files"
maintenance_state="$(
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ config:system:get maintenance \
2>/dev/null || true
)"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
php -r '
$infoFile = "/var/www/html/apps/activity/appinfo/info.xml";
if [ "${maintenance_state}" = "true" ] || \
[ "${maintenance_state}" = "1" ]; then
echo "Maintenance mode could not be disabled" >&2
exit 1
fi
if (!is_file($infoFile) || filesize($infoFile) === 0) {
fwrite(STDERR, "Activity application metadata is missing\n");
exit(1);
}
$xml = @simplexml_load_file($infoFile);
if ($xml === false) {
fwrite(STDERR, "Activity application metadata is invalid\n");
exit(1);
}
if ((string) $xml->id !== "activity") {
fwrite(STDERR, "Unexpected activity application ID\n");
exit(1);
}
echo "Activity application version: ";
echo (string) $xml->version, PHP_EOL;
'
echo "Nextcloud container initialization completed"
current_step="running Nextcloud database upgrade"
echo "Running the Nextcloud database upgrade"
# Do not disable maintenance mode before this command. A previous failed
# upgrade may have intentionally left maintenance mode enabled. OCC
# upgrade manages the required maintenance state itself.
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ upgrade --no-interaction
@@ -503,6 +602,42 @@
{{ nextcloud_container | quote }} \
php occ status
current_step="verifying final Nextcloud state"
echo "Verifying final Nextcloud state"
maintenance_state="$(
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ config:system:get maintenance \
2>/dev/null || true
)"
if [ "${maintenance_state}" = "true" ] || \
[ "${maintenance_state}" = "1" ]; then
echo "Nextcloud is still in maintenance mode" >&2
exit 1
fi
needs_db_upgrade="$(
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ status --output=json |
php -r '
$status = json_decode(stream_get_contents(STDIN), true);
if (!is_array($status)) {
exit(2);
}
echo !empty($status["needsDbUpgrade"]) ? "true" : "false";
'
)"
if [ "${needs_db_upgrade}" != "false" ]; then
echo "Nextcloud still requires a database upgrade" >&2
exit 1
fi
echo "Nextcloud upgrade commands completed successfully"
# -------------------------------------------------------------------------
@@ -593,6 +728,35 @@
exit 1
fi
current_step="checking database upgrade state"
needs_db_upgrade="$(
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ status --output=json |
php -r '
$status = json_decode(stream_get_contents(STDIN), true);
if (!is_array($status)) {
exit(2);
}
echo !empty($status["needsDbUpgrade"]) ? "true" : "false";
'
)"
if [ "${needs_db_upgrade}" != "false" ]; then
echo "Nextcloud still requires a database upgrade" >&2
exit 1
fi
current_step="checking activity application integrity"
echo "Checking activity application integrity"
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ integrity:check-app activity
echo "Post-upgrade checks completed successfully"
pre_tasks:
@@ -660,7 +824,6 @@
src: "{{ nextcloud_compose_local_file }}"
dest: "{{ nextcloud_compose_controller_staging_file }}"
mode: "0600"
register: nextcloud_compose_controller_copy
- name: Nextcloud | Upload Compose file to VM staging path
ansible.builtin.command:
@@ -681,8 +844,8 @@
environment:
SSHPASS: "{{ vm_password }}"
register: nextcloud_compose_upload
changed_when: nextcloud_compose_upload.rc == 0
no_log: false
changed_when: false
no_log: true
- name: Nextcloud | Validate and install Compose file on VM
ansible.builtin.command:
@@ -1035,6 +1198,10 @@
fail_msg: |
Nextcloud upgrade failed.
Maintenance mode has intentionally not been disabled because
the upgrade command failed. Resolve the reported error and rerun
the playbook or complete the OCC upgrade manually.
Return code:
{{ nc_upgrade.rc }}
@@ -1046,6 +1213,11 @@
quiet: true
always:
# Disable maintenance mode when:
# - the backup failed before the upgrade task was started, or
# - the upgrade completed successfully.
#
# Do not disable maintenance mode after a failed OCC upgrade.
- name: Nextcloud | Disable maintenance mode
ansible.builtin.command:
argv:
@@ -1080,6 +1252,8 @@
changed_when: false
failed_when: false
no_log: true
when:
- nc_upgrade is not defined or nc_upgrade.rc == 0
- name: Nextcloud | Show maintenance mode result
ansible.builtin.debug:
@@ -1092,6 +1266,7 @@
STDERR:
{{ nc_maint_off.stderr | default('') | trim }}
when:
- nc_upgrade is not defined or nc_upgrade.rc == 0
- debug_level == 1 or nc_maint_off.rc != 0
- name: Nextcloud | Verify maintenance mode was disabled
@@ -1110,6 +1285,8 @@
STDERR:
{{ nc_maint_off.stderr | default('') | trim }}
quiet: true
when:
- nc_upgrade is not defined or nc_upgrade.rc == 0
# -------------------------------------------------------------------------
# Public Nextcloud readiness
@@ -1283,6 +1460,7 @@
Redis container: OK
MariaDB readiness: OK
Redis readiness: OK
Nextcloud application integrity: OK
Nextcloud public status endpoint: OK
Collabora root endpoint: OK
Collabora discovery endpoint: OK