Files
nult/roles/gitea/tasks/backup.yml
Mark a9554f3e5d Initial commit: nult - Ansible deployment toolkit
Merged from veridion-gitea and veridion-act-runner-gitea repos.

nult (Null-T) - instant teleportation from Strugatsky's Noon Universe.
Like Null-T, this toolkit instantly deploys infrastructure.

Roles:
- gitea: Gitea server with PostgreSQL (Docker Compose)
- act_runner: Gitea Actions runner

Playbooks:
- gitea.yml: Deploy Gitea server
- act-runner.yml: Deploy Act Runner
- site.yml: Deploy all services

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 15:34:07 +01:00

139 lines
5.9 KiB
YAML

---
# =============================================================================
# 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