Files
ansible/nextcloud/update_nextcloud_v2.yml
T
2026-08-17 15:59:31 +02:00

1490 lines
44 KiB
YAML

# nextcloud/update_nextcloud_v2.yml
- name: Update Nextcloud on VM via Proxmox
hosts: proxmox_nextcloud
gather_facts: false
become: true
become_user: root
become_method: sudo
vars:
# vm_pass is stored in Semaphore Secrets as an environment variable.
vm_password: "{{ lookup('ansible.builtin.env', 'vm_pass') }}"
# Values stored in Semaphore Extra variables.
vm_use_sudo: "{{ use_sudo | default(false) | bool }}"
debug_level: "{{ DEBUG | default(0) | int }}"
retry_count: "{{ RETRIES | default(25) | int }}"
nextcloud_version: >-
{{ NEXTCLOUD_VERSION | default('') | string | trim }}
# Docker Compose configuration.
nextcloud_project: "nextcloud-collabora"
nextcloud_service: "nextcloud"
# Compose file stored in Git.
nextcloud_compose_local_file: >-
{{ playbook_dir }}/../docker-compose/docker-compose-nextcloud.yml.j2
# Compose file location on the Nextcloud VM.
nextcloud_compose_file: >-
/data/compose/nextcloud/docker-compose-nextcloud.yml
# Temporary Compose file on the Proxmox host.
nextcloud_compose_controller_staging_file: >-
/tmp/docker-compose-nextcloud-{{ backup_timestamp }}.yml
# Temporary Compose file on the Nextcloud VM.
nextcloud_compose_vm_staging_file: >-
/tmp/docker-compose-nextcloud-{{ backup_timestamp }}.yml
# Container names.
nextcloud_container: "nextcloud"
nextcloud_db_container: "nextcloud-db"
redis_container: "redis"
# Public endpoints.
nextcloud_base_url: "https://cloud.martinfencl.eu"
nextcloud_status_url: "{{ nextcloud_base_url }}/status.php"
collabora_url: "https://collabora.martinfencl.eu/"
collabora_discovery_url: >-
https://collabora.martinfencl.eu/hosting/discovery
# Backup configuration.
backup_root: "/data/compose/nextcloud"
# Docker command without CLI hints.
docker_cmd: "env DOCKER_CLI_HINTS=0 docker"
nextcloud_version_command: >-
{{ docker_cmd }} exec -u www-data
{{ nextcloud_container | quote }}
php occ -V
nextcloud_maintenance_off_command: >-
{{ docker_cmd }} exec -u www-data
{{ nextcloud_container | quote }}
php occ maintenance:mode --off
# -------------------------------------------------------------------------
# Compose installation script
# -------------------------------------------------------------------------
compose_install_script: |
set -Eeuo pipefail
current_step="Compose installation initialization"
trap '
rc=$?
echo >&2
echo "Compose installation failed" >&2
echo "Step: ${current_step}" >&2
echo "Line: ${LINENO}" >&2
echo "Command: ${BASH_COMMAND}" >&2
echo "Return code: ${rc}" >&2
exit "${rc}"
' ERR
source_file={{ nextcloud_compose_vm_staging_file | quote }}
destination_file={{ nextcloud_compose_file | quote }}
destination_dir="$(dirname "${destination_file}")"
cleanup() {
rm -f "${source_file}"
}
trap cleanup EXIT
current_step="checking staged Compose file"
echo "Checking staged Docker Compose file"
test -s "${source_file}"
current_step="validating staged Compose file"
echo "Validating staged Docker Compose file"
{{ docker_cmd }} compose \
-p {{ nextcloud_project | quote }} \
-f "${source_file}" \
config --quiet
current_step="creating Compose destination directory"
echo "Ensuring Compose destination directory exists"
mkdir -p "${destination_dir}"
current_step="comparing Compose files"
if [ -f "${destination_file}" ] && \
cmp -s "${source_file}" "${destination_file}"
then
echo "Compose file is already up to date"
exit 0
fi
current_step="installing Compose file"
echo "Installing Compose file"
install \
-m 0644 \
"${source_file}" \
"${destination_file}"
current_step="validating installed Compose file"
echo "Validating installed Docker Compose file"
{{ docker_cmd }} compose \
-p {{ nextcloud_project | quote }} \
-f "${destination_file}" \
config --quiet
echo "Compose file updated"
# -------------------------------------------------------------------------
# Preflight script
# -------------------------------------------------------------------------
preflight_script: |
set -Eeuo pipefail
current_step="preflight initialization"
trap '
rc=$?
echo >&2
echo "Preflight check failed" >&2
echo "Step: ${current_step}" >&2
echo "Line: ${LINENO}" >&2
echo "Command: ${BASH_COMMAND}" >&2
echo "Return code: ${rc}" >&2
exit "${rc}"
' ERR
current_step="checking Docker executable"
echo "Checking Docker executable"
command -v docker >/dev/null
current_step="checking Docker daemon"
echo "Checking Docker daemon"
docker version >/dev/null
current_step="checking Docker Compose"
echo "Checking Docker Compose"
docker compose version >/dev/null
current_step="checking Docker Compose file"
echo "Checking Docker Compose file"
test -f {{ nextcloud_compose_file | quote }}
current_step="validating Docker Compose configuration"
echo "Validating Docker Compose configuration"
{{ docker_cmd }} compose \
-p {{ nextcloud_project | quote }} \
-f {{ nextcloud_compose_file | quote }} \
config --quiet
current_step="checking required containers"
echo "Checking required containers"
for container in \
{{ nextcloud_container | quote }} \
{{ nextcloud_db_container | quote }} \
{{ redis_container | quote }}
do
state="$(
docker inspect \
--format '{% raw %}{{.State.Running}}{% endraw %}' \
"${container}" \
2>/dev/null || true
)"
if [ "${state}" != "true" ]; then
echo "Container ${container} is not running" >&2
exit 1
fi
done
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 || true
current_step="checking MariaDB"
echo "Checking MariaDB"
{{ docker_cmd }} exec \
{{ nextcloud_db_container | quote }} \
sh -c '
if command -v mariadb-admin >/dev/null 2>&1; then
exec mariadb-admin ping \
-h 127.0.0.1 \
--silent
else
exec mysqladmin ping \
-h 127.0.0.1 \
--silent
fi
'
current_step="checking Redis"
echo "Checking Redis"
{{ docker_cmd }} exec \
{{ redis_container | quote }} \
redis-cli -h 127.0.0.1 ping |
grep -qx PONG
echo "Preflight checks completed successfully"
# -------------------------------------------------------------------------
# Backup script
# -------------------------------------------------------------------------
backup_script: |
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
}
trap cleanup EXIT
trap '
rc=$?
echo >&2
echo "Nextcloud backup failed" >&2
echo "Step: ${current_step}" >&2
echo "Line: ${LINENO}" >&2
echo "Command: ${BASH_COMMAND}" >&2
echo "Return code: ${rc}" >&2
exit "${rc}"
' ERR
current_step="creating backup directory"
echo "Creating backup directory: {{ backup_dir }}"
mkdir -p {{ backup_dir | quote }}
current_step="enabling maintenance mode"
echo "Enabling Nextcloud maintenance mode"
{{ docker_cmd }} exec -u www-data \
{{ 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"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
sh -c '
tar czf /tmp/nextcloud_conf.tgz \
-C /var/www/html \
config custom_apps
'
current_step="copying configuration archive"
echo "Copying configuration archive"
{{ docker_cmd }} cp \
{{ nextcloud_container | quote }}:/tmp/nextcloud_conf.tgz \
{{ (backup_dir ~ '/nextcloud_conf.tgz') | quote }}
current_step="removing temporary configuration archive"
echo "Removing temporary configuration archive"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
rm -f /tmp/nextcloud_conf.tgz
current_step="creating database dump"
echo "Backing up Nextcloud database"
{{ docker_cmd }} exec \
{{ nextcloud_db_container | quote }} \
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"
fi
' > {{ (backup_dir ~ '/db.sql') | quote }}
current_step="validating configuration archive"
echo "Validating configuration archive"
test -s {{ (backup_dir ~ '/nextcloud_conf.tgz') | quote }}
current_step="validating database dump"
echo "Validating database dump"
test -s {{ (backup_dir ~ '/db.sql') | quote }}
current_step="disabling maintenance mode after backup"
echo "Disabling Nextcloud maintenance mode after backup"
{{ docker_cmd }} exec -u www-data \
{{ 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"
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 "Maintenance mode is still enabled after backup" >&2
exit 1
fi
echo "Backup completed successfully"
echo "Backup directory: {{ backup_dir }}"
# -------------------------------------------------------------------------
# Upgrade script
# -------------------------------------------------------------------------
upgrade_script: |
set -Eeuo pipefail
current_step="upgrade initialization"
trap '
rc=$?
echo >&2
echo "Nextcloud upgrade failed" >&2
echo "Step: ${current_step}" >&2
echo "Line: ${LINENO}" >&2
echo "Command: ${BASH_COMMAND}" >&2
echo "Return code: ${rc}" >&2
exit "${rc}"
' ERR
current_step="validating Docker Compose configuration"
echo "Validating Docker Compose configuration"
{{ docker_cmd }} compose \
-p {{ nextcloud_project | quote }} \
-f {{ nextcloud_compose_file | quote }} \
config --quiet
current_step="pulling Nextcloud image"
echo "Pulling the current Nextcloud image"
{{ docker_cmd }} compose \
-p {{ nextcloud_project | quote }} \
-f {{ nextcloud_compose_file | quote }} \
pull {{ nextcloud_service | quote }}
current_step="recreating Nextcloud container"
echo "Recreating the Nextcloud container"
{{ docker_cmd }} compose \
-p {{ nextcloud_project | quote }} \
-f {{ nextcloud_compose_file | quote }} \
up -d \
--no-deps \
--force-recreate \
{{ nextcloud_service | quote }}
current_step="waiting for Nextcloud container"
echo "Waiting for the Nextcloud container"
container_deadline=$((SECONDS + 180))
while true
do
container_state="$(
docker inspect \
--format '{% raw %}{{.State.Running}}{% endraw %}' \
{{ nextcloud_container | quote }} \
2>/dev/null || true
)"
if [ "${container_state}" = "true" ]; then
break
fi
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 initialization"
echo "Waiting for Nextcloud container initialization"
initialization_deadline=$((SECONDS + 300))
while true
do
if {{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
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 "${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="validating Nextcloud PHP runtime"
echo "Validating Nextcloud PHP runtime"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
php -r '
require "/var/www/html/version.php";
if (empty($OC_Version)) {
fwrite(STDERR, "Nextcloud version is unavailable\n");
exit(1);
}
echo "Nextcloud code version: ";
echo implode(".", $OC_Version), PHP_EOL;
'
current_step="validating bundled application files"
echo "Validating bundled Nextcloud application files"
{{ docker_cmd }} exec \
{{ nextcloud_container | quote }} \
php -r '
$infoFile = "/var/www/html/apps/activity/appinfo/info.xml";
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
current_step="updating Nextcloud applications"
echo "Updating Nextcloud applications"
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ app:update --all
current_step="running database upgrade after application updates"
echo "Running database upgrade after application updates"
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ upgrade --no-interaction
current_step="running Nextcloud maintenance repair"
echo "Running Nextcloud maintenance repair"
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ maintenance:repair --include-expensive
current_step="reading final Nextcloud status"
echo "Reading Nextcloud status"
{{ docker_cmd }} exec -u www-data \
{{ 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 |
{{ docker_cmd }} exec -i -u www-data \
{{ nextcloud_container | quote }} \
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"
# -------------------------------------------------------------------------
# Post-upgrade script
# -------------------------------------------------------------------------
postcheck_script: |
set -Eeuo pipefail
current_step="post-upgrade initialization"
trap '
rc=$?
echo >&2
echo "Post-upgrade check failed" >&2
echo "Step: ${current_step}" >&2
echo "Line: ${LINENO}" >&2
echo "Command: ${BASH_COMMAND}" >&2
echo "Return code: ${rc}" >&2
exit "${rc}"
' ERR
current_step="checking required containers"
echo "Checking required containers after upgrade"
for container in \
{{ nextcloud_container | quote }} \
{{ nextcloud_db_container | quote }} \
{{ redis_container | quote }}
do
state="$(
docker inspect \
--format '{% raw %}{{.State.Running}}{% endraw %}' \
"${container}" \
2>/dev/null || true
)"
if [ "${state}" != "true" ]; then
echo "Container ${container} is not running" >&2
exit 1
fi
done
current_step="checking MariaDB"
echo "Checking MariaDB after upgrade"
{{ docker_cmd }} exec \
{{ nextcloud_db_container | quote }} \
sh -c '
if command -v mariadb-admin >/dev/null 2>&1; then
exec mariadb-admin ping \
-h 127.0.0.1 \
--silent
else
exec mysqladmin ping \
-h 127.0.0.1 \
--silent
fi
'
current_step="checking Redis"
echo "Checking Redis after upgrade"
{{ docker_cmd }} exec \
{{ redis_container | quote }} \
redis-cli -h 127.0.0.1 ping |
grep -qx PONG
current_step="checking Nextcloud status"
echo "Checking Nextcloud status after upgrade"
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ status
current_step="checking Nextcloud maintenance 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
current_step="checking database upgrade state"
needs_db_upgrade="$(
{{ docker_cmd }} exec -u www-data \
{{ nextcloud_container | quote }} \
php occ status --output=json |
{{ docker_cmd }} exec -i -u www-data \
{{ nextcloud_container | quote }} \
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:
- name: Validate requested Nextcloud version
ansible.builtin.assert:
that:
- nextcloud_version | length > 0
- 'nextcloud_version is match("^[0-9]+[.][0-9]+[.][0-9]+$")'
fail_msg: >-
NEXTCLOUD_VERSION is required and must have format X.Y.Z,
for example 34.0.3.
quiet: true
- name: Validate VM connection variables
ansible.builtin.assert:
that:
- vm_ip is defined
- vm_ip | string | trim | length > 0
- vm_user is defined
- vm_user | string | trim | length > 0
- vm_password | string | length > 0
- retry_count | int > 0
fail_msg: >-
Missing or invalid vm_ip, vm_user, vm_pass or RETRIES.
Check the attached Semaphore Variable Group.
quiet: true
no_log: true
- name: Generate backup timestamp
ansible.builtin.set_fact:
backup_timestamp: "{{ now(utc=true, fmt='%Y-%m-%d-%H%M%S') }}"
changed_when: false
- name: Set backup directory
ansible.builtin.set_fact:
backup_dir: >-
{{ backup_root }}/backup-{{ backup_timestamp }}
changed_when: false
tasks:
- name: Ensure sshpass is installed
ansible.builtin.apt:
name: sshpass
state: present
update_cache: true
# -------------------------------------------------------------------------
# Compose synchronization
# -------------------------------------------------------------------------
- name: Nextcloud | Check Compose file in repository
ansible.builtin.stat:
path: "{{ nextcloud_compose_local_file }}"
register: nextcloud_compose_local_stat
delegate_to: localhost
run_once: true
changed_when: false
- name: Nextcloud | Fail when repository Compose file is missing
ansible.builtin.assert:
that:
- nextcloud_compose_local_stat.stat.exists
- nextcloud_compose_local_stat.stat.isreg
- nextcloud_compose_local_stat.stat.size | int > 0
fail_msg: >-
Nextcloud Compose file is missing or empty:
{{ nextcloud_compose_local_file }}
quiet: true
run_once: true
- name: Nextcloud | Synchronize Compose file to VM
block:
- name: Nextcloud | Render Compose file on Proxmox host
ansible.builtin.template:
src: "{{ nextcloud_compose_local_file }}"
dest: "{{ nextcloud_compose_controller_staging_file }}"
mode: "0600"
- name: Nextcloud | Upload Compose file to VM staging path
ansible.builtin.command:
argv:
- sshpass
- -e
- scp
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- "{{ nextcloud_compose_controller_staging_file }}"
- "{{ vm_user }}@{{ vm_ip }}:{{ nextcloud_compose_vm_staging_file }}"
environment:
SSHPASS: "{{ vm_password }}"
register: nextcloud_compose_upload
changed_when: false
no_log: true
- name: Nextcloud | Validate and install Compose file on VM
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(compose_install_script | quote))
if vm_use_sudo
else
('bash -c ' ~
(compose_install_script | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nextcloud_compose_install
changed_when: >-
'Compose file updated' in
(nextcloud_compose_install.stdout | default(''))
failed_when: false
no_log: true
- name: Nextcloud | Show Compose installation result
ansible.builtin.debug:
msg: |
Compose installation return code:
{{ nextcloud_compose_install.rc }}
STDOUT:
{{ nextcloud_compose_install.stdout | default('') | trim }}
STDERR:
{{ nextcloud_compose_install.stderr | default('') | trim }}
when:
- debug_level == 1 or nextcloud_compose_install.rc != 0
- name: Nextcloud | Fail when Compose installation failed
ansible.builtin.assert:
that:
- nextcloud_compose_install.rc == 0
fail_msg: |
Failed to install Nextcloud Compose file.
Source file:
{{ nextcloud_compose_local_file }}
Destination file:
{{ nextcloud_compose_file }}
Return code:
{{ nextcloud_compose_install.rc }}
STDOUT:
{{ nextcloud_compose_install.stdout | default('') | trim }}
STDERR:
{{ nextcloud_compose_install.stderr | default('') | trim }}
quiet: true
always:
- name: Nextcloud | Remove staged Compose file from Proxmox host
ansible.builtin.file:
path: "{{ nextcloud_compose_controller_staging_file }}"
state: absent
changed_when: false
- name: Nextcloud | Remove staged Compose file from VM
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- "{{ vm_user }}@{{ vm_ip }}"
- >-
rm -f
{{ nextcloud_compose_vm_staging_file | quote }}
environment:
SSHPASS: "{{ vm_password }}"
changed_when: false
failed_when: false
no_log: true
# -------------------------------------------------------------------------
# Preflight
# -------------------------------------------------------------------------
- name: Nextcloud | Run preflight checks on VM
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(preflight_script | quote))
if vm_use_sudo
else
('bash -c ' ~
(preflight_script | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nc_preflight
changed_when: false
failed_when: false
no_log: true
- name: Nextcloud | Show preflight result
ansible.builtin.debug:
msg: |
Preflight return code: {{ nc_preflight.rc }}
STDOUT:
{{ nc_preflight.stdout | default('') | trim }}
STDERR:
{{ nc_preflight.stderr | default('') | trim }}
when:
- debug_level == 1 or nc_preflight.rc != 0
- name: Nextcloud | Fail when preflight checks failed
ansible.builtin.assert:
that:
- nc_preflight.rc == 0
fail_msg: |
Nextcloud preflight checks failed.
Return code:
{{ nc_preflight.rc }}
STDOUT:
{{ nc_preflight.stdout | default('') | trim }}
STDERR:
{{ nc_preflight.stderr | default('') | trim }}
quiet: true
# -------------------------------------------------------------------------
# Current version
# -------------------------------------------------------------------------
- name: Nextcloud | Read current version before upgrade
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(nextcloud_version_command | quote))
if vm_use_sudo
else
('bash -c ' ~
(nextcloud_version_command | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nc_version_before
changed_when: false
failed_when: false
no_log: true
- name: Nextcloud | Print current version
ansible.builtin.debug:
msg: >-
Current version:
{{
nc_version_before.stdout
| default('Nextcloud version is unavailable')
| trim
}}
# -------------------------------------------------------------------------
# Backup and upgrade
# -------------------------------------------------------------------------
- name: Nextcloud | Run backup and upgrade
block:
- name: Nextcloud | Create configuration and database backup
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(backup_script | quote))
if vm_use_sudo
else
('bash -c ' ~
(backup_script | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nc_backup
changed_when: nc_backup.rc == 0
failed_when: false
no_log: true
- name: Nextcloud | Show backup output
ansible.builtin.debug:
msg: |
Backup directory: {{ backup_dir }}
Backup return code: {{ nc_backup.rc }}
STDOUT:
{{ nc_backup.stdout | default('') | trim }}
STDERR:
{{ nc_backup.stderr | default('') | trim }}
when:
- debug_level == 1 or nc_backup.rc != 0
- name: Nextcloud | Fail when backup failed
ansible.builtin.assert:
that:
- nc_backup.rc == 0
fail_msg: |
Nextcloud backup failed.
Backup directory:
{{ backup_dir }}
Return code:
{{ nc_backup.rc }}
STDOUT:
{{ nc_backup.stdout | default('') | trim }}
STDERR:
{{ nc_backup.stderr | default('') | trim }}
quiet: true
- name: Nextcloud | Pull image and run upgrade
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(upgrade_script | quote))
if vm_use_sudo
else
('bash -c ' ~
(upgrade_script | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nc_upgrade
changed_when: nc_upgrade.rc == 0
failed_when: false
no_log: true
- name: Nextcloud | Show upgrade output
ansible.builtin.debug:
msg: |
Upgrade return code: {{ nc_upgrade.rc }}
STDOUT:
{{ nc_upgrade.stdout | default('') | trim }}
STDERR:
{{ nc_upgrade.stderr | default('') | trim }}
- name: Nextcloud | Fail when upgrade command failed
ansible.builtin.assert:
that:
- nc_upgrade.rc == 0
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 }}
STDOUT:
{{ nc_upgrade.stdout | default('') | trim }}
STDERR:
{{ nc_upgrade.stderr | default('') | trim }}
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:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(nextcloud_maintenance_off_command | quote))
if vm_use_sudo
else
('bash -c ' ~
(nextcloud_maintenance_off_command | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nc_maint_off
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:
msg: |
Maintenance return code: {{ nc_maint_off.rc }}
STDOUT:
{{ nc_maint_off.stdout | default('') | trim }}
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
ansible.builtin.assert:
that:
- nc_maint_off.rc == 0
fail_msg: |
Failed to disable Nextcloud maintenance mode.
Return code:
{{ nc_maint_off.rc }}
STDOUT:
{{ nc_maint_off.stdout | default('') | trim }}
STDERR:
{{ nc_maint_off.stderr | default('') | trim }}
quiet: true
when:
- nc_upgrade is not defined or nc_upgrade.rc == 0
# -------------------------------------------------------------------------
# Public Nextcloud readiness
# -------------------------------------------------------------------------
- name: Nextcloud | Wait for public status.php
ansible.builtin.uri:
url: "{{ nextcloud_status_url }}"
method: GET
return_content: true
validate_certs: true
status_code: 200
timeout: 20
register: nc_status
delegate_to: localhost
run_once: true
retries: "{{ retry_count }}"
delay: 4
until:
- nc_status.status | default(0) | int == 200
- nc_status.json is defined
- nc_status.json.installed | default(false) | bool
- not (nc_status.json.maintenance | default(true) | bool)
- not (nc_status.json.needsDbUpgrade | default(true) | bool)
changed_when: false
# -------------------------------------------------------------------------
# Post-upgrade checks
# -------------------------------------------------------------------------
- name: Nextcloud | Run post-upgrade checks on VM
ansible.builtin.command:
argv:
- sshpass
- -e
- ssh
- -o
- StrictHostKeyChecking=no
- -o
- UserKnownHostsFile=/dev/null
- -o
- LogLevel=ERROR
- -o
- ConnectTimeout=15
- -o
- ServerAliveInterval=10
- -o
- ServerAliveCountMax=3
- "{{ vm_user }}@{{ vm_ip }}"
- >-
{{
('sudo -n bash -c ' ~
(postcheck_script | quote))
if vm_use_sudo
else
('bash -c ' ~
(postcheck_script | quote))
}}
environment:
SSHPASS: "{{ vm_password }}"
register: nc_postcheck
changed_when: false
failed_when: false
no_log: true
- name: Nextcloud | Show post-upgrade checks
ansible.builtin.debug:
msg: |
Post-upgrade return code: {{ nc_postcheck.rc }}
STDOUT:
{{ nc_postcheck.stdout | default('') | trim }}
STDERR:
{{ nc_postcheck.stderr | default('') | trim }}
when:
- debug_level == 1 or nc_postcheck.rc != 0
- name: Nextcloud | Fail when post-upgrade checks failed
ansible.builtin.assert:
that:
- nc_postcheck.rc == 0
fail_msg: |
Nextcloud post-upgrade checks failed.
Return code:
{{ nc_postcheck.rc }}
STDOUT:
{{ nc_postcheck.stdout | default('') | trim }}
STDERR:
{{ nc_postcheck.stderr | default('') | trim }}
quiet: true
# -------------------------------------------------------------------------
# External Collabora checks
# -------------------------------------------------------------------------
- name: Nextcloud | Check external Collabora root endpoint
ansible.builtin.uri:
url: "{{ collabora_url }}"
method: GET
return_content: true
validate_certs: true
status_code: 200
timeout: 20
register: collabora_root
delegate_to: localhost
run_once: true
changed_when: false
failed_when: >-
(collabora_root.status | default(0) | int != 200)
or
('OK' not in (collabora_root.content | default('')))
- name: Nextcloud | Check external Collabora discovery endpoint
ansible.builtin.uri:
url: "{{ collabora_discovery_url }}"
method: GET
return_content: true
validate_certs: true
status_code: 200
timeout: 20
register: collabora_discovery
delegate_to: localhost
run_once: true
changed_when: false
failed_when: >-
(collabora_discovery.status | default(0) | int != 200)
or
('<wopi-discovery' not in
(collabora_discovery.content | default('')))
# -------------------------------------------------------------------------
# Final result
# -------------------------------------------------------------------------
- name: Nextcloud | Print final status
ansible.builtin.debug:
msg: |
Nextcloud update completed successfully.
Version before update:
{{ nc_version_before.stdout | default('?') | trim }}
Version after update:
{{ nc_status.json.versionstring
| default(nc_status.json.version | default('?')) }}
Backup directory:
{{ backup_dir }}
Compose source:
{{ nextcloud_compose_local_file }}
Compose destination:
{{ nextcloud_compose_file }}
Installed:
{{ nc_status.json.installed | default('?') }}
Maintenance:
{{ nc_status.json.maintenance | default('?') }}
Needs database upgrade:
{{ nc_status.json.needsDbUpgrade | default('?') }}
Nextcloud container: OK
MariaDB container: OK
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
run_once: true