Files
deploy_home/ansible/roles/podman/tasks/containers/skudak/cloud.yml
T
Bastian de Byl bc110ce69e back up Gitea + Skudak app data; drop PartKeepr and Pi-hole
Extends the Nextcloud backup machinery rather than adding a second
mechanism. cloud-backup.sh.j2 gains three guarded options, all no-ops for
the existing callers:

  backup_podman_user  Gitea runs rootless under `git`, not `podman`
  backup_db_type      postgres (Gitea) and mysql (BookStack) alongside
                      mariadb; each engine's completion trailer differs,
                      and grepping for the wrong one fails every run
  backup_sqlite_dbs   `sqlite3 .backup` for live WAL-mode SQLite, gated on
                      `pragma integrity_check` before promotion -- rsync
                      is either stale (no -wal) or torn (with it)

New instances: gitea-debyl, skudak-gitea, bookstack, partsy-skudak. The
alert handler is rendered once and shared, so its wording is now generic
rather than per-product; TAG stays nextcloud-backup because an external
Graylog rule matches on it.

`apply:` on the includes is load-bearing -- tags on a dynamic
include_tasks do not reach the tasks inside it.

Business data (skudak-gitea, bookstack, partsy-skudak) goes to TrueNAS
and on to Skudak's own iDrive account; the personal bucket's
/skudak*/** excludes are permanent, not a stopgap.

Removals: PartKeepr is superseded by Partsy, and its teardown never
finished -- it targeted /etc/systemd/system/podman-partkeepr*.service,
wrong prefix and wrong scope, leaving enabled user units in failed state.
Pi-hole's role was already orphaned (absent from deploy_home.yml); its
port 53 rule went with it after confirming nothing listens there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:08:47 -04:00

207 lines
7.8 KiB
YAML

---
- 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"