- name: Resilient & Environment-Aware Nextcloud Upgrade (Standalone) hosts: all become: yes vars: # ---------------------------------------------------------------------- # ⚠️ REQUIRED USER VARIABLES (Set by you) # ---------------------------------------------------------------------- # 1. The root path to your Nextcloud installation (Confirmed) nextcloud_dir: /var/www/nextcloud # 2. The *exact* PHP CLI binary path (Confirmed or set to standard) php_cli_path: /usr/bin/php # 3. The service name for your webserver or PHP-FPM (Set to standard) web_service_name: httpd # ---------------------------------------------------------------------- # INTERNAL PLAYBOOK VARIABLES # ---------------------------------------------------------------------- # Environment for all occ commands occ_env: TMPDIR: /tmp # Core apps to NOT disable during upgrade core_apps: - activity - twofactor_nextcloud_notification - files - settings - dav - updater - workflowengine - theming - user_ldap - dashboard - federation - updatestatus - accessibility - serverinfo tasks: - name: Reminder - ensure backups exist debug: msg: "⚠️ CRITICAL: Ensure you have valid DB + data directory backups BEFORE PROCEEDING!" - name: Pause playbook for 10 seconds to allow backup cancellation pause: seconds: 10 prompt: "Press Ctrl+C and then 'A' to abort if you do not have backups." # ---------------------------------------------------------------------- # 🛡️ STAGE 1: PREFLIGHT CHECKS & FACT GATHERING # ---------------------------------------------------------------------- - name: 1.1 - Validate 'nextcloud_dir' stat: path: "{{ nextcloud_dir }}/config/config.php" register: config_stat - name: 1.2 - Fail if 'config.php' not found fail: msg: | 'config.php' not found at '{{ nextcloud_dir }}/config/config.php'. Please correct the 'nextcloud_dir' variable in the playbook. when: not config_stat.stat.exists - name: 1.3 - 🧠 DISCOVER 'nc_web_user' from 'config.php' owner set_fact: nc_web_user: "{{ config_stat.stat.owner }}" - name: 1.4 - Display discovered file owner debug: msg: "Discovered Nextcloud web user: {{ nc_web_user }}" - name: 1.5 - Validate 'php_cli_path' stat: path: "{{ php_cli_path }}" register: php_stat - name: 1.6 - Fail if 'php_cli_path' is not an executable file fail: msg: | 'php_cli_path' ('{{ php_cli_path }}') does not exist or is not executable. Please correct the 'php_cli_path' variable. when: not php_stat.stat.exists or not php_stat.stat.isexe - name: 1.7 - Validate 'web_service_name' service_facts: - name: 1.8 - Fail if 'web_service_name' is not a known service fail: msg: | 'web_service_name' ('{{ web_service_name }}') is not a valid service on this host. Please correct the 'web_service_name' variable. when: "web_service_name not in ansible_facts.services" - name: 1.9 - Get current NC status (and check if installed) command: "{{ php_cli_path }} occ status" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" register: occ_status changed_when: false failed_when: "'installed: true' not in occ_status.stdout" - name: 1.10 - Parse and show current version set_fact: current_version: "{{ occ_status.stdout | regex_search('versionstring: (.*)', '\\1') | first | default('unknown') }}" when: occ_status.stdout is defined - name: 1.11 - Show current version debug: msg: "Current Nextcloud version: {{ current_version }}" # ---------------------------------------------------------------------- # 🔄 STAGE 2: PRE-UPGRADE PREPARATION # ---------------------------------------------------------------------- - name: 2.1 - Get list of installed apps command: "{{ php_cli_path }} occ app:list" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" register: app_list changed_when: false - name: 2.2 - Disable all non-core 3rd party apps command: "{{ php_cli_path }} occ app:disable {{ item }}" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" loop: "{{ app_list.stdout_lines | select('match', '^[ ]*-[ ](.*):[ ]enabled$') | map('regex_replace', '^[ ]*-[ ](.*):[ ]enabled$', '\\1') | list }}" register: app_disable_result when: item not in core_apps ignore_errors: yes - name: 2.3 - Save list of successfully disabled apps for re-enabling set_fact: disabled_apps: "{{ app_disable_result.results | selectattr('changed', 'defined') | selectattr('changed') | map(attribute='item') | list }}" when: app_disable_result is defined and app_disable_result.results is defined # ---------------------------------------------------------------------- # 🚀 STAGE 3: TRANSACTIONAL UPGRADE BLOCK # ---------------------------------------------------------------------- - name: 3.0 - Perform Nextcloud Upgrade (Transactional) block: - name: 3.1 - Enable maintenance mode command: "{{ php_cli_path }} occ maintenance:mode --on" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" - name: 3.2 - Run Nextcloud CLI updater (with retry logic) command: "{{ php_cli_path }} updater/updater.phar --no-interaction" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" register: updater_result until: - updater_result.rc == 0 - "'Update successful' in updater_result.stdout or 'Nextcloud is already latest version' in updater_result.stdout" retries: 3 delay: 10 failed_when: - updater_result.rc != 0 - "'Nextcloud is already latest version' not in updater_result.stdout" - name: 3.3 - Run occ upgrade (Critical Database Step) command: "{{ php_cli_path }} occ upgrade" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" # CRITICAL: This must fail if the upgrade fails. - name: 3.4 - Run optional cleanup commands command: "{{ item }}" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" loop: - "{{ php_cli_path }} occ db:add-missing-indices" - "{{ php_cli_path }} occ db:convert-filecache-bigint --no-interaction" ignore_errors: yes - name: 3.5 - Disable maintenance mode (SUCCESS) command: "{{ php_cli_path }} occ maintenance:mode --off" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" - name: 3.6 - Re-enable previously disabled 3rd party apps command: "{{ php_cli_path }} occ app:enable {{ item }}" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" loop: "{{ disabled_apps | default([]) }}" when: disabled_apps is defined and disabled_apps | length > 0 ignore_errors: yes - name: 3.7 - Restart web service service: name: "{{ web_service_name }}" state: restarted - name: 3.8 - Verify new version command: "{{ php_cli_path }} occ status" args: chdir: "{{ nextcloud_dir }}" become_user: "{{ nc_web_user }}" environment: "{{ occ_env }}" register: occ_status_after changed_when: false - name: 3.9 - Display new version info debug: var: occ_status_after.stdout - name: 3.10 - ✅ Upgrade Complete debug: msg: "Nextcloud successfully upgraded!" # ---------------------------------------------------------------------- # 🆘 STAGE 4: RESCUE BLOCK (This ONLY runs if STAGE 3 fails) # ---------------------------------------------------------------------- rescue: - name: ❌ UPGRADE FAILED - REPORTING debug: msg: | !!!!!!!!!! CRITICAL UPGRADE FAILURE !!!!!!!!!! The playbook has failed during the upgrade block. The server is being LEFT IN MAINTENANCE MODE for manual inspection. Host: {{ inventory_hostname }} Discovered Web User: {{ nc_web_user }} Please SSH to the host and check logs to fix manually. Logs to check: - {{ nextcloud_dir }}/data/updater.log - {{ nextcloud_dir }}/data/nextcloud.log Once fixed, run this command manually as the web user: '{{ php_cli_path }} occ maintenance:mode --off' - name: 4.1 - Fail the playbook run fail: msg: "Nextcloud upgrade failed. Host is in maintenance mode."