From 43d910a44ce17aeb576a1b671d03e4dd8ff080cc Mon Sep 17 00:00:00 2001 From: "martin.fencl" Date: Thu, 6 Aug 2026 20:50:07 +0200 Subject: [PATCH] feat: add Docker Compose configuration for Nextcloud, including services and environment variables --- docker-compose/docker-compose-nextcloud.yml | 73 +++++ nextcloud/update_nextcloud_v2.yml | 328 +++++++++++++++++++- 2 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 docker-compose/docker-compose-nextcloud.yml diff --git a/docker-compose/docker-compose-nextcloud.yml b/docker-compose/docker-compose-nextcloud.yml new file mode 100644 index 0000000..20f86fc --- /dev/null +++ b/docker-compose/docker-compose-nextcloud.yml @@ -0,0 +1,73 @@ +services: + nextcloud: + image: nextcloud:33.0-apache + container_name: nextcloud + restart: unless-stopped + networks: + - cloud + depends_on: + - nextclouddb + - redis + ports: + - 8080:80 + volumes: + - /data/compose/nextcloud/config:/var/www/html/config + - /data/compose/nextcloud/data:/var/www/html/data + - /data/compose/nextcloud/custom_apps:/var/www/html/custom_apps + environment: + - TZ=Europe/Prague + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=dbpassword + - MYSQL_HOST=nextclouddb + - REDIS_HOST=redis + - NEXTCLOUD_ADMIN_USER=root + - NEXTCLOUD_ADMIN_PASSWORD=1234SilneHeslo.-. + + + nextclouddb: + image: mariadb + container_name: nextcloud-db + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW + networks: + - cloud + volumes: + - /data/compose/nextcloud/nextclouddb:/var/lib/mysql + environment: + - TZ=Europe/Prague + - MYSQL_RANDOM_ROOT_PASSWORD=true + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=dbpassword + - MARIADB_AUTO_UPGRADE=1 + + redis: + image: redis:alpine + container_name: redis + restart: unless-stopped + networks: + - cloud + volumes: + - /data/compose/nextcloud/redis:/data + + collabora: + image: collabora/code + container_name: collabora + restart: unless-stopped + networks: + - cloud + environment: + - TZ=Europe/Prague + - password=password + - username=nextcloud + - domain=cloud.martinfencl.eu + - extra_params=--o:ssl.enable=false --o:ssl.termination=true + - aliasgroup1=https://cloud.martinfencl.eu:443,https://collabora.martinfencl.eu:443 + - dictionaries=de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru cs_CZ + ports: + - 9980:9980 + +networks: + cloud: + driver: bridge diff --git a/nextcloud/update_nextcloud_v2.yml b/nextcloud/update_nextcloud_v2.yml index a814953..83bcb9f 100644 --- a/nextcloud/update_nextcloud_v2.yml +++ b/nextcloud/update_nextcloud_v2.yml @@ -18,9 +18,24 @@ # Docker Compose configuration. nextcloud_project: "nextcloud-collabora" - nextcloud_compose_file: "/data/compose/nextcloud/docker-compose-nextcloud.yml" nextcloud_service: "nextcloud" + # Compose file stored in Git. + nextcloud_compose_local_file: >- + {{ playbook_dir }}/../docker-compose/docker-compose-nextcloud.yml + + # 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" @@ -50,6 +65,79 @@ {{ 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 # ------------------------------------------------------------------------- @@ -88,6 +176,7 @@ current_step="validating Docker Compose configuration" echo "Validating Docker Compose configuration" + {{ docker_cmd }} compose \ -p {{ nextcloud_project | quote }} \ -f {{ nextcloud_compose_file | quote }} \ @@ -101,7 +190,12 @@ {{ nextcloud_db_container | quote }} \ {{ redis_container | quote }} do - state="$(docker inspect --format '{% raw %}{{.State.Running}}{% endraw %}' "${container}" 2>/dev/null || true)" + state="$( + docker inspect \ + --format '{% raw %}{{.State.Running}}{% endraw %}' \ + "${container}" \ + 2>/dev/null || true + )" if [ "${state}" != "true" ]; then echo "Container ${container} is not running" >&2 @@ -111,12 +205,14 @@ current_step="checking Nextcloud status" echo "Checking Nextcloud status" + {{ docker_cmd }} exec -u www-data \ {{ nextcloud_container | quote }} \ php occ status current_step="checking MariaDB" echo "Checking MariaDB" + {{ docker_cmd }} exec \ {{ nextcloud_db_container | quote }} \ sh -c ' @@ -133,6 +229,7 @@ current_step="checking Redis" echo "Checking Redis" + {{ docker_cmd }} exec \ {{ redis_container | quote }} \ redis-cli -h 127.0.0.1 ping | @@ -166,12 +263,14 @@ current_step="enabling maintenance mode" echo "Enabling Nextcloud maintenance mode" + {{ docker_cmd }} exec -u www-data \ {{ nextcloud_container | quote }} \ php occ maintenance:mode --on current_step="creating configuration archive" echo "Backing up Nextcloud configuration and custom applications" + {{ docker_cmd }} exec \ {{ nextcloud_container | quote }} \ sh -c ' @@ -182,18 +281,21 @@ 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 ' @@ -220,6 +322,7 @@ 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 @@ -265,6 +368,7 @@ current_step="validating Docker Compose configuration" echo "Validating Docker Compose configuration" + {{ docker_cmd }} compose \ -p {{ nextcloud_project | quote }} \ -f {{ nextcloud_compose_file | quote }} \ @@ -272,6 +376,7 @@ current_step="pulling Nextcloud image" echo "Pulling the current Nextcloud image" + {{ docker_cmd }} compose \ -p {{ nextcloud_project | quote }} \ -f {{ nextcloud_compose_file | quote }} \ @@ -279,6 +384,7 @@ current_step="recreating Nextcloud container" echo "Recreating the Nextcloud container" + {{ docker_cmd }} compose \ -p {{ nextcloud_project | quote }} \ -f {{ nextcloud_compose_file | quote }} \ @@ -294,7 +400,12 @@ while true do - container_state="$(docker inspect --format '{% raw %}{{.State.Running}}{% endraw %}' {{ nextcloud_container | quote }} 2>/dev/null || true)" + container_state="$( + docker inspect \ + --format '{% raw %}{{.State.Running}}{% endraw %}' \ + {{ nextcloud_container | quote }} \ + 2>/dev/null || true + )" if [ "${container_state}" = "true" ]; then break @@ -343,12 +454,14 @@ if [ "${maintenance_state}" = "true" ] || \ [ "${maintenance_state}" = "1" ]; then echo "Maintenance mode is enabled; disabling it" + {{ docker_cmd }} exec -u www-data \ {{ nextcloud_container | quote }} \ php occ maintenance:mode --off fi current_step="verifying maintenance mode before OCC upgrade" + maintenance_state="$( {{ docker_cmd }} exec -u www-data \ {{ nextcloud_container | quote }} \ @@ -364,24 +477,28 @@ current_step="running Nextcloud database upgrade" echo "Running the Nextcloud database upgrade" + {{ 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 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 @@ -416,7 +533,12 @@ {{ nextcloud_db_container | quote }} \ {{ redis_container | quote }} do - state="$(docker inspect --format '{% raw %}{{.State.Running}}{% endraw %}' "${container}" 2>/dev/null || true)" + state="$( + docker inspect \ + --format '{% raw %}{{.State.Running}}{% endraw %}' \ + "${container}" \ + 2>/dev/null || true + )" if [ "${state}" != "true" ]; then echo "Container ${container} is not running" >&2 @@ -426,6 +548,7 @@ current_step="checking MariaDB" echo "Checking MariaDB after upgrade" + {{ docker_cmd }} exec \ {{ nextcloud_db_container | quote }} \ sh -c ' @@ -442,6 +565,7 @@ current_step="checking Redis" echo "Checking Redis after upgrade" + {{ docker_cmd }} exec \ {{ redis_container | quote }} \ redis-cli -h 127.0.0.1 ping | @@ -449,11 +573,13 @@ 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 }} \ @@ -503,6 +629,168 @@ 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 | Stage Compose file on Proxmox host + ansible.builtin.copy: + src: "{{ nextcloud_compose_local_file }}" + dest: "{{ nextcloud_compose_controller_staging_file }}" + mode: "0600" + register: nextcloud_compose_controller_copy + + - 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: nextcloud_compose_upload.rc == 0 + 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 -lc ' ~ + (compose_install_script | quote)) + if vm_use_sudo + else + ('bash -lc ' ~ + (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 # ------------------------------------------------------------------------- @@ -528,10 +816,12 @@ - "{{ vm_user }}@{{ vm_ip }}" - >- {{ - ('sudo -n bash -lc ' ~ (preflight_script | quote)) + ('sudo -n bash -lc ' ~ + (preflight_script | quote)) if vm_use_sudo else - ('bash -lc ' ~ (preflight_script | quote)) + ('bash -lc ' ~ + (preflight_script | quote)) }} environment: SSHPASS: "{{ vm_password }}" @@ -646,10 +936,12 @@ - "{{ vm_user }}@{{ vm_ip }}" - >- {{ - ('sudo -n bash -lc ' ~ (backup_script | quote)) + ('sudo -n bash -lc ' ~ + (backup_script | quote)) if vm_use_sudo else - ('bash -lc ' ~ (backup_script | quote)) + ('bash -lc ' ~ + (backup_script | quote)) }} environment: SSHPASS: "{{ vm_password }}" @@ -713,10 +1005,12 @@ - "{{ vm_user }}@{{ vm_ip }}" - >- {{ - ('sudo -n bash -lc ' ~ (upgrade_script | quote)) + ('sudo -n bash -lc ' ~ + (upgrade_script | quote)) if vm_use_sudo else - ('bash -lc ' ~ (upgrade_script | quote)) + ('bash -lc ' ~ + (upgrade_script | quote)) }} environment: SSHPASS: "{{ vm_password }}" @@ -869,10 +1163,12 @@ - "{{ vm_user }}@{{ vm_ip }}" - >- {{ - ('sudo -n bash -lc ' ~ (postcheck_script | quote)) + ('sudo -n bash -lc ' ~ + (postcheck_script | quote)) if vm_use_sudo else - ('bash -lc ' ~ (postcheck_script | quote)) + ('bash -lc ' ~ + (postcheck_script | quote)) }} environment: SSHPASS: "{{ vm_password }}" @@ -969,6 +1265,12 @@ Backup directory: {{ backup_dir }} + Compose source: + {{ nextcloud_compose_local_file }} + + Compose destination: + {{ nextcloud_compose_file }} + Installed: {{ nc_status.json.installed | default('?') }} @@ -986,4 +1288,4 @@ Nextcloud public status endpoint: OK Collabora root endpoint: OK Collabora discovery endpoint: OK - run_once: true \ No newline at end of file + run_once: true