diff --git a/docker-compose/docker-compose-nextcloud.yml b/docker-compose/docker-compose-nextcloud.yml index 94952f6..025626a 100644 --- a/docker-compose/docker-compose-nextcloud.yml +++ b/docker-compose/docker-compose-nextcloud.yml @@ -27,7 +27,7 @@ services: nextclouddb: - image: mariadb + image: mariadb@sha256:e1bcd6f85781f4a875abefb11c4166c1d79e4237c23de597bf0df81fec225b40 container_name: nextcloud-db restart: unless-stopped command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW diff --git a/docker-compose/docker-compose-nextcloud.yml.j2 b/docker-compose/docker-compose-nextcloud.yml.j2 index 13195eb..dc90fb9 100644 --- a/docker-compose/docker-compose-nextcloud.yml.j2 +++ b/docker-compose/docker-compose-nextcloud.yml.j2 @@ -27,7 +27,7 @@ services: nextclouddb: - image: mariadb + image: mariadb@sha256:e1bcd6f85781f4a875abefb11c4166c1d79e4237c23de597bf0df81fec225b40 container_name: nextcloud-db restart: unless-stopped command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW diff --git a/nextcloud/update_nextcloud_v3.yml b/nextcloud/update_nextcloud_v3.yml deleted file mode 100644 index 45f72da..0000000 --- a/nextcloud/update_nextcloud_v3.yml +++ /dev/null @@ -1,1499 +0,0 @@ -# nextcloud/update_nextcloud_v3.yml - -- name: Update Nextcloud on VM via Proxmox - hosts: proxmox_nextcloud - gather_facts: false - become: true - become_user: root - become_method: sudo - - vars: -# vm_pass is stored in Semaphore Secrets as an environment variable. - vm_password: "{{ lookup('ansible.builtin.env', 'vm_pass') }}" - -# Values stored in Semaphore Extra variables. - vm_use_sudo: "{{ use_sudo | default(false) | bool }}" - debug_level: "{{ DEBUG | default(0) | int }}" - retry_count: "{{ RETRIES | default(25) | int }}" - nextcloud_version: >- - {{ NEXTCLOUD_VERSION | default('') | string | trim }} - -# Docker Compose configuration. - nextcloud_project: "nextcloud-collabora" - nextcloud_service: "nextcloud" - -# Compose file stored in Git. - nextcloud_compose_local_file: >- - {{ playbook_dir }}/../docker-compose/docker-compose-nextcloud.yml.j2 - -# Compose file location on the Nextcloud VM. - nextcloud_compose_file: >- - /data/compose/nextcloud/docker-compose-nextcloud.yml - -# Temporary Compose file on the Proxmox host. - nextcloud_compose_controller_staging_file: >- - /tmp/docker-compose-nextcloud-{{ backup_timestamp }}.yml - -# Temporary Compose file on the Nextcloud VM. - nextcloud_compose_vm_staging_file: >- - /tmp/docker-compose-nextcloud-{{ backup_timestamp }}.yml - -# Container names. - nextcloud_container: "nextcloud" - nextcloud_db_container: "nextcloud-db" - redis_container: "redis" - -# Public endpoints. - nextcloud_base_url: "https://cloud.martinfencl.eu" - nextcloud_status_url: "{{ nextcloud_base_url }}/status.php" - - collabora_url: "https://collabora.martinfencl.eu/" - collabora_discovery_url: >- - https://collabora.martinfencl.eu/hosting/discovery - -# Backup configuration. - backup_root: "/data/compose/nextcloud" - -# Docker command without CLI hints. - docker_cmd: "env DOCKER_CLI_HINTS=0 docker" - - nextcloud_version_command: >- - {{ docker_cmd }} exec -u www-data - {{ nextcloud_container | quote }} - php occ -V - - nextcloud_maintenance_off_command: >- - {{ docker_cmd }} exec -u www-data - {{ nextcloud_container | quote }} - php occ maintenance:mode --off - -# ------------------------------------------------------------------------- -# Compose installation script -# ------------------------------------------------------------------------- - - compose_install_script: | - set -Eeuo pipefail - - current_step="Compose installation initialization" - - trap ' - rc=$? - echo >&2 - echo "Compose installation failed" >&2 - echo "Step: ${current_step}" >&2 - echo "Line: ${LINENO}" >&2 - echo "Command: ${BASH_COMMAND}" >&2 - echo "Return code: ${rc}" >&2 - exit "${rc}" - ' ERR - - source_file={{ nextcloud_compose_vm_staging_file | quote }} - destination_file={{ nextcloud_compose_file | quote }} - destination_dir="$(dirname "${destination_file}")" - - cleanup() { - rm -f "${source_file}" - } - - trap cleanup EXIT - - current_step="checking staged Compose file" - echo "Checking staged Docker Compose file" - test -s "${source_file}" - - current_step="validating staged Compose file" - echo "Validating staged Docker Compose file" - - {{ docker_cmd }} compose \ - -p {{ nextcloud_project | quote }} \ - -f "${source_file}" \ - config --quiet - - current_step="creating Compose destination directory" - echo "Ensuring Compose destination directory exists" - mkdir -p "${destination_dir}" - - current_step="comparing Compose files" - - if [ -f "${destination_file}" ] && \ - cmp -s "${source_file}" "${destination_file}" - then - echo "Compose file is already up to date" - exit 0 - fi - - current_step="installing Compose file" - echo "Installing Compose file" - - install \ - -m 0644 \ - "${source_file}" \ - "${destination_file}" - - current_step="validating installed Compose file" - echo "Validating installed Docker Compose file" - - {{ docker_cmd }} compose \ - -p {{ nextcloud_project | quote }} \ - -f "${destination_file}" \ - config --quiet - - echo "Compose file updated" - -# ------------------------------------------------------------------------- -# Preflight script -# ------------------------------------------------------------------------- - - preflight_script: | - set -Eeuo pipefail - - current_step="preflight initialization" - - trap ' - rc=$? - echo >&2 - echo "Preflight check failed" >&2 - echo "Step: ${current_step}" >&2 - echo "Line: ${LINENO}" >&2 - echo "Command: ${BASH_COMMAND}" >&2 - echo "Return code: ${rc}" >&2 - exit "${rc}" - ' ERR - - current_step="checking Docker executable" - echo "Checking Docker executable" - command -v docker >/dev/null - - current_step="checking Docker daemon" - echo "Checking Docker daemon" - docker version >/dev/null - - current_step="checking Docker Compose" - echo "Checking Docker Compose" - docker compose version >/dev/null - - current_step="checking Docker Compose file" - echo "Checking Docker Compose file" - test -f {{ nextcloud_compose_file | quote }} - - current_step="validating Docker Compose configuration" - echo "Validating Docker Compose configuration" - - {{ docker_cmd }} compose \ - -p {{ nextcloud_project | quote }} \ - -f {{ nextcloud_compose_file | quote }} \ - config --quiet - - current_step="checking required containers" - echo "Checking required containers" - - for container in \ - {{ nextcloud_container | quote }} \ - {{ nextcloud_db_container | quote }} \ - {{ redis_container | quote }} - do - state="$( - docker inspect \ - --format '{% raw %}{{.State.Running}}{% endraw %}' \ - "${container}" \ - 2>/dev/null || true - )" - - if [ "${state}" != "true" ]; then - echo "Container ${container} is not running" >&2 - exit 1 - fi - done - - current_step="checking Nextcloud runtime" - echo "Checking Nextcloud runtime" - - {{ docker_cmd }} exec \ - {{ nextcloud_container | quote }} \ - sh -c ' - command -v php >/dev/null - test -s /var/www/html/occ - test -s /var/www/html/version.php - test -s /var/www/html/config/config.php - ' - - current_step="reading Nextcloud status" - echo "Reading Nextcloud status" - - # The status command is informational during preflight. A previous failed - # upgrade may legitimately leave maintenance mode enabled or a database - # upgrade pending. Such a state must not prevent a recovery run. - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ status || true - - current_step="checking MariaDB" - echo "Checking MariaDB" - - {{ docker_cmd }} exec \ - {{ nextcloud_db_container | quote }} \ - sh -c ' - if command -v mariadb-admin >/dev/null 2>&1; then - exec mariadb-admin ping \ - -h 127.0.0.1 \ - --silent - else - exec mysqladmin ping \ - -h 127.0.0.1 \ - --silent - fi - ' - - current_step="checking Redis" - echo "Checking Redis" - - {{ docker_cmd }} exec \ - {{ redis_container | quote }} \ - redis-cli -h 127.0.0.1 ping | - grep -qx PONG - - echo "Preflight checks completed successfully" - -# ------------------------------------------------------------------------- -# Backup script -# ------------------------------------------------------------------------- - - backup_script: | - set -Eeuo pipefail - - current_step="backup initialization" - maintenance_enabled_by_script="false" - - cleanup() { - rc=$? - - if [ "${rc}" -ne 0 ] && \ - [ "${maintenance_enabled_by_script}" = "true" ] - then - echo "Backup failed; attempting to disable maintenance mode" >&2 - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ maintenance:mode --off >/dev/null 2>&1 || true - fi - - } - - trap cleanup EXIT - - trap ' - rc=$? - echo >&2 - echo "Nextcloud backup failed" >&2 - echo "Step: ${current_step}" >&2 - echo "Line: ${LINENO}" >&2 - echo "Command: ${BASH_COMMAND}" >&2 - echo "Return code: ${rc}" >&2 - exit "${rc}" - ' ERR - - current_step="creating backup directory" - echo "Creating backup directory: {{ backup_dir }}" - mkdir -p {{ backup_dir | quote }} - - current_step="enabling maintenance mode" - echo "Enabling Nextcloud maintenance mode" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ maintenance:mode --on - - maintenance_enabled_by_script="true" - - current_step="creating configuration archive" - echo "Backing up Nextcloud configuration and custom applications" - - {{ docker_cmd }} exec \ - {{ nextcloud_container | quote }} \ - sh -c ' - tar czf /tmp/nextcloud_conf.tgz \ - -C /var/www/html \ - config custom_apps - ' - - current_step="copying configuration archive" - echo "Copying configuration archive" - - {{ docker_cmd }} cp \ - {{ nextcloud_container | quote }}:/tmp/nextcloud_conf.tgz \ - {{ (backup_dir ~ '/nextcloud_conf.tgz') | quote }} - - current_step="removing temporary configuration archive" - echo "Removing temporary configuration archive" - - {{ docker_cmd }} exec \ - {{ nextcloud_container | quote }} \ - rm -f /tmp/nextcloud_conf.tgz - - current_step="creating database dump" - echo "Backing up Nextcloud database" - - {{ docker_cmd }} exec \ - {{ nextcloud_db_container | quote }} \ - sh -c ' - if command -v mariadb-dump >/dev/null 2>&1; then - exec mariadb-dump \ - --single-transaction \ - --quick \ - --routines \ - --triggers \ - -u"$MYSQL_USER" \ - -p"$MYSQL_PASSWORD" \ - "$MYSQL_DATABASE" - else - exec mysqldump \ - --single-transaction \ - --quick \ - --routines \ - --triggers \ - -u"$MYSQL_USER" \ - -p"$MYSQL_PASSWORD" \ - "$MYSQL_DATABASE" - fi - ' > {{ (backup_dir ~ '/db.sql') | quote }} - - current_step="validating configuration archive" - echo "Validating configuration archive" - test -s {{ (backup_dir ~ '/nextcloud_conf.tgz') | quote }} - - current_step="validating database dump" - echo "Validating database dump" - test -s {{ (backup_dir ~ '/db.sql') | quote }} - - current_step="disabling maintenance mode after backup" - echo "Disabling Nextcloud maintenance mode after backup" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ maintenance:mode --off - - maintenance_enabled_by_script="false" - - current_step="verifying maintenance mode after backup" - echo "Verifying maintenance mode after backup" - - maintenance_state="$( - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ config:system:get maintenance \ - 2>/dev/null || true - )" - - if [ "${maintenance_state}" = "true" ] || \ - [ "${maintenance_state}" = "1" ]; then - echo "Maintenance mode is still enabled after backup" >&2 - exit 1 - fi - - echo "Backup completed successfully" - echo "Backup directory: {{ backup_dir }}" - -# ------------------------------------------------------------------------- -# Upgrade script -# ------------------------------------------------------------------------- - - upgrade_script: | - set -Eeuo pipefail - - current_step="upgrade initialization" - - trap ' - rc=$? - echo >&2 - echo "Nextcloud upgrade failed" >&2 - echo "Step: ${current_step}" >&2 - echo "Line: ${LINENO}" >&2 - echo "Command: ${BASH_COMMAND}" >&2 - echo "Return code: ${rc}" >&2 - exit "${rc}" - ' ERR - - current_step="validating Docker Compose configuration" - echo "Validating Docker Compose configuration" - - {{ docker_cmd }} compose \ - -p {{ nextcloud_project | quote }} \ - -f {{ nextcloud_compose_file | quote }} \ - config --quiet - - current_step="pulling Nextcloud image" - echo "Pulling the current Nextcloud image" - - {{ docker_cmd }} compose \ - -p {{ nextcloud_project | quote }} \ - -f {{ nextcloud_compose_file | quote }} \ - pull {{ nextcloud_service | quote }} - - current_step="recreating Nextcloud container" - echo "Recreating the Nextcloud container" - - {{ docker_cmd }} compose \ - -p {{ nextcloud_project | quote }} \ - -f {{ nextcloud_compose_file | quote }} \ - up -d \ - --no-deps \ - --force-recreate \ - {{ nextcloud_service | quote }} - - current_step="waiting for Nextcloud container" - echo "Waiting for the Nextcloud container" - - container_deadline=$((SECONDS + 180)) - - while true - do - container_state="$( - docker inspect \ - --format '{% raw %}{{.State.Running}}{% endraw %}' \ - {{ nextcloud_container | quote }} \ - 2>/dev/null || true - )" - - if [ "${container_state}" = "true" ]; then - break - fi - - if [ "${SECONDS}" -ge "${container_deadline}" ]; then - echo "Timeout waiting for container {{ nextcloud_container }}" >&2 - - {{ docker_cmd }} logs \ - --tail 100 \ - {{ nextcloud_container | quote }} >&2 || true - - exit 124 - fi - - sleep 3 - done - - current_step="waiting for Nextcloud initialization" - echo "Waiting for Nextcloud container initialization" - - initialization_deadline=$((SECONDS + 1200)) - - while true - do - if {{ docker_cmd }} exec \ - {{ nextcloud_container | quote }} \ - sh -c ' - set -eu - - # Apache becomes PID 1 only after the Nextcloud entrypoint completes. - # Do not match the apache2-foreground argument here. - read -r pid1_name < /proc/1/comm - - if [ "${pid1_name}" != "apache2" ]; then - exit 1 - fi - - # Ensure the official entrypoint has released its initialization lock. - flock -w 10 /var/www/html/nextcloud-init-sync.lock true - - command -v php >/dev/null - - test -s /var/www/html/occ - test -s /var/www/html/version.php - test -s /var/www/html/config/config.php - - test -d /var/www/html/apps - test -s /var/www/html/apps/activity/appinfo/info.xml - ' >/dev/null 2>&1 - then - break - fi - - if [ "${SECONDS}" -ge "${initialization_deadline}" ]; then - echo "Timeout waiting for Nextcloud initialization" >&2 - - {{ docker_cmd }} logs \ - --tail 200 \ - {{ nextcloud_container | quote }} >&2 || true - - exit 124 - fi - - sleep 3 - done - - current_step="validating Nextcloud PHP runtime" - echo "Validating Nextcloud PHP runtime" - - {{ docker_cmd }} exec \ - {{ nextcloud_container | quote }} \ - php -r ' - require "/var/www/html/version.php"; - - if (empty($OC_Version)) { - fwrite(STDERR, "Nextcloud version is unavailable\n"); - exit(1); - } - - $actual = implode(".", array_slice($OC_Version, 0, 3)); - $expected = "{{ nextcloud_version }}"; - if ($actual !== $expected) { - fwrite(STDERR, "Expected Nextcloud $expected, found $actual\n"); - exit(1); - } - - echo "Nextcloud code version: "; - echo implode(".", $OC_Version), PHP_EOL; - ' - - current_step="validating bundled application files" - echo "Validating bundled Nextcloud application files" - - {{ docker_cmd }} exec \ - {{ nextcloud_container | quote }} \ - php -r ' - $infoFile = "/var/www/html/apps/activity/appinfo/info.xml"; - - if (!is_file($infoFile) || filesize($infoFile) === 0) { - fwrite(STDERR, "Activity application metadata is missing\n"); - exit(1); - } - - $xml = @simplexml_load_file($infoFile); - - if ($xml === false) { - fwrite(STDERR, "Activity application metadata is invalid\n"); - exit(1); - } - - if ((string) $xml->id !== "activity") { - fwrite(STDERR, "Unexpected activity application ID\n"); - exit(1); - } - - echo "Activity application version: "; - echo (string) $xml->version, PHP_EOL; - ' - - current_step="checking Nextcloud OCC autoloader" - echo "Checking Nextcloud OCC before database upgrade" - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ --version - - echo "Nextcloud container initialization completed" - - current_step="running Nextcloud database upgrade" - echo "Running the Nextcloud database upgrade" - - # Do not disable maintenance mode before this command. A previous failed - # upgrade may have intentionally left maintenance mode enabled. OCC - # upgrade manages the required maintenance state itself. - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ upgrade --no-interaction - - current_step="updating Nextcloud applications" - echo "Updating Nextcloud applications" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ app:update --all - - current_step="running database upgrade after application updates" - echo "Running database upgrade after application updates" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ upgrade --no-interaction - - current_step="running Nextcloud maintenance repair" - echo "Running Nextcloud maintenance repair" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ maintenance:repair --include-expensive - - current_step="reading final Nextcloud status" - echo "Reading Nextcloud status" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ status - - current_step="verifying final Nextcloud state" - echo "Verifying final Nextcloud state" - - maintenance_state="$( - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ config:system:get maintenance \ - 2>/dev/null || true - )" - - if [ "${maintenance_state}" = "true" ] || \ - [ "${maintenance_state}" = "1" ]; then - echo "Nextcloud is still in maintenance mode" >&2 - exit 1 - fi - - needs_db_upgrade="$( - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ status --output=json | - {{ docker_cmd }} exec -i -u www-data \ - {{ nextcloud_container | quote }} \ - php -r ' - $status = json_decode(stream_get_contents(STDIN), true); - - if (!is_array($status)) { - exit(2); - } - - echo !empty($status["needsDbUpgrade"]) ? "true" : "false"; - ' - )" - - if [ "${needs_db_upgrade}" != "false" ]; then - echo "Nextcloud still requires a database upgrade" >&2 - exit 1 - fi - - echo "Nextcloud upgrade commands completed successfully" - -# ------------------------------------------------------------------------- -# Post-upgrade script -# ------------------------------------------------------------------------- - - postcheck_script: | - set -Eeuo pipefail - - current_step="post-upgrade initialization" - - trap ' - rc=$? - echo >&2 - echo "Post-upgrade check failed" >&2 - echo "Step: ${current_step}" >&2 - echo "Line: ${LINENO}" >&2 - echo "Command: ${BASH_COMMAND}" >&2 - echo "Return code: ${rc}" >&2 - exit "${rc}" - ' ERR - - current_step="checking required containers" - echo "Checking required containers after upgrade" - - for container in \ - {{ nextcloud_container | quote }} \ - {{ nextcloud_db_container | quote }} \ - {{ redis_container | quote }} - do - state="$( - docker inspect \ - --format '{% raw %}{{.State.Running}}{% endraw %}' \ - "${container}" \ - 2>/dev/null || true - )" - - if [ "${state}" != "true" ]; then - echo "Container ${container} is not running" >&2 - exit 1 - fi - done - - current_step="checking MariaDB" - echo "Checking MariaDB after upgrade" - - {{ docker_cmd }} exec \ - {{ nextcloud_db_container | quote }} \ - sh -c ' - if command -v mariadb-admin >/dev/null 2>&1; then - exec mariadb-admin ping \ - -h 127.0.0.1 \ - --silent - else - exec mysqladmin ping \ - -h 127.0.0.1 \ - --silent - fi - ' - - current_step="checking Redis" - echo "Checking Redis after upgrade" - - {{ docker_cmd }} exec \ - {{ redis_container | quote }} \ - redis-cli -h 127.0.0.1 ping | - grep -qx PONG - - current_step="checking Nextcloud status" - echo "Checking Nextcloud status after upgrade" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ status - - current_step="checking Nextcloud maintenance state" - - maintenance_state="$( - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ config:system:get maintenance \ - 2>/dev/null || true - )" - - if [ "${maintenance_state}" = "true" ] || \ - [ "${maintenance_state}" = "1" ]; then - echo "Nextcloud is still in maintenance mode" >&2 - exit 1 - fi - - current_step="checking database upgrade state" - - needs_db_upgrade="$( - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ status --output=json | - {{ docker_cmd }} exec -i -u www-data \ - {{ nextcloud_container | quote }} \ - php -r ' - $status = json_decode(stream_get_contents(STDIN), true); - - if (!is_array($status)) { - exit(2); - } - - echo !empty($status["needsDbUpgrade"]) ? "true" : "false"; - ' - )" - - if [ "${needs_db_upgrade}" != "false" ]; then - echo "Nextcloud still requires a database upgrade" >&2 - exit 1 - fi - - current_step="checking activity application integrity" - echo "Checking activity application integrity" - - {{ docker_cmd }} exec -u www-data \ - {{ nextcloud_container | quote }} \ - php occ integrity:check-app activity - - echo "Post-upgrade checks completed successfully" - - pre_tasks: - - name: Validate requested Nextcloud version - ansible.builtin.assert: - that: - - nextcloud_version | length > 0 - - 'nextcloud_version is match("^[0-9]+[.][0-9]+[.][0-9]+$")' - fail_msg: >- - NEXTCLOUD_VERSION is required and must have format X.Y.Z, - for example 34.0.3. - quiet: true - - - 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_password | string | length > 0 - - retry_count | int > 0 - fail_msg: >- - Missing or invalid vm_ip, vm_user, vm_pass or RETRIES. - Check the attached Semaphore Variable Group. - quiet: true - no_log: true - - - name: Generate backup timestamp - ansible.builtin.set_fact: - backup_timestamp: "{{ now(utc=true, fmt='%Y-%m-%d-%H%M%S') }}" - changed_when: false - - - name: Set backup directory - ansible.builtin.set_fact: - backup_dir: >- - {{ backup_root }}/backup-{{ backup_timestamp }} - changed_when: false - - tasks: - - name: Ensure sshpass is installed - ansible.builtin.apt: - name: sshpass - state: present - update_cache: true - -# ------------------------------------------------------------------------- -# Compose synchronization -# ------------------------------------------------------------------------- - - - name: Nextcloud | Check Compose file in repository - ansible.builtin.stat: - path: "{{ nextcloud_compose_local_file }}" - register: nextcloud_compose_local_stat - delegate_to: localhost - run_once: true - changed_when: false - - - name: Nextcloud | Fail when repository Compose file is missing - ansible.builtin.assert: - that: - - nextcloud_compose_local_stat.stat.exists - - nextcloud_compose_local_stat.stat.isreg - - nextcloud_compose_local_stat.stat.size | int > 0 - fail_msg: >- - Nextcloud Compose file is missing or empty: - {{ nextcloud_compose_local_file }} - quiet: true - run_once: true - - - name: Nextcloud | Synchronize Compose file to VM - block: - - name: Nextcloud | Render Compose file on Proxmox host - ansible.builtin.template: - src: "{{ nextcloud_compose_local_file }}" - dest: "{{ nextcloud_compose_controller_staging_file }}" - mode: "0600" - - - name: Nextcloud | Upload Compose file to VM staging path - ansible.builtin.command: - argv: - - sshpass - - -e - - scp - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - "{{ nextcloud_compose_controller_staging_file }}" - - "{{ vm_user }}@{{ vm_ip }}:{{ nextcloud_compose_vm_staging_file }}" - environment: - SSHPASS: "{{ vm_password }}" - register: nextcloud_compose_upload - changed_when: false - no_log: true - - - name: Nextcloud | Validate and install Compose file on VM - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (compose_install_script | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (compose_install_script | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nextcloud_compose_install - changed_when: >- - 'Compose file updated' in - (nextcloud_compose_install.stdout | default('')) - failed_when: false - no_log: true - - - name: Nextcloud | Show Compose installation result - ansible.builtin.debug: - msg: | - Compose installation return code: - {{ nextcloud_compose_install.rc }} - - STDOUT: - {{ nextcloud_compose_install.stdout | default('') | trim }} - - STDERR: - {{ nextcloud_compose_install.stderr | default('') | trim }} - when: - - debug_level == 1 or nextcloud_compose_install.rc != 0 - - - name: Nextcloud | Fail when Compose installation failed - ansible.builtin.assert: - that: - - nextcloud_compose_install.rc == 0 - fail_msg: | - Failed to install Nextcloud Compose file. - - Source file: - {{ nextcloud_compose_local_file }} - - Destination file: - {{ nextcloud_compose_file }} - - Return code: - {{ nextcloud_compose_install.rc }} - - STDOUT: - {{ nextcloud_compose_install.stdout | default('') | trim }} - - STDERR: - {{ nextcloud_compose_install.stderr | default('') | trim }} - quiet: true - - always: - - name: Nextcloud | Remove staged Compose file from Proxmox host - ansible.builtin.file: - path: "{{ nextcloud_compose_controller_staging_file }}" - state: absent - changed_when: false - - - name: Nextcloud | Remove staged Compose file from VM - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - rm -f - {{ nextcloud_compose_vm_staging_file | quote }} - environment: - SSHPASS: "{{ vm_password }}" - changed_when: false - failed_when: false - no_log: true - -# ------------------------------------------------------------------------- -# Preflight -# ------------------------------------------------------------------------- - - - name: Nextcloud | Run preflight checks on VM - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (preflight_script | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (preflight_script | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nc_preflight - changed_when: false - failed_when: false - no_log: true - - - name: Nextcloud | Show preflight result - ansible.builtin.debug: - msg: | - Preflight return code: {{ nc_preflight.rc }} - - STDOUT: - {{ nc_preflight.stdout | default('') | trim }} - - STDERR: - {{ nc_preflight.stderr | default('') | trim }} - when: - - debug_level == 1 or nc_preflight.rc != 0 - - - name: Nextcloud | Fail when preflight checks failed - ansible.builtin.assert: - that: - - nc_preflight.rc == 0 - fail_msg: | - Nextcloud preflight checks failed. - - Return code: - {{ nc_preflight.rc }} - - STDOUT: - {{ nc_preflight.stdout | default('') | trim }} - - STDERR: - {{ nc_preflight.stderr | default('') | trim }} - quiet: true - -# ------------------------------------------------------------------------- -# Current version -# ------------------------------------------------------------------------- - - - name: Nextcloud | Read current version before upgrade - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (nextcloud_version_command | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (nextcloud_version_command | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nc_version_before - changed_when: false - failed_when: false - no_log: true - - - name: Nextcloud | Print current version - ansible.builtin.debug: - msg: >- - Current version: - {{ - nc_version_before.stdout - | default('Nextcloud version is unavailable') - | trim - }} - -# ------------------------------------------------------------------------- -# Backup and upgrade -# ------------------------------------------------------------------------- - - - name: Nextcloud | Run backup and upgrade - block: - - name: Nextcloud | Create configuration and database backup - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (backup_script | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (backup_script | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nc_backup - changed_when: nc_backup.rc == 0 - failed_when: false - no_log: true - - - name: Nextcloud | Show backup output - ansible.builtin.debug: - msg: | - Backup directory: {{ backup_dir }} - Backup return code: {{ nc_backup.rc }} - - STDOUT: - {{ nc_backup.stdout | default('') | trim }} - - STDERR: - {{ nc_backup.stderr | default('') | trim }} - when: - - debug_level == 1 or nc_backup.rc != 0 - - - name: Nextcloud | Fail when backup failed - ansible.builtin.assert: - that: - - nc_backup.rc == 0 - fail_msg: | - Nextcloud backup failed. - - Backup directory: - {{ backup_dir }} - - Return code: - {{ nc_backup.rc }} - - STDOUT: - {{ nc_backup.stdout | default('') | trim }} - - STDERR: - {{ nc_backup.stderr | default('') | trim }} - quiet: true - - - name: Nextcloud | Pull image and run upgrade - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (upgrade_script | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (upgrade_script | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nc_upgrade - changed_when: nc_upgrade.rc == 0 - failed_when: false - no_log: true - - - name: Nextcloud | Show upgrade output - ansible.builtin.debug: - msg: | - Upgrade return code: {{ nc_upgrade.rc }} - - STDOUT: - {{ nc_upgrade.stdout | default('') | trim }} - - STDERR: - {{ nc_upgrade.stderr | default('') | trim }} - - - name: Nextcloud | Fail when upgrade command failed - ansible.builtin.assert: - that: - - nc_upgrade.rc == 0 - fail_msg: | - Nextcloud upgrade failed. - - Maintenance mode has intentionally not been disabled because - the upgrade command failed. Resolve the reported error and rerun - the playbook or complete the OCC upgrade manually. - - Return code: - {{ nc_upgrade.rc }} - - STDOUT: - {{ nc_upgrade.stdout | default('') | trim }} - - STDERR: - {{ nc_upgrade.stderr | default('') | trim }} - quiet: true - - always: -# Disable maintenance mode when: -# - the backup failed before the upgrade task was started, or -# - the upgrade completed successfully. -# -# Do not disable maintenance mode after a failed OCC upgrade. - - name: Nextcloud | Disable maintenance mode - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (nextcloud_maintenance_off_command | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (nextcloud_maintenance_off_command | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nc_maint_off - changed_when: false - failed_when: false - no_log: true - when: - - nc_upgrade is not defined or nc_upgrade.rc == 0 - - - name: Nextcloud | Show maintenance mode result - ansible.builtin.debug: - msg: | - Maintenance return code: {{ nc_maint_off.rc }} - - STDOUT: - {{ nc_maint_off.stdout | default('') | trim }} - - STDERR: - {{ nc_maint_off.stderr | default('') | trim }} - when: - - nc_upgrade is not defined or nc_upgrade.rc == 0 - - debug_level == 1 or nc_maint_off.rc != 0 - - - name: Nextcloud | Verify maintenance mode was disabled - ansible.builtin.assert: - that: - - nc_maint_off.rc == 0 - fail_msg: | - Failed to disable Nextcloud maintenance mode. - - Return code: - {{ nc_maint_off.rc }} - - STDOUT: - {{ nc_maint_off.stdout | default('') | trim }} - - STDERR: - {{ nc_maint_off.stderr | default('') | trim }} - quiet: true - when: - - nc_upgrade is not defined or nc_upgrade.rc == 0 - -# ------------------------------------------------------------------------- -# Public Nextcloud readiness -# ------------------------------------------------------------------------- - - - name: Nextcloud | Wait for public status.php - ansible.builtin.uri: - url: "{{ nextcloud_status_url }}" - method: GET - return_content: true - validate_certs: true - status_code: 200 - timeout: 20 - register: nc_status - delegate_to: localhost - run_once: true - retries: "{{ retry_count }}" - delay: 4 - until: - - nc_status.status | default(0) | int == 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) - - nc_status.json.versionstring | default('') == nextcloud_version - changed_when: false - -# ------------------------------------------------------------------------- -# Post-upgrade checks -# ------------------------------------------------------------------------- - - - name: Nextcloud | Run post-upgrade checks on VM - ansible.builtin.command: - argv: - - sshpass - - -e - - ssh - - -o - - StrictHostKeyChecking=no - - -o - - UserKnownHostsFile=/dev/null - - -o - - LogLevel=ERROR - - -o - - ConnectTimeout=15 - - -o - - ServerAliveInterval=10 - - -o - - ServerAliveCountMax=3 - - "{{ vm_user }}@{{ vm_ip }}" - - >- - {{ - ('sudo -n bash -c ' ~ - (postcheck_script | quote)) - if vm_use_sudo - else - ('bash -c ' ~ - (postcheck_script | quote)) - }} - environment: - SSHPASS: "{{ vm_password }}" - register: nc_postcheck - changed_when: false - failed_when: false - no_log: true - - - name: Nextcloud | Show post-upgrade checks - ansible.builtin.debug: - msg: | - Post-upgrade return code: {{ nc_postcheck.rc }} - - STDOUT: - {{ nc_postcheck.stdout | default('') | trim }} - - STDERR: - {{ nc_postcheck.stderr | default('') | trim }} - when: - - debug_level == 1 or nc_postcheck.rc != 0 - - - name: Nextcloud | Fail when post-upgrade checks failed - ansible.builtin.assert: - that: - - nc_postcheck.rc == 0 - fail_msg: | - Nextcloud post-upgrade checks failed. - - Return code: - {{ nc_postcheck.rc }} - - STDOUT: - {{ nc_postcheck.stdout | default('') | trim }} - - STDERR: - {{ nc_postcheck.stderr | default('') | trim }} - quiet: true - -# ------------------------------------------------------------------------- -# External Collabora checks -# ------------------------------------------------------------------------- - - - name: Nextcloud | Check external Collabora root endpoint - ansible.builtin.uri: - url: "{{ collabora_url }}" - method: GET - return_content: true - validate_certs: true - status_code: 200 - timeout: 20 - register: collabora_root - delegate_to: localhost - run_once: true - changed_when: false - failed_when: >- - (collabora_root.status | default(0) | int != 200) - or - ('OK' not in (collabora_root.content | default(''))) - - - name: Nextcloud | Check external Collabora discovery endpoint - ansible.builtin.uri: - url: "{{ collabora_discovery_url }}" - method: GET - return_content: true - validate_certs: true - status_code: 200 - timeout: 20 - register: collabora_discovery - delegate_to: localhost - run_once: true - changed_when: false - failed_when: >- - (collabora_discovery.status | default(0) | int != 200) - or - ('