GZip, Cache-Dauer fuer statische Dateien, Sicherungsskript
GZip: Die Startseite ging unkomprimiert raus - 9725 Byte, jetzt 1900. Zuletzt angemeldet heisst zuerst ausgefuehrt, die Komprimierung liegt damit aussen. Statische Dateien: StaticFiles kennt keine Cache-Dauer, per Unterklasse nachgeruestet. Bewusst nur eine Stunde - die Dateien tragen keinen Versionsanhaenger, eine lange Dauer wuerde nach einem Update die alte Fassung festhalten. (Die hochgeladenen Bilder hatten ihre Header laengst; der Plan behauptete faelschlich das Gegenteil, korrigiert.) Sicherung: sicherung.sh sichert Datenbank UND Bilder - eine Datenbank ohne die Fotos ist wertlos. Pfade werden aus der Anwendung gelesen statt geraten. Die Pruefung oeffnet die Kopie, nicht das Original. Erprobt: 4 Eintraege, 4 Bilder, vollstaendig; ein entferntes Bild wird erkannt. 81 Tests weiterhin gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
This commit is contained in:
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/sh
|
||||
# Sichert Datenbank UND Bilder der Kleiderboerse.
|
||||
#
|
||||
# Beides gehoert zusammen: eine Datenbank ohne die zugehoerigen Fotos ist
|
||||
# wertlos, und Fotos ohne Datenbank sind ein Haufen Dateien mit zufaelligen
|
||||
# Namen. Anders als bei der Kantone-App laesst sich hier nichts neu
|
||||
# importieren - was weg ist, ist weg.
|
||||
#
|
||||
# Warum nicht einfach cp: SQLite schreibt nicht atomar. Eine Kopie mitten im
|
||||
# Schreibvorgang kann unbrauchbar sein, und das faellt erst auf, wenn man sie
|
||||
# braucht. "VACUUM INTO" liefert auch bei laufendem Betrieb einen
|
||||
# konsistenten Stand.
|
||||
#
|
||||
# Aufruf (Container muss laufen):
|
||||
# ./sicherung.sh [zielordner]
|
||||
#
|
||||
# Taeglich um 03:00 per cron:
|
||||
# 0 3 * * * /pfad/zu/sicherung.sh /pfad/zu/sicherungen >> /var/log/kleiderboerse-sicherung.log 2>&1
|
||||
|
||||
set -eu
|
||||
|
||||
CONTAINER="${KLEIDER_CONTAINER:-kleiderboerse-app-1}"
|
||||
ZIEL="${1:-./sicherungen}"
|
||||
BEHALTEN="${BEHALTEN:-7}"
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
|
||||
echo "FEHLER: Container '$CONTAINER' laeuft nicht." >&2
|
||||
echo "Anderer Name? Ueber KLEIDER_CONTAINER setzen." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$ZIEL"
|
||||
STAND="$(date +%Y%m%d-%H%M%S)"
|
||||
ORDNER="$ZIEL/kleiderboerse-$STAND"
|
||||
mkdir -p "$ORDNER"
|
||||
|
||||
# Erzeugen und pruefen laufen im Container: der Host braucht nur Docker.
|
||||
# Die Pruefung oeffnet bewusst die KOPIE, nicht das Original - nur dann sagt
|
||||
# die Zahl etwas ueber die Sicherung aus.
|
||||
ANZAHL="$(docker exec "$CONTAINER" python -c '
|
||||
import os, sqlite3
|
||||
from app.config import einstellungen
|
||||
# Nicht raten: die Anwendung kennt ihre Pfade, und sie koennen ueber
|
||||
# DATABASE_PATH / UPLOAD_PATH abweichen.
|
||||
quelle = str(einstellungen().datenbank_pfad)
|
||||
ziel = "/tmp/sicherung.sqlite"
|
||||
if os.path.exists(ziel):
|
||||
os.unlink(ziel)
|
||||
con = sqlite3.connect(quelle)
|
||||
con.execute("VACUUM INTO ?", (ziel,))
|
||||
con.close()
|
||||
kopie = sqlite3.connect(ziel)
|
||||
print(kopie.execute("SELECT COUNT(*) FROM items").fetchone()[0])
|
||||
kopie.close()
|
||||
' 2>/dev/null)"
|
||||
|
||||
if ! printf '%s' "${ANZAHL:-}" | grep -qE '^[0-9]+$'; then
|
||||
echo "FEHLER: Sicherung der Datenbank fehlgeschlagen (keine Zahl zurueck)." >&2
|
||||
rmdir "$ORDNER" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker cp "$CONTAINER:/tmp/sicherung.sqlite" "$ORDNER/kleiderboerse.sqlite"
|
||||
docker exec "$CONTAINER" rm -f /tmp/sicherung.sqlite
|
||||
|
||||
# Die Bilder dazu. Ohne sie zeigt die wiederhergestellte Galerie nur Luecken.
|
||||
UPLOADS="$(docker exec "$CONTAINER" python -c '
|
||||
from app.config import einstellungen
|
||||
print(einstellungen().upload_pfad)
|
||||
')"
|
||||
docker cp "$CONTAINER:$UPLOADS" "$ORDNER/uploads" 2>/dev/null || mkdir -p "$ORDNER/uploads"
|
||||
BILDER="$(find "$ORDNER/uploads" -name '*.webp' | wc -l)"
|
||||
|
||||
echo "$(date '+%d.%m.%Y %H:%M') $ORDNER ($ANZAHL Eintraege, $BILDER Bilder, $(du -sh "$ORDNER" | cut -f1))"
|
||||
|
||||
# Warnen, wenn Eintraege da sind, aber keine Bilder - dann stimmt am Pfad
|
||||
# etwas nicht und die Sicherung waere halb.
|
||||
if [ "$ANZAHL" -gt 0 ] && [ "$BILDER" -eq 0 ]; then
|
||||
echo " WARNUNG: $ANZAHL Eintraege, aber kein einziges Bild gesichert. Upload-Pfad pruefen." >&2
|
||||
fi
|
||||
|
||||
# Alte Staende entfernen, neueste zuerst behalten.
|
||||
ls -1dt "$ZIEL"/kleiderboerse-*/ 2>/dev/null | tail -n "+$((BEHALTEN + 1))" | while read -r alt; do
|
||||
echo " entferne alten Stand: $(basename "$alt")"
|
||||
rm -rf "$alt"
|
||||
done
|
||||
Reference in New Issue
Block a user