Direkt zum Hauptinhalt springen

Inkrementelle Backups mit rsync

Datensicherung ist Datenschutz

Python Code

Foto von Chris Ried auf Unsplash

Einleitung

Backups werden immer wichtiger. Datensicherung kostet fast nichts und erspart Ihnen im Endeffekt womöglich viel Mühe, Ärger und Geld. In Zeiten von Ransomeware, welche Ihre Daten verschlüsselt und nur nach Bezahlung von Lösegeld wieder-herstellt, wenn überhaupt, kann ein einfaches Backup das Problem einfach lösen.

Prinzipien

  • Lokale Backups unbedingt mit im Internet gespeicherten Backups ergänzen. Wenn Ihr Büro samt PC abbrennt, brennt auch die externe Festplatte neben dem PC mit ab! Wo möglich redundant speichern.
  • Backups und Wiederherstellung regelmäßig testen. Sollten Dateien fehlerhaft, oder gar nicht gesichert werden bringt das Ganze gar nichts.
  • Automatisieren. Lassen Sie Ihre Backups am Besten mehrmals täglich automatisch laufen, um das Vergessen zu vermeiden.
  • Art der Datensicherung überlegen. Als Beispiel sparen inkrementelle Backups Platz, weil nur Daten gesichert werden, die seit dem letzten Backup neu oder geändert wurden

Meine Backup Strategie

Zusammenfassung

  • Zwei mal täglich laufen systemd timer/services, die ein Python Programm aktivieren.
  • Das Python Programm lässt rsync als subprocess laufen.
  • Rsync sichert bestimmte Ordner auf einem lokalem NAS, einem kleinem eigenständigen und am Netzwerk angehängtem Server über SSH.
  • Ich verwende das jeweilige Datum im ISO Format (2018-04-17) als Ordnername für die Backups.
  • Die rsync Funktion –link-dest=2018-04-16 wird verwendet, um Hardlinks zu erstellen wenn sich nichts geändert hat. Das spart Zeit und Platz. Nur neue und geänderte Dateien werden neu übertragen.
  • Außerdem: Ein weiterer Ordner mit den wichtigsten Daten wird verschlüsselt bei einem EU Cloud Service gesichert.

Code Beispiele

Anmerkung: Ich verwende Linux als Betriebsystem, was vieles einfacher macht im Umgang mit rsync, systemd, Python etc.

Zuerst die systemd timer und service unit files:

# rsync_backup.timer
[Unit]
Description=Rsync Backups Timer

[Timer]
OnCalendar=08,20:05

[Install]
WantedBy=timers.target

Mein systemd service läuft als user, root wäre unnötig und umständlicher.

# rsync_backup.service
[Unit]
Description=Rsync Backups Service

[Service]
ExecStart=/usr/bin/python3 /home/martin/rsync_backup/rsync_backup.py
Type=oneshot
User=martin
Group=martin

[Install]
WantedBy=multi-user.target

Zuerst läuft ein Python Programm, welches die vorhandenen Ordner auf dem NAS holt.

import os
import datetime
from subprocess import check_output

def get_folder_list(remote_path):
    ssh = "ssh -p 22 user@nas ls {remote_path}".format(remote_path=remote_path)
    try:
        folder_list = check_output(ssh, shell=True, universal_newlines=True, timeout=60)
        return folder_list.split()
    except Exception as error:
        print(error)
        return False

if __name__ == "__main__":
    pc_path = "/volume1/NetBackup/pc_path/"

    pc_path_list = get_folder_list(pc_path)

    pc_path_list_path = os.path.expanduser("~/gitrepos/private/dated_rsync_backups/pc_path_list.py")
    with open(pc_path_list_path, "w") as pc_path_list_file:
        if pc_path_list:
            pc_path_list_file.write("pc_path_list = {}".format(pc_path_list))
        else:
            pc_path_list_file.write("pc_path_list = False")

Dann kommt das Backup Programm.

import os
import datetime
from subprocess import call
from time import sleep
from pc_path_list import pc_path_list

home_path = os.path.expanduser("~")

included = {
    # dotfiles
    os.path.join(home_path, ".gitconfig"): "home/",
    # dotdirs
    os.path.join(home_path, ".config"): "home/",
    os.path.join(home_path, ".mozilla"): "home/",
    # dirs
    "/mnt/festplatte/Bilder": "festplatte/",
    "/mnt/festplatte/Dokumente": "festplatte/",
}

def is_date(last_date_string):
    try:
        datetime.datetime.strptime(last_date_string, "%Y-%m-%d")
        return True
    except Exception as error:
        print(error)
        return False

def rsync_backup_initial(local_path, remote_path, today):
    options = "-avh --delete -e 'ssh -p 22' --cvs-exclude --exclude=node_modules"
    destination="user@nas::NetBackup/pc_path/{today}/{remote_path}".format(
        today=today,
        remote_path=remote_path,
    )
    rsync = "rsync {options} {local_path} {destination}".format(
        options=options,
        local_path=local_path,
        destination=destination,
    )
    call(rsync, shell=True)


def rsync_backup(local_path, remote_path, today, last_date_string):
    options = "-avh --delete -e 'ssh -p 22' --cvs-exclude --link-dest=/pc_path/{last_date_string}/{remote_path}/".format(
        last_date_string=last_date_string,
        remote_path=remote_path,
    )
    destination="user@nas::NetBackup/pc_path/{today}/{remote_path}".format(
        today=today,
        remote_path=remote_path,
    )
    rsync = "rsync {options} {local_path} {destination}".format(
        options=options,
        local_path=local_path,
        destination=destination,
    )
    call(rsync, shell=True)

if __name__ == "__main__":
    today = datetime.date.today().isoformat()
    try:
        today_dir = os.path.join("/volume1/NetBackup/pc_path/", str(today))
        mkdir_today = "ssh -p 22 user@nas mkdir {today_dir}".format(today_dir=today_dir)
        call(mkdir_today, shell=True)

        home_dir = os.path.join(today_dir, "home")
        mkdir_home = "ssh -p 22 user@nas mkdir {home_dir}".format(home_dir=home_dir)
        call(mkdir_home, shell=True)

        festplatte_dir = os.path.join(today_dir, "festplatte")
        mkdir_festplatte = "ssh -p 22 user@nas mkdir {festplatte_dir}".format(festplatte_dir=festplatte_dir)
        call(mkdir_festplatte, shell=True)
    except:
        pass

    if pc_path_list:
        if today in pc_path_list:
            pc_path_list.remove(today)
        last_date_string = max(pc_path_list)
        if is_date(last_date_string):
            for local_path, remote_path in included.items():
                rsync_backup(local_path, remote_path, today, last_date_string)
                sleep(1)
    else:
        for local_path, remote_path in included.items():
            rsync_backup_initial(local_path, remote_path, today)
            sleep(1)

Danach läuft noch ein Programm, welches die ältesten Ordner vom NAS löscht sollte sie mehr als 30 Tage alt sein.

Feedback bitte per E-Mail.


Stichworte

Kategorien