mirror of
https://github.com/FAUSheppy/athq-vm-management
synced 2025-12-06 05:41:35 +01:00
feat: backup script generation
This commit is contained in:
72
backup.py
Normal file
72
backup.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import jinja2
|
||||||
|
import os
|
||||||
|
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="./templates"))
|
||||||
|
|
||||||
|
def createBackupScriptStructure(backupList, baseDomain=""):
|
||||||
|
|
||||||
|
rsyncScriptTemplate = environment.get_template("rsync-backup.sh.j2")
|
||||||
|
rsyncFilterTemplate = environment.get_template("rsync-filter.txt.j2")
|
||||||
|
|
||||||
|
scriptNames = []
|
||||||
|
for backup in backupList:
|
||||||
|
|
||||||
|
hostnameBase = backup["hostname"]
|
||||||
|
if baseDomain:
|
||||||
|
hostname = "{}.{}".format(hostnameBase, baseDomain.lstrip("."))
|
||||||
|
else:
|
||||||
|
hostname = hostnameBase
|
||||||
|
|
||||||
|
paths = backup["paths"]
|
||||||
|
icingaToken = backup["token"]
|
||||||
|
|
||||||
|
if not hostname.replace(".","").isalnum():
|
||||||
|
print("Warning: Backup hostname is not alphanum: '{}'".format(hostname))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# add base paths for rsync filter (e.g. /var/ for /var/lib/anything/)
|
||||||
|
# because we use - /** for excluding anything else #
|
||||||
|
basePaths = []
|
||||||
|
for p in paths:
|
||||||
|
|
||||||
|
if not os.path.isabs(p):
|
||||||
|
print("WARNING: Non-absolute path for backup {} (skipping..)".format(p))
|
||||||
|
continue
|
||||||
|
elif "//" in p:
|
||||||
|
print("WARNING: Illegal double-slash in backup path {} (skipping..)".format(p))
|
||||||
|
continue
|
||||||
|
elif "/" == p:
|
||||||
|
print("WARNING: Root (/) is not allowed as backup path (skipping..)".format(p))
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
basePaths.append("/{}/".format(p.split("/")[1]))
|
||||||
|
|
||||||
|
# keep order (important!)
|
||||||
|
paths = list(set(basePaths)) + [ p.rstrip("/") + "/***" for p in paths ]
|
||||||
|
|
||||||
|
rsyncScript = rsyncScriptTemplate.render(hostname=hostname, token=icingaToken,
|
||||||
|
hostname_base=hostnameBase)
|
||||||
|
rsyncFilter = rsyncFilterTemplate.render(paths=paths)
|
||||||
|
|
||||||
|
path = "./build/backup/"
|
||||||
|
|
||||||
|
# write script #
|
||||||
|
scriptName = "rsync-backup-{}.sh".format(hostnameBase)
|
||||||
|
scriptNames.append(scriptName)
|
||||||
|
with open(os.path.join(path, scriptName), "w") as f:
|
||||||
|
f.write(rsyncScript)
|
||||||
|
os.chmod(os.path.join(path, scriptName), 0o700)
|
||||||
|
|
||||||
|
# write filter #
|
||||||
|
filterName = "rsync-filter-{}.txt".format(hostnameBase)
|
||||||
|
with open(os.path.join(path, filterName), "w") as f:
|
||||||
|
f.write(rsyncFilter)
|
||||||
|
|
||||||
|
# endfor #
|
||||||
|
|
||||||
|
# write wrapper script #
|
||||||
|
wrapperName = "wrapper.sh"
|
||||||
|
with open(os.path.join(path, wrapperName), "w") as f:
|
||||||
|
for n in scriptNames:
|
||||||
|
f.write("./{}".format(n))
|
||||||
|
f.write("\n")
|
||||||
|
os.chmod(os.path.join(path, wrapperName), 0o700)
|
||||||
5
main.py
5
main.py
@@ -3,6 +3,7 @@ import vm
|
|||||||
import sys
|
import sys
|
||||||
import jinja2
|
import jinja2
|
||||||
import icinga
|
import icinga
|
||||||
|
import backup
|
||||||
|
|
||||||
ACME_CONTENT = '''
|
ACME_CONTENT = '''
|
||||||
location /.well-known/acme-challenge/ {
|
location /.well-known/acme-challenge/ {
|
||||||
@@ -113,3 +114,7 @@ if __name__ == "__main__":
|
|||||||
f.write(" Port {}\n".format(vmo.sshOutsidePort))
|
f.write(" Port {}\n".format(vmo.sshOutsidePort))
|
||||||
f.write(" User root\n")
|
f.write(" User root\n")
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
|
# backup #
|
||||||
|
with open("./config/backup.json") as f:
|
||||||
|
backup.createBackupScriptStructure(json.load(f), baseDomain=MASTER_ADDRESS)
|
||||||
|
|||||||
44
templates/rsync-backup.sh.j2
Normal file
44
templates/rsync-backup.sh.j2
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mkdir -p {{ hostname_base }}
|
||||||
|
cd {{ hostname_base }}
|
||||||
|
|
||||||
|
dest=./
|
||||||
|
for x in backup-*; do
|
||||||
|
test -d "$x" || continue
|
||||||
|
dest="../$x" # relative to destination directory
|
||||||
|
done
|
||||||
|
|
||||||
|
target="backup-$(date '+%Y-%m-%d-%H-%M-%S')"
|
||||||
|
target_tmp="partial-$target"
|
||||||
|
|
||||||
|
mkdir "$target_tmp"
|
||||||
|
|
||||||
|
rsync \
|
||||||
|
--verbose --itemize-changes --human-readable \
|
||||||
|
--archive --acls --xattrs --hard-links --sparse --numeric-ids \
|
||||||
|
--one-file-system \
|
||||||
|
--link-dest="$dest" \
|
||||||
|
--filter="merge ../rsync-filter-{{ hostname_base }}.txt" \
|
||||||
|
root@{{ hostname }}:/ \
|
||||||
|
"$target_tmp"
|
||||||
|
|
||||||
|
RSYNC_SUCCESS=$?
|
||||||
|
|
||||||
|
mv "$target_tmp" "$target"
|
||||||
|
|
||||||
|
CONTENT_TYPE="Content-Type: application/json"
|
||||||
|
ASYNC_ICINGA_ADDRESS="https://async-icinga.atlantishq.de/"
|
||||||
|
SERVICE="backup_{{ hostname_base }}"
|
||||||
|
TOKEN="{{ token }}"
|
||||||
|
|
||||||
|
if [ $RSYNC_SUCCESS -eq 0 ]; then
|
||||||
|
curl -H "${CONTENT_TYPE}" -X POST "${ASYNC_ICINGA_ADDRESS}" -d \
|
||||||
|
'{"service": "${SERVICE}", "token": "${TOKEN}", "status": "OK", "info": ""}'
|
||||||
|
else
|
||||||
|
curl -H "${CONTENT_TYPE}" -X POST "${ASYNC_ICINGA_ADDRESS}" -d \
|
||||||
|
'{"service": "${SERVICE}", "token": "${TOKEN}", "status": "CRITICAL", "info": ""}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
5
templates/rsync-filter.txt.j2
Normal file
5
templates/rsync-filter.txt.j2
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{%- for path in paths -%}
|
||||||
|
+ {{ path }}{% if not last %}
|
||||||
|
{% endif %}
|
||||||
|
{%- endfor -%}
|
||||||
|
- /**
|
||||||
Reference in New Issue
Block a user