--- - name: create required skudak cloud volumes become: true ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ podman_subuid.stdout }}" group: "{{ podman_subuid.stdout }}" mode: 0755 notify: restorecon podman loop: - "{{ cloud_skudak_path }}/apps" - "{{ cloud_skudak_path }}/config" - "{{ cloud_skudak_path }}/data" - "{{ cloud_skudak_path }}/mysql" - "{{ cloud_skudak_path }}/scripts" - name: unshare chown the skudak cloud volumes become: true become_user: "{{ podman_user }}" changed_when: false ansible.builtin.command: | podman unshare chown -R 33:33 {{ cloud_skudak_path }}/apps {{ cloud_skudak_path }}/data {{ cloud_skudak_path}}/config - name: flush handlers ansible.builtin.meta: flush_handlers - name: copy skudak cloud libresign setup script become: true ansible.builtin.template: src: nextcloud/libresign-setup.sh.j2 dest: "{{ cloud_skudak_path }}/scripts/libresign-setup.sh" owner: "{{ podman_subuid.stdout }}" group: "{{ podman_subuid.stdout }}" mode: 0755 notify: restorecon podman - import_tasks: podman/podman-check.yml vars: container_name: skudak-cloud-db container_image: "{{ db_image }}" - name: create skudak-cloud-db container become: true become_user: "{{ podman_user }}" containers.podman.podman_container: name: skudak-cloud-db image: "{{ db_image }}" restart_policy: on-failure:3 log_driver: journald network: - shared env: MYSQL_ROOT_PASSWORD: "{{ cloud_skudak_db_root_pass }}" MYSQL_DATABASE: skucloud MYSQL_PASSWORD: "{{ cloud_skudak_db_pass }}" MYSQL_USER: skucloud volumes: - "{{ cloud_skudak_path }}/mysql:/var/lib/mysql" - name: create systemd startup job for skudak-cloud-db include_tasks: podman/systemd-generate.yml vars: container_name: skudak-cloud-db - import_tasks: podman/podman-check.yml vars: container_name: skudak-cloud container_image: "{{ image }}" - name: create skudak cloud container become: true become_user: "{{ podman_user }}" containers.podman.podman_container: name: skudak-cloud image: "{{ image }}" restart_policy: on-failure:3 log_driver: journald network: - shared env: MYSQL_PASSWORD: "{{ cloud_skudak_db_pass }}" MYSQL_DATABASE: skucloud MYSQL_HOST: skudak-cloud-db MYSQL_USER: skucloud volumes: - "{{ cloud_skudak_path }}/apps:/var/www/html/custom_apps" - "{{ cloud_skudak_path }}/data:/var/www/html/data" - "{{ cloud_skudak_path }}/config:/var/www/html/config" - "{{ cloud_skudak_path }}/scripts/libresign-setup.sh:/docker-entrypoint-hooks.d/before-starting/libresign-setup.sh:ro" ports: - "8090:80" - name: create systemd startup job for cloud include_tasks: podman/systemd-generate.yml vars: container_name: skudak-cloud # Install poppler-utils for pdfsig/pdfinfo (LibreSign handles java/pdftk/jsignpdf via occ) # This needs to be reinstalled on each container recreation - name: install poppler-utils in skudak-cloud become: true become_user: "{{ podman_user }}" ansible.builtin.command: cmd: > podman exec -u 0 skudak-cloud sh -c "apt-get update && apt-get install -y --no-install-recommends poppler-utils && rm -rf /var/lib/apt/lists/*" register: poppler_install changed_when: "'is already the newest version' not in poppler_install.stdout" failed_when: false - name: disable nextcloud signup link in config become: true ansible.builtin.lineinfile: path: "{{ cloud_skudak_path }}/config/config.php" regexp: "^\\s*'simpleSignUpLink\\.shown'\\s*=>" line: " 'simpleSignUpLink.shown' => false," insertbefore: '^\);' create: false failed_when: false # Add cloud.skudak.com to Nextcloud trusted_domains - name: add cloud.skudak.com to nextcloud trusted_domains become: true become_user: "{{ podman_user }}" ansible.builtin.command: > podman exec -u www-data skudak-cloud php occ config:system:set trusted_domains 1 --value="cloud.skudak.com" register: trusted_domain_result changed_when: "'System config value trusted_domains' in trusted_domain_result.stdout" failed_when: false # This instance was left at loglevel 0 (DEBUG) and had written a 64 GB # nextcloud.log, almost entirely repeated deprecation notices. 2 = Warning, # which is both the Nextcloud default and what the home instance already uses. - name: set nextcloud loglevel for skudak-cloud become: true become_user: "{{ podman_user }}" ansible.builtin.command: > podman exec -u www-data skudak-cloud php occ config:system:set loglevel --value 2 --type integer register: skudak_loglevel changed_when: "'System config value loglevel' in skudak_loglevel.stdout" failed_when: false # Unbounded by default; see the equivalent task in containers/home/cloud.yml. - name: cap nextcloud log rotation size for skudak-cloud become: true become_user: "{{ podman_user }}" ansible.builtin.command: > podman exec -u www-data skudak-cloud php occ config:system:set log_rotate_size --value 10485760 --type integer register: skudak_log_rotate changed_when: "'System config value log_rotate_size' in skudak_log_rotate.stdout" failed_when: false - include_tasks: containers/cloud-cron.yml vars: cron_name: skudak-cloud cron_container: skudak-cloud cron_script_path: /usr/local/bin/skudak-cloud-cron.sh # This instance is BUSINESS data and deliberately backs up to TrueNAS ONLY. # # It used to reach personal cloud storage too: the TrueNAS "iDrive E2 Backup" # cloud-sync task pushes /mnt/glacier to a personal iDrive e2 bucket, which # swept skudakcloud/ along with it. That task now carries an explicit # `/skudakcloud/**` exclude, and on 2026-07-30 the stranded copy was purged # from the bucket -- business data does not belong in personal storage. # # The copy was also worthless as a backup: 30 objects against 20,802 files on # TrueNAS (0.14%), stale since 2026-05-20. Worse, the bucket is VERSIONED and # the sync runs in COPY mode (never deletes), so every daily run retained # another ~60 GB version of the pre-cap nextcloud.log -- 56 of them, 3.46 TB, # 99.3% of a 3.49 TB footprint. Deleting current objects alone reclaims # nothing on a versioned bucket; the versions must be purged explicitly. # # Offsite is now BUSINESS-OWNED: Skudak's own iDrive e2 account, bucket # `backup-all`, pushed by TrueNAS cloud-sync task "Skudak iDrive - Nextcloud" # (id 8, /mnt/glacier/skudakcloud -> /skudakcloud, daily 06:00). That bucket # has a 90-day NoncurrentVersionExpiration policy so the version bloat above # cannot repeat. The personal task's `/skudakcloud/**` exclude is PERMANENT -- # it is what keeps business data out of personal storage, not a stopgap. # # Still outstanding: the data itself lives on personal TrueNAS hardware. To # finish separating, add an S3 stage to cloud-backup.sh.j2 guarded by a # `backup_s3_*` var so only this instance opts in -- awscli2 is already # installed on the host -- and then drop the TrueNAS rsync below. - include_tasks: containers/cloud-backup.yml vars: backup_name: skudak-cloud data_path: "{{ cloud_skudak_path }}/data" config_path: "{{ cloud_skudak_path }}/config" db_container: skudak-cloud-db ssh_key_path: /etc/ssh/backup_keys/skudak-cloud ssh_key_content: "{{ cloud_skudak_backup_ssh_key }}" ssh_user: skucloud remote_path: /mnt/glacier/skudakcloud script_path: /usr/local/bin/skudak-cloud-backup.sh # skudakcloud/data is mode 770, so the receiving side needs traversable # dirs. This flag was hand-added on the host and was being silently # reverted by every `make deploy TAGS=skudak-cloud`; it now lives in git. backup_rsync_extra_args: "--chmod=Du=rwx,Dgo=rx" # Staggered so both instances finish before the 05:00 TrueNAS snapshot. backup_oncalendar: "*-*-* 04:30:00"