Backups mit Borg und ZFS/LVM Snapshots
Das Backuptool borg
1 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 etckeeper
6, 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 zpool
s 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.