--- # ============================================================================= # Backup Tasks - Using Official Gitea Dump # ============================================================================= # # Uses the official `gitea dump` command for comprehensive backup. # Reference: https://docs.gitea.com/administration/backup-and-restore # # The dump creates a ZIP containing: # - app.ini and custom/ directory (configuration) # - All git repositories # - Database SQL dump (gitea-db.sql) # - Attachments, avatars, LFS objects # # Note: We run the dump while Gitea is running. The official docs recommend # stopping Gitea for perfect consistency, but docker exec requires a running # container. The consistency risk during the brief dump operation is minimal. # ============================================================================= # Set up variables for this backup run. # gitea_backup_timestamp: Used in filename for unique identification (e.g., 20260114T143022) # gitea_backup_dir_path: Where backups are stored on the host filesystem - name: Set backup variables ansible.builtin.set_fact: gitea_backup_timestamp: "{{ ansible_facts['date_time']['iso8601_basic_short'] }}" gitea_backup_dir_path: "{{ gitea_install_dir }}/{{ gitea_backup_dir }}" # Ensure the backup directory exists on the host. # Mode 0750: owner read/write/execute, group read/execute, others no access - name: Ensure backup directory exists ansible.builtin.file: path: "{{ gitea_backup_dir_path }}" state: directory mode: "0750" # ----------------------------------------------------------------------------- # Create Backup Using Official Gitea Dump # ----------------------------------------------------------------------------- # Run the official gitea dump command inside the running container. # # Command breakdown: # docker exec {{ container }} - Execute command inside the container # /usr/local/bin/gitea dump - The gitea binary with dump subcommand # -c /data/gitea/conf/app.ini - Path to config file (tells gitea where data is) # -w /tmp - Working directory for temporary files # -f /tmp/gitea-backup-*.zip - Output filename for the backup archive # # Note: We write to /tmp inside the container first, then copy out. # This avoids permission issues with mounted volumes. - name: Run gitea dump command ansible.builtin.command: cmd: >- docker exec -u git {{ gitea_container_name }} /usr/local/bin/gitea dump -c /data/gitea/conf/app.ini -w /tmp -f /tmp/gitea-backup-{{ gitea_backup_timestamp }}.zip register: gitea_dump_result changed_when: true # Copy backup from container to host filesystem # docker cp copies files between container and host. # Format: docker cp container:/path/in/container /path/on/host - name: Copy backup from container to host ansible.builtin.command: cmd: >- docker cp {{ gitea_container_name }}:/tmp/gitea-backup-{{ gitea_backup_timestamp }}.zip {{ gitea_backup_dir_path }}/gitea-backup-{{ gitea_backup_timestamp }}.zip changed_when: true # Clean up the backup file inside the container # We don't want to leave large files in the container's /tmp # failed_when: false - Don't fail if cleanup fails (backup already succeeded) - name: Remove backup file from container ansible.builtin.command: cmd: >- docker exec -u git {{ gitea_container_name }} rm /tmp/gitea-backup-{{ gitea_backup_timestamp }}.zip changed_when: true failed_when: false # ----------------------------------------------------------------------------- # Backup Verification # ----------------------------------------------------------------------------- # Verify the backup was actually created and is not empty. # This catches cases where the command succeeded but produced no output. - name: Verify backup file exists ansible.builtin.stat: path: "{{ gitea_backup_dir_path }}/gitea-backup-{{ gitea_backup_timestamp }}.zip" register: gitea_backup_file # Display backup info for operator visibility # Size calculation: bytes / 1048576 = megabytes (1024*1024) - name: Display backup info ansible.builtin.debug: msg: >- Backup completed: {{ gitea_backup_dir_path }}/gitea-backup-{{ gitea_backup_timestamp }}.zip Size: {{ (gitea_backup_file.stat.size / 1048576) | round(2) }}MB when: gitea_backup_file.stat.exists | default(false) - name: Fail if backup file is missing or empty ansible.builtin.fail: msg: "Backup file is missing or empty. Cannot proceed." when: - not ansible_check_mode - not gitea_backup_file.stat.exists or gitea_backup_file.stat.size == 0 # ----------------------------------------------------------------------------- # Backup Rotation # ----------------------------------------------------------------------------- # Keep only the N most recent backups (controlled by gitea_backup_retention). # This prevents disk space from being exhausted by old backups. # Find all existing backup files matching our naming pattern - name: Find old backup files ansible.builtin.find: paths: "{{ gitea_backup_dir_path }}" patterns: "gitea-backup-*.zip" file_type: file register: gitea_old_backup_files # Delete old backups, keeping only the most recent ones. # Logic breakdown: # gitea_old_backup_files.files | sort(attribute='mtime') - Sort by modification time (oldest first) # [:-gitea_backup_retention] - Slice: all except last N items # (Python slice notation: [:-5] = all but last 5) # Example: If we have 7 backups and retention=5, this deletes the 2 oldest. - name: Remove old backups beyond retention limit ansible.builtin.file: path: "{{ item.path }}" state: absent loop: "{{ (gitea_old_backup_files.files | sort(attribute='mtime'))[:-gitea_backup_retention] }}" loop_control: label: "{{ item.path | basename }}" when: gitea_old_backup_files.files | length > gitea_backup_retention