diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8a80fa3c..880e509d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,3 +19,4 @@ jobs: - run: yamllint --strict -c .yamllint . - run: ansible-lint + diff --git a/.github/workflows/molecule.yml b/.github/workflows/molecule.yml index 1ed46436..5eee674b 100644 --- a/.github/workflows/molecule.yml +++ b/.github/workflows/molecule.yml @@ -20,3 +20,4 @@ jobs: run: molecule test -- -e opencast_postgresql_password=123 env: PY_COLORS: '1' + diff --git a/README.md b/README.md index 3fe74c5b..f2edd647 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Role Variables -------------- - `opencast_postgresql_version` - - PostgreSQL major version to install (default: `12`) + - PostgreSQL major version to install (default: `16`) - Enables CentOS AppStream - `opencast_postgresql_user:` - - Database user to create (default: `opencast`) + - Database user to create (default: `postgres`) - `opencast_postgresql_password` - Databse password for user (_required_) - `opencast_postgresql_database` diff --git a/defaults/main.yml b/defaults/main.yml index 74bd42ff..594ae6af 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,8 +1,16 @@ --- - opencast_postgresql_version: 16 -opencast_postgresql_user: opencast +opencast_postgresql_user: postgres opencast_postgresql_database: opencast opencast_postgresql_connection_hosts: - 127.0.0.1/32 - ::1/128 + +# === Database backup feature (disabled by default) === +database_backup_enabled: false +database_backup_output_path: None +database_backup_schedule: "*-*-* 05:00:00" # Systemd OnCalendar format +database_backup_keep: 7 +database_backup_dbs: [] +database_backup_owner: postgres +database_backup_group: postgres diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index 23b7512f..0223844f 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -2,6 +2,6 @@ - name: Converge hosts: all tasks: - - name: "Include opencast_postgresql" + - name: Include opencast_postgresql ansible.builtin.include_role: name: elan.opencast_postgresql diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 39690b1e..7b4bc42d 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -1,11 +1,14 @@ --- -- name: Verify PostgreSQL Installation +- name: Verify PostgreSQL & Backup Configuration hosts: all gather_facts: true vars_files: - ../../defaults/main.yml tasks: + # ─────────────────────────────────────────────────────────── + # Basic PostgreSQL installation + # ─────────────────────────────────────────────────────────── - name: Ensure PostgreSQL service is running on RedHat/CentOS ansible.builtin.systemd: name: "postgresql-{{ opencast_postgresql_version }}" @@ -39,3 +42,57 @@ ansible.builtin.debug: msg: "PostgreSQL version on {{ inventory_hostname }} (Debian): {{ psql_version_debian.stdout }}" when: ansible_os_family == "Debian" + + # ─────────────────────────────────────────────────────────── + # Backup configuration (only when enabled) + # ─────────────────────────────────────────────────────────── + - name: Verify backup configuration when enabled + when: database_backup_enabled | default(false) + block: + - name: Assert backups are enabled + ansible.builtin.assert: + that: + - database_backup_enabled | default(false) + fail_msg: "Backups are disabled; skipping backup verification." + + - name: Ensure backup directory exists + ansible.builtin.stat: + path: "{{ database_backup_output_path }}" + register: backup_dir_stat + + - name: Assert backup directory is present and writable + ansible.builtin.assert: + that: + - backup_dir_stat.stat.exists + - backup_dir_stat.stat.isdir + fail_msg: > + Backup directory {{ database_backup_output_path }} + is missing or not a directory. + + - name: Check database-backup.service is installed and enabled + ansible.builtin.systemd: + name: database-backup.service + enabled: true + state: started + + - name: Check database-backup.timer is installed and enabled + ansible.builtin.systemd: + name: database-backup.timer + enabled: true + state: started + + - name: Slurp timer unit file for inspection + ansible.builtin.slurp: + path: /etc/systemd/system/database-backup.timer + register: timer_unit + + - name: Assert OnCalendar line in timer unit matches schedule + ansible.builtin.assert: + that: + - "'OnCalendar={{ database_backup_schedule }}' in (timer_unit.content | b64decode)" + fail_msg: > + database-backup.timer does not contain + OnCalendar={{ database_backup_schedule }}. + success_msg: > + Timer unit file correctly contains + OnCalendar={{ database_backup_schedule }}. diff --git a/tasks/backup.yml b/tasks/backup.yml new file mode 100644 index 00000000..d65a0e43 --- /dev/null +++ b/tasks/backup.yml @@ -0,0 +1,51 @@ +--- +- name: Fail if backup enabled but no output path given + ansible.builtin.fail: + msg: "database_backup_output_path must be set when database_backup_enabled = true" + when: + - database_backup_enabled + - database_backup_output_path | length == 0 + +- name: Ensure backup output directory exists + ansible.builtin.file: + path: "{{ database_backup_output_path }}" + state: directory + owner: "{{ database_backup_owner }}" + group: "{{ database_backup_group }}" + mode: "0750" + when: database_backup_enabled + +- name: Install backup script + ansible.builtin.template: + src: database-backup.sh.j2 + dest: "{{ database_backup_output_path }}/database-backup.sh" + owner: "{{ database_backup_owner }}" + group: "{{ database_backup_group }}" + mode: "0750" + when: database_backup_enabled + +- name: Install systemd service unit + ansible.builtin.template: + src: database-backup.service.j2 + dest: /etc/systemd/system/database-backup.service + mode: "0644" + when: database_backup_enabled + +- name: Install systemd timer unit + ansible.builtin.template: + src: database-backup.timer.j2 + dest: /etc/systemd/system/database-backup.timer + mode: "0644" + when: database_backup_enabled + +- name: Reload systemd daemon (if timers changed) + ansible.builtin.systemd: + daemon_reload: true + when: database_backup_enabled + +- name: Ensure backup timer is enabled and running + ansible.builtin.systemd: + name: database-backup.timer + enabled: true + state: started + when: database_backup_enabled diff --git a/tasks/main.yml b/tasks/main.yml index f999592c..c7e44eba 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,7 +1,4 @@ --- -############################################################################### -# CentOS / RHEL -############################################################################### - name: Install PostgreSQL PGDG repository (CentOS/RHEL) ansible.builtin.dnf: @@ -145,3 +142,10 @@ community.postgresql.postgresql_db: name: "{{ opencast_postgresql_database }}" owner: "{{ opencast_postgresql_user }}" + +############################################################################### +# database backup +############################################################################### +- name: Include backup setup tasks + ansible.builtin.include_tasks: backup.yml + when: database_backup_enabled diff --git a/templates/database-backup.service.j2 b/templates/database-backup.service.j2 new file mode 100644 index 00000000..7f25ace8 --- /dev/null +++ b/templates/database-backup.service.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=Opencast Database Backup +After=network.target +After=local-fs.target +After=remote-fs.target + +[Service] +Type=oneshot +User={{ database_backup_owner }} +Group={{ database_backup_group }} +ExecStart={{ database_backup_output_path }}/database-backup.sh + +[Install] +WantedBy=multi-user.target + diff --git a/templates/database-backup.sh.j2 b/templates/database-backup.sh.j2 new file mode 100644 index 00000000..220e4cef --- /dev/null +++ b/templates/database-backup.sh.j2 @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# DATABASE="{{ opencast_postgresql_database }}" +DBUSER="{{ database_backup_owner }}" +OUTDIR="{{ database_backup_output_path }}" +KEEP={{ database_backup_keep }} +DBS=( {{ database_backup_dbs | join(" ") }} ) +TS=$(date +%Y%m%d-%H%M%S) + +# Export PostgreSQL password so pg_dump doesn't prompt +export PGPASSWORD="{{opencast_postgresql_password}}" + +# Loop through each database name +for DB in "${DBS[@]}"; do + echo "Backing up $DB → $OUTDIR/db-backup-${DB}-${TS}.dump.gz" + + # Run pg_dump and compress into a .gz file + pg_dump -F c "$DB" \ + | gzip > "${OUTDIR}/db-backup-${DB}-${TS}.dump.gz" + + # Remove older dumps, keep only the newest $KEEP + ls -1t "${OUTDIR}/db-backup-${DB}-"*.dump.gz \ + | tail -n +$((KEEP + 1)) \ + | xargs -r rm -- +done \ No newline at end of file diff --git a/templates/database-backup.timer.j2 b/templates/database-backup.timer.j2 new file mode 100644 index 00000000..42355bdd --- /dev/null +++ b/templates/database-backup.timer.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=Run database backup daily + +[Timer] +OnCalendar={{ database_backup_schedule }} +Persistent=true + +[Install] +WantedBy=timers.target +