diff --git a/.gitignore b/.gitignore index 748eae7..40a3359 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ vars/minio.yml vars/woodpecker_production.yml vars/woodpecker_staging.yml + +vars/backup.yml diff --git a/README.md b/README.md index a8a8cb0..9329a29 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Automation to create/configure the infrastructure for all services related to [f - Replace the dummy values with the real ones (values are only available after the manual creation of an OAuth2 app) - Copy `vars/minio.yml.example` to `vars/minio.yml` - Replace the dummy values with the real ones +- Copy `vars/backup.yml.example` to `vars/backup.yml` + - Replace the dummy values with the real ones ## Terraform diff --git a/backup.yaml b/backup.yaml new file mode 100644 index 0000000..f5d1806 --- /dev/null +++ b/backup.yaml @@ -0,0 +1,8 @@ +--- +- name: Check SSH port + import_playbook: ssh.yaml +- name: Deploy backup + hosts: production + roles: + - restic + become: true diff --git a/playbook.yaml b/playbook.yaml index 7e6bcae..277edfa 100644 --- a/playbook.yaml +++ b/playbook.yaml @@ -7,3 +7,5 @@ import_playbook: forgejo-staging.yaml - name: Forgejo production import_playbook: forgejo-prod.yaml +- name: Backup + import_playbook: backup.yaml diff --git a/roles/restic/tasks/main.yml b/roles/restic/tasks/main.yml new file mode 100644 index 0000000..2e99054 --- /dev/null +++ b/roles/restic/tasks/main.yml @@ -0,0 +1,75 @@ +--- +- name: Include backup vars + ansible.builtin.include_vars: + file: backup.yml + name: backup_config + +- name: Install restic dependencies + ansible.builtin.apt: + name: + - fuse + - bzip2 + - pigz + state: present + +- name: Download restic + ansible.builtin.get_url: + url: "https://github.com/restic/restic/releases/download/v{{ backup_config.restic_version }}/restic_{{ backup_config.restic_version }}_linux_amd64.bz2" + dest: "/tmp/restic_{{ backup_config.restic_version }}_linux_amd64.bz2" + +- name: Extract restic + command: "bzip2 -d /tmp/restic_{{ backup_config.restic_version }}_linux_amd64.bz2" + args: + creates: "/tmp/restic_{{ backup_config.restic_version }}_linux_amd64" + +- name: Create restic directory + ansible.builtin.file: + path: /opt/restic + state: directory + mode: '0755' + +- name: Install restic + ansible.builtin.copy: + remote_src: true + src: "/tmp/restic_{{ backup_config.restic_version }}_linux_amd64" + dest: "/opt/restic/restic" + mode: 0755 + +- name: Remove downloaded file + ansible.builtin.file: + path: "/tmp/restic_{{ backup_config.restic_version }}_linux_amd64" + state: absent + +- name: Copy scripts + ansible.builtin.template: + src: "{{ item }}.j2" + dest: /opt/restic/{{ item }} + mode: 0755 + loop: + - backup.sh + - restore.sh + +- name: Create log folder + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: 0644 + loop: + - /var/log/restic + +- name: Create empty log file + ansible.builtin.copy: + content: "" + dest: /var/log/restic/backup.log + force: false + group: sys + owner: root + mode: 0644 + +- name: Add daily cronjob for backups + ansible.builtin.cron: + name: "restic backup" + user: root + minute: "0" + hour: "4" + job: "/opt/restic/backup.sh >> /var/log/restic/backup.log" diff --git a/roles/restic/templates/backup.sh.j2 b/roles/restic/templates/backup.sh.j2 new file mode 100644 index 0000000..7462f90 --- /dev/null +++ b/roles/restic/templates/backup.sh.j2 @@ -0,0 +1,60 @@ +#!/bin/bash + +set -e + +# Set crypto passphrase for encryption +export AWS_ACCESS_KEY_ID={{ backup_config.access_key }} +export AWS_SECRET_ACCESS_KEY={{ backup_config.secret_key }} +export RESTIC_PASSWORD={{ backup_config.restic_key }} +export RESTIC_REPOSITORY={{ backup_config.restic_target }} +restic="/opt/restic/restic" +dir_prefix="/backup/dbs" + +# Write beginning date to backup log +echo 'Backup date' $(date)'.' +echo ' ' + +# Create database dump folders +mkdir -p $dir_prefix/forgejo/postgres +mkdir -p $dir_prefix/woodpecker/postgres + +# Remove previous dumps +rm -f $dir_prefix/forgejo/postgres/dump.sql +rm -f $dir_prefix/woodpecker/postgres/dump.sql + +# Dump databases +docker exec -t forgejo_db_1 pg_dumpall -c -U forgejo > $dir_prefix/forgejo/postgres/dump.sql +docker exec -t woodpecker_woodpecker-database_1 pg_dumpall -c -U postgres > $dir_prefix/woodpecker/postgres/dump.sql + +# Check if repo must be initialized +if $restic cat config >/dev/null 2>&1; then + echo 'Repo was already initialized' +else + echo 'Repo not initialized.' + $restic init +fi + +# Unlock lock of repo +$restic unlock + +# Do a backup +$restic --verbose backup /var/log /var/lib/docker/volumes /srv /backup +$restic --verbose backup /etc /opt +$restic --verbose backup /home /root + +# Clean up older backups +$restic --verbose forget --keep-last 5 --keep-daily 14 --keep-weekly 4 --keep-monthly 24 + +# Data clean up +$restic --verbose prune + +# Write end to log file +echo ' ' +echo '============================' +echo ' ' + +# Unset ENVs +unset AWS_ACCESS_KEY_ID +unset AWS_SECRET_ACCESS_KEY +unset RESTIC_PASSWORD +unset RESTIC_REPOSITORY diff --git a/roles/restic/templates/restore.sh.j2 b/roles/restic/templates/restore.sh.j2 new file mode 100644 index 0000000..4e237e3 --- /dev/null +++ b/roles/restic/templates/restore.sh.j2 @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e + +# Set crypto passphrase for encryption +export AWS_ACCESS_KEY_ID={{ backup_config.access_key }} +export AWS_SECRET_ACCESS_KEY={{ backup_config.secret_key }} +export RESTIC_PASSWORD={{ backup_config.restic_key }} +export RESTIC_REPOSITORY={{ backup_config.restic_target }} +restic="/opt/restic/restic" +dir_prefix="/backup/dbs" + +# Write beginning date to backup log +echo 'Restore date' $(date)'.' +echo ' ' + +# Unlock lock of repo +$restic unlock + +# Show snapshots +$restic snapshots + +# Restore the latest backup to /tmp/restic/restore +mkdir -p /tmp/restic/restore +# One of each paths is enough to match filter +$restic --verbose restore latest --target /tmp/restic/restore --path "/var/log" --host "{{prod_url}}" +$restic --verbose restore latest --target /tmp/restic/restore --path "/etc" --host "{{prod_url}}" +$restic --verbose restore latest --target /tmp/restic/restore --path "/root" --host "{{prod_url}}" + +# Write end to log file +echo ' ' +echo '============================' +echo ' ' + +# Unset ENVs +unset AWS_ACCESS_KEY_ID +unset AWS_SECRET_ACCESS_KEY +unset RESTIC_PASSWORD +unset RESTIC_REPOSITORY diff --git a/vars/backup.yml.example b/vars/backup.yml.example new file mode 100644 index 0000000..d2c2f31 --- /dev/null +++ b/vars/backup.yml.example @@ -0,0 +1,11 @@ +--- + +# +# Backup configuration +# + +restic_version: "0.15.1" +restic_target: "s3:https://s3.TODO.com/BUCKET" +restic_key: "TODO" +access_key: "TODO" +secret_key: "TODO"