21x9.org | System Administration | Home Automation | Smart Home
13.07.2023

Backups mit Borg und ZFS/LVM Snapshots

Das Backuptool borg1 habe ich hier ein wenig beschrieben. Nun soll es darum gehen mittels borg saubere Backups zu erstellen und auch sicherzustellen, diese jederzeit wiederherstellen zu können.

Bevor man mit dem Backup mittels borg beginnen kann, benötigen wir erst einmal eine Location an der wir unser Backup sicher ablegen können. Das kann ein lokaler Speicher sein, da die Datenmengen die bei einem Backup mit borg anfallen aber - vom ersten Backup abgesehen - relativ gering sind, kommen auch Remote-Locations in Frage. Diese haben natürlich auch gleich den Vorteil unsere Daten vor Einbruchdiebstahl und Feuer zu schützen.

Allerdings müssen wir bei der Auswahl unserer Location darauf achten, dass borg mit dieser auch wirklich genutzt werden kann. borg überträgt die Daten über eine SSH-Verbindung, benötigt aber borg auf der Gegenstelle um korrekt zu arbeiten. Anbieter solcher Locations sind u.a.: 2 3 4

Hat man sich für eine Location entschieden und diese ggf. eingerichtet, kann man sich daran machen zu planen, welche Daten man sicher möchte und ob es hierbei spezielle Dinge zu berücksichtigen gibt. Diese spezielle Dinge können z.B. Datenbanken sein. Aufgrund deren Größe und der Tatsache, dass sich dort ständig Daten verändern können, ist ein Backup gar nicht so einfach. Eine mehrere GB große Datei einzulesen und hochzuladen dauert ja bekanntlich einige Zeit. Wenn sich während des Backups aber die Daten in den Datenbankdateien ändern, erhalten wir kein konsistentes Backup. Im schlimmsten Fall können wir die Datenbank gar nicht mehr gebrauchen. Den Dienst während des Backups herunterzufahren ist aber auch keine echte Alternative, so ein Backuplauf kann ja auch gerne mal mehrere Stunden dauern.

Hier kann man jetzt entweder auf spezielle Tools (bei MySQL z.B. mysqldump) zurückgreifen, doch auch bei diesen ist die Datenbank u.U. längere Zeit nicht erreichbar. Außerdem benötigt man hier - mehr oder weniger viel - Spezialwissen in der Nutzung dieser Tools. Auch muss man den Output dieser Tools erst einmal irgendwo zwischenlagern, bei großen Datenbanken kann das schon einmal problematisch werden. Auch der Faktor Zeit spielt hier eine Rolle.

Besser ist es hier einfach dafür zur Sorgen, dass sich die zu sichernden Daten während des Backup einfach gar nicht mehr verändern können. Man könnte z.B. einfach alle Schreibzugriffe im RAM zwischenlagern und diese erst nach dem Backup auf die Datenträger schreiben. Je nach Szenario bräuchte man hierfür aber große Mengen RAM und bei einem Stromausfall oder Reboot sind die Daten unwiederbringlich verloren. Zum Glück gibt es sog. Copy-on-write5 Verfahren und sog. Snapshots. Hierbei werden - vereinfacht gesagt - Dateien auf die ein Schreibzugriff erfolgt zunächst kopiert. Im Backup landet dann stets die unveränderte Datei von vor dem Schreibzugriff. So ist gewährleistet, dass sich die Daten während des Backups nicht verändern, sondern alle dem Stand zu Beginn des Backups entsprechen.

Ich nutze zwei unterschiedliche Verfahren um Snapshots zu erzeugen. Zum einen LVM, zum anderen ZFS. Für LVM Snapshots muss in der sog. Volume Group eines Volumes ausreichend freier Speicher auf dem Datenträger zur Verfügung stehen. Läuft dieser freie Speicherplatz während des Backups voll, wird der Snapshot invalidiert und die Daten im Backup würden sich doch wieder verändern. Dieses Szenario sollte man also unbedingt vermeiden. Ich empfehle daher immer etwa um die 5-10% des Speichers einer Volume Group "ungenutzt" zu lassen. (Bei ZFS wird natürlich ebenfalls physikalischer Speicher zur Verfügung stehen, allerdings muss dieser nicht explizit freigehalten werden, es wird einfach regulärer, freier Speicher des jeweiligen zpool genutzt.)

Wie viel freier Speicher einer Volume Group noch zur Verfügung steht kann man mit dem Befehl vgdisplay anzeigen lassen:

--- Volume group ---
  VG Name               vg0
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  7
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               28.12 GiB
  PE Size               4.00 MiB
  Total PE              7200
  Alloc PE / Size       6675 / 26.07 GiB
  Free  PE / Size       525 / 2.05 GiB
  VG UUID               7fHOdE-XBrm-JaHR-knAL-qVmB-DrkH-ytjX0u

Welche Volumes wir überhaupt mit einem Snapshot versehen können, zeigt uns der Befehl lvdisplay:

  --- Logical volume ---
  LV Path                /dev/vg0/swap
  LV Name                swap
  VG Name                vg0
  LV UUID                8sUXNJ-iEdP-wWAT-1I2F-Oi9d-Tvfi-3vpcz0
  LV Write Access        read/write
  LV Creation host, time foobar, 2022-11-26 17:00:58 +0100
  LV Status              available
  # open                 2
  LV Size                <7.45 GiB
  Current LE             1907
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:1

  --- Logical volume ---
  LV Path                /dev/vg0/root
  LV Name                root
  VG Name                vg0
  LV UUID                apgn6K-2fQx-SMQR-EIc8-aVYQ-EjkG-Ijt85C
  LV Write Access        read/write
  LV Creation host, time foobar, 2022-11-26 17:01:37 +0100
  LV Status              available
  # open                 1
  LV Size                18.62 GiB
  Current LE             4768
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:2

In diesem Fall haben wir also die Volumes /dev/vg0/swap und /dev/vg0/root. Da es wenig Sinn ergibt vom Swapspace ein Backup zu erzeugen, soll uns also nur /dev/vg0/root interessieren. Mit dem Befehl lvcreate -l100%FREE -s -n root-snapshot /dev/vg0/root können wir einen Snapshot erzeugen. Dieser nutzt 100% des freien Speichers der Volume Group und trägt den Namen root-snapshot. Rufen wir lvdisplay erneut auf, sehen wir dies auch bestätigt:

--- Logical volume ---
  LV Path                /dev/vg0/root-snapshot
  LV Name                root-snapshot
  VG Name                vg0
  LV UUID                FWfx1z-dm0g-OevE-5Tt6-SFB7-nj0V-Yh5cln
  LV Write Access        read/write
  LV Creation host, time foobar, 2023-07-13 18:15:44 +0200
  LV snapshot status     active destination for root
  LV Status              available
  # open                 0
  LV Size                18.62 GiB
  Current LE             4768
  COW-table size         2.05 GiB
  COW-table LE           525
  Allocated to snapshot  0.01%
  Snapshot chunk size    4.00 KiB
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:5

Hier sehen wir in der Zeile Allocated to snapshot auch, wie viel % unseres Snapshots schon für geänderte Daten genutzt werden. Dieser Wert darf - wie oben beschrieben - niemals 100% erreichen. Benötigen wir den Snapshot nicht länger, können wir ihn mit lvremove /dev/vg0/root-snapshot -f entfernen. Der Parameter -f sorgt hierbei dafür, dass wir nicht mehr mit Rückfragen konfrontiert werden, also sollte man hier gut aufpassen. Sollte sich hier doch mal der Fehlerteufel eingeschlichen haben und man löscht beispielsweise statt des Snapshots das eigentliche Volume ist aber auch nicht alles verloren, zumindest wenn man es sofort bemerkt. In der Datei /etc/lvm/backup/vg0 legt LVM automatisch ein Backup der Konfiguration einer Volume Group ab. Dieses würde zwar unsere Konfiguration nicht mehr enthalten, da dieses Backup nach der Ausführung von lvremove erzeugt wurde, nutzt man aber Tools wie etckeeper6, die automatisch Änderungen in /etc/ festhalten, hat man bestenfalls noch eine korrekte Version der Datei und kann diese mit dem Befehl vgcfgrestore wiederherstellen. Alternativ lassen sich die nötigen Informationen auch aus einem alten lvdisplay Output auslesen, hier sind besonders die Zeilen Current LE relevant, da sich aus diesen Start und Ende des Volumes rekonstruieren lassen. Ausführlicher ist dieser und ähnliche Rettungsvorgänge unter 7 beschrieben. Aber zurück zum Thema.

Um ein Backup mit borg zu erzeugen, müssen wir zunächst mit dem Befehl borg init ein Backup-Repository erzeugen. Hierbei gibt es ein paar Vorüberlegungen zu beachten. borg Repositories sind standardmäßig verschlüsselt. Was genau borg hier tun soll wird mit dem Parameter --encryption festgelegt, hier gibt es die Optionen none, authenticated, repokey sowie keyfile (genaugenommen gibt es noch jeweils keyfile-blake2, repokey-blake2 und authenticated-blake2 diese unterscheiden sich jedoch nicht in ihrer Funktion von ihren jeweiligen Pendants ohne -blake2, sondern in der Art der Verschlüsselung und Hashes).

  • none schaltet die Verschlüsselung des Repos ab. Dies kann man bei lokalen Backups auf verschlüsselten Datenträgern nutzen.
  • repokey speichert den Verschlüsselungskey im Repo ab, dieser sollte daher mit einem sehr guten Passwort geschützt werden.
  • keyfile speichert den Verschlüsselungskey auf dem lokalen Rechner. Dieser Key muss daher separat gesichert werden, andernfalls kann ich mein Backup im Fall eines vollständigen Datenverlustes nicht mehr wiederherstellen.
  • authenticated schaltet die Verschlüsselung des Repos ebenfalls ab, stellt aber die Authentizität der Inhalte im Repo sicher. Der hierzu genutzte Key wird - wie beim Repokey Verfahren - im Repo verwahrt.

Ich verwende in der Regel die keyfile oder repokey Methode. In beiden Fällen lasse ich mir den Key mit dem Befehl borg key export --paper ausgeben. Das Format dieser Ausgabe ist so gestaltet, dass man sich den Output gut ausdrucken und im Notfall händisch wieder eingeben kann. Den Ausdruck kann man dann z.B. in einem Bankschließfach sicher verwahren.

Aber zurück zum init Befehl. borg muss natürlich wissen, wo wir unsere Backups speichern möchten. Damit man diese, mitunter recht unhandliche, Angabe nicht ständig erneut machen muss, kann man diese als Umgebungsvariable BORG_REPO speichern. Nutzt man SSH um die Verbindung zu einer Backuplocation herzustellen kann der Wert für diese Variable z.B. so aussehen: ssh://user@backuplocation.example.org:22/./borg/foobar. Die Angabe von ssh:// gibt das Protokoll vor, user ist unser Benutzername für den Zugriff und backuplocation.example.org ist der Server, auf dem wir unser Backup ablegen wollen, 22 ist der genutzte Port (könnte man hier weglassen, da es sich um den SSH-Standardport handelt), /./borg/foobar letztlich ist das Verzeichnis in dem wir unser Repository anlegen möchten. foobar würde ich in diesem Fall durch den Namen des Rechner von dem die Daten stammen ersetzen.

Mittels export BORG_REPO=ssh://user@backuplocation.example.org:22/./borg/foobar können wir unsere Umgebungsvariable setzen und unsere nachfolgenden borg Befehle werden um einiges handlicher.

Zum Initialisieren unseres Repos reicht es nun aus einfach borg init -e repokey oder z.B. borg init -e keyfile aufzurufen. In beiden Fällen wird borg uns nach einer Passphrase fragen, um unseren Repokey zu schützen. Die Passphrase sollte wirklich gut sein, immerhin schützt sie später ggf. sehr sensible Informationen. Außerdem müssen wir die Passphrase nur selten bis gar nicht manuell eingeben, denn auch hierfür kann Borg eine Umgebungsvariable nutzen. Wer seine Backups zu Zeiten machen muss in denen die Internetleitung auch noch für andere Dinge genutzt wird, freut sich wahrscheinlich noch über den Parameter --upload-ratelimit mit dem man die genutzte Bandbreite limitieren kann, die Angabe erfolgt hierbei in KiByte/s. Wer den Backupspeicher nicht exclusiv nutzt, kann zudem noch die Größe des Repos mit dem Parameter --storage-quota einschränken, die Angabe erfolgt hier in Form von Angaben wie z.B. 5G oder 1T. Die Option --make-parent-dirs erspart es uns die Verzeichnisstruktur für das Repo vorab händisch anzulegen.

borg init -e keyfile --make-parent-dirs --storage-quota 1T --upload-ratelimit 5000
Enter new passphrase:
Enter same passphrase again:
Do you want your passphrase to be displayed for verification? [yN]: n

By default repositories initialized with this version will produce security
errors if written to with an older version (up to and including Borg 1.0.8).

If you want to use these older versions, you can disable the check by running:
borg upgrade --disable-tam ssh://user@backuplocation.example.org:22/./borg/foobar

See https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability for details about the security implications.

IMPORTANT: you will need both KEY AND PASSPHRASE to access this repo!
If you used a repokey mode, the key is stored in the repo, but you should back it up separately.
Use "borg key export" to export the key, optionally in printable format.
Write down the passphrase. Store both at safe place(s).

Dem Hinweis meinen Repokey mittels borg key export zu sichern komme ich natürlich umgehend nach. Statt im Bankschließfach ist der Output, nebst Passphrase, natürlich auch in einem Passwortsafe gut aufgehoben.

Nachdem die Initialisierung vollzogen ist, steht unserem ersten Backup mit dem Befehl borg create eigentlich auch schon nichts mehr im Wege. Aber halt, zunächst erzeugen wir uns natürlich unseren Snapshot und mounten diesen, dann nutzen wir ihn um das Backup anzulegen:

export BORG_PASSPHRASE=mysupersecurepassphraseyouwillneverguess
mkdir /mnt/root-snapshot
lvcreate -l100%FREE -s -n root-snapshot /dev/vg0/root
mount /dev/vg0/root-snapshot /mnt/root-snapshot
borg create ::{now} /mnt/root-snapshot/etc/ /mnt/root-snapshot/var/lib/ /mnt/root-snapshot/var/www/ /home/user/ --show-rc
umount /mnt/root-snapshot
lvremove /dev/vg0/root-snapshot -f

Der Output sollte in etwa so aussehen:

  Logical volume "root-snapshot" created.
terminating with success status, rc 0
  Logical volume "root-snapshot" successfully removed.

Mit dem Befehl borg info können wir uns ein paar Informationen zu unserem Repo anzeigen lassen:

Repository ID: <redacted>
Location: <redacted>
Encrypted: Yes (key file)
Key file: <redacted>
Cache: <redacted>
Security dir: <redacted>
------------------------------------------------------------------------------
                       Original size      Compressed size    Deduplicated size
All archives:                9.15 kB              6.62 kB              5.84 kB

                       Unique chunks         Total chunks
Chunk index:                       9                   14

Mit borg list erhalten wir eine Liste aller im Repo vorhandenen Backups, inklusive der Angabe wann sie erzeugt wurden:

2023-07-13T19:23:33                  Thu, 2023-07-13 19:23:41 [c285340cea26750b5f1761b201068c6b4b66a475596dd27ce935140b9f5e6d0c]
2023-07-13T19:24:04                  Thu, 2023-07-13 19:24:08 [9a4afd6904efd4fb0e6b02f8704e2214af59cc181d33157cfda801d567e16b33]
2023-07-13T19:24:21                  Thu, 2023-07-13 19:24:24 [0a87a1f0e77f87c155ee9fd90ed3e74969a5e2d287358656261f1a96d3a61efa]

Die Angabe des Datums mag hier redundant erscheinen, liegt aber daran, wie wir unser borg create Kommando gestaltet haben. Die Angabe von ::{now} erzeugt eben genau dieses Datumsformat. Stattdessen könnte man z.B. auch ::foobar_monday nutzen, um noch einmal zu verdeutlichen, dass das Backup vom Rechner foobar stammt und am Montag erzeugt wurde.

foobar_monday                        Thu, 2023-07-13 19:35:54 [4a64f3a9a457e93f50aa449164f5e87175bafc6ba8a6b415f720314768a06edd]

Aber Achtung, existiert bereits ein Backup mit dem Namen foobar_monday wird Borg sich weigern ein Backup zu erzeugen. Ich finde daher die Angabe von {now} extrem praktisch, von welchem Rechner das Backup stammt habe ich ja ohnehin schon durch das Repo-Verzeichnis kenntlich gemacht.

Mit dem Befehl borg check können wir die Integrität unseres Backups überprüfen:

borg check -v
Remote: Starting repository check
Remote: finished segment check at segment 28
Remote: Starting repository index check
Remote: Index object count match.
Remote: Finished full repository check, no problems found.
Starting archive consistency check...
Analyzing archive 2023-07-13T19:21:04 (1/6)
Analyzing archive 2023-07-13T19:23:33 (2/6)
Analyzing archive 2023-07-13T19:24:04 (3/6)
Analyzing archive 2023-07-13T19:24:21 (4/6)
Analyzing archive 2023-07-13T19:31:23 (5/6)
Analyzing archive foobar_monday (6/6)
Archive consistency check complete, no problems found.

Wer sich wundert, dass dieser Check sehr schnell vonstatten geht, hat Recht. borg führt hierbei nur einen oberflächlichen Check auf Basis von Prüfsummen durch. Wer auch wirklich die Konsistent der gespeicherten Daten sicherstellen möchte, muss deutlich mehr Zeit und den Parameter --verify-data mitbringen:

borg check --verify-data -v
Remote: Starting repository check
Remote: finished segment check at segment 28
Remote: Starting repository index check
Remote: Index object count match.
Remote: Finished full repository check, no problems found.
Starting archive consistency check...
Starting cryptographic data integrity verification...
Finished cryptographic data integrity verification, verified 12 chunks with 0 integrity errors.
Analyzing archive 2023-07-13T19:21:04 (1/6)
Analyzing archive 2023-07-13T19:23:33 (2/6)
Analyzing archive 2023-07-13T19:24:04 (3/6)
Analyzing archive 2023-07-13T19:24:21 (4/6)
Analyzing archive 2023-07-13T19:31:23 (5/6)
Analyzing archive foobar_monday (6/6)
Archive consistency check complete, no problems found.

Dies kommt dann im Grunde einem kompletten Restore aller Daten im Repo gleich. Einen regelmäßigen Restoretest sollte man aber dennoch vornehmen, denn vor Bugs ist man nie 100% sicher und außerdem ist es immer gut, wenn man auch den Restoreprozess im Schlaf durchführen kann.

Sollte borg bei der Überprüfung eines Repos wirklich einmal auf einen Fehler stoßen, kann man versuchen diesen mit der Option borg check --repair beheben zu lassen. Das Feature warnt zwar, dass es eventuell zu Datenverlust kommen kann, wenn ein Fehler nicht erfolgreich behoben werden kann, bisher habe ich damit aber durchweg gute Erfahrungen gemacht.

Zum Glück ist ein Restore mit borg ein wirklich sehr angenehmer und auch erstaunlich schneller Vorgang, wir erzeugen uns hierfür erst einmal ein Verzeichnis um dort unser Backup mounten zu können, dann nutzen wir den borg mount Befehl:

mkdir /mnt/borg-restore
borg mount :: /mnt/borg-restore

Wenn wir uns daraufhin /mnt/borg-restore einmal genauer anschauen, finden wir folgendes:

❯ ls -lah /mnt/borg-restore
total 4.0K
drwxr-xr-x 1 root root    0 Jul 13 19:45 .
drwxr-xr-x 8 root root 4.0K Jul 13 19:45 ..
drwxr-xr-x 1 root root    0 Jul 13 19:21 2023-07-13T19:21:04
drwxr-xr-x 1 root root    0 Jul 13 19:23 2023-07-13T19:23:33
drwxr-xr-x 1 root root    0 Jul 13 19:24 2023-07-13T19:24:04
drwxr-xr-x 1 root root    0 Jul 13 19:24 2023-07-13T19:24:21
drwxr-xr-x 1 root root    0 Jul 13 19:31 2023-07-13T19:31:23
drwxr-xr-x 1 root root    0 Jul 13 19:35 foobar_monday

borg stellt uns alle Backupgenerationen als Verzeichnis zur Verfügung. Dies liegt an der Angabe :: im mount Befehl. Wenn wir eine bestimmte Backupgeneration hätten haben wollen, hätten wir hier ::foobar_monday angeben können. Die gemounteten Daten können wir nun ganz normal mit unseren üblichen Werkzeugen nutzen und natürlich auch öffnen. Borg baut uns diese on-the-fly aus den gespeicherten Deltas zusammen.

Damit ist der Weg zu konsistenten, verschlüsselten und verifizierten Backups frei, die in der Regel auch über eine DSL-Leitung auf eine Remote-Location erfolgen können. Das macht zumindest meine Bingo!-Karte voll.

Mit ZFS geht das natürlich ebenfalls. Hier müssen wir lediglich die Befehle für die Snapshoterzeugung anpassen. ZFS bietet hierbei sogar noch den Vorteil besser mit dauerhaft existierenden Snapshots umgehen zu können. Wir könnten hier also auf die Entfernung des Snapshots nach dem Backup verzichten oder diese erst dann Löschen, wenn wir auch unsere Backupgenerationen verwerfen.

Zum Erzeugen eines Snapshots müssen wir zunächst den Namen unseres zpools ermitteln. Ein zpool ist quasi das Gegenstück zu unserer LVM Volume Group. Dies können wir mit dem Befehl zpool list erledigen:

zpool list
NAME   SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
dump   952G   552G   400G        -         -     1%    58%  1.00x    ONLINE  -
tank   232G  80.7G   151G        -         -    35%    34%  1.00x    ONLINE  -

In diesem Fall haben wir also zwei Pools: dump und tank. Welche Volumes sich in diesen Pool befinden können wir mit zfs list -t filesystem ermitteln:

zfs list -t filesystem
NAME                                                                                USED  AVAIL     REFER  MOUNTPOINT
dump                                                                                552G   370G       24K  /dump
dump/data                                                                           552G   370G       98K  /dump/data
dump/data/nextcloud                                                                 552G   370G      552G  /DATA/AppData/nextcloud
tank                                                                               80.7G   144G       24K  /tank
tank/data                                                                          69.4G   144G      816M  /DATA
tank/data/gitea                                                                    1004M   144G      165M  /DATA/AppData/gitea
tank/data/miniflux                                                                  950M   144G      234M  /DATA/AppData/miniflux
tank/data/wallabag                                                                 16.7M   144G     11.9M  /DATA/AppData/wallabag
tank/docker                                                                        10.1G   144G      110M  /var/lib/docker

Ob wir schon bestehende Snapshots haben, können wir übrigens mit zfs list -t snapshot herausfinden. Eine gemeinsame Liste erzeugt zfs list, die Snapshots sind hier am @ im Namen zu erkennen.

Einen neuen Snapshot können wir ganz einfach mit dem Befehl zfs snapshot erzeugen. Als Parameter geben wir einfach den zpool, das filesystem und den Namen des Snapshots an:

zfs snapshot tank/data/gitea@backup
zfs list -t snapshot | grep @backup
tank/data/gitea@backup                                                                          0B      -      165M  -

0B sagt uns hier, wie viel Speicherplatz durch den Snapshot zusätzlich verbraucht wird. Die Angabe 165M bezieht sich auf den Speicherbedarf des Filesystems. Benötigen wir unseren Snapshot nicht mehr, können wir ihn mit dem Befehl zfs destroy entfernen:

zfs destroy tank/data/gitea@backup
zfs list -t snapshot | grep @backup

Wenn wir jetzt also einen vollständigen Backupdurchlauf machen möchten, sieht dieser so aus:

export BORG_PASSPHRASE=mysupersecurepassphraseyouwillneverguess
mkdir /mnt/gitea-snapshot
zfs snapshot tank/data/gitea@backup
mount -t zfs tank/data/gitea@backup /mnt/gitea-snapshot
borg create ::{now} /mnt/gitea-snapshot/ --show-rc
umount /mnt/gitea-snapshot
zfs destry tank/data/gitea

Man sieht hier schon ein wenig, dass sich meine Datenstruktur zwischen Systemen mit LVM und ZFS etwas unterscheidet. Auf ZFS Systemen habe ich für jeden Dienst ein eigenes ZFS-Volume und sichere dieses auch separat, außerdem nutze ich ZFS bisher nicht als root-Filesystem. Da ich aber nicht für jeden Dienst jeden Tag manuell ein Backup anschieben möchte, habe ich mir hierfür natürlich ein kleines Script geschrieben:

#!/bin/bash

export BORG_REPO="ssh://user@backuplocation.example.org:22/./borg/foobar"
export BORG_PASSPHRASE="mysupersecurepassphraseyouwillneverguess"

volumes="data/miniflux data/wallabag data/gitea"

BOOTUP=mono
LOGFILE="/tmp/backup.log"
RES_COL=70
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
MOVE_TO_TAB="echo -en \t\t\t\t\t"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"

echo_success() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    [ "$BOOTUP" = "color" ] || $MOVE_TO_TAB
    echo -n "  ["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
    echo -n $"  OK  "
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 0
}

echo_failure() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    [ "$BOOTUP" = "color" ] || $MOVE_TO_TAB
    echo -n "  ["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo -n $"FAILED"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
}

step() {
    echo -n "$@"

    echo "" >> ${LOGFILE}
    echo "$(date)" >> ${LOGFILE}
    echo "$*" >> ${LOGFILE}

    STEP_OK=0
    [[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
}

try() {
    "$@" &>> ${LOGFILE}

    local EXIT_CODE=$?

    if [[ $EXIT_CODE -ne 0 ]]; then
        STEP_OK=$EXIT_CODE
        [[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$

        if [[ -n $LOG_STEPS ]]; then
            local FILE=$(readlink -m "${BASH_SOURCE[1]}")
            local LINE=${BASH_LINENO[0]}

            echo "$FILE: line $LINE: Command \`$*' failed with exit code $EXIT_CODE." >> ${LOGFILE}
        fi
    fi

    return $EXIT_CODE
}

next() {
    [[ -f /tmp/step.$$ ]] && { STEP_OK=$(< /tmp/step.$$); rm -f /tmp/step.$$; }
    [[ $STEP_OK -eq 0 ]]  && echo_success || echo_failure
    echo

    return $STEP_OK
}

LOCKFILE="/var/lock/$(basename $0)"
LOCKFD=99

_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

_prepare_locking

exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock
exerror()           { echo "$1";exit "${2:-1}"; }

exlock_now || exerror "ERROR: Lockfile exists, is there another instance already running? Exiting..."

trap 'echo "ERROR: Backup interrupted"; exit 2' INT TERM

echo "Starting Backup $(date)"
echo "-------------------------------------------------------------------------------"
echo ""

step "Removing stale locks         "
try 
next

step "Generating variables         "
for x in ${volumes}; do
  backup="${backup} /mnt/backup/${x}"
done

try echo "Backup locations: ${backup}"
next

step "Create mountpoints           "
for x in ${volumes}; do
  try echo "  - ${x}"
  try mkdir /mnt/backup/${x} -p
done
try mkdir /mnt/root-snapshot
next

step "Create snapshots             "
for x in ${volumes}; do
  try echo "  - ${x}"
  try /usr/sbin/zfs snapshot tank/${x}@backup
try /sbin/lvcreate -l100%FREE -s -n root-snapshot /dev/vg0/root
done

next

step "Mount snapshots              "
for x in ${volumes}; do
  try echo "  - ${x}"
  try /bin/mount -t zfs tank/${x}@backup /mnt/backup/${x}
done
try /bin/mount /dev/vg0/root-snapshot /mnt/backup/root
next

step "Create backup                "
try /usr/bin/borg create ::{now} /mnt/root-snapshot/etc /mnt/root-snapshot/root ${backup} --show-rc
next

step "Umount snapshots             "
for x in ${volumes}; do
  try echo "  - ${x}"
  try /bin/umount /mnt/backup/${x}
done
try /bin/umount/root-snapshot
next

step "Destroy snapshots            "
for x in ${volumes}; do
  try echo "  - ${x}"
  try /usr/sbin/zfs destroy tank/${x}@backup
done
try /sbin/lvremove /dev/vg0/root-snapshot -f
next

step "Prune backup generations     "
try /usr/bin/borg prune --keep-daily=14 --show-rc
next

if (( $(date +"%u") == 1)); then
  step "Backup Check (verify)        "
    try /usr/bin/borg check --verify-data --show-rc
  next
else
  step "Backup Check (quick)         "
    try /usr/bin/borg check --show-rc
  next
fi

step "Removing lock                "
  try /usr/bin/borg break-lock
next

step "Exporting Repokey            "
  try /usr/bin/borg key export :: --paper
next

step "Backup List                  "
  try /usr/bin/borg list
next

step "Backup Info                  "
  try /usr/bin/borg info
next

echo ""
echo "-------------------------------------------------------------------------------"
echo "Logfile:"
cat ${LOGFILE}
rm -f ${LOGFILE}
echo "-------------------------------------------------------------------------------"

Was genau dieses Script tut und warum, werde ich mal in einem separaten Artikel erläutern. Den Teil mit step, try und next habe ich hier bereits beschrieben. Der Lockfile-Part wurde hier etwas näher betrachtet. Den Part dp_cleanup() und trap habe ich hier beschrieben.


  1. https://www.borgbackup.org ↩

  2. https://www.borgbase.com ↩

  3. https://www.rsync.net/products/borg.html ↩

  4. https://www.hetzner.com/de/storage/storage-box ↩

  5. https://en.wikipedia.org/wiki/Copy-on-write ↩

  6. https://etckeeper.branchable.com ↩

  7. https://www.golinuxcloud.com/recover-lvm2-partition-restore-vg-pv-metadata/ ↩

Tags: linux bash borg backup lvm zfs snapshots

Mehr

  • Docker
  • Heimserver mit Debian
  • Serverüberwachung mit checkmk
  • Flexible, bootfähige USB-Sticks mit Ventoy
  • Serververwaltung mit Ansible

Tags

linux bash borg backup lvm zfs snapshots

Archiv

  • Mar 2025 (2)
  • May 2024 (2)
  • Oct 2023 (1)
  • Aug 2023 (5)
  • Jul 2023 (31)

  • Ältere Einträge (95)

Feeds

Atom 1.0 RSS JSON
  • Datenschutz
  • Impressum
  • Archiv