copr: incremental backup to storinator, part 1
These scripts are based on my personal "Don't Delay Backups" project, which is not yet available as a public role.
This commit is contained in:
parent
e1818e7a8c
commit
c1335a72d9
7 changed files with 218 additions and 0 deletions
|
@ -75,3 +75,13 @@ root_auth_users: msuchy frostyx praiskup nikromen
|
|||
aws_cloudfront_distribution: E2PUZIRCXCOXTG
|
||||
|
||||
nrpe_client_uid: 500
|
||||
|
||||
rsnapshot_push:
|
||||
server_host: storinator01.rdu-cc.fedoraproject.org
|
||||
backup_dir: /srv/nfs/copr-be
|
||||
timing_plan: copr_be
|
||||
cases:
|
||||
copr-be-copr-user:
|
||||
user: copr
|
||||
rsync_args: --relative /home/copr/provision
|
||||
command: rsnapshot_copr_backend
|
||||
|
|
|
@ -62,3 +62,5 @@
|
|||
- copr/backend
|
||||
- role: messaging/base
|
||||
when: copr_messaging
|
||||
- role: rsnapshot-push
|
||||
when: env == "production"
|
||||
|
|
15
roles/rsnapshot-push/defaults/main.yml
Normal file
15
roles/rsnapshot-push/defaults/main.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
rsnapshot_push_defaults:
|
||||
timing_plans:
|
||||
normal:
|
||||
push: [0, 25]
|
||||
daily: [86400, 10]
|
||||
weekly: [604800, 4]
|
||||
monthly: [2592000, 6]
|
||||
yearly: [31536000, 3]
|
||||
|
||||
# we can't keep monthly increments for too large deltas
|
||||
copr_be:
|
||||
# Sunday / Wednesday
|
||||
push: [0, 3]
|
||||
biweekly: [1209600, 3]
|
55
roles/rsnapshot-push/tasks/main.yml
Normal file
55
roles/rsnapshot-push/tasks/main.yml
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
- name: backup script
|
||||
template:
|
||||
src: client-backup-script.sh.j2
|
||||
dest: /usr/local/bin/"{{ item.value.command }}"
|
||||
owner: "{{ item.value.user }}"
|
||||
group: "{{ item.value.user }}"
|
||||
mode: 0700
|
||||
with_dict:
|
||||
- "{{ rsnapshot_push.cases }}"
|
||||
tags: rsnapshot_push
|
||||
|
||||
- name: server-side case-specific backup dir
|
||||
file:
|
||||
path: "{{ '/'.join([rsnapshot_push.backup_dir, item.key]) }}"
|
||||
state: directory
|
||||
owner: "{{ item.value.user }}"
|
||||
group: "{{ item.value.user }}"
|
||||
mode: 0700
|
||||
delegate_to: "{{ rsnapshot_push.server_host }}"
|
||||
tags: rsnapshot_push
|
||||
|
||||
- name: server-side custom rsnapshot daemon script
|
||||
template:
|
||||
src: server-daemon.sh.j2
|
||||
dest: "{{ '/'.join([rsnapshot_push.backup_dir, item.key, 'sync-daemon']) }}"
|
||||
owner: "{{ item.value.user }}"
|
||||
group: "{{ item.value.user }}"
|
||||
mode: 0700
|
||||
with_dict:
|
||||
- "{{ rsnapshot_push.cases }}"
|
||||
delegate_to: "{{ rsnapshot_push.server_host }}"
|
||||
tags: rsnapshot_push
|
||||
|
||||
- name: rsnapshot call wrapper
|
||||
template:
|
||||
src: server-rsnapshot.py.j2
|
||||
dest: "{{ '/'.join([rsnapshot_push.backup_dir, item.key, 'rsnapshot']) }}"
|
||||
owner: "{{ item.value.user }}"
|
||||
group: "{{ item.value.user }}"
|
||||
mode: 0700
|
||||
with_dict:
|
||||
- "{{ rsnapshot_push.cases }}"
|
||||
delegate_to: "{{ rsnapshot_push.server_host }}"
|
||||
tags: rsnapshot_push
|
||||
|
||||
- name: backup praiskup data
|
||||
cron: name="backup documents - {{ item.key }}"
|
||||
minute="0/5"
|
||||
hour="*"
|
||||
user={{ item.value.user }}
|
||||
job=/usr/local/bin/"{{ item.value.command }}"
|
||||
with_dict:
|
||||
- "{{ rsnapshot_push.cases }}"
|
||||
tags: rsnapshot_push
|
|
@ -0,0 +1,8 @@
|
|||
#! /bin/bash
|
||||
|
||||
# the ::push is defined server-side target
|
||||
|
||||
exec rsync -av --xattrs --acls \
|
||||
--delete --delete-excluded \
|
||||
{{ item.value.rsync_args }} \
|
||||
{{ item.value.user }}@{{ rsnapshot_push.server_host }}::push
|
57
roles/rsnapshot-push/templates/server-daemon.sh.j2
Normal file
57
roles/rsnapshot-push/templates/server-daemon.sh.j2
Normal file
|
@ -0,0 +1,57 @@
|
|||
#! /bin/bash
|
||||
|
||||
dirname=$(dirname "$(readlink -f "$0")" )
|
||||
rsync_config=$dirname/rsync.conf
|
||||
rsnapshot_config=$dirname/rsnapshot.conf
|
||||
|
||||
backup=$dirname/backup
|
||||
sync_to=$backup/.sync
|
||||
lock="$dirname/.rsync.lock"
|
||||
mkdir -p "$sync_to"
|
||||
|
||||
cat >"$rsnapshot_config" <<EOF
|
||||
config_version 1.2
|
||||
|
||||
cmd_cp /usr/bin/cp
|
||||
cmd_rm /usr/bin/rm
|
||||
cmd_rsync /usr/bin/rsync
|
||||
cmd_du /usr/bin/du
|
||||
cmd_ssh /usr/bin/ssh
|
||||
|
||||
{% if 'timing_plan' in item.value %}
|
||||
{% set timing_plan = item.value.timing_plan %}
|
||||
{% else %}
|
||||
{% set timing_plan = 'normal' %}
|
||||
{% endif %}
|
||||
{% if timing_plan in rsnapshot_push_defaults.timing_plans %}
|
||||
{% set plan = rsnapshot_push_defaults.timing_plans[timing_plan] %}
|
||||
{% endif %}
|
||||
{% if 'timing_plans' in rsnapshot_push and timing_plan in rsnapshot_push.timing_plans %}
|
||||
{% set plan = rsnapshot_push.timing_plans[timing_plan] %}
|
||||
{% endif %}
|
||||
|
||||
{% for name, spec in plan.items() %}
|
||||
retain {{ name }} {{ spec[1] }}
|
||||
{% endfor %}
|
||||
|
||||
sync_first 1
|
||||
|
||||
snapshot_root $backup
|
||||
backup $sync_to ./
|
||||
logfile $dirname/rsnapshot.log
|
||||
lockfile $dirname/rsnapshot.pid
|
||||
EOF
|
||||
|
||||
cat >"$rsync_config" <<EOF
|
||||
[push]
|
||||
path = $sync_to
|
||||
use chroot = 0
|
||||
read only = 0
|
||||
write only = 1
|
||||
fake super = 1
|
||||
max connections = 1
|
||||
lock file = $lock
|
||||
post-xfer exec = $dirname/rsnapshot
|
||||
EOF
|
||||
|
||||
/usr/bin/rsync --server --daemon "--config=$rsync_config" .
|
71
roles/rsnapshot-push/templates/server-rsnapshot.py.j2
Normal file
71
roles/rsnapshot-push/templates/server-rsnapshot.py.j2
Normal file
|
@ -0,0 +1,71 @@
|
|||
#! /usr/bin/python3
|
||||
|
||||
"""
|
||||
Rotate backups with appropriate rsnapshot level(s).
|
||||
{% if 'timing_plan' in item.value %}
|
||||
{% set timing_plan = item.value.timing_plan %}
|
||||
{% else %}
|
||||
{% set timing_plan = 'normal' %}
|
||||
{% endif %}
|
||||
{% if timing_plan in rsnapshot_push_defaults.timing_plans %}
|
||||
{% set plan = rsnapshot_push_defaults.timing_plans[timing_plan] %}
|
||||
{% endif %}
|
||||
{% if 'timing_plans' in rsnapshot_push and timing_plan in rsnapshot_push.timing_plans %}
|
||||
{% set plan = rsnapshot_push.timing_plans[timing_plan] %}
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
DB = "{{ '/'.join([rsnapshot_push.backup_dir, item.key, 'rsnapshot_push.db']) }}"
|
||||
CONFIG = "{{ '/'.join([rsnapshot_push.backup_dir, item.key, 'rsnapshot.conf']) }}"
|
||||
LEVELS = {
|
||||
{% for name, spec in plan.items() %}
|
||||
'{{ name }}': {{ spec[0] }},
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
|
||||
def _get_db():
|
||||
try:
|
||||
with open(DB, 'r') as fdb:
|
||||
db_dict = json.loads(fdb.read())
|
||||
except FileNotFoundError:
|
||||
db_dict = {}
|
||||
# initiate the levels which are not yet in DB
|
||||
now = time.time()
|
||||
force_reset = False
|
||||
for key in LEVELS:
|
||||
if key not in db_dict or force_reset:
|
||||
db_dict[key] = now
|
||||
force_reset = True
|
||||
return db_dict
|
||||
|
||||
|
||||
def rotate(database):
|
||||
""" rotate backups as needed, per last runs stored in database """
|
||||
now = time.time()
|
||||
for level, delay in sorted(LEVELS.items(), key=lambda x: x[1]):
|
||||
cmd = ['/bin/rsnapshot', '-c', CONFIG, level]
|
||||
last_run = database[level]
|
||||
if (now - last_run) > delay:
|
||||
print("running " + ' '.join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
database[level] = now
|
||||
else:
|
||||
print("skipping " + level)
|
||||
|
||||
|
||||
def _main():
|
||||
database = _get_db()
|
||||
try:
|
||||
rotate(database)
|
||||
finally:
|
||||
with open(DB, 'w') as fdb:
|
||||
fdb.write(json.dumps(database))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
Loading…
Add table
Add a link
Reference in a new issue