Backup beim Anstecken von USB-Storage

Ziel: Beim Anstecken eines USB-Speichersticks oder einer externen USB-Festplatte sollen wichtige Daten automatisch auf selbigen Gerät gesichert werden.

Warnung

Aktiviertes Festplattenmonitoring führt bereits zum automatischen Mounten des Backup-Datenträgers! So z.B. durch Collectd; bitte systemctl stop collectd und systemctl disable collectd ausführen.

Um die Daten tatsächlich nur auf die vorgesehene Ablagefläche zu sichern, ziehen wir als Auslöser der Vorgänge zwei Merkmale heran:

  1. Seriennummer der Major-Devices

    Zum Beispiel für /dev/sdb (siehe unten bei udevadm info ...), sie ist in der Hardware fest verankert.

  2. UUID des Dateisystems

    Für spätere Mount- und Backupvorgänge sind die Partition eindeutig zu identifizieren, hier soll es das erste Minor-Device /dev/sdb1 sein, siehe dazu die Ausgabe der UUID mittels blkid /dev/sdb1. Diese ID wird zumeist beim Aufbringen des Dateisystems generiert, lässt sich aber mit tune2fs auch auf Gegebenheiten anpassen.

Hardwareerkennung

Wir gehen von der Annahme aus, dass der angesteckte USB-Stick vom Kernel als 2. Festplatte erkannt wird und zumindest eine Partition aufweist (/dev/sdb1), die wir später für Backupzwecke benutzen wollen. Daher suchen wir nun zur eindeutigen Hardware-Identifizierung die Seriennummer von /dev/sdb heraus:

root@host $ udevadm info -a /dev/sdb | grep serial
    ATTRS{serial}=="18030900014548"
    ATTRS{serial}=="0000:00:1d.0"
root@host $

Die erste ATTRS-Zeile beinhaltet die gesuchte Nummer, auf die wir nachher zurückgreifen werden. Dabei spielt die Option -a, --attribute-walk eine wichtige Rolle, durch welche die Eigenschaften des Geräts entlang des sysfs ausgegeben werden. Diese Werte können dann direkt in den udev-Regeln Verwendung finden.

Udev/Mount auf Unix-Art

= Variante ohne den komplexen System-Daemon

Betriebssystem: Devuan Linux, Artix Linux, PCLinuxOS oder eine andere Distribution ohne Systemd-Startverfahren.

Wir erstellen eine neue Datei für die Udev-Regeln und nennen sie /etc/udev/rules.d/99-mystorage.rules. Sie bekommt diesen Inhalt:

SUBSYSTEMS=="usb", ATTRS{serial}=="18030900014548", KERNEL=="sd[b-z]1", \
            SYMLINK+="mystorage", RUN+="/usr/local/bin/bkup.sh"

Zum eindeutigen Spezifizieren eines bestimmten Geräts muss es hierbei in zwei Punkten Überstimmung geben:

  1. Bezüglich der Elterngeräte (eines von SUBSYSTEMS=, ATTRS=, KERNELS=, … „S“ -> Mehrzahl!)

    In unserem Falle spezifizieren wir die Hardware mittels: ATTRS{serial}=="18030900014548"

  2. In Bezug auf das Gerät selbst (eines von SUBSYSTEM=, ATTR=, KERNEL=, …)

    In unserem Falle filtern wir neue Major-Devices mit sdb beginnend sowie die 1. Partition mittels: KERNEL=="sd[b-z]1" heraus.

Wichtig ist hier gerade auch der 2. Punkt: existieren z.B. drei Partitionen auf dem Stick, würde udev ohne dieses KERNEL-Filter das Ereignis gleich drei mal hintereinander auslösen! Siehe dazu auch den kurzen Abschnitt unter http://reactivated.net/writing_udev_rules.html#sysfstree

Mit der folgenden Zeile lesen wir die Änderungen ein, womit sich meist das komplette Neustarten des udev-Daemons vermeiden lässt:

root@host $ udevadm control --reload-rules && udevadm trigger

Backupskript erstellen

Nun benötigen wir noch das Shellskript /usr/local/bin/bkup.sh, das hierbei das Volume direkt mounten kann. Zum Backupskript können wir es z.B. mittels ‚rsync‘ machen (Zeile 4), während die Zeilen 5 und 6 nur zu Testzwecken enthalten sind. Insbesondere ‚sleep 30‘, um den erfolgreichen Mountvorgang in der Datei ‚/proc/self/mounts‘ bequem kontrollieren zu können:

1#!/bin/sh
2mount /dev/mystorage /mnt
3logger -t UDEV-TRIGGERED "Ereignis festgestellt: `df -h /mnt`"
4#rsync -a ~/Dokumente/ /mnt/MyDokumente.synced/
5echo "123, fertig ist das Backup!" > /mnt/123.testdatei-$(date +%F-%H-%M)
6sleep 30
7umount /mnt

Das Skript müssen wir natürlich noch ausführbar machen:

root@host $ chmod +x /usr/local/bin/bkup.sh

Udev/Mounting mit Systemd

= Variante mit neuem Linux System Daemon

Betriebssystem: Debian 10 (oder eine vergleichbare Systemd-Distribution)

Wir nennen die Datei für die Udev-Regeln /etc/udev/rules.d/99-mystorage.rules und befüllen sie folgendermaßen (ACHTUNG: dank dem Backlash wurde die lange Zeile zweimal umgebrochen; danach darf kein Leerzeichen etc. folgen, lediglich ein Newline):

SUBSYSTEMS=="usb", ATTRS{serial}=="18030900014548", KERNEL=="sd[b-z]1", \
    ACTION=="add", ENV{SYSTEMD_WANTS}="mybackup.service", \
    SYMLINK+="mystorage", RUN+="/usr/bin/logger BACKUP VIA UDEV TRIGGERED"

Mit der folgenden Zeile lesen wir die Änderungen ein, womit sich meist das komplette Neustarten des udev-Daemons vermeiden lässt:

root@host $ udevadm control --reload-rules && udevadm trigger

Stick mounten - Variante 1: via /etc/fstab - Automount

Automounter mit Systemd

Am einfachsten ist es, den Stick mittels des neuen Systemd-Automounters selbständig ein- und auch wieder aushängen zu lassen. Dazu muss nur eine Zeile in die Datei /etc/fstab geschrieben werden, der systemd-fstab-generator erzeugt daraus live eine .automount-Unit.

Diese Zeile sieht hierbei folgendermaßen aus (die UUID kann mit blkid ermittelt werden):

UUID=7C74-7B5A  /media/mystick  vfat  noauto,x-systemd.automount,x-systemd.device-timeout=10,x-systemd.idle-timeout=30  0  2

Nach dem Hinzufügen dieses Eintrags müssen folgende Kommandos ausgeführt werden, um die Änderung einzulesen (wer auf Nummer sicher gehen möchte: systemctl reboot):

root@host $ systemctl daemon-reload
root@host $ systemctl restart local-fs.target remote-fs.target

Jetzt erst einmal gründlich testen, ob sich der Stick automatsch ein- und aushängen lässt! Dazu einfach mit ls -F /media/mystick ins Mountverzeichniss hineinschauen, wobei das Mounten geschehen muss. Das kontrollieren wir am besten mit df -h | grep mystick, alternativ lassen sich die letzten Zeilen der /proc/self/mounts heranziehen.

Unit-Datei für Backupservice (fstab-automount)

Nun erzeugen wir uns eine eigene Systemd Unit Datei, die später das Backupskript starten wird. Die Datei definiert lediglich, welches Skript auszuführen ist („ExecStart=“) und welches Abhängigkeitsverhältnis beim abschließenden systemctl enable… zu beachten ist („WantedBy=“).

Wir nennen die Datei /etc/systemd/system/mybackup.service und geben ihr folgenden Inhalt:

[Unit]
Description=Make Backup via /etc/fstab and Automount

[Service]
ExecStart=/home/tux/bin/mybackup

[Install]
WantedBy=media-mystick.automount

Abschließend müssen wir noch den Mountpunkt anlegen und den Service aktivieren:

root@host $ mkdir /media/mystick
root@host $ systemctl enable mybackup.service

Stick mounten - Variante 2: via separater Mount-Units

Anstelle den einfacheren Weg über den systemd-fstab-generator zu gehen, können wir manuell zwei Dateien anlegen, die das Ganze besser spezifizieren.

Prinzipiell muss zuerst einmal eine passende, normale ‚.mount‘ Unit-Datei existieren; aus man systemd.automount: „For each automount unit file a matching mount unit file (see systemd.mount(5) for details) must exist…“

Mount-Unit

Zuerst legen wir also eine neue Datei namens /etc/systemd/system/media-mystick.mount an. Sie bekommt nachfolgenden Inhalt, wobei zu beachten ist, dass der Präfix (‚media-mystick‘) im Dateinamen ‚media-mystick.mount‘ dem späteren Mountpunkt-Pfad ‚/media/mystick‘ entsprechen muss. Im Dateinamen ist also der Name des Mountverzeichnisses verankert; das Dateinamenssuffix ‚.mount‘ spielt für den Mountpunkt keine Rolle, wohl aber für den Unit-Typ:

[Unit]
Description=Additional drive
After=local-fs.target

[Mount]
What=UUID=7C74-7B5A
Where=/media/mystick
Type=vfat
Options=defaults

[Install]
WantedBy=multi-user.target

Natürlich muss das erwähnte Mountpunktverzeichnis /media/mystick gemäß des Dateinamens ‚media-mystick.mount‘ existieren, außerdem müssen wir diese Unit noch mit systemctl enable media-mystick.mount aktivieren. Ein manuelles Starten ist nicht notwendig - das Volume wird ja beim udev-Ereignis „USB-Stick wurde angesteckt“ automatisch gemountet.

Automount-Unit

Nun wollen wir aber auch, dass der Stick nach dem Backup wieder automatisch ausgehängt wird. Das bedeutet, dass Systemd das Gerät überwachen muss, ob es noch in Benutzung ist oder nicht. Falls keine Prozesse mehr vorhanden sind, wird ein „umount“ versucht. Dazu benötigen wir aber zusätzlich eine .automount-Unitdatei namens /etc/systemd/system/media-mystick.automount, die wir nun erzeugen und wie folgt befüllen:

[Unit]
Description=Automount additional drive

[Automount]
Where=/media/mystick
TimeoutIdleSec=7s

[Install]
WantedBy=multi-user.target

Diese Datei muss ebenso wie obige Mount-Unitdatei heißen, nur mit dem Unterschied, dass der Dateisuffix nicht .mount sondern .automount lautet. Das Präfix „media-mystick“ muss mit dem Verzeichnispfad für den Mountpunkt „/media/mystick“ korrespondieren, der Pfad wird aber zusätzlich nochmal bei Where=/media/mystick angegeben.

Diese neue Automount-Unit ist ein echter Service und muss nicht nur aktiviert, sondern auch gestartet werden:

root@host $ systemctl enable media-mystick.automount
root@host $ systemctl start media-mystick.automount

Falls etwas in den Unit-Files geändert werden muss, bitte nicht vergessen systemctl daemon-reload auszuführen! Dadurch werden die vorherigen Konfigurationen, die von Generatoren stammen, verworfen und neu erzeugt.

Nun geht es wieder ans testen, aber VORSICHT: Das Monitoring mit watch -n1 -d  ls -l /media/mystick/ hält den Stick im gemounteten Zustand! Besser ist, das Ganze mit df -h | grep mystick anzugehen.

Unit-Datei für Backupservice (mount, automount unit)

Nun erzeugen wir uns eine Systemd Unit Datei, die später das Backupskript starten wird. Dabei muss hier allerdings „Requires“ und „After“ im Unit-Abschnitt verwendet werden, weil dieser Service auf die funktionierende, separate Mount-Unit angewiesen ist:

Der neu anzulegenden Datei /etc/systemd/system/mybackup.service geben wir folgenden Inhalt:

[Unit]
Description=Make Backup via mount and automount unit files
Requires=media-mystick.mount
After=media-mystick.mount

[Service]
ExecStart=/usr/local/bin/bkup-systemd.sh

[Install]
WantedBy=media-mystick.mount

Abschließend müssen wir noch den Mountpunkt anlegen und den Service aktivieren:

root@host $ mkdir /media/mystick
root@host $ systemctl enable mybackup.service

Backupskript erstellen

Zum Schluss benötigen wir wieder ein Backupskript, was jetzt allerdings keine Mountaufgaben mehr wahrnehmen kann. Wir erzeugen dazu eine neue Datei namens /usr/local/bin/bkup-systemd.sh und befüllen sie folgendermaßen:

#!/bin/sh
logger -t UDEV-TRIGGERED "Ereignis festgestellt: `grep '/media/mystick' /proc/self/mounts`"
#rsync -a ~/Dokumente/ /media/mystick/MyDokumente.synced/
echo "123, fertig ist das Backup!" > /media/mystick/123.testdatei-$(date +%F-%H-%M)

Das Skript müssen wir schließlich wieder ausführbar machen:

root@host $ chmod +x /usr/local/bin/bkup-systemd.sh

Inbetriebnahme/Tests

ACHTUNG: Falls collectd mit den Standard Plugins läuft, löst er in einer Endlosschleife immer wieder das das automatische Mounten aus! Genauso kann ein laufender Nagios-Daemon Probleme bereiten!

Außerdem ist es ratsam, das Hauptlog im Auge zu behalten, hier einfach mittels journalctl -f.

Weitere Tipps:

  1. Stets den Stick sauber aus der VM lösen (via VirtualBox unter „Geräte“, „USB“, …)

  2. Zum Testen/Triggern den Stick einfach wieder per selbigen Menü hineinreichen.

  3. Notfalls nach Schritt 1 den USB-Stick richtig herausziehen, danach wieder hineinreichen.

  4. Bitte Aufpassen, dass die grafischen Dateimanager nicht schon den Stick mounten, am besten: ‚init 3‘; wer PCManFM verwendet, kann unter „Bearbeiten“, „Einstellungen“, „Dateinträgerverwaltung“ das automatische Mounten deaktivieren.

Halten wir fest:

  • Es braucht nicht immer den Aufwand mit zwei Dateien:

    • /etc/systemd/system/media-mystick.mount

    • /etc/systemd/system/media-mystick.automount

  • … alternativ reicht eine Zeile in der /etc/fstab mit noauto,x-systemd.automount,...

  • Leider greift in diesem Zusammenhang die Option ‚noauto‘ nicht: Wenn der Stick beim Booten angesteckt ist, wird bereits das Backupskript getriggert (=> „man systemd.automount“).

Zusammenfassung

Für die Variante mit Systemd und der separaten Mount-Unitdatei hier noch einmal alle beteiligten Dateien und Kommandos im Überblick:

  1. Hardwareerkennung durch Udev, getriggert wird aufgrund des USB-Subsystems, der Seriennummer und der 1. Partition des Geräts:

root@d10:~# cat /etc/udev/rules.d/99-mystorage.rules
### Dies ist Hook number one: ATTRS{serial}=="18030900014548"
SUBSYSTEMS=="usb", ATTRS{serial}=="18030900014548", KERNEL=="sd[b-z]1", \
        ACTION=="add", ENV{SYSTEMD_WANTS}="mybackup.service", \
        SYMLINK+="mystorage", RUN+="/usr/bin/logger BACKUP VIA UDEV TRIGGERED"
root@d10:~#
  1. Das Backup-Skript (ohne systemd ist dies direkt via Udev’s RUN+="/usr/local/bin/bkup.sh möglich):

root@d10:~# cat /home/tux/bin/mybackup
#!/bin/sh
logger -t UDEV-TRIGGERED "Ereignis festgestellt: `grep '/media/mystick' /proc/self/mounts`"
#rsync -a ~/Dokumente/ /media/mystick/MyDokumente.synced/
echo "123, fertig ist das Backup!" > /media/mystick/123.testdatei-$(date +%F-%H-%M)
  1. Wegen Systemd müssen wir aber in der Udev-Regeldatei ENV{SYSTEMD_WANTS}="mybackup.service" verwenden und diese Datei erzeugen:

root@d10:~# cat /etc/systemd/system/mybackup.service
[Unit]
Description=Make Backup via Mount- und Automount-Units
Requires=media-mystick.mount
After=media-mystick.mount

[Service]
ExecStart=/home/tux/bin/mybackup

[Install]
WantedBy=media-mystick.mount
root@d10:~#
  1. Die erforderliche Mountunit, was wir soeben unter 3.) gesehen haben (Requires=media-mystick.mount):

root@d10:~# cat /etc/systemd/system/media-mystick.mount
[Unit]
Description=Additional drive

[Mount]
What=UUID=7C74-7B5A
Where=/media/mystick
Type=vfat
Options=defaults

[Install]
WantedBy=multi-user.target
root@d10:~#
  1. Die Automount-Unit, die wir nur für das automatische Aushängen nach 7 Sekunden benötigen:

root@d10:~# cat /etc/systemd/system/media-mystick.automount
[Unit]
Description=Automount additional drive

[Automount]
Where=/media/mystick
TimeoutIdleSec=7s

[Install]
WantedBy=multi-user.target
root@d10:~#

Kommandos zum aktivieren, deaktivieren, starten und stoppen:

systemctl stop collectd
systemctl disable collectd
udevadm control --reload-rules && udevadm trigger
systemctl enable mybackup.service
systemctl enable media-mystick.mount
systemctl enable media-mystick.automount
systemctl start media-mystick.automount

Und nicht vergessen, systemctl daemon-reload auszuführen, falls etwas in den Unit-Files geändert werden musste!