Setup backup server configuration for backup replication.

This commit is contained in:
Lars Hahn 2024-08-11 20:57:32 +02:00
parent 768da9cf45
commit 692e199868
9 changed files with 249 additions and 12 deletions

View File

@ -26,3 +26,19 @@ backup_script:
echo "This is executed before borg backup. Please collect data for backup in path: {{ backup_storage }}" echo "This is executed before borg backup. Please collect data for backup in path: {{ backup_storage }}"
postwork_restore: | postwork_restore: |
echo "This is executed after borg restore. Please collect data during restore from path: {{ backup_storage }}" echo "This is executed after borg restore. Please collect data during restore from path: {{ backup_storage }}"
backup_ssh_port: 22
backup_host: my-borg-repo.tld
backup_clients: []
# app1:
# owner: user1
# app: app1
# borgkey: BorgRepoKey1
# sshpubkey: SshPubKey1
# sshprivkey: SshPrivKey1
# app2:
# owner: user2
# app: app2
# borgkey: BorgRepoKey2
# sshpubkey: SshPubKey2
# sshprivkey: SshPrivKey2

View File

@ -0,0 +1,117 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
from argparse import ArgumentParser, FileType
def filter_backups_timeslot(offset, backups, timeslot='days', keep=7, mode='max', printy=True):
keep_backups = []
for timeslot_off in range(1, keep):
indices = [
idx
for idx,value in enumerate(backups)
if (
value[1] >= (offset - relativedelta(**{timeslot:timeslot_off}))
)
]
if not indices:
continue
if mode == "min":
#removed not considered backups for this slot!
for idx in reversed(indices[1:]):
backups.pop(idx)
keep_backups.append(backups.pop(indices[0]))
elif mode == "max":
keep_backups.append(backups.pop(indices[-1]))
#removed not considered backups for this slot!
for idx in reversed(indices[:-1]):
backups.pop(idx)
else:
for idx in sorted(indices, reverse=True):
keep_backups.append(backups.pop(idx))
return keep_backups, backups
def filter_backups(backuplist, show_keep=True, keep_hours=24, keep_days=7, keep_weeks=4, keep_months=12, keep_years=10):
backups = sorted(
[
(backup, datetime.fromtimestamp(int(backup.split('-')[1])))
for backup in backuplist
], key=lambda v: v[1], reverse=True
)
offset = backups[0][1]
keep_backups = [backups.pop(0)]
part_keep_backups, backups = filter_backups_timeslot(offset, backups, timeslot='hours',keep=keep_hours,mode='all')
keep_backups.extend(part_keep_backups)
part_keep_backups, backups = filter_backups_timeslot(offset, backups, timeslot='days',keep=keep_days,mode='max')
keep_backups.extend(part_keep_backups)
part_keep_backups, backups = filter_backups_timeslot(offset, backups, timeslot='weeks',keep=keep_weeks,mode='max')
keep_backups.extend(part_keep_backups)
part_keep_backups, backups = filter_backups_timeslot(offset, backups, timeslot='months',keep=keep_months,mode='max')
keep_backups.extend(part_keep_backups)
part_keep_backups, backups = filter_backups_timeslot(offset, backups, timeslot='years',keep=keep_years,mode='max')
keep_backups.extend(part_keep_backups)
keeps = [backup[0] for backup in keep_backups]
if not show_keep:
return [backup for backup in backuplist if backup not in keeps]
return keeps
def main():
parser = ArgumentParser(description="Program to print backups to keep or delete based on time stamps.")
parser.add_argument(
"-f", "--file", metavar="FILE", type=FileType('r'), required=True,
help="Path to file with list of backups to filter. Expects per line one backup name in format <appname>-<timestamp>")
parser.add_argument(
"-d", "--print-delete", action="store_true", required=False,
help="Instead of objects to kept, print out objects to be deleted."
)
parser.add_argument(
"-H", "--hours-keep", metavar="KEEP", type=int, required=False,
default=24, help="Number of objects to keep on hourly basis."
)
parser.add_argument(
"-D", "--days-keep", metavar="KEEP", type=int, required=False,
default=7, help="Number of objects to keep on daily basis."
)
parser.add_argument(
"-W", "--weeks-keep", metavar="KEEP", type=int, required=False,
default=4, help="Number of objects to keep on weekly basis."
)
parser.add_argument(
"-M", "--months-keep", metavar="KEEP", type=int, required=False,
default=12, help="Number of objects to keep on monthly basis."
)
parser.add_argument(
"-Y", "--years-keep", metavar="KEEP", type=int, required=False,
default=10, help="Number of objects to keep on yearly basis."
)
args = parser.parse_args()
show_keep = not args.print_delete
keep_hours = args.hours_keep
keep_days = args.days_keep
keep_weeks = args.weeks_keep
keep_months = args.months_keep
keep_years = args.years_keep
backuplist = [
line.strip()
for line in args.file
if len(line.strip()) > 0
]
fail = False
for idx,backup in enumerate(backuplist):
if len(backup) < 10 or not backup[-10:].isnumeric():
print(f"WARNING! Line {idx+1} has no timestamp suffix")
fail = True
if fail:
exit(1)
considered_backups = filter_backups(
backuplist, show_keep,
keep_hours, keep_days, keep_weeks, keep_months, keep_years
)
for backup in considered_backups:
print(backup)
if __name__ == '__main__':
main()

View File

@ -7,14 +7,6 @@
group: "{{ backup_group }}" group: "{{ backup_group }}"
mode: "0600" mode: "0600"
- name: Setup ssh folder
file:
state: directory
path: "{{ backup_home }}/.ssh"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0700"
- name: Setup ssh keys - name: Setup ssh keys
copy: copy:
content: "{{ item.content }}" content: "{{ item.content }}"

View File

@ -25,9 +25,17 @@
name: "{{ backup_owner }}" name: "{{ backup_owner }}"
group: "{{ backup_group }}" group: "{{ backup_group }}"
comment: backup user comment: backup user
shell: /sbin/nologin shell: /bin/bash
home: "{{ backup_home }}" home: "{{ backup_home }}"
- name: Setup ssh folder
file:
state: directory
path: "{{ backup_home }}/.ssh"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0700"
- name: setup backup storage - name: setup backup storage
file: file:
state: directory state: directory
@ -36,7 +44,7 @@
group: "{{ backup_group }}" group: "{{ backup_group }}"
mode: "0777" mode: "0777"
- include_tasks: master.yml - include_tasks: server.yml
when: not backup_client when: not backup_client
- include_tasks: client.yml - include_tasks: client.yml

View File

@ -1 +0,0 @@
---

67
tasks/server.yml Normal file
View File

@ -0,0 +1,67 @@
---
- name: setup backup installation folder
file:
state: directory
path: "{{ backup_inst }}"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0750"
- name: setup filter program for backup pruning
copy:
src: opt/backup/filter_backups.py
dest: "{{ backup_inst }}/filter_backups.py"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0750"
- name: setup ssh public keys of clients
copy:
content: "{{ backup_clients[item].sshpubkey }}"
dest: "{{ backup_home }}/.ssh/{{ item }}.pub"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0600"
loop: "{{ backup_clients.keys() }}"
- name: setup ssh private keys of clients
copy:
content: "{{ backup_clients[item].sshprivkey }}"
dest: "{{ backup_home }}/.ssh/{{ item }}"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0600"
loop: "{{ backup_clients.keys() }}"
- name: setup ssh config of clients
template:
src: "home/backup/.ssh/config.j2"
dest: "{{ backup_home }}/.ssh/config"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0600"
- name: setup backup key folder
file:
state: directory
path: "{{ backup_key_folder }}"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0700"
- name: setup borg repository keys
copy:
content: "{{ backup_clients[item].borgkey }}"
dest: "{{ backup_key_folder }}/{{ backup_clients[item].app }}"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0600"
loop: "{{ backup_clients.keys() }}"
- name: setup borg server script
template:
src: "opt/backup/inst/cloud-server-backup.j2"
dest: "{{ backup_inst }}/cloud-server-backup"
owner: "{{ backup_owner }}"
group: "{{ backup_group }}"
mode: "0750"

View File

@ -0,0 +1,7 @@
{% for client in backup_clients %}
Host {{ client }}
HostName {{ backup_host }}
Port {{ backup_ssh_port }}
User {{ backup_clients[client].owner }}
IdentityFile {{ backup_home }}/.ssh/{{ client }}
{% endfor %}

View File

@ -0,0 +1,29 @@
#!/bin/bash
BORGUSER="{{ backup_owner }}";
RUNFOLDER="{{ backup_storage }}";
APPLIST="{{ backup_clients | json_query('*.app') | join(' ') }}"
declare -A BORG_CLIENT_MAP
{% for client in backup_clients %}
BORG_CLIENT_MAP[{{ backup_clients[client].app }}]="{{ client }}"
{% endfor %}
declare -A BORG_USER_MAP
{% for client in backup_clients %}
BORG_USER_MAP[{{ backup_clients[client].app }}]="{{ backup_clients[client].owner }}"
{% endfor %}
for APP in $APPLIST;
do
REPOSITORYCLIENT=${BORG_CLIENT_MAP[$APP]}
BORG_REPO_USER=${BORG_USER_MAP[$APP]}
REPOLOCATION="ssh://$BORG_REPO_USER@{{ backup_host }}:{{ backup_ssh_port }}/./$APP"
BORG_PASSPHRASE=$(cat {{ backup_key_folder }}/$APP)
BORG_RSH="ssh -i {{ backup_home }}/.ssh/$REPOSITORYCLIENT"
sudo -H -u $BORGUSER bash -c '
BORG_PASSPHRASE='$BORG_PASSPHRASE';
BORG_RSH='$BORG_RSH';
REPOLOCATION='$REPOLOCATION';
borg list $REPOLOCATION'
done

View File

@ -1,3 +1,5 @@
--- ---
backup_home: "{{ backup_root }}/home" backup_home: "{{ backup_root }}/home"
backup_storage: "{{ backup_root }}/storage" backup_inst: "{{ backup_root }}/inst"
backup_storage: "{{ backup_root }}/storage"
backup_key_folder: "{{ backup_home }}/.keys"