--- - name: Install offsite backup packages ansible.builtin.apt: name: - restic - rclone - sqlite3 - ca-certificates state: present update_cache: true - name: Ensure rclone config directory exists ansible.builtin.file: path: /root/.config/rclone state: directory owner: root group: root mode: "0700" - name: Install rclone config ansible.builtin.copy: src: "{{ offsite_rclone_config_local }}" dest: /root/.config/rclone/rclone.conf owner: root group: root mode: "0600" no_log: true - name: Ensure restic config directory exists ansible.builtin.file: path: /etc/restic state: directory owner: root group: root mode: "0700" - name: Install restic repository password ansible.builtin.copy: src: "{{ offsite_restic_password_local }}" dest: "/etc/restic/{{ offsite_profile }}.password" owner: root group: root mode: "0600" no_log: true - name: Ensure offsite backup directories exist ansible.builtin.file: path: "{{ item }}" state: directory owner: root group: root mode: "0700" loop: - /etc/homelab-restic - /var/lib/homelab-restic - /var/log/homelab-restic - name: Install restic exclude file ansible.builtin.copy: dest: "/etc/homelab-restic/{{ offsite_profile }}.exclude" owner: root group: root mode: "0644" content: | {% for exclude in offsite_excludes %} {{ exclude }} {% endfor %} - name: Install restic environment file ansible.builtin.copy: dest: "/etc/homelab-restic/{{ offsite_profile }}.env" owner: root group: root mode: "0600" content: | RESTIC_REPOSITORY={{ offsite_repository }} RESTIC_PASSWORD_FILE=/etc/restic/{{ offsite_profile }}.password RCLONE_CONFIG=/root/.config/rclone/rclone.conf HOMELAB_RESTIC_PROFILE={{ offsite_profile }} HOMELAB_RESTIC_SOURCE={{ offsite_source_path }} HOMELAB_RESTIC_SQLITE_DB={{ offsite_sqlite_db | default('') }} HOMELAB_RESTIC_MARIADB_CONTAINER={{ offsite_mariadb_container | default('') }} HOMELAB_RESTIC_MARIADB_DATABASE={{ offsite_mariadb_database | default('') }} HOMELAB_RESTIC_MARIADB_USER={{ offsite_mariadb_user | default('') }} HOMELAB_RESTIC_MARIADB_ENV={{ offsite_mariadb_env | default('') }} HOMELAB_RESTIC_MARIADB_DUMP_NAME={{ offsite_mariadb_dump_name | default('') }} HOMELAB_RESTIC_EXCLUDE_FILE=/etc/homelab-restic/{{ offsite_profile }}.exclude HOMELAB_RESTIC_STAGING=/var/lib/homelab-restic/{{ offsite_profile }} HOMELAB_RESTIC_TAGS={{ offsite_backup_tag }} HOMELAB_RESTIC_KEEP_DAILY=14 HOMELAB_RESTIC_KEEP_WEEKLY=8 HOMELAB_RESTIC_KEEP_MONTHLY=6 - name: Install restic offsite backup script ansible.builtin.copy: dest: /usr/local/sbin/homelab-restic-offsite-backup owner: root group: root mode: "0755" content: | #!/bin/sh set -eu if [ "$#" -ne 1 ]; then echo "usage: $0 " >&2 exit 64 fi env_file="/etc/homelab-restic/$1.env" if [ ! -f "$env_file" ]; then echo "missing env file: $env_file" >&2 exit 66 fi set -a . "$env_file" set +a export RESTIC_REPOSITORY RESTIC_PASSWORD_FILE RCLONE_CONFIG mkdir -p "$HOMELAB_RESTIC_STAGING" rm -rf "$HOMELAB_RESTIC_STAGING"/* if [ -n "${HOMELAB_RESTIC_SQLITE_DB:-}" ] && [ -f "$HOMELAB_RESTIC_SQLITE_DB" ]; then sqlite_backup="$HOMELAB_RESTIC_STAGING/$(basename "$HOMELAB_RESTIC_SQLITE_DB")" sqlite3 -cmd 'PRAGMA busy_timeout=30000;' "$HOMELAB_RESTIC_SQLITE_DB" ".backup '$sqlite_backup'" sqlite3 -cmd 'PRAGMA busy_timeout=30000;' "$sqlite_backup" "PRAGMA integrity_check;" | grep -qx ok fi if [ -n "${HOMELAB_RESTIC_MARIADB_CONTAINER:-}" ]; then if [ ! -f "$HOMELAB_RESTIC_MARIADB_ENV" ]; then echo "missing MariaDB env file: $HOMELAB_RESTIC_MARIADB_ENV" >&2 exit 66 fi set -a . "$HOMELAB_RESTIC_MARIADB_ENV" set +a dump_tmp="$HOMELAB_RESTIC_STAGING/${HOMELAB_RESTIC_MARIADB_DUMP_NAME}.tmp" dump_final="$HOMELAB_RESTIC_STAGING/$HOMELAB_RESTIC_MARIADB_DUMP_NAME" docker exec -e MYSQL_PWD="$DB_PASSWORD" "$HOMELAB_RESTIC_MARIADB_CONTAINER" \ mariadb-dump --user="$HOMELAB_RESTIC_MARIADB_USER" --single-transaction --routines --events \ --databases "$HOMELAB_RESTIC_MARIADB_DATABASE" > "$dump_tmp" test -s "$dump_tmp" mv "$dump_tmp" "$dump_final" { printf '%s\n' 'grimmory_image=grimmory/grimmory:v3.2.4@sha256:dfa7afdfcf25d649fd664497a62385dd00cd9678c37546e182c172e41c8e80cb' printf '%s\n' 'mariadb_image=lscr.io/linuxserver/mariadb:11.4.8@sha256:91de7f701bc7fc3a424b81beafca7a7c6c4c5b7c8be6afd2ae148698695c0b0c' printf 'created_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" } > "$HOMELAB_RESTIC_STAGING/manifest.txt" fi restic snapshots >/dev/null 2>&1 || restic init tag_args="" old_ifs="$IFS" IFS=, for tag in $HOMELAB_RESTIC_TAGS; do tag_args="$tag_args --tag $tag" done IFS="$old_ifs" # shellcheck disable=SC2086 restic backup "$HOMELAB_RESTIC_SOURCE" "$HOMELAB_RESTIC_STAGING" \ --exclude-file "$HOMELAB_RESTIC_EXCLUDE_FILE" \ --one-file-system \ $tag_args restic forget --prune \ --keep-daily "$HOMELAB_RESTIC_KEEP_DAILY" \ --keep-weekly "$HOMELAB_RESTIC_KEEP_WEEKLY" \ --keep-monthly "$HOMELAB_RESTIC_KEEP_MONTHLY" if [ -n "${HOMELAB_RESTIC_MARIADB_CONTAINER:-}" ]; then rm -rf "$HOMELAB_RESTIC_STAGING"/* fi - name: Install restic offsite systemd service ansible.builtin.copy: dest: "/etc/systemd/system/homelab-restic-offsite-{{ offsite_profile }}.service" owner: root group: root mode: "0644" content: | [Unit] Description=HomeLab restic offsite backup ({{ offsite_profile }}) Wants=network-online.target After=network-online.target [Service] Type=oneshot ExecStart=/usr/bin/flock -w 1800 /var/lock/homelab-restic-{{ offsite_profile }}.lock /usr/local/sbin/homelab-restic-offsite-backup {{ offsite_profile }} - name: Install restic offsite systemd timer ansible.builtin.copy: dest: "/etc/systemd/system/homelab-restic-offsite-{{ offsite_profile }}.timer" owner: root group: root mode: "0644" content: | [Unit] Description=Run HomeLab restic offsite backup ({{ offsite_profile }}) [Timer] OnCalendar={{ offsite_timer_oncalendar }} Persistent=true RandomizedDelaySec=15m [Install] WantedBy=timers.target - name: Reload systemd ansible.builtin.systemd: daemon_reload: true - name: Enable restic offsite timer ansible.builtin.systemd: name: "homelab-restic-offsite-{{ offsite_profile }}.timer" enabled: true state: started