From 9366ff091266db77d5ac4b92c0c3c13e6bfd45ee Mon Sep 17 00:00:00 2001 From: "martin.fencl" Date: Wed, 24 Dec 2025 00:58:57 +0100 Subject: [PATCH] add v3 --- nextcloud/update_nextcloud_v3.yml | 343 ++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 nextcloud/update_nextcloud_v3.yml diff --git a/nextcloud/update_nextcloud_v3.yml b/nextcloud/update_nextcloud_v3.yml new file mode 100644 index 0000000..8cd5733 --- /dev/null +++ b/nextcloud/update_nextcloud_v3.yml @@ -0,0 +1,343 @@ +# nextcloud/update_nextcloud.yml + +- name: Update Nextcloud on VM via Proxmox + hosts: proxmox_nextcloud + gather_facts: false + become: true + become_user: root + become_method: sudo + + vars: + # --- Connection to VM (provided by Semaphore env vars) --- + vm_ip: "{{ lookup('env', 'VM_IP') }}" + vm_user: "{{ lookup('env', 'VM_USER') }}" + vm_pass: "{{ lookup('env', 'VM_PASS') }}" + use_sudo: false + + # --- Debug / retries --- + DEBUG: "{{ lookup('env', 'DEBUG') | default(0) | int }}" + RETRIES: "{{ lookup('env', 'RETRIES') | default(25) | int }}" + + # --- Nextcloud specifics --- + nextcloud_project: "nextcloud-collabora" + nextcloud_compose_file: "/data/compose/nextcloud/docker-compose-nextcloud.yml" + nextcloud_service: "nextcloud" + + # Backup directory on the VM (timestamped on controller) + backup_dir: "/data/compose/nextcloud/backup-{{ lookup('pipe', 'date +%F-%H%M%S') }}" + + nextcloud_base_url: "https://cloud.martinfencl.eu" + nextcloud_status_url: "{{ nextcloud_base_url }}/status.php" + + # Docker command prefix (consistent behavior and quiet hints) + docker_prefix: "unalias docker 2>/dev/null || true; DOCKER_CLI_HINTS=0; command docker" + + # --- Backup phase commands (run on VM) --- + nextcloud_backup_commands: + - >- + mkdir -p "{{ backup_dir }}" + - >- + {{ docker_prefix }} exec -u www-data nextcloud php occ maintenance:mode --on + - >- + {{ docker_prefix }} exec nextcloud sh -c 'tar czf /tmp/nextcloud_conf.tgz -C /var/www/html config custom_apps' + - >- + {{ docker_prefix }} cp nextcloud:/tmp/nextcloud_conf.tgz "{{ backup_dir }}/nextcloud_conf.tgz" + - >- + {{ docker_prefix }} exec nextcloud rm /tmp/nextcloud_conf.tgz || true + - >- + {{ docker_prefix }} exec nextcloud-db sh -c 'command -v mariadb-dump >/dev/null && mariadb-dump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" || mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' > "{{ backup_dir }}/db.sql" + + tasks: + - name: Ensure sshpass is installed (for password-based SSH) + ansible.builtin.apt: + name: sshpass + state: present + update_cache: yes + + - name: Nextcloud | Show current version before upgrade (DEBUG) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ docker_prefix }} exec -u www-data nextcloud php occ -V || true" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_version_before + changed_when: false + failed_when: false + when: DEBUG == 1 + no_log: "{{ DEBUG == 0 }}" + + # ------------------------- + # Backup phase + # ------------------------- + - name: Nextcloud | Run backup commands on VM (via SSH) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') + item }}" + environment: + SSHPASS: "{{ vm_pass }}" + loop: "{{ nextcloud_backup_commands }}" + loop_control: + index_var: idx + label: "backup-cmd-{{ idx }}" + register: nc_backup_cmds + changed_when: false + failed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | Show outputs of backup commands (DEBUG) + ansible.builtin.debug: + msg: | + CMD: {{ item.item }} + RC: {{ item.rc }} + STDOUT: + {{ (item.stdout | default('')).strip() }} + STDERR: + {{ (item.stderr | default('')).strip() }} + loop: "{{ nc_backup_cmds.results }}" + when: DEBUG == 1 + + - name: Nextcloud | Fail play if any backup command failed + ansible.builtin.assert: + that: "item.rc == 0" + fail_msg: "Nextcloud backup step failed on VM: {{ item.item }} (rc={{ item.rc }})" + success_msg: "All Nextcloud backup commands succeeded." + loop: "{{ nc_backup_cmds.results }}" + loop_control: + index_var: idx + label: "backup-cmd-{{ idx }}" + + # ------------------------- + # Upgrade phase (with always cleanup) + # ------------------------- + - name: Nextcloud | Upgrade block + block: + - name: Nextcloud | Pull image + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} compose -p {{ nextcloud_project }} -f {{ nextcloud_compose_file }} pull {{ nextcloud_service }}" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_pull + changed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | Recreate service + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} compose -p {{ nextcloud_project }} -f {{ nextcloud_compose_file }} up -d --no-deps --force-recreate {{ nextcloud_service }}" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_up + changed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | Ensure maintenance is OFF before occ upgrade + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} exec -u www-data nextcloud php occ maintenance:mode --off || true" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_maint_off_before + changed_when: false + failed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | occ upgrade (must succeed) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} exec -u www-data nextcloud php occ upgrade" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_occ_upgrade + changed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | Update apps (best-effort) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} exec -u www-data nextcloud php occ app:update --all || true" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_app_update + changed_when: false + failed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | Repair (best-effort) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} exec -u www-data nextcloud php occ maintenance:repair --include-expensive || true" + environment: + SSHPASS: "{{ vm_pass }}" + register: nc_repair + changed_when: false + failed_when: false + no_log: "{{ DEBUG == 0 }}" + + rescue: + - name: Nextcloud | Show occ upgrade output (DEBUG) + ansible.builtin.debug: + msg: | + occ upgrade FAILED + RC: {{ nc_occ_upgrade.rc | default('n/a') }} + STDOUT: + {{ (nc_occ_upgrade.stdout | default('')).strip() }} + STDERR: + {{ (nc_occ_upgrade.stderr | default('')).strip() }} + when: DEBUG == 1 + + - name: Nextcloud | Try to force-disable maintenance flag (best-effort) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} exec -u www-data nextcloud php occ config:system:set maintenance --type=boolean --value=false || true" + environment: + SSHPASS: "{{ vm_pass }}" + changed_when: false + failed_when: false + no_log: "{{ DEBUG == 0 }}" + + - name: Nextcloud | Fail explicitly + ansible.builtin.fail: + msg: >- + Nextcloud occ upgrade failed. Check nextcloud.log inside the container (data/nextcloud.log). + stdout={{ (nc_occ_upgrade.stdout | default('') | trim) }} + stderr={{ (nc_occ_upgrade.stderr | default('') | trim) }} + + always: + - name: Nextcloud | Ensure maintenance mode is OFF (always) + ansible.builtin.command: + argv: + - sshpass + - -e + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ ('sudo ' if use_sudo else '') }}{{ docker_prefix }} exec -u www-data nextcloud php occ maintenance:mode --off || true" + environment: + SSHPASS: "{{ vm_pass }}" + changed_when: false + failed_when: false + no_log: "{{ DEBUG == 0 }}" + + # ------------------------- + # Readiness check (status.php) + # ------------------------- + - name: Nextcloud | Wait for status.php (controller first) + ansible.builtin.uri: + url: "{{ nextcloud_status_url }}" + method: GET + return_content: true + validate_certs: true + status_code: 200 + register: nc_status_controller + delegate_to: localhost + run_once: true + retries: "{{ RETRIES }}" + delay: 4 + until: nc_status_controller.status == 200 + failed_when: false + changed_when: false + + - name: Nextcloud | Print concise status summary (DEBUG) + ansible.builtin.debug: + msg: >- + Nextcloud {{ nc_status_controller.json.version | default('?') }} + (installed={{ nc_status_controller.json.installed | default('?') }}, + maintenance={{ nc_status_controller.json.maintenance | default('?') }}, + needsDbUpgrade={{ nc_status_controller.json.needsDbUpgrade | default('?') }}) + when: DEBUG == 1 and nc_status_controller.json is defined