Der erste echte Lauf hat einen Mangel sichtbar gemacht, den das Skript selbst verursacht hat: root legt Dateien standardmaessig mit 644 an, also welt-lesbar. Nachgemessen als gewoehnliches Konto, ohne sudo: Die Sicherung liess sich nach /tmp kopieren und daraus Kundennamen, E-Mail-Adressen und Paketwahl auslesen; das Upload-Archiv ebenso. Auf dieser Maschine bestehen fuenf Konten. Eine Sicherung buendelt an einer Stelle, was sonst verstreut liegt -- sie muss enger geschuetzt sein als das Original, nicht lockerer. umask 077 fuer alles Neue; fuer die bereits angelegten Verzeichnisse zusaetzlich ausdruecklich 700 bzw. 600, denn umask wirkt nur auf neu Erzeugtes. Der gleiche Mangel besteht beim Original selbst (644 dogiintern) -- das kann ich nicht aendern, es gehoert nicht mir. Wird gemeldet. Co-Authored-By: Claude Opus 5 <[email protected]>
149 lines
5.8 KiB
Bash
Executable File
149 lines
5.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# =====================================================================
|
|
# Taegliche Sicherung des internen Bereichs.
|
|
#
|
|
# WARUM NICHT EINFACH "cp"
|
|
#
|
|
# SQLite schreibt im WAL-Betrieb nicht direkt in die .db-Datei, sondern
|
|
# zuerst in eine Begleitdatei (.db-wal). Auf diesem Server ist dieses
|
|
# WAL 4,1 MB gross -- also groesser als die Datenbank selbst mit
|
|
# 778 KB. Eine Kopie der .db allein waere damit nicht bloss veraltet,
|
|
# sondern unvollstaendig: Alles seit dem letzten Zusammenfuehren fehlt.
|
|
#
|
|
# Kopiert man beide Dateien nacheinander, waehrend geschrieben wird,
|
|
# passen sie ausserdem womoeglich nicht zusammen -- das Ergebnis ist
|
|
# eine Datei, die erst beim Wiederherstellen als kaputt auffaellt. Und
|
|
# das ist der schlechteste Zeitpunkt.
|
|
#
|
|
# ".backup" macht es richtig: Der Befehl kennt das WAL, sperrt sauber
|
|
# und liefert einen in sich stimmigen Stand, auch waehrend der Dienst
|
|
# weiterlaeuft.
|
|
#
|
|
# WAS GESICHERT WIRD
|
|
# - die Datenbank (Kunden, Projekte, Zahlungen, Widerrufsnachweise)
|
|
# - die hochgeladenen Dateien (rund 16 MB)
|
|
#
|
|
# Beides gehoert zusammen: Eine Datenbank, die auf Dateien verweist, die
|
|
# es nicht mehr gibt, ist nur die halbe Wiederherstellung.
|
|
# =====================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
# ---------------------------------------------------------------------
|
|
# WER DARF DIE SICHERUNG LESEN
|
|
#
|
|
# Hier liegen Namen, E-Mail-Adressen, Zahlungen und Widerrufsnachweise
|
|
# von Kunden -- an einem Stueck und bequem kopierbar.
|
|
#
|
|
# Ohne diese Zeile legt root neue Dateien mit 644 an, also fuer jeden
|
|
# Systembenutzer lesbar. Auf dieser Maschine sind das fuenf Konten. Das
|
|
# wurde nachgemessen: Ein gewoehnliches Konto konnte die Sicherung nach
|
|
# /tmp kopieren und die Kundendaten auslesen -- ohne sudo, ohne Huerde.
|
|
#
|
|
# Eine Sicherung buendelt alles, was sonst verstreut liegt. Sie gehoert
|
|
# damit zu den lohnendsten Zielen ueberhaupt und muss enger geschuetzt
|
|
# sein als das Original, nicht lockerer.
|
|
# ---------------------------------------------------------------------
|
|
umask 077
|
|
|
|
QUELLE_DB="/var/lib/dogfather-internal/dogfather-internal.db"
|
|
QUELLE_UPLOADS="/var/lib/dogfather-internal/uploads"
|
|
ZIEL="/var/backups/dogfather"
|
|
PROTOKOLL="$ZIEL/sicherung.log"
|
|
|
|
# Wie viele Staende aufbewahrt werden.
|
|
TAEGLICH=14
|
|
WOECHENTLICH=8
|
|
|
|
melde() {
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$PROTOKOLL"
|
|
echo "$*"
|
|
}
|
|
|
|
fehler() {
|
|
melde "FEHLER: $*"
|
|
exit 1
|
|
}
|
|
|
|
mkdir -p "$ZIEL/taeglich" "$ZIEL/woechentlich" || fehler "Zielverzeichnis nicht anlegbar"
|
|
|
|
# umask wirkt nur auf NEU angelegte Dateien. Verzeichnisse, die schon
|
|
# bestehen, behalten ihre alten Rechte -- deshalb hier ausdruecklich.
|
|
chmod 700 "$ZIEL" "$ZIEL/taeglich" "$ZIEL/woechentlich" 2>/dev/null
|
|
find "$ZIEL" -type f -exec chmod 600 {} + 2>/dev/null
|
|
find "$ZIEL" -mindepth 1 -type d -exec chmod 700 {} + 2>/dev/null
|
|
|
|
STEMPEL=$(date '+%Y-%m-%d')
|
|
ARBEIT="$ZIEL/taeglich/$STEMPEL"
|
|
mkdir -p "$ARBEIT"
|
|
|
|
# ---------------------------------------------------------------------
|
|
# 1. Datenbank
|
|
# ---------------------------------------------------------------------
|
|
[ -f "$QUELLE_DB" ] || fehler "Datenbank nicht gefunden: $QUELLE_DB"
|
|
|
|
sqlite3 "$QUELLE_DB" ".backup '$ARBEIT/dogfather-internal.db'" \
|
|
|| fehler "Sicherung der Datenbank fehlgeschlagen"
|
|
|
|
# Eine Sicherung, die niemand geprueft hat, ist keine Sicherung.
|
|
# Der Test kostet Sekunden und beantwortet die einzige Frage, auf die es
|
|
# im Ernstfall ankommt: Laesst sich die Datei ueberhaupt oeffnen?
|
|
PRUEFUNG=$(sqlite3 "$ARBEIT/dogfather-internal.db" "PRAGMA integrity_check;" 2>&1)
|
|
[ "$PRUEFUNG" = "ok" ] || fehler "Sicherung ist beschaedigt: $PRUEFUNG"
|
|
|
|
# Gegenprobe auf Inhalt: Eine formal heile, aber leere Datei waere der
|
|
# stillste aller Datenverluste.
|
|
TABELLEN=$(sqlite3 "$ARBEIT/dogfather-internal.db" \
|
|
"SELECT count(*) FROM sqlite_master WHERE type='table';" 2>/dev/null)
|
|
[ "${TABELLEN:-0}" -gt 5 ] || fehler "Sicherung enthaelt nur $TABELLEN Tabellen -- zu wenig"
|
|
|
|
# ---------------------------------------------------------------------
|
|
# 2. Hochgeladene Dateien
|
|
# ---------------------------------------------------------------------
|
|
if [ -d "$QUELLE_UPLOADS" ]; then
|
|
tar -czf "$ARBEIT/uploads.tar.gz" -C "$(dirname "$QUELLE_UPLOADS")" \
|
|
"$(basename "$QUELLE_UPLOADS")" 2>/dev/null \
|
|
|| fehler "Sicherung der Dateien fehlgeschlagen"
|
|
tar -tzf "$ARBEIT/uploads.tar.gz" > /dev/null 2>&1 \
|
|
|| fehler "Dateisicherung ist nicht lesbar"
|
|
else
|
|
melde "Hinweis: kein Upload-Verzeichnis vorhanden"
|
|
fi
|
|
|
|
GROESSE=$(du -sh "$ARBEIT" | cut -f1)
|
|
melde "Sicherung $STEMPEL angelegt ($GROESSE, $TABELLEN Tabellen)"
|
|
|
|
# ---------------------------------------------------------------------
|
|
# 3. Wochenstand
|
|
#
|
|
# Sonntags wandert eine Ausfertigung in die laengere Aufbewahrung. Der
|
|
# Grund ist nicht Platz, sondern Zeit: Ein Fehler, der eine Datenbank
|
|
# still verdirbt, faellt manchmal erst nach Wochen auf -- und dann sind
|
|
# vierzehn taegliche Staende alle schon vom selben Fehler betroffen.
|
|
# ---------------------------------------------------------------------
|
|
if [ "$(date '+%u')" = "7" ]; then
|
|
cp -r "$ARBEIT" "$ZIEL/woechentlich/$STEMPEL" 2>/dev/null \
|
|
&& melde "Wochenstand $STEMPEL abgelegt"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------
|
|
# 4. Alte Staende entfernen
|
|
# ---------------------------------------------------------------------
|
|
entferne_alte() {
|
|
local ordner="$1" behalten="$2"
|
|
local anzahl
|
|
anzahl=$(find "$ordner" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
|
if [ "$anzahl" -gt "$behalten" ]; then
|
|
find "$ordner" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' \
|
|
| sort | head -n "$((anzahl - behalten))" \
|
|
| while read -r alt; do
|
|
rm -rf "${ordner:?}/$alt" && melde "alten Stand entfernt: $alt"
|
|
done
|
|
fi
|
|
}
|
|
entferne_alte "$ZIEL/taeglich" "$TAEGLICH"
|
|
entferne_alte "$ZIEL/woechentlich" "$WOECHENTLICH"
|
|
|
|
melde "fertig"
|
|
exit 0
|