Setup backup server configuration for backup replication.
This commit is contained in:
parent
768da9cf45
commit
18ae11b9cc
@ -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
|
||||||
117
files/opt/backup/filter_backups.py
Normal file
117
files/opt/backup/filter_backups.py
Normal 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()
|
||||||
@ -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 }}"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
---
|
|
||||||
67
tasks/server.yml
Normal file
67
tasks/server.yml
Normal 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"
|
||||||
7
templates/home/backup/.ssh/config.j2
Normal file
7
templates/home/backup/.ssh/config.j2
Normal 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 %}
|
||||||
150
templates/opt/backup/inst/cloud-server-backup.j2
Normal file
150
templates/opt/backup/inst/cloud-server-backup.j2
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#!/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 %}
|
||||||
|
|
||||||
|
|
||||||
|
help (){
|
||||||
|
echo "cloud-server-backup - backup and restore script on cloud backup with borg target v1.0 by L.Hahn.
|
||||||
|
|
||||||
|
Usage: $0 COMMAND [APPS]
|
||||||
|
|
||||||
|
COMMAND:
|
||||||
|
- list-apps List available apps in remote borg repositories.
|
||||||
|
- list-remote [APPS] List available archives for apps in remote borg repositories.
|
||||||
|
- list List available archives of remote repositories on local repository.
|
||||||
|
- prune [APPS] Prune app (or all) borg repositories.
|
||||||
|
- backup Perform backup of remote borg repositories and create a new archive in local borg repository.
|
||||||
|
- restore ARCHIVENAME [APPS] Upload backup to borg repository to restore older file states.
|
||||||
|
May turn off your application if still running.
|
||||||
|
If latest is provided as ARCHIVENAME, the latest one based on timestamp is taken.
|
||||||
|
IF ARCHIVENAME is provided, will try to download it; throws error if not found.
|
||||||
|
If APP is provided, only the mentioned app repositories are restored.
|
||||||
|
|
||||||
|
|
||||||
|
APPS: A signle app or (sub-)set of below listed apps to be considered.
|
||||||
|
{% for client in backup_clients %}
|
||||||
|
- {{ backup_clients[client].app }}
|
||||||
|
{% endfor %}
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get_app () {
|
||||||
|
for APP in $APPLIST;
|
||||||
|
do
|
||||||
|
echo $APP;
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
get_remote_archives () {
|
||||||
|
CHECKLIST=$1;
|
||||||
|
for APP in $CHECKLIST;
|
||||||
|
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)
|
||||||
|
KEYFILE="{{ backup_home }}/.ssh/$REPOSITORYCLIENT"
|
||||||
|
sudo -H -u $BORGUSER bash -c '
|
||||||
|
export BORG_PASSPHRASE='$BORG_PASSPHRASE';
|
||||||
|
KEYFILE='$KEYFILE';
|
||||||
|
REPOLOCATION='$REPOLOCATION';
|
||||||
|
borg list $REPOLOCATION --rsh "/usr/bin/ssh -i $KEYFILE"'
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
has_argument () {
|
||||||
|
if [[ -z "$1" ]];
|
||||||
|
then
|
||||||
|
echo "Missing required paramter!";
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_apps_or_default () {
|
||||||
|
VALID=1;
|
||||||
|
if [[ -z "$1" ]];
|
||||||
|
then
|
||||||
|
echo $APPLIST;
|
||||||
|
else
|
||||||
|
for APP in $1;
|
||||||
|
do
|
||||||
|
if [[ ! ${APPLIST[@]} =~ $APP ]]
|
||||||
|
then
|
||||||
|
VALID=0;
|
||||||
|
echo "$APP is not a valid app.";
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ ! $VALID == 1 ]]
|
||||||
|
then
|
||||||
|
echo "Aborting.";
|
||||||
|
exit 1;
|
||||||
|
else
|
||||||
|
echo $1;
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
flock -n 9 || {
|
||||||
|
echo "BACKUP ALREADY RUNNING! ABORTING.";
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $# = 0 ]; then
|
||||||
|
help;
|
||||||
|
exit 0;
|
||||||
|
fi
|
||||||
|
|
||||||
|
action=$1
|
||||||
|
case $action in
|
||||||
|
"list-apps")
|
||||||
|
get_app;
|
||||||
|
;;
|
||||||
|
|
||||||
|
"list-remote")
|
||||||
|
CHECKLIST=$(validate_apps_or_default $2);
|
||||||
|
get_remote_archives $CHECKLIST;
|
||||||
|
;;
|
||||||
|
|
||||||
|
"list")
|
||||||
|
#get_archives;
|
||||||
|
;;
|
||||||
|
|
||||||
|
"prune")
|
||||||
|
#prune_remote_archives;
|
||||||
|
;;
|
||||||
|
|
||||||
|
"backup")
|
||||||
|
#local_backup;
|
||||||
|
;;
|
||||||
|
|
||||||
|
"restore")
|
||||||
|
has_argument $2;
|
||||||
|
#local_restore $2;
|
||||||
|
;;
|
||||||
|
"delete")
|
||||||
|
has_argument $2;
|
||||||
|
CHECKLIST=$(validate_apps_or_default $3);
|
||||||
|
#local_delete $2;
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
help;
|
||||||
|
exit 0;
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo ""
|
||||||
|
) 9>/var/run/lock/cloud-server-backup.lock;
|
||||||
@ -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"
|
||||||
Loading…
Reference in New Issue
Block a user