Backups mit Borg und Raspberry Pi

Backups mit Borg und Raspberry Pi

Backups mit Borg und Raspberry Pi

Wer vielleicht eh schon einen Raspberry Pi am heimischen Router hängen hat, kann diesen ganz einfach als Backup-Server verwenden. Damit nicht zu viel Strom verschwendet wird, wird die Backup-HDD einfach bei Bedarf eingeschaltet und vom System automatisch ein- und ausgehängt.

Hardware

Am Raspberry Pi (in meinem Fall ein Pi 4, aber ein Pi 3 tut es sicherlich auch) ist über USB eine HDD-Dockingstation angeschlossen, die etwas an einen Toaster erinnert. Es ist ein Kästchen mit einem Schacht, in den man z. B. eine 3,5 Zoll Festplatte stecken kann.

Empfehlenswert ist, wenn der Raspberry Pi über ein Ethernetkabel am Router angeschlossen ist.

Betriebssystem

Auf dem Raspberry Pi kann man ein ganz normales Raspberry Pi OS installieren. Ich habe mich für das 64 Bit Raspberry Pi OS Lite entschieden. Zu installieren sind die Pakete borgbackup sowie autofs.

Automount

Wie erwähnt soll die Backup-Platte nicht ständig in Betrieb sein. Dies würde nur unnötig Strom fressen. Auch wäre die Lärmbelästigung, je nach Festplatte und Standort, unter Umständen unangenehm. Was wir also brauchen ist ein Mechanismus, der die Platte bei Bedarf ein- und auch wieder aushängt. An das Einschalten der HDD-Dockingstation muss man natürlich selbst denken. Das Paket autofs bietet uns genau diesen Mechanismus.

Zunächst erstellen wir uns das Basisverzeichnis für die Automounts:

# mkdir /automnt

Anschließend erstellen wir eine autofs-Masterdatei (/etc/auto.master.d/automnt.autofs) mit folgendem Inhalt:

/automnt /etc/auto.automnt --timeout=10 --ghost

--timeout=10 gibt übrigens an, dass die Platte nach 10 Sekunden Inaktivität wieder ausgehängt werden soll.

Dazugehörig ist die Datei /etc/auto.automnt. In dieser Datei geben wir an, was und wie genau gemountet werden soll:

backup -fstype=ext4,sync :/dev/disk/by-label/Backup

Wenn also auf das Verzeichnis /automnt/backup zugegriffen wird, dann soll die Festplatte bzw. Partition mit dem Label „Backup“, auf der sich ein ext4-Dateisystem befindet, eingehängt werden. Je nach Bedarf können diese Parameter natürlich angepasst werden.

Ein systemctl reload autofs liest die Konfiguration ein.

Ist die Platte in Betrieb, kann man nun etwa als root in das backup-Verzeichnis wechseln, welches dann automatisch eingebunden wird:

# cd /automnt/backup

Borg

Als „Besitzer“ für die Backups erstellen wir auf dem Pi einen Benutzer borg:

# useradd -m -s /bin/bash borg

Diesem übereignen wir auch gleich das gerade gemountete Backup-Verzeichnis:

# chown borg:borg /automnt/backup

Auf den Clients, also auf den Rechnern, für die wir ein Backup erstellen wollen, haben wir für den User root einen SSH-Key erstellt. Hier als Beispiel auf dem Rechner cerana:

[root@cerana ~]# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:IsWzqIEbmshxQsH2s/kTGL+3XhMGPyzj2P7Htk5jpBg root@cerana
The key's randomart image is:
+---[RSA 3072]----+
|..               |
| o.  .           |
|...   +.         |
|.. + o o+        |
|oo..X oES* .     |
|+++* ++.* =      |
|=.. ..o+ +.+     |
|     +... ++.    |
|      ++o.+o.    |
+----[SHA256]-----+

Den öffentlichen Schlüssel hiervon – den wir unter /root/.ssh/id_rsa.pub finden – hinterlegen wir wiederum auf dem Raspberry Pi im Home-Verzeichnis des Users borg. Das hat den Zweck, dass die Anmeldung am Backup-Server ohne Eingabe eines Passworts, das wir für den User borg übrigens gar nicht angelegt haben, funktioniert. Aus Sicherheitsgründen schränken wir die Rechte jedoch so ein, dass andere Aktionen, etwa auch der Zugriff auf fremde Borg-Repositories, nicht möglich sind. Das Hinterlegen des öffentlichen Schlüssels mit diesen Einschränkungen sieht dann in etwa so aus (Datei |home|borg|.ssh|authorized_keys auf dem Pi):

command="borg serve --restrict-to-path /automnt/backup/cerana",restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRaRQ75+zjEdOEiHdCDLsJfAomxEOc6WJGYybJ4R+OLjvA7xieRBZClgiuSF2k0V8PSCU+Ue52D121dp//weTdq2o2NCXBlDqFTBNbIwyoQoeO3x5/bu71ENtPofU5ICXcOgyWeKPtZTKZ1WszLHWfgTqRUQ2U+f6y3PKGpbC8bHa9JbGrYscvbyBoTpb/ax8nu4jK7mhLa/1JAeglQC0Hm+tOD4qzmd6+UzaeEQ6/y6JeVZLXbtdTL5Et+h6QhJbbDJPiz+hU0dunVfxH+CqUW9b7aKdmW2/2PEZRPzWn4FhfBxULF+dfZYdqIpne6voFYoOb7BNv/hNCGnkc1r9hqFoP/7h2nZZhXN8s6cJwdEBP8527TdHLPnVWqmRZRm73OU7I5NPjDcdCUHpkmuIhYirbynF3vdaMOpxK00uK1j79W8qU0XGLkXUd3eC9+KcxJHae7r0H+klGL0+H8iPLHiRNoUaF1N1umaPEqDC89+zfONXt4FtaVWRTDTpnTY8= root@cerana

(Falls man das Verzeichnis .ssh und/oder darin die Datei authorized_keys erstellt hat, ist wichtig, dass nur der User borg Schreib-/Lesezugriff darauf hat. Alle anderen müssen keine Berechtigungen haben.)

Als User borg erstellen wir nun das Backup-Repository für den Rechner cerana:

$ sudo su - borg
$ borg init -e repokey /automnt/backup/cerana
Enter new passphrase: 
Enter same passphrase again:
[…]

Die Option -e repokey bewirkt, dass das Repository verschlüsselt wird. Wir vergeben dafür ein sicheres Passwort, das wir uns gut merken.

Was fehlt jetzt noch? Genau, ein Backup-Skript, das wir auf dem Rechner cerana und ggf. weiteren Backupkandidaten aufrufen können. Hier sind der Phantasie natürlich keine Grenzen gesetzt. Ich greife hierfür auf ein Skript von mir zurück, das sich bewährt hat und den Vorteil von Snapshots auf Systemen mit Btrfs nutzt. Es nimmt an, dass das System sowie /home in separaten Subvolumes liegen. Bei Fedora 38 etwa ist das standardmäßig der Fall. Mit dem Skript könnte man auch einfach auf eine lokal angeschlossene Festplatte Backups machen (Schalter -l). Außerdem muss man vorher die Verzeichnisse /.snapshot, /mnt/backup-system sowie /mnt/backup-home erstellen. Unschön, und Beizeit werde ich das mal ändern. :)

#!/usr/bin/env bash

# some helpers and error handling:
info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; }

backup_home() {
    export SNAPSHOT_PATH=/.snapshots/home-$DATETIME
    BACKUP_PATH=/mnt/backup-home

    info "Starting backup of home dirs"
    btrfs subvolume snapshot -r /home $SNAPSHOT_PATH
    sync
    mount --bind $SNAPSHOT_PATH $BACKUP_PATH

    cd $BACKUP_PATH

    # Backup the home directories into an archive named after
    # the machine this script is currently running on:
    borg create                         \
        --progress                      \
        --stats                         \
        --compression zstd              \
        --exclude-caches                \
        --exclude '*/.cache/*'          \
                                        \
        ::"home-$DATETIME"              \
        *                               \

    cd /tmp

    #echo "Deleting snapshot"
    umount $BACKUP_PATH
    btrfs subvolume delete $SNAPSHOT_PATH

    info "Pruning repository"

    # Use the `prune` subcommand to maintain 7 daily, 4 weekly and 6 monthly
    # archives of THIS home backup. The 'home-' prefix is very important to
    # limit prune's operation to these archives and not apply to
    # other archives also:

    borg prune                          \
        --list                          \
        --glob-archives 'home-*'        \
        --keep-daily    7               \
        --keep-weekly   2               \
        --keep-monthly  3               \

}

backup_system() {
    export SNAPSHOT_PATH=/.snapshots/system-$DATETIME
    BACKUP_PATH=/mnt/backup-system

    info "Starting backup of system dirs"
    btrfs subvolume snapshot -r / $SNAPSHOT_PATH
    sync
    mount --bind $SNAPSHOT_PATH $BACKUP_PATH

    cd $BACKUP_PATH

    # Backup the system directories into an archive named after
    # the machine this script is currently running on:
    borg create                         \
        --progress                      \
        --stats                         \
        --one-file-system               \
        --compression zstd              \
        --exclude-caches                \
        --exclude 'root/.cache'         \
        --exclude 'dev/*'               \
        --exclude 'proc/*'              \
        --exclude 'sys/*'               \
        --exclude 'var/run/*'           \
        --exclude 'var/cache/pacman/pkg/*' \
        --exclude 'lost+found'          \
                                        \
        ::"system-$DATETIME"            \
        . /boot                         \

    cd /tmp

    #info "Deleting snapshot"
    umount $BACKUP_PATH
    btrfs subvolume delete $SNAPSHOT_PATH

    info "Pruning repository"

    # Use the `prune` subcommand to maintain 7 daily, 4 weekly and 6 monthly
    # archives of THIS system backup. The 'system-' prefix is very important 
    # to limit prune's operation to these archives and not apply to
    # other archives also:

    borg prune                          \
        --list                          \
        --glob-archives 'system-*'      \
        --keep-daily    7               \
        --keep-weekly   2               \
        --keep-monthly  3               
}

usage() {
    echo "usage: backup_borg.sh [-l] [-t [home | system]] | [-h]"
}

## MAIN
# See the section "Passphrase notes" for more infos.
export BORG_PASSPHRASE='MeinSuperGeheimesPasswort'
DATETIME=`date +%Y%m%d-%H%M%S`
bkuptype=""
is_local=false
while [ "$1" != "" ]; do
    case $1 in
        -l | --local )   is_local=true
                         ;;
        -t | --type )    shift
		         bkuptype=$1
                         ;;
        -h | --help )    usage
                         exit
                         ;;
        * )              usage
                         exit 1
    esac
    shift
done

# Setting this, so the repo does not need to be given on the commandline:
if [ $is_local == true ]; then
    export BORG_REPO=/run/media/$SUDO_USER/Backup/`hostname`
else
    export BORG_REPO=ssh://borg@myraspi:22/automnt/backup/`hostname`
fi

if [ "$bkuptype" == "home" ]; then
    backup_home
elif [ "$bkuptype" == "system" ]; then
    backup_system
else
    usage
fi

Das Skript wird also mittels sudo auf dem Client aufgerufen (etwa sudo backup_borg.sh -t home). Auf diesem muss natürlich auch borgbackup installiert sein. Das Backup sollte nun durchgeführt werden.

Weil die Backup-Festplatte zentral am Raspberry Pi hängt, könnte man parallel auch von weiteren Rechnern den Backupprozess starten. In jedem Fall erspart man sich das Umhertragen und Umstöpseln der Dockingstation. Denn es sind oft Kleinigkeiten, die einen vom Backup erstellen abhalten. Ist man mit der Prozedur fertig, kann man die Festplatte (nach kurzer Wartezeit) einfach ausschalten.

Nachtrag 15.05.2023: Eine Alternative zu autofs ist natürlich systemd. Ein Beispieleintrag in /etc/fstab könnte z. B. so aussehen:

LABEL="Backup" /mnt/backup  ext4  defaults,noatime,x-systemd.automount,x-systemd.idle-timeout=10 0 0

Außerdem sollte der Raspberry Pi mit ausreichend RAM ausgestattet sein. Mein 1 GB-Modell hat sich hier als nicht ausreichend erwiesen. Erst durch Bereitstellung von genügend Swap-Speicher konnte ein umfangreicheres Backup durchlaufen. Der Swap-Speicher lässt sich in der Datei /etc/dphys-swapfile einstellen. Ein Wert von 2048 war in meinem Fall ausreichend.