Gregor Horvath, Ing.

Industrieberatung

Softwareentwicklung

Backupserver mit rotierenden außer Haus Festplatten

1 Über diese Dokumentation

Diese Dokumentation enthält die Besonderheiten meiner Backup Einrichtung, die nicht in freier Dokumentation enthalten ist. (Man Pages etc.).

Einerseits da ich eine lange Betriebszeit der Lösung anstrebe und in 10 Jahren noch wissen will was ich wie und warum so eingerichtet habe und andererseits hege ich die Hoffnung, dass andere aus den Informationen nutzen ziehen können und ich damit den Einsatz freier Software und folglich die Erlangung informationeller Selbstbestimmung und digitaler Freiheit unserer Gesellschaft unterstütze.

Das Ziel ist kein How To und keine Schritt für Schritt Anleitung, sondern eine Ergänzung zu im Netz und freier Softwaredokumentation vorhandenen Informationen. Verweise auf diese werden ggf. gegeben.

Bei Fragen, Hinweisen, Fehlern oder kommerziellen Beratungs/Installations/Anpassungsbedarf bitte melden.

Diese Dokumentation ist lizenziert unter CC BY-SA 4.0. Der Quellcode unter GNU General Public License v3 or later.

2 Zielsetzung

Für die Sicherung aller unserer Geräte und um im Notfall eine idente, freie Austauschhardware für meinen Web/Emailserver zu haben habe ich einen Backupserver mit derselben Olimex A20 Hardware (freie Konstruktion) eingerichtet. Dieser kleine, sehr stromsparende ARM basierte SoC Server sichert automatisch laufend diverse Server und Laptops, PC's und Handys über das Netzwerk (Wlan, Lan, Internet) auf 2 externe verschlüsselte USB Festplatten. Eine davon ist immer extern physikalisch getrennt verwahrt, während auf die andere laufend vom Server gesichert wird. Wöchentlich werden die Platten getauscht. Auf den zu sichernden Geräten sollte einzig rsync und ssh mit passwortlosem Login notwendig sein. Das Backup sollte zentral vom Server ausgelöst, gesteuert und überwacht werden. Das erleichtert die Administration.

3 Hardware

3.1 Olimex A20-OlinuXino-MICRO wie Webserver, mit folgenden Unterschieden:

  • blaues Gehäuse statt schwarz (zwecks Unterscheidbarkeit)
  • kleinere 250mA Batterie, die 1400mAh des Webservers sind für eine USV überdimensioniert.
  • T2 Prozessor statt dem A20. T hat einen größeren Temperaturbereich (Industrieausführung), ansonsten ident. T hat es zum Zeitpunkt als ich den Webserver kaufte noch nicht gegeben.

3.2 3 x 2,5'' externe USB Platten 4T

Die externen Festplatten werden am oberen USB Port 1, der 1000 mA liefert, angeschlossen. Der untere Port 2 liefert nur 523mA und ist daher zu schwach.

UAS funktioniert mit den Seagate Platten und dem Linux Kernel nicht zuverlässig und musste deaktiviert werden:

$ cat /etc/modprobe.d/blacklist_uas.conf
options usb-storage quirks=0bc2:ab28:u

Im Olimex Kernel 5.10.180-olimex ist usb-storage nicht als Modul kompiliert, sonder statisch. Daher muss UAS mit Linux-Boot Kommandozeilenparametern deaktiviert werden. Dazu mit einem seriellen Kabel und screen Terminal booten

$ screen /dev/ttyUSB0 115200

und eine beliebige Taste drücken und in der u-boot Kommandozeile das einstellen:

$ setenv bootargs 'usb-storage.quirks=0bc2:ab28:u,059f:1093:u,1058:2621:u,0bc2:203a:u'
$ saveenv                                                                             

Im gestarteten Linux kann überprüft werden ob es funktioniert hat mit:

$ cat /proc/cmdline                                                                   

Die WD Platten funktionieren mit uas.

Die Platten sind mit dm-crypt/luks verschlüsselt und mit ext4 Dateisystem formatiert. Es ist wichtig die Verschlüsselung und Formatierung auf dem A20 und nicht auf einem Laptop oder anderem Gerät mit mehr RAM zu machen, weil cryptsetup die RAM Größe bei luksFormat ermittelt und dann auch wieder für die Verschlüsselung braucht. Es kann also passieren, dass ein Gerät das mit viel RAM verschlüsselt wurde auf einem Gerät mit wenig Ram nicht mehr entschlüsselbar ist. siehe Zwecks Unterscheidbarkeit haben die Festplatten unterschiedliche Gehäusefarben, das Kaufdatum / Garantiezeit und Händler wird darauf notiert.

Zuerst wurden 2 Platten verwendet, aber später auf 3 geändert, da bei Defekten einer Platte eine neue mit den Daten kopiert werden muss und das Tage dauern kann, währenddessen kein Backup möglich ist. Das Schreiben einer kompletten 4 TB HDD dauert ca. 1,5 Tage. Das Datei basierte kopieren einer verschlüsselten 4 TB Platte dauert bis zu 3 Tage, da sie zuerst auch mit /dev/zero überschrieben werden muss und dann die Daten kopiert.

cryptsetup luksFormat /dev/sdX
cryptsetup luksOpen /dev/sdX encrypted-external-drive
dd if=/dev/zero of=/dev/mapper/encrypted-external-drive bs=64K status=progress
mkfs.ext4 /dev/mapper/encrypted-external-drive
mount /dev/mapper/encrypted-external-drive /tmp/neue_hdd
rsync --info=progress2 -a /tmp/alte_hdd/rsnapshot /tmp/neue_hdd/

Vermutlich ist ein bitweises Kopieren mit dd und ändern der UUID's besser.

Mit 3 Platten gibt es keine Unterbrechungen des Backups wenn eine Platte defekt wird. Die Seagate Consumer Platten haben beide leider nicht lange gehalten und auch die Lacie Rugged Platte war innerhalb der Garantiezeit von 3 Jahren defekt. Auch eine WD Elements SE Platte war nach einem Sturz auf den Boden defekt. Um Komplettausfälle aufgrund von Serienfehlern zu vermeiden ist es ratsam verschiedenste Hersteller / Typen für die Platten zu verwenden und auf eine lange Garantiezeit zu achten.

3.3 interne SATA Platte

Ursprünglich 1 x vorhandene interne SATA HDD 320 GB Samsung HM329JI.

Im Jänner 2021 wurde eine neue SSD (SSD-WD-WDS120G2G0A Green SSD WDS120G2G0A, 120GB SSD, intern, 2.5" (6.4 cm), SATA 6Gb/s) eingebaut, und das Betriebssystem darauf neu installiert, da die microSD Karte defekt wurde.

4 Software

4.1 Betriebssystem

Installiert wurde wie auf dem Webserver Standard Debian Stretch, allerdings mit unverschlüsselter Root Partition ursprünglich auf der SD/MMC1 microSD Karte und dem von Olimex mit deren Image mitgelieferten u-boot. Von dem größeren SD/MMC2 SD Karten Schacht ist leider kein Betriebssystemstart möglich. (von Olimex dokumentiert)

Leider ist diese microSD Karte im Jänner 2021 defekt geworden, sodass eine Neuinstallation auf einer neuen SATA SSD notwendig war. u-boot blieb auf einer neuen microSD Karte. Es wurde wieder Debian Oldstable (Stretch) installiert, weil unter Buster cryptsetup mit aes-xts-plain64 cipher für die externen Platten aufgrund eines Bugs nicht funktionierte.

Diverse Absicherungen des Servers wurden konfiguriert / installiert.

Um ein versehentliches Schreiben auf das nicht eingehängte Mount-Verzeichnis der externen Platte und damit ein Vollschreiben der Root Partition zu vermeiden, wurde das Verzeichnis mit chattr schreibgeschützt.

Zur Erhöhung der Sicherheit und um Strom zu sparen werden die externen USB Platten vor dem Backup eingeschaltet und nach dem Backup wieder komplett abgeschaltet:

$ cat /usr/local/bin/mount-exthdd
#!/bin/bash
# exit 2
source getbkpdev
source lockwait

timeout=30 # seconds to wait for the device appearing
lockfile=/var/lock/mount-exthdd.lock

lockwait $lockfile 10 60

trap "rm -f ${lockfile}; exit" INT TERM EXIT
echo $$ > ${lockfile}

if mount | grep "/dev/mapper/exthdd_crypt" > /dev/null; then
    exit 0;
fi

if ! get_dev >/dev/null ; then
    # probably before powered off usb device, reload kernel module to turn it on again
    rmmod ehci_platform
    modprobe ehci_platform

    # wait for the device to appear
    i=0
    until get_dev > /dev/null ; do
	sleep 10
	let "i++"
	if [ $i -eq $timeout ]; then
	    echo "timout searching for the device waited times: $i"
	    exit 1
	fi
	# echo "waiting... nr $i"
    done
fi

if ! get_dev > /dev/null; then
    echo "unable to find backup device" 1>&2
    exit 1
fi

cryptsetup luksOpen $(get_dev) exthdd_crypt --key-file=xxxx

if [ $? ]; then
  mount /dev/mapper/exthdd_crypt /mnt/exthdd
fi
$ cat /usr/local/bin/lockwait

function dbg {
    # logger -id "$srcname.lockwait" -p syslog.debug "$1"
    # echo $1
    }

function lockwait () {
    lockfile=$1
    sleeptime=$2
    timeout=$3
    scrname=$(echo $0 | awk -F "/" '{print $NF}')

    if [ -f $lockfile ]; then
	PID=$(cat $lockfile)
	dbg "lockfile $lockfile : andere PID=$PID eigene=$$"
	if [ $PID -ne $$ ]; then
	    if kill -0 $PID; then
		t="waiting for '$lockfile' pid: $PID"
		logger -id $scrname -p syslog.info "$t"
		dbg "$t"
		i=0
		while ps -p $PID >/dev/null 2>&1
		do
		    dbg "sleep $sleeptime nr $i"
		    sleep $sleeptime
		    let "i++"
		    if [ $i -eq $timeout ]; then
			t="timeout waiting for '$lockfile' pid: $PID"
			logger -id $scrname -p syslog.err $t
			dbg "$t"
			exit 1
		    fi
		done
		dbg "$PID nicht mehr da"
	    fi
	fi
    else
	dbg "lockfile $lockfile nicht vorhanden"
    fi
}
$ cat /usr/local/bin/getbkpdev
#!/bin/bash

dev_schwarz=/dev/disk/by-uuid/xxxxxxx-xxxx-xxx-xxxx-xxxxxx
dev_silber=/dev/disk/by-uuid/xxxxxxx-xxxx-xxx-xxxx-xxxxxx

get_dev () {
    if test -b $dev_schwarz; then
	echo $dev_schwarz
	return 0
    elif test -b $dev_silber; then
	echo $dev_silber
	return 0
    else
	return 1
    fi
}
$ cat /usr/local/bin/umount-exthdd
#!/bin/bash

source getbkpdev
timeout=10 # seconds to wait for the device powering off

umount /mnt/exthdd && cryptsetup luksClose /dev/mapper/exthdd_crypt

if [ $? -eq 0 ] && get_dev > /dev/null; then
  i=0
  until udisksctl power-off -b $(get_dev) || [ $i -eq $timeout ]; do
    sleep 1
    i=$(( i++ ))
  done
fi

4.1.1 Instabilitäten

  1. Watchdog

    Wegen seltenen plötzlichen Stillständen mit Schmierzeichen in /var/log/syslog wurde der Hardware Watchdog konfiguriert:

    $ apt install watchdog 
    
    $ grep -i watchdog /etc/systemd/system.conf                           
    RuntimeWatchdogSec=10                                                                       
    #RebootWatchdogSec=10min                                                                    
    ShutdownWatchdogSec=10min                                                                   
    #KExecWatchdogSec=0                                                                         
    #WatchdogDevice=  
    
    $ reboot now
    

    Watchdog testen mit künstlich ausgelösten Kernel Panic:

    echo c > /proc/sysrq-trigger
    
  2. CPU Frequenz Governor

    Diese Stillstände sind auch am Webserver aufgetreten und auch mit Tausch der Hardware nicht verschwunden. Es liegt die Vermutung nahe, dass es am Kernel liegt:

    $ uname -r
    $ 5.10.180-olimex
    

    Die Vermutung im Olimex Forum, dass es an der CPU Frequenz Steuerung liegt kann ich nicht bestätigen, da es auch nach der Umstellung auf Performance Governor (siehe unten) zu Stillständen kam. Olimex Forums Einträge zu dem Thema

    Der Standard Governor scheint problematisch zu sein, und der Performance Governor verursacht kaum relevanten Strommehrverbrauch: https://linux-sunxi.org/Cpufreq

    Daher wurde am 28.1.2024 das umgestellt:

    $ cat /etc/default/cpufrequtils 
    # valid values: userspace conservative powersave ondemand performance
    # get them from cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors 
    GOVERNOR="performance"
    

4.2 Backup Software

Für die inkrementelle Sicherung täglich, wöchentlich, monatlich wird rsnapshot verwendet. BorgBackup wird nicht verwendet weil es nicht in Dateien, sondern in ein eigenes Container Format sichert. Allerdings ist für mich die Einfachheit, Solidität und direkter, langlebiger, zukunftssicherer Dateizugriff eine zentrales Kriterium eines Backups. Die Deduplizierung auf Byteebene die BorgBackup im Gegensatz zu rsnapshot bietet, ist bei unseren Daten nicht entscheidend relevant. Darüberhinaus bin ich der Meinung, dass die Deduplizierung eine separate Aufgabe des Dateisystems oder einer dedizierten Schicht zwischen Dateisystem und Anwendung ist. Leider sind die Programme dazu (btrfs, zfs etc.) für GNU/Linux nicht vorhanden bzw. noch nicht ausgereift.

Für rdiff-backup gilt Ähnliches (zumindest für die Inkrements), darüber hinaus scheint es derzeit nicht wirklich aktiv gepflegt/genutzt zu werden. rsnapshot ist im Aufbau recht einfach und setzt über Jahrzehnte bewährte, simple Unix Hausmittel wie Hardlinks, cp, rsync, mv, touch etc. ein.

Für die Auslösung, Steuerung und Überwachung der Sicherung werden cron, anacron und selbst erstellte Bash Skripte verwendet. Systemd wird abseits des Debian Standards nicht verwendet, da die Architektur nicht der Unix Philosophie entspricht.

Server die dauernd eingeschaltet sind und Endgeräte die nur sporadisch im Netz verfügbar sind benötigen eine separate Konfiguration:

4.2.1 Nicht andauernd eingeschaltete Geräte

Über cron wird regelmäßig geprüft ob das Gerät im Netz ist und nicht im Batteriebetrieb läuft (ansonsten leert das Backup die Batterie) und ob dieses an diesem Tag noch nicht gesichert wurde:

$ cat /etc/cron.d/backup_check_hosts
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# m h dom mon dow user  command
*/2 * * * *       root    /usr/local/bin/backup_check_hosts
$ cat /usr/local/bin/backup_check_hosts
#!/bin/bash

hosts="host1 host2 host3"
lockfile=/var/run/rsnapshot.pid
debug=0

dbg () {
    if [ ! $debug == "0" ]; then
       echo $1
    fi
}

if [ -f $lockfile ]; then
    if kill -0 $(cat $lockfile); then
	dbg "rsnapshot already running. $lockfile: PID $(cat $lockfile)"
	exit 0
    fi
fi

for host in $hosts; do
    if nc -z -w 1 $host 22 &> /dev/null; then
	if [ $debug == "0" ]; then
	    ssh $host /usr/bin/on_ac_power 2> /dev/null
	else
	    ssh $host /usr/bin/on_ac_power
	fi
	if [ $? -ne 1 ]; then
	    dbg "run anacron for $host"
	    anacron -q -s -d -n -t /etc/anacrontab-bkp $host.*
	else
	    dbg "$host not on ac power"
	fi
    fi
done

Anacron wird verwendet um die Intervalle der Sicherungen zu steuern. Für die Steuerung der Debian Standard /etc/cron.* jobs wurde cron statt anacron konfiguriert, da ja der Backupserver selbst kein anacron benötigt, da er immer läuft. Im Debian-Standard wird dafür anacron verwendet sobald es installiert ist.

$ cat /etc/anacrontab-bkp
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
HOME=/root
LOGNAME=root

# days          delay   job             command
1               10      host1.daily   backup host1 daily
7               5       host1.weekly  backup host1 weekly
@monthly        0       host1.monthly backup host1 monthly

1               10      host3.daily    backup host3 daily
7               5       host3.weekly   backup host3 weekly
@monthly        0       host3.monthly  backup host3 monthly

1               10      host2.daily       backup host2 daily
7               5       host2.weekly      backup host2 weekly
@monthly        0       host2.monthly     backup host2 monthly
$ cat /usr/local/bin/backup
#!/bin/bash

source lockwait

lockfile=/var/run/rsnapshot.pid

lockwait $lockfile 300 40

/usr/local/bin/mount-exthdd && /usr/bin/rsnapshot -c /etc/rsnapshot-$1.conf $2
/usr/local/bin/umount-exthdd

Da die Endgeräte üblicherweise tagsüber gesichert werden, wird um den sonstigen Netzwerkverkehr nicht zu beeinträchtigen für rsync ein Bandbreitenlimit abhängig von der typischen Netzwerkverbindung des Gerätes (WLAN, LAN, WAN) gesetzt. Beispiel einer rechnerspezifischen rsnapshot Konfiguration:

$ cat /etc/rsnapshot-host1.conf
include_conf    /etc/rsnapshot.conf
snapshot_root   /mnt/exthdd/rsnapshot/host1
exclude         /media
exclude         /mnt
backup          host1:/       ./      +rsync_long_args=--bwlimit=4166K

4.2.2 Server

Das Backup der Server wird über eine typische, übliche rsnapshot Konfiguration mit cron gesteuert.

$ cat /etc/cron.d/rsnapshot
# This is a sample cron file for rsnapshot.
# The values used correspond to the examples in /etc/rsnapshot.conf.
# There you can also set the backup points and many other things.
#
# To activate this cron file you have to uncomment the lines below.
# Feel free to adapt it to your needs.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

#m  h dom mon dow       user    command
# 0 */4         * * *           root    /usr/bin/rsnapshot alpha
30 2    * * *           root    /usr/local/bin/backup server daily
0  2    * * 1           root    /usr/local/bin/backup server weekly
30 1    1 * *           root    /usr/local/bin/backup server monthly

4.2.3 Überwachung

Um zu überpüfen ob und welche Dateien tatsächlich gesichert wurden, ruft ein separater Cron Job wöchentlich find auf um die Dateien zu finden, deren Änderungszeit jünger als 24 h ist. Das Ergebnis wird gemailt:

$ cat /etc/cron.weekly/mailbackupdateien
#!/bin/sh

/usr/local/bin/mount-exthdd && find /mnt/exthdd/rsnapshot -path '*/daily.0/*' -mtime -1 | /usr/bin/mail -s "Backup Dateien exthdd - 1 Tag alt xy" i@example.com; /usr/local/bin/umount-exthdd

Täglich werden einzelne Dateien geprüft die sich jeden Tag ändern und daher im Backup vorhanden sein müssen:

$ cat /etc/cron.d/check_backups
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

#m  h dom mon dow   	user	command
0 8 * * *       root    /usr/local/bin/mount-exthdd && /usr/local/bin/check_backups ; /usr/local/bin/umount-exthdd
cat /usr/local/bin/check_backups
#!/bin/bash

BASE=/mnt/exthdd/rsnapshot
for SERVER in s1 s2 s3 s4
do
  check_backup $BASE/server/daily.0/$SERVER/var/log/syslog
done

for CLIENT in c1 c2
do
    check_backup $BASE/$CLIENT/daily.0/var/log/syslog
done
check_backup $BASE/x/daily.0/var/lib/sqlite3/xjournal
cat /usr/local/bin/check_backup
#!/usr/bin/python3
import os
import time
import sys

MAX_AGE = 24 # hours

def file_age(path:str) -> int: # hours
    return (time.time() - os.path.getmtime(path)) / 3600

if __name__ == '__main__':
    try:
	path = sys.argv[1]
    except IndexError:
	sys.stderr.write("Bitte den Pfad der zu prüfenden Datei als ersten Parameter angeben.\n")
	sys.exit(2)
    age = file_age(path)
    if age > MAX_AGE:
	sys.stderr.write("Datei '%s' ist %.2f h alt. Das ist mehr als das erlaubte Maximum von %sh.\n" % (
	    path, age, MAX_AGE))
	sys.exit(3)