diff --git a/backup.py b/backup.py new file mode 100644 index 0000000..aa39750 --- /dev/null +++ b/backup.py @@ -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) diff --git a/main.py b/main.py index 11e28d5..41981b9 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ import vm import sys import jinja2 import icinga +import backup ACME_CONTENT = ''' location /.well-known/acme-challenge/ { @@ -113,3 +114,7 @@ if __name__ == "__main__": f.write(" Port {}\n".format(vmo.sshOutsidePort)) f.write(" User root\n") f.write("\n") + + # backup # + with open("./config/backup.json") as f: + backup.createBackupScriptStructure(json.load(f), baseDomain=MASTER_ADDRESS) diff --git a/templates/rsync-backup.sh.j2 b/templates/rsync-backup.sh.j2 new file mode 100644 index 0000000..108e5a8 --- /dev/null +++ b/templates/rsync-backup.sh.j2 @@ -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 .. diff --git a/templates/rsync-filter.txt.j2 b/templates/rsync-filter.txt.j2 new file mode 100644 index 0000000..a5029c7 --- /dev/null +++ b/templates/rsync-filter.txt.j2 @@ -0,0 +1,5 @@ +{%- for path in paths -%} ++ {{ path }}{% if not last %} +{% endif %} +{%- endfor -%} +- /**