diff --git a/roles/borgrepo/defaults/main.yaml b/roles/borgrepo/defaults/main.yaml new file mode 100644 index 0000000..4362fa7 --- /dev/null +++ b/roles/borgrepo/defaults/main.yaml @@ -0,0 +1,10 @@ +--- +host_fqdn: '{{ ansible_hostname }}.dmz.{{ domain }}' +borgrepo_force_new_key: false +borgrepo_backup_host: 'backup' +borgrepo_repos: + picture: + folder: a + baobab: + pgsq: fff +... diff --git a/roles/borgrepo/handlers/main.yaml b/roles/borgrepo/handlers/main.yaml new file mode 100644 index 0000000..3339a13 --- /dev/null +++ b/roles/borgrepo/handlers/main.yaml @@ -0,0 +1,5 @@ +--- +- name: 'reload systemd' + debug: + msg: noop +... diff --git a/roles/borgrepo/tasks/main.yaml b/roles/borgrepo/tasks/main.yaml new file mode 100644 index 0000000..2ec7733 --- /dev/null +++ b/roles/borgrepo/tasks/main.yaml @@ -0,0 +1,167 @@ +--- +- name: 'install borg' + apt: + pkg: 'borgbackup' + state: 'latest' + update_cache: true + cache_valid_time: 3600 + +- name: 'create .ssh folder' + file: + path: '/root/.ssh' + mode: '0700' + state: 'directory' + +- name: 'create backup keypair' + openssh_keypair: + path: '/root/.ssh/id_ed25519_BORG' + force: '{{ borgrepo_force_new_key }}' + type: 'ed25519' + comment: 'backup@{{ host_fqdn }}' + register: ssh_keypair + +- name: 'create host repos namespace' + file: + path: '/home/backup/repos/{{ host_fqdn }}' + owner: 'backup' + group: 'backup' + mode: '0700' + state: 'directory' + delegate_to: '{{ borgrepo_backup_host }}' + +- name: 'authorize host key' + lineinfile: + path: '/home/backup/.ssh/authorized_keys' + owner: 'backup' + group: 'backup' + mode: '0600' + create: yes + line: >- + command="cd {{ repodir }}; borg serve --append-only --restrict-to-path {{ repodir }}",restrict + {{ ssh_keypair.public_key }} + regexp: '{{ ssh_keypair.comment }}$' + state: 'present' + vars: + repodir: '/home/backup/repos/{{ host_fqdn }}' + delegate_to: '{{ borgrepo_backup_host }}' + + +- name: 'upload host ssh ca' + copy: + content: | + {% for ca in ssh_server_ca %} + @cert-authority *.dmz.{{ domain }} {{ ca }} + {% endfor %} + dest: '/root/.ssh/known_hosts' + mode: '0600' + +##ToDo setup encryption +- name: 'initialize repo' + shell: + cmd: > + borg init -e none backup@{{ borgrepo_backup_host }}.dmz.{{ domain }}:{{ item.key }} + register: borgrepo_init_cmd + failed_when: + - borgrepo_init_cmd.rc != 0 + - borgrepo_init_cmd.stderr !='A repository already exists at backup@backup.dmz.lilik.it:'+item.key+'.' + changed_when: borgrepo_init_cmd.rc == 0 + environment: + BORG_RSH: 'ssh -i /root/.ssh/id_ed25519_BORG' + loop: '{{ borgrepo_repos|dict2items }}' + +- name: 'create backup directory' + file: + path: '/etc/backup' + state: 'directory' + owner: 'root' + group: 'root' + mode: '0700' + +- name: 'create log backup directory' + file: + path: '/var/log/backup-status' + state: 'directory' + owner: 'root' + group: 'root' + mode: '0755' + +- name: 'create repo log directory' + file: + path: '/var/log/backup-status/{{ item.key }}' + state: 'directory' + owner: 'root' + group: 'root' + mode: '0755' + loop: '{{ borgrepo_repos|dict2items }}' + +- name: 'create backup scripts' + template: + src: 'backupscript.sh.j2' + dest: '/etc/backup/{{ item.key }}.sh' + owner: 'root' + group: 'root' + mode: '0700' + loop: '{{ borgrepo_repos|dict2items }}' + +- name: 'create systemd service' + template: + src: 'backupservice.service' + dest: '/etc/systemd/system/borg-backup@.service' + notify: reload systemd + +- name: 'create systemd timers' + copy: + content: | + [Unit] + Description=BorgBackup %I repo timer. + + [Timer] + WakeSystem=false + OnCalendar=*-*-* 02:00:00 + RandomizedDelaySec=20min + + [Install] + WantedBy=timers.target + dest: '/etc/systemd/system/borg-backup@.timer' + notify: reload systemd + +- name: 'enable systemd timers' + systemd: + name: 'borg-backup@{{ item.key }}.timer' + daemon_reload: true + enabled: true + state: 'restarted' + loop: '{{ borgrepo_repos|dict2items }}' + +- name: 'MONITORING | create entry' + set_fact: + borg_monitoring_repos: > + {{ borg_monitoring_repos|d({})|combine({ + item.key: + { + "backup_wage": item.value.interval|d(86400)|int, + "backup_cage": (item.value.interval|d(86400)|int+7200)*2 + } + }) }} + loop: '{{ borgrepo_repos|dict2items }}' + tags: + - 'monitoring' + +- name: 'MONITORING | update facts' + set_fact: + monitoring_facts: > + {{ hostvars[monitoring_host]["monitoring_facts"] + | default({}) + | combine({ + host_fqdn: + { + "address": ansible_host, + "borg_repos": borg_monitoring_repos + } + }, recursive=True) }} + delegate_to: '{{ monitoring_host }}' + delegate_facts: true + loop: '{{ borgrepo_repos|dict2items }}' + tags: + - 'monitoring' +... diff --git a/roles/borgrepo/templates/backupscript.sh.j2 b/roles/borgrepo/templates/backupscript.sh.j2 new file mode 100644 index 0000000..02c6a3a --- /dev/null +++ b/roles/borgrepo/templates/backupscript.sh.j2 @@ -0,0 +1,160 @@ +#!/bin/bash + +REPO="backup@{{ borgrepo_backup_host }}.dmz.{{ domain }}:{{ item.key }}" + +export BORG_RSH="ssh -i /root/.ssh/id_ed25519_BORG" +export BORG_PASSPHRASE="" + +export BORG_RELOCATED_REPO_ACCESS_IS_OK="no" +export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK="no" + +borg --version + + + +{% for folder in item.value.folders|d({})|dict2items %} +##### Folder {{ folder.key }} +BEGIN_EPOCH=$(date +%s) +DATE="folder-{{ folder.key }}-$(date --iso-8601)-$(hostname)" +echo "Starting backup for $DATE" +borg create \ + --verbose \ + --filter AME \ + --list \ + --stats \ + --show-rc \ + --compression lz4 \ + --exclude-caches \ +{% for exclude in folder.value.excludes|d([]) %} + --exclude '{{ exclude }}' \ +{% endfor %} + \ + ${REPO}::'folder-{{ folder.key }}-{hostname}-{now:%Y-%m-%d@%H:%M}' \ + {{ folder.value.path }} + +backup_rc=$? +prune_rc=99 + +if [ "${backup_rc}" = "2" ]; then + echo "Failed backup for $DATE" +else + echo "Completed backup for $DATE" + + echo "Pruning archives for $DATE" + borg prune \ + --list \ + --prefix 'folder-{{ folder.key }}-{hostname}-' \ + --show-rc \ + --keep-daily {{ folder.value.daily|d(7) }} \ + --keep-weekly {{ folder.value.weekly|d(4) }} \ + --keep-monthly {{ folder.value.monthly|d(6) }} \ + ${REPO} + + prune_rc=$? + + if [ "${prune_rc}" = "2" ]; then + echo "Failed pruning for $DATE" + else + echo "Completed pruning for $DATE" + fi +fi + +echo "$(date +%s)|${BEGIN_EPOCH}|${backup_rc}|${prune_rc}" > /var/log/backup-status/{{ item.key }}/folder-{{ folder.key }} + +{% endfor %} +##### + +{% for db in item.value.pgsql_dbs|d({})|dict2items %} +##### pgSQL DB {{ db.key }} +DATE="pgsqldb-{{ db.key }}-$(date --iso-8601)-$(hostname)" +echo "Starting backup for $DATE" +su postgres -c 'pg_dump -d {{ db.value.dbname }}' | borg create \ + --verbose \ + --filter AME \ + --list \ + --stats \ + --show-rc \ + --compression lz4 \ + --stdin-name "{{ db.value.dbname }}.sql" \ + \ + ${REPO}::'pgsqldb-{{ db.key }}-{hostname}-{now:%Y-%m-%d@%H:%M}' \ + - + +backup_rc=$? +prune_rc=99 + +if [ "${backup_rc}" = "2" ]; then + echo "Failed backup for $DATE" +else + echo "Completed backup for $DATE" + + echo "Pruning archives for $DATE" + borg prune \ + --list \ + --prefix 'pgsqldb-{{ db.key }}-{hostname}-' \ + --show-rc \ + --keep-daily {{ db.value.daily|d(7) }} \ + --keep-weekly {{ db.value.weekly|d(4) }} \ + --keep-monthly {{ db.value.monthly|d(6) }} \ + ${REPO} + + prune_rc=$? + + if [ "${prune_rc}" = "2" ]; then + echo "Failed pruning for $DATE" + else + echo "Completed pruning for $DATE" + fi +fi + +echo "$(date +%s)|${BEGIN_EPOCH}|${backup_rc}|${prune_rc}" > /var/log/backup-status/{{ item.key }}/pgsqldb-{{ db.key }} + +{% endfor %} + + +{% for db in item.value.ldap_dbs|d({})|dict2items %} +##### LDAP DB {{ db.key }} +DATE="ldapdb-{{ db.key }}-$(date --iso-8601)-$(hostname)" +echo "Starting backup for $DATE" +slapcat -n {{ db.value.dbnum }} | borg create \ + --verbose \ + --filter AME \ + --list \ + --stats \ + --show-rc \ + --compression lz4 \ + --stdin-name "slapcat.db{{ db.value.dbnum }}.ldif" \ + \ + ${REPO}::'ldapdb-{{ db.key }}-{hostname}-{now:%Y-%m-%d@%H:%M}' \ + - + +backup_rc=$? +prune_rc=99 + +if [ "${backup_rc}" = "2" ]; then + echo "Failed backup for $DATE" +else + echo "Completed backup for $DATE" + + echo "Pruning archives for $DATE" + borg prune \ + --list \ + --prefix 'ldapdb-{{ db.key }}-{hostname}-' \ + --show-rc \ + --keep-daily {{ db.value.daily|d(7) }} \ + --keep-weekly {{ db.value.weekly|d(4) }} \ + --keep-monthly {{ db.value.monthly|d(6) }} \ + ${REPO} + + prune_rc=$? + + if [ "${prune_rc}" = "2" ]; then + echo "Failed pruning for $DATE" + else + echo "Completed pruning for $DATE" + fi +fi + +echo "$(date +%s)|${BEGIN_EPOCH}|${backup_rc}|${prune_rc}" > /var/log/backup-status/{{ item.key }}/ldapdb-{{ db.key }} + +{% endfor %} diff --git a/roles/borgrepo/templates/backupservice.service b/roles/borgrepo/templates/backupservice.service new file mode 100644 index 0000000..ef5dcb5 --- /dev/null +++ b/roles/borgrepo/templates/backupservice.service @@ -0,0 +1,15 @@ +[Unit] +Description=BorgBackup script for Repository %I +After=network.target + +[Service] +Type=oneshot +Nice=19 +IOSchedulingClass=2 +IOSchedulingPriority=7 +Environment=BORG_RSH="ssh -i /root/.ssh/id_ed25519_BORG" +ExecStartPre=/usr/bin/borg break-lock backup@{{ borgrepo_backup_host }}.dmz.{{ domain }}:%i +ExecStart=/etc/backup/%i.sh +PIDFile=/tmp/borg_backup_%i.pid +User=root +Group=root diff --git a/roles/borgserver/tasks/main.yaml b/roles/borgserver/tasks/main.yaml new file mode 100644 index 0000000..0807337 --- /dev/null +++ b/roles/borgserver/tasks/main.yaml @@ -0,0 +1,46 @@ +--- +- name: 'install borgbackup' + apt: + pkg: 'borgbackup' + state: 'latest' + update_cache: true + cache_valid_time: 3600 + +- name: 'create backup group' + group: + name: 'backup' + state: 'present' + +- name: 'create backup user' + user: + name: 'backup' + shell: '/bin/bash' + home: '/home/backup' + createhome: true + group: 'backup' + state: 'present' + +- name: 'create home folder' + file: + path: '/home/backup/repos' + owner: 'backup' + group: 'backup' + mode: '0700' + state: 'directory' + +- name: 'create .ssh folder' + file: + path: '/home/backup/repos' + owner: 'backup' + group: 'backup' + mode: '0700' + state: 'directory' + +- name: 'create repos folder' + file: + path: '/home/backup/repos' + owner: 'backup' + group: 'backup' + mode: '0700' + state: 'directory' +...