Automatisches indi-allsky Backup auf externem Laufwerk (Fritzbox NAS)

In diesem Beitrag beschreibe ich, wie ich meine indi-allsky Installation auf meinem Raspberry Pi vollautomatisiert auf einer externen Festplatte sichere.

Die offizielle indi-allsky-Dokumentation beschreibt sehr gut, was gesichert werden sollte – lässt aber offen, wie man daraus ein belastbares, automatisiertes Backup macht.

In meinem Setup läuft indi-allsky dauerhaft auf einem Raspberry Pi. Die Sicherung erfolgt nicht lokal, sondern auf einer externen SSD an einer Fritzbox, angebunden per SMB.

*** Update – meine neue Backup-Strategie findest du hier! ***

Ziel war es, die Empfehlungen aus der offiziellen Dokumentation: Backup and Recovery – indi-allsky konsequent umzusetzen und produktionsreif zu erweitern:

  • externes Ziel
  • automatische Backups
  • Integritätsprüfungen
  • zeitbasierte Retention
  • Mail-Benachrichtigung bei Fehlern

…und so bin ich vorgegangen!

Was laut offizieller indi-allsky-Doku gesichert werden muss

Basierend auf der offiziellen Dokumentation sind relevant:

  • SQLite-Datenbank – /var/lib/indi-allsky/indi-allsky.sqlite
  • Flask-Konfiguration – /etc/indi-allsky/flask.json
  • Service-Environment (optional) – /etc/indi-allsky/indi-allsky.env
  • Database Migrations – /var/lib/indi-allsky/migrations/versions/

Videos, Fotos & Thumbnails sind zunächst nicht Teil dieser Anleitung. Hierzu folgt demnächst Phase 2!

Externes Laufwerk vorbereiten

Die SSD wird unter Linux mit ext4 formatiert:

sudo parted /dev/sda mklabel gpt mkpart BACKUP ext4 0% 100% quit sudo mkfs.ext4 -L backup_allsky /dev/sda1

Anschließend wird die SSD an die Fritzbox angeschlossen und über FRITZ!NAS (SMB) bereitgestellt. Wichtig: Es wird zwingend ein Nutzer benötigt, der Zugriff auf die NAS hat!

SMB-Mount auf dem Raspberry Pi

Mountpunkt anlegen:

sudo mkdir -p /mnt/backup_allsky

Benutzerdaten ablegen (Root-only) von dem Nutzer, der Zugriff auf die NAS hat:

sudo nano /root/.smbcredentials
username=DEIN_NUTZER
password=DEIN_PASSWORT

/etc/fstab-Eintrag hinterlegen – Achtung: Eine Zeile:

//192.168.178.1/FRITZ.NAS /mnt/backup_allsky cifs credentials=/root/.smbcredentials,vers=3.1.1,iocharset=utf8,noforceuid,noforcegid,nounix,soft,_netdev,noserverino 0 0

noserverino ist gegen „stale file handle“ bei Fritzbox bewusst gesetzt!
Mount testen:

sudo mount /mnt/backup_allsky mountpoint /mnt/backup_allsky

Ausgabe sollte sein „is mountpoint“!

Backup-Script für indi-allsky (vollständig)

Datei anlegen:

sudo nano /usr/local/bin/backup_allsky.sh

Vollständiges, produktives Script – dieses

  • führt ein Backup durch,
  • prüft dieses auf Konsistenz
  • und löscht alle Backups die älter sind als 6 Wochen.

Sollte es Fehler geben, geht eine Mail raus.

.sh
#!/bin/bash
set -euo pipefail

# ======================================================
# Configuration
# ======================================================
BACKUP_ROOT="/mnt/backup_allsky/backup_allsky"

DB_SRC="/var/lib/indi-allsky/indi-allsky.sqlite"
CONFIG_DIR="/etc/indi-allsky"
MIGRATIONS_DIR="/var/lib/indi-allsky/migrations/versions"

LOG="/var/log/backup_allsky.log"
MAIL_TO="mail@stefanwatzinger.de"
HOST="$(hostname)"

RETENTION_DAYS=42   # 6 weeks

# ======================================================
# Logging
# ======================================================
exec >>"$LOG" 2>&1

# ======================================================
# Error handling (MAIL ONLY ON FAILURE)
# ======================================================
error_handler() {
  RC=$?
  {
    echo "Allsky Phase-1 Backup FEHLGESCHLAGEN"
    echo "Host: $HOST"
    echo "Zeit: $(date)"
    echo "Exit-Code: $RC"
    echo
    echo "Letzte Logzeilen:"
    tail -n 50 "$LOG"
  } | mail -s "Allsky Backup FEHLER ($HOST)" "$MAIL_TO"
  exit $RC
}
trap error_handler ERR

# ======================================================
# Start
# ======================================================
echo "=== Backup Start: $(date) ==="

# ======================================================
# Defensive Re-Mount (prevents stale handles)
# ======================================================
umount /mnt/backup_allsky || true
mount /mnt/backup_allsky
mountpoint -q /mnt/backup_allsky

# ======================================================
# Prepare directories
# ======================================================
mkdir -p \
  "$BACKUP_ROOT/db" \
  "$BACKUP_ROOT/config" \
  "$BACKUP_ROOT/migrations"

# ======================================================
# Database backup (SQLite dump + gzip)
# ======================================================
echo "--- DB Backup ---"
DB_BACKUP="$BACKUP_ROOT/db/backup_indi-allsky_sqlite_$(date +%Y%m%d_%H%M%S).sql.gz"

sqlite3 "$DB_SRC" .dump \
| gzip -9 \
> "$DB_BACKUP"

echo "--- DB Integrity Check ---"
gunzip -t "$DB_BACKUP"

# ======================================================
# Config backup
# ======================================================
echo "--- Config Backup ---"
cp -v "$CONFIG_DIR/flask.json" "$BACKUP_ROOT/config/"

if [ -f "$CONFIG_DIR/indi-allsky.env" ]; then
  cp -v "$CONFIG_DIR/indi-allsky.env" "$BACKUP_ROOT/config/"
fi

echo "--- Config Integrity Check ---"
test -s "$BACKUP_ROOT/config/flask.json"

# ======================================================
# Database migrations backup
# ======================================================
echo "--- Migrations Backup ---"
MIG_BACKUP="$BACKUP_ROOT/migrations/backup_migrations_$(date +%Y%m%d_%H%M%S).tgz"

tar -C "$MIGRATIONS_DIR" -czf "$MIG_BACKUP" .

echo "--- Migrations Integrity Check ---"
tar -tzf "$MIG_BACKUP" > /dev/null

# ======================================================
# Retention cleanup (AFTER successful backup)
# ======================================================
echo "--- Retention Cleanup (> ${RETENTION_DAYS} days) ---"

find "$BACKUP_ROOT/db" \
  -type f -name "backup_indi-allsky_sqlite_*.sql.gz" \
  -mtime +"$RETENTION_DAYS" -print -delete

find "$BACKUP_ROOT/migrations" \
  -type f -name "backup_migrations_*.tgz" \
  -mtime +"$RETENTION_DAYS" -print -delete

# ======================================================
# Finish
# ======================================================
echo "=== Backup Ende: $(date) ==="

Rechte setzen:

sudo chmod 700 /usr/local/bin/backup_allsky.sh

Cronjob (täglich, tagsüber)

sudo crontab -e
0 10 * * * /usr/local/bin/backup_allsky.sh

Logrotation in /etc/logrotate.d/backup_allsky

/var/log/backup_allsky.log {
weekly
rotate 8
compress
missingok
notifempty
create 640 root adm
}

Check-Ups & Verifikation

Zur Sicherheit macht es Sinn, nach dem initialen Backup einmal die Konsistenz der Daten zu checken:

Datenbank:

gunzip -t backup_indi-allsky_sqlite_*.sql.gz

Migrations:

tar -tzf backup_migrations_*.tgz | head

Config:

python3 -m json.tool flask.json > /dev/null

Troubleshooting: Mailversand

Technisch basiert der Mailversand auf msmtp, das als leichtgewichtiger SMTP-Client auf dem Raspberry Pi installiert ist und sich als Ersatz für sendmail verhält.

Installation:

sudo apt install msmtp msmtp-mta bsd-mailx

msmtp wird so konfiguriert, dass es über den vorhandenen SMTP-Account des eigenen Mailproviders versendet. Die Konfiguration liegt systemweit in /etc/msmtprc und enthält Host, Port, Benutzer und Passwort des SMTP-Servers.

Im Backup-Script selbst wird keine eigene Mail-Logik implementiert, sondern ausschließlich mit:

set -euo pipefail
trap error_handler ERR

gearbeitet.

Das bedeutet: Jeder Fehler (Mount-Probleme, defekte Archive, fehlgeschlagene Integritätschecks)

  • führt zu einem sofortigen Abbruch des Scripts
  • triggert die error_handler-Funktion
  • und versendet eine konkrete Fehlermail mit Log-Auszug

Der Inhalt der Mail enthält:

  • Hostname
  • Zeitpunkt
  • Exit-Code
  • die letzten Logzeilen des Backup-Laufs

Erfolgreiche Backups erzeugen keine Mails!

Hat dir dieser Beitrag gefallen?

Du kannst allsky-rodgau.de mit einem kleinen Kaffee auf BuyMeACoffee unterstützen.

Jetzt Kaffee spendieren!