feat: Add Nextcloud major version upgrade playbook (v30→v32)
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 <noreply@anthropic.com>
This commit is contained in:
parent
27d59e4cd3
commit
14256bcbce
2 changed files with 388 additions and 0 deletions
118
ansible/playbooks/260123-upgrade-nextcloud-stage.yml
Normal file
118
ansible/playbooks/260123-upgrade-nextcloud-stage.yml
Normal file
|
|
@ -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 }}
|
||||
============================================================
|
||||
270
ansible/playbooks/260123-upgrade-nextcloud.yml
Normal file
270
ansible/playbooks/260123-upgrade-nextcloud.yml
Normal file
|
|
@ -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.
|
||||
============================================================
|
||||
Loading…
Add table
Reference in a new issue