# 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: # Normalized values from Semaphore Variable Group vm_use_sudo: "{{ use_sudo | default(false) | bool }}" debug_level: "{{ DEBUG | default(0) | int }}" retry_count: "{{ RETRIES | default(25) | int }}" # Nextcloud configuration nextcloud_project: "nextcloud-collabora" nextcloud_compose_file: "/data/compose/nextcloud/docker-compose-nextcloud.yml" nextcloud_service: "nextcloud" nextcloud_container: "nextcloud" nextcloud_db_container: "nextcloud-db" nextcloud_base_url: "https://cloud.martinfencl.eu" nextcloud_status_url: "{{ nextcloud_base_url }}/status.php" backup_timestamp: "{{ lookup('pipe', 'date +%F-%H%M%S') }}" backup_dir: "/data/compose/nextcloud/backup-{{ backup_timestamp }}" docker_prefix: >- unalias docker 2>/dev/null || true; export DOCKER_CLI_HINTS=0; command docker nextcloud_version_command: >- docker exec -u www-data {{ nextcloud_container }} php occ -V nextcloud_maintenance_off_command: >- docker exec -u www-data {{ nextcloud_container }} php occ maintenance:mode --off # Commands executed before the container update nextcloud_backup_commands: - >- mkdir -p {{ backup_dir | quote }} - >- {{ docker_prefix }} exec -u www-data {{ nextcloud_container }} php occ maintenance:mode --on - >- {{ docker_prefix }} exec {{ nextcloud_container }} sh -c 'tar czf /tmp/nextcloud_conf.tgz -C /var/www/html config custom_apps' - >- {{ docker_prefix }} cp {{ nextcloud_container }}:/tmp/nextcloud_conf.tgz {{ (backup_dir ~ '/nextcloud_conf.tgz') | quote }} - >- {{ docker_prefix }} exec {{ nextcloud_container }} rm -f /tmp/nextcloud_conf.tgz - >- set -o pipefail; {{ docker_prefix }} exec {{ nextcloud_db_container }} sh -c 'if command -v mariadb-dump >/dev/null 2>&1; then exec mariadb-dump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"; else exec mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"; fi' > {{ (backup_dir ~ '/db.sql') | quote }} - >- test -s {{ (backup_dir ~ '/nextcloud_conf.tgz') | quote }} - >- test -s {{ (backup_dir ~ '/db.sql') | quote }} # Commands executed while maintenance mode is enabled nextcloud_upgrade_commands: - >- {{ docker_prefix }} compose -p {{ nextcloud_project | quote }} -f {{ nextcloud_compose_file | quote }} pull {{ nextcloud_service | quote }} - >- {{ docker_prefix }} compose -p {{ nextcloud_project | quote }} -f {{ nextcloud_compose_file | quote }} up -d --no-deps --force-recreate {{ nextcloud_service | quote }} - >- timeout 180s bash -c 'until docker inspect --format="{{ '{{' }}.State.Running{{ '}}' }}" {{ nextcloud_container | quote }} 2>/dev/null | grep -qx true; do sleep 3; done' - >- timeout 180s bash -c 'until docker exec -u www-data {{ nextcloud_container | quote }} php occ status >/dev/null 2>&1; do sleep 3; done' - >- {{ docker_prefix }} exec -u www-data {{ nextcloud_container }} php occ upgrade - >- {{ docker_prefix }} exec -u www-data {{ nextcloud_container }} php occ app:update --all - >- {{ docker_prefix }} exec -u www-data {{ nextcloud_container }} php occ maintenance:repair --include-expensive - >- {{ docker_prefix }} exec -u www-data {{ nextcloud_container }} php occ status pre_tasks: - 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_pass is defined - vm_pass | string | length > 0 - retry_count | int > 0 fail_msg: >- Missing or invalid vm_ip, vm_user, vm_pass or RETRIES. Configure them in the attached Semaphore Variable Group. quiet: true no_log: true tasks: - name: Ensure sshpass is installed ansible.builtin.apt: name: sshpass state: present update_cache: true - name: Nextcloud | Show current version before upgrade ansible.builtin.command: argv: - sshpass - -e - ssh - -o - StrictHostKeyChecking=no - -o - UserKnownHostsFile=/dev/null - -o - ConnectTimeout=15 - "{{ vm_user }}@{{ vm_ip }}" - >- {{ ('sudo -n bash -lc ' ~ (nextcloud_version_command | quote)) if vm_use_sudo else ('bash -lc ' ~ (nextcloud_version_command | quote)) }} environment: SSHPASS: "{{ vm_pass }}" register: nc_version_before changed_when: false failed_when: false when: debug_level == 1 - name: Nextcloud | Print current version ansible.builtin.debug: msg: >- {{ nc_version_before.stdout | default('Nextcloud version is unavailable') | trim }} when: debug_level == 1 - name: Nextcloud | Run backup and upgrade block: # --------------------------------------------------------------------- # Backup # --------------------------------------------------------------------- - name: Nextcloud | Run backup commands on VM ansible.builtin.command: argv: - sshpass - -e - ssh - -o - StrictHostKeyChecking=no - -o - UserKnownHostsFile=/dev/null - -o - ConnectTimeout=15 - -o - ServerAliveInterval=10 - -o - ServerAliveCountMax=3 - "{{ vm_user }}@{{ vm_ip }}" - >- {{ ('sudo -n bash -lc ' ~ (item | quote)) if vm_use_sudo else ('bash -lc ' ~ (item | quote)) }} environment: SSHPASS: "{{ vm_pass }}" loop: "{{ nextcloud_backup_commands }}" loop_control: index_var: backup_index label: "backup-cmd-{{ backup_index }}" register: nc_backup_cmds changed_when: false failed_when: false - name: Nextcloud | Show backup command outputs ansible.builtin.debug: msg: | CMD: {{ item.item }} RC: {{ item.rc }} STDOUT: {{ item.stdout | default('') | trim }} STDERR: {{ item.stderr | default('') | trim }} loop: "{{ nc_backup_cmds.results }}" loop_control: index_var: backup_debug_index label: "backup-cmd-{{ backup_debug_index }}" when: debug_level == 1 - name: Nextcloud | Fail if any backup command failed ansible.builtin.assert: that: - item.rc == 0 fail_msg: | Nextcloud backup command failed. Command: {{ item.item }} RC: {{ item.rc }} STDERR: {{ item.stderr | default('') | trim }} quiet: true loop: "{{ nc_backup_cmds.results }}" loop_control: index_var: backup_assert_index label: "backup-cmd-{{ backup_assert_index }}" # --------------------------------------------------------------------- # Upgrade # --------------------------------------------------------------------- - name: Nextcloud | Run upgrade commands on VM ansible.builtin.command: argv: - sshpass - -e - ssh - -o - StrictHostKeyChecking=no - -o - UserKnownHostsFile=/dev/null - -o - ConnectTimeout=15 - -o - ServerAliveInterval=10 - -o - ServerAliveCountMax=3 - "{{ vm_user }}@{{ vm_ip }}" - >- {{ ('sudo -n bash -lc ' ~ (item | quote)) if vm_use_sudo else ('bash -lc ' ~ (item | quote)) }} environment: SSHPASS: "{{ vm_pass }}" loop: "{{ nextcloud_upgrade_commands }}" loop_control: index_var: upgrade_index label: "upgrade-cmd-{{ upgrade_index }}" register: nc_upgrade_cmds changed_when: false failed_when: false - name: Nextcloud | Show upgrade command outputs ansible.builtin.debug: msg: | CMD: {{ item.item }} RC: {{ item.rc }} STDOUT: {{ item.stdout | default('') | trim }} STDERR: {{ item.stderr | default('') | trim }} loop: "{{ nc_upgrade_cmds.results }}" loop_control: index_var: upgrade_debug_index label: "upgrade-cmd-{{ upgrade_debug_index }}" when: debug_level == 1 - name: Nextcloud | Fail if any upgrade command failed ansible.builtin.assert: that: - item.rc == 0 fail_msg: | Nextcloud upgrade command failed. Command: {{ item.item }} RC: {{ item.rc }} STDERR: {{ item.stderr | default('') | trim }} quiet: true loop: "{{ nc_upgrade_cmds.results }}" loop_control: index_var: upgrade_assert_index label: "upgrade-cmd-{{ upgrade_assert_index }}" always: - name: Nextcloud | Disable maintenance mode ansible.builtin.command: argv: - sshpass - -e - ssh - -o - StrictHostKeyChecking=no - -o - UserKnownHostsFile=/dev/null - -o - ConnectTimeout=15 - "{{ vm_user }}@{{ vm_ip }}" - >- {{ ('sudo -n bash -lc ' ~ (nextcloud_maintenance_off_command | quote)) if vm_use_sudo else ('bash -lc ' ~ (nextcloud_maintenance_off_command | quote)) }} environment: SSHPASS: "{{ vm_pass }}" register: nc_maint_off changed_when: false failed_when: false - name: Nextcloud | Show maintenance mode result ansible.builtin.debug: msg: | RC: {{ nc_maint_off.rc }} STDOUT: {{ nc_maint_off.stdout | default('') | trim }} STDERR: {{ nc_maint_off.stderr | default('') | trim }} when: debug_level == 1 - name: Nextcloud | Fail if maintenance mode could not be disabled ansible.builtin.assert: that: - nc_maint_off.rc == 0 fail_msg: | Failed to disable Nextcloud maintenance mode. RC: {{ nc_maint_off.rc }} STDERR: {{ nc_maint_off.stderr | default('') | trim }} quiet: true # ------------------------------------------------------------------------- # Readiness check # ------------------------------------------------------------------------- - name: Nextcloud | Wait for status.php ansible.builtin.uri: url: "{{ nextcloud_status_url }}" method: GET return_content: true validate_certs: true status_code: 200 register: nc_status delegate_to: localhost run_once: true retries: "{{ retry_count }}" delay: 4 until: - nc_status.status | default(0) == 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 - name: Nextcloud | Print final status ansible.builtin.debug: msg: >- Nextcloud {{ nc_status.json.version | default('?') }} (installed={{ nc_status.json.installed | default('?') }}, maintenance={{ nc_status.json.maintenance | default('?') }}, needsDbUpgrade={{ nc_status.json.needsDbUpgrade | default('?') }})