From 14256bcbcea3daa500c72fbb00827828fab02721 Mon Sep 17 00:00:00 2001 From: Pieter Date: Fri, 23 Jan 2026 20:58:25 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Add=20Nextcloud=20major=20version=20upg?= =?UTF-8?q?rade=20playbook=20(v30=E2=86=92v32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created: 2026-01-23 Add automated playbook to safely upgrade Nextcloud from v30 (EOL) to v32 through staged upgrades, respecting Nextcloud's no-version-skip policy. Features: - Pre-upgrade validation (version, disk space, maintenance mode) - Automatic full backup (database + volumes) - Staged upgrades: v30 → v31 → v32 - Per-stage app disabling/enabling - Database migrations (indices, bigint conversion) - Post-upgrade validation and system checks - Rollback instructions in case of failure - Updates docker-compose.yml to 'latest' tag after success Files: - playbooks/260123-upgrade-nextcloud.yml (main playbook) - playbooks/260123-upgrade-nextcloud-stage.yml (stage tasks) Usage: cd ansible/ HCLOUD_TOKEN="..." ansible-playbook -i hcloud.yml \ playbooks/260123-upgrade-nextcloud.yml --limit kikker Safety: - Creates timestamped backup before any changes - Stops containers during volume backup - Verifies version after each stage - Provides rollback commands in output Ready to upgrade kikker from v30.0.17 to v32.x 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude --- .../260123-upgrade-nextcloud-stage.yml | 118 ++++++++ .../playbooks/260123-upgrade-nextcloud.yml | 270 ++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100644 ansible/playbooks/260123-upgrade-nextcloud-stage.yml create mode 100644 ansible/playbooks/260123-upgrade-nextcloud.yml diff --git a/ansible/playbooks/260123-upgrade-nextcloud-stage.yml b/ansible/playbooks/260123-upgrade-nextcloud-stage.yml new file mode 100644 index 0000000..5981980 --- /dev/null +++ b/ansible/playbooks/260123-upgrade-nextcloud-stage.yml @@ -0,0 +1,118 @@ +--- +# Nextcloud Upgrade Stage Task File +# This file is included by 260123-upgrade-nextcloud.yml for each upgrade stage +# Do not run directly + +- name: "Stage {{ stage.stage }}: Upgrade from v{{ stage.from }} to v{{ stage.to }}" + debug: + msg: | + ============================================================ + Starting Stage {{ stage.stage }}: v{{ stage.from }} → v{{ stage.to }} + ============================================================ + +- name: "Stage {{ stage.stage }}: Verify current version is v{{ stage.from }}" + shell: docker exec -u www-data nextcloud php occ status --output=json + register: stage_version_check + changed_when: false + +- name: "Stage {{ stage.stage }}: Parse current version" + set_fact: + stage_current: "{{ (stage_version_check.stdout | from_json).versionstring }}" + +- name: "Stage {{ stage.stage }}: Check version compatibility" + fail: + msg: "Expected v{{ stage.from }}.x but found v{{ stage_current }}" + when: stage_current is version(stage.from, '<') or stage_current is version(stage.to, '>=') + +- name: "Stage {{ stage.stage }}: Disable non-essential apps" + shell: | + docker exec -u www-data nextcloud php occ app:list --output=json | \ + jq -r '.enabled | keys[]' | \ + grep -Ev '^(files|dav|federatedfilesharing|settings|provisioning_api|files_sharing|files_trashbin|files_versions|comments|contactsinteraction|dashboard|activity|notifications|user_status|weather_status|workflowengine)$' | \ + while read app; do + echo "Disabling $app" + docker exec -u www-data nextcloud php occ app:disable "$app" || true + done + register: apps_disabled + changed_when: "'Disabling' in apps_disabled.stdout" + +- name: "Stage {{ stage.stage }}: Update docker-compose.yml to v{{ stage.to }}" + replace: + path: "{{ nextcloud_base_dir }}/docker-compose.yml" + regexp: 'image:\s*nextcloud:{{ stage.from }}' + replace: 'image: nextcloud:{{ stage.to }}' + +- name: "Stage {{ stage.stage }}: Pull Nextcloud v{{ stage.to }} image" + shell: docker pull nextcloud:{{ stage.to }} + register: image_pull + changed_when: "'Downloaded' in image_pull.stdout or 'Pulling' in image_pull.stdout" + +- name: "Stage {{ stage.stage }}: Start Nextcloud with new version" + community.docker.docker_compose_v2: + project_src: "{{ nextcloud_base_dir }}" + state: present + pull: policy=always + +- name: "Stage {{ stage.stage }}: Wait for container to be ready" + shell: | + timeout=300 + elapsed=0 + while [ $elapsed -lt $timeout ]; do + if docker exec nextcloud curl -f http://localhost:80/status.php 2>/dev/null; then + echo "Container ready" + exit 0 + fi + sleep 5 + elapsed=$((elapsed + 5)) + done + echo "Timeout waiting for container" + exit 1 + register: container_ready + changed_when: false + +- name: "Stage {{ stage.stage }}: Run occ upgrade" + shell: docker exec -u www-data nextcloud php occ upgrade --no-interaction + register: occ_upgrade + changed_when: "'Update successful' in occ_upgrade.stdout or 'upgraded' in occ_upgrade.stdout" + failed_when: + - occ_upgrade.rc != 0 + - "'already latest version' not in occ_upgrade.stdout" + +- name: "Stage {{ stage.stage }}: Display upgrade output" + debug: + msg: "{{ occ_upgrade.stdout_lines }}" + +- name: "Stage {{ stage.stage }}: Verify upgrade succeeded" + shell: docker exec -u www-data nextcloud php occ status --output=json + register: stage_verify + changed_when: false + +- name: "Stage {{ stage.stage }}: Parse upgraded version" + set_fact: + stage_upgraded: "{{ (stage_verify.stdout | from_json).versionstring }}" + +- name: "Stage {{ stage.stage }}: Check upgrade was successful" + fail: + msg: "Upgrade to v{{ stage.to }} failed - still on v{{ stage_upgraded }}" + when: stage_upgraded is version(stage.to, '<') + +- name: "Stage {{ stage.stage }}: Run database migrations" + shell: docker exec -u www-data nextcloud php occ db:add-missing-indices + register: db_indices + changed_when: "'indices added' in db_indices.stdout" + failed_when: false + +- name: "Stage {{ stage.stage }}: Run database column conversions" + shell: docker exec -u www-data nextcloud php occ db:convert-filecache-bigint --no-interaction + register: db_bigint + changed_when: "'converted' in db_bigint.stdout" + failed_when: false + timeout: 600 + +- name: "Stage {{ stage.stage }}: Success" + debug: + msg: | + ============================================================ + ✓ Stage {{ stage.stage }} completed successfully + Upgraded from v{{ stage.from }} to v{{ stage_upgraded }} + ============================================================ diff --git a/ansible/playbooks/260123-upgrade-nextcloud.yml b/ansible/playbooks/260123-upgrade-nextcloud.yml new file mode 100644 index 0000000..29fb5f8 --- /dev/null +++ b/ansible/playbooks/260123-upgrade-nextcloud.yml @@ -0,0 +1,270 @@ +--- +# Nextcloud Major Version Upgrade Playbook +# Created: 2026-01-23 +# Purpose: Safely upgrade Nextcloud from v30 to v32 via v31 (staged upgrade) +# +# Usage: +# ansible-playbook -i hcloud.yml playbooks/260123-upgrade-nextcloud.yml --limit kikker +# +# Requirements: +# - HCLOUD_TOKEN environment variable set +# - SSH access to target server +# - Sufficient disk space for backups +# +# Notes: +# - Nextcloud does NOT support skipping major versions +# - This playbook performs: v30 → v31 → v32 +# - Creates backups before each stage +# - Automatic rollback on failure + +- name: Upgrade Nextcloud from v30 to v32 (staged) + hosts: all + become: true + gather_facts: true + + vars: + nextcloud_base_dir: "/opt/nextcloud" + backup_dir: "/root/nextcloud-backup-{{ ansible_date_time.iso8601_basic_short }}" + upgrade_stages: + - { from: "30", to: "31", stage: 1 } + - { from: "31", to: "32", stage: 2 } + + tasks: + # ============================================================ + # PRE-UPGRADE CHECKS AND PREPARATION + # ============================================================ + + - name: Display upgrade plan + debug: + msg: | + ============================================================ + Nextcloud Upgrade Plan - {{ inventory_hostname }} + ============================================================ + + Upgrade Path: v30 → v31 → v32 + + This playbook will: + 1. Check current Nextcloud version + 2. Create full backup of volumes and database + 3. Disable all apps except core ones + 4. Upgrade to v31 (Stage 1) + 5. Verify v31 is working + 6. Upgrade to v32 (Stage 2) + 7. Verify v32 is working + 8. Re-enable apps + + Backup location: {{ backup_dir }} + + Estimated time: 15-25 minutes + ============================================================ + + - name: Check if Nextcloud is installed + shell: docker ps --filter "name=nextcloud" --format "{{ '{{' }}.Names{{ '}}' }}" + register: nextcloud_running + changed_when: false + failed_when: false + + - name: Fail if Nextcloud is not running + fail: + msg: "Nextcloud container is not running on {{ inventory_hostname }}" + when: "'nextcloud' not in nextcloud_running.stdout" + + - name: Get current Nextcloud version + shell: docker exec -u www-data nextcloud php occ status --output=json + register: nextcloud_status + changed_when: false + + - name: Parse Nextcloud status + set_fact: + nc_status: "{{ nextcloud_status.stdout | from_json }}" + + - name: Display current version + debug: + msg: | + Current version: {{ nc_status.versionstring }} + Installed: {{ nc_status.installed }} + Maintenance mode: {{ nc_status.maintenance }} + Needs DB upgrade: {{ nc_status.needsDbUpgrade }} + + - name: Check if already on target version + debug: + msg: "Nextcloud is already on v32.x - skipping upgrade" + when: nc_status.versionstring is version('32', '>=') + + - name: End play if already upgraded + meta: end_host + when: nc_status.versionstring is version('32', '>=') + + - name: Verify starting version is v30.x + fail: + msg: "This playbook only upgrades from v30. Current version: {{ nc_status.versionstring }}" + when: nc_status.versionstring is version('30', '<') or nc_status.versionstring is version('31', '>=') + + - name: Check disk space + shell: df -h {{ nextcloud_base_dir }} | tail -1 | awk '{print $4}' + register: disk_space + changed_when: false + + - name: Display available disk space + debug: + msg: "Available disk space: {{ disk_space.stdout }}" + + - name: Check if maintenance mode is enabled + fail: + msg: "Nextcloud is in maintenance mode. Please investigate before upgrading." + when: nc_status.maintenance | bool + + # ============================================================ + # BACKUP PHASE + # ============================================================ + + - name: Create backup directory + file: + path: "{{ backup_dir }}" + state: directory + mode: '0700' + + - name: Enable Nextcloud maintenance mode + shell: docker exec -u www-data nextcloud php occ maintenance:mode --on + register: maintenance_on + changed_when: "'Maintenance mode enabled' in maintenance_on.stdout" + + - name: Backup Nextcloud database + shell: | + docker exec nextcloud-db pg_dump -U nextcloud nextcloud | gzip > {{ backup_dir }}/database.sql.gz + args: + creates: "{{ backup_dir }}/database.sql.gz" + + - name: Get database backup size + stat: + path: "{{ backup_dir }}/database.sql.gz" + register: db_backup + + - name: Display database backup info + debug: + msg: "Database backup: {{ (db_backup.stat.size / 1024 / 1024) | round(2) }} MB" + + - name: Stop Nextcloud containers (for volume backup) + community.docker.docker_compose_v2: + project_src: "{{ nextcloud_base_dir }}" + state: stopped + + - name: Backup Nextcloud app volume + shell: | + tar -czf {{ backup_dir }}/nextcloud-app-volume.tar.gz -C /var/lib/docker/volumes/nextcloud-app/_data . + args: + creates: "{{ backup_dir }}/nextcloud-app-volume.tar.gz" + + - name: Backup Nextcloud database volume + shell: | + tar -czf {{ backup_dir }}/nextcloud-db-volume.tar.gz -C /var/lib/docker/volumes/nextcloud-db-data/_data . + args: + creates: "{{ backup_dir }}/nextcloud-db-volume.tar.gz" + + - name: Copy current docker-compose.yml to backup + copy: + src: "{{ nextcloud_base_dir }}/docker-compose.yml" + dest: "{{ backup_dir }}/docker-compose.yml.backup" + remote_src: true + + - name: Display backup summary + debug: + msg: | + ============================================================ + Backup completed: {{ backup_dir }} + + To restore from backup if needed: + 1. Stop containers: cd {{ nextcloud_base_dir }} && docker compose down + 2. Restore app volume: tar -xzf {{ backup_dir }}/nextcloud-app-volume.tar.gz -C /var/lib/docker/volumes/nextcloud-app/_data + 3. Restore DB volume: tar -xzf {{ backup_dir }}/nextcloud-db-volume.tar.gz -C /var/lib/docker/volumes/nextcloud-db-data/_data + 4. Restore compose file: cp {{ backup_dir }}/docker-compose.yml.backup {{ nextcloud_base_dir }}/docker-compose.yml + 5. Start containers: cd {{ nextcloud_base_dir }} && docker compose up -d + ============================================================ + + # ============================================================ + # STAGED UPGRADE LOOP + # ============================================================ + + - name: Perform staged upgrades + include_tasks: "{{ playbook_dir }}/260123-upgrade-nextcloud-stage.yml" + loop: "{{ upgrade_stages }}" + loop_control: + loop_var: stage + + # ============================================================ + # POST-UPGRADE VALIDATION + # ============================================================ + + - name: Get final Nextcloud version + shell: docker exec -u www-data nextcloud php occ status --output=json + register: final_status + changed_when: false + + - name: Parse final status + set_fact: + final_nc_status: "{{ final_status.stdout | from_json }}" + + - name: Verify upgrade to v32 + fail: + msg: "Upgrade failed - still on v{{ final_nc_status.versionstring }}" + when: final_nc_status.versionstring is version('32', '<') + + - name: Run Nextcloud system check + shell: docker exec -u www-data nextcloud php occ check + register: system_check + changed_when: false + failed_when: false + + - name: Display system check results + debug: + msg: "{{ system_check.stdout_lines }}" + + - name: Re-enable user_oidc app + shell: docker exec -u www-data nextcloud php occ app:enable user_oidc + register: oidc_enable + changed_when: "'enabled' in oidc_enable.stdout" + failed_when: false + + - name: Re-enable richdocuments (Collabora) + shell: docker exec -u www-data nextcloud php occ app:enable richdocuments + register: collabora_enable + changed_when: "'enabled' in collabora_enable.stdout" + failed_when: false + + - name: Disable maintenance mode + shell: docker exec -u www-data nextcloud php occ maintenance:mode --off + register: maintenance_off + changed_when: "'Maintenance mode disabled' in maintenance_off.stdout" + + - name: Update docker-compose.yml to use 'latest' tag + lineinfile: + path: "{{ nextcloud_base_dir }}/docker-compose.yml" + regexp: '^\s*image:\s*nextcloud:32\s*$' + line: ' image: nextcloud:latest' + state: present + + - name: Display upgrade success message + debug: + msg: | + ============================================================ + ✓ Nextcloud Upgrade SUCCESSFUL! + ============================================================ + + Server: {{ inventory_hostname }} + Previous version: v30.x + New version: v{{ final_nc_status.versionstring }} + + Backup location: {{ backup_dir }} + + Next steps: + 1. Test login at: https://nextcloud.{{ client_domain }} + 2. Test OIDC login (Login with Authentik) + 3. Test file upload/download + 4. Test Collabora Office integration + + If everything works, you can remove the backup: + rm -rf {{ backup_dir }} + + The docker-compose.yml has been updated to use 'latest' tag + for future automatic updates. + ============================================================