Files
dogfather-universe/server-internal/sicherung.sh
T
DogFatherGitandClaude Opus 5 75ab3f713f Sicherung: Kundendaten nicht mehr fuer jedes Konto lesbar
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]>
2026-08-26 00:34:05 +02:00

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