Filipe, mit Bildschirmfoto: "man muss die chats auch geloescht bekommen!!! am besten waere es auch wenn man da auch im chat pdfs schicken koennte. pdfs und fotos." Zwei Entscheidungen vorher abgestimmt, weil sie den Bau bestimmen: Geloescht wird NUR BEI MIR. Schicken duerfen ALLE im Gespraech, auch Creator -- anders als in der Dateiablage, wo etwas in einem Bereich landet, den mehrere sehen. Hier bekommt es genau der, mit dem man ohnehin gerade spricht. --- ANHAENGE --- Drei Wege zur selben Sache, weil Leute unterschiedlich arbeiten: die Bueroklammer, Hineinziehen und Einfuegen mit Strg+V (der Weg fuer einen Screenshot -- der liegt in der Zwischenablage und nirgends als Datei). Alle drei laufen durch dieselbe Funktion; drei Fassungen waeren drei Gelegenheiten, dass eine die Pruefung vergisst. Ein Foto wird GEZEIGT, ein PDF wird als Karte ANGEBOTEN. Das ist kein Schoenheitsunterschied: Ein Bild erkennt man in einer Zehntelsekunde, ein PDF muss man ohnehin oeffnen -- eine Vorschau davon waere ein grauer Kasten, der so tut, als koennte man etwas lesen. DIE SICHERHEIT STEHT IN dateiErkennen(). Dateiname und Content-Type kommen vom Absender und sind frei erfunden; der Typ wird deshalb aus den ersten Bytes bestimmt (PNG/GIF/WebP/JPEG/PDF, mit Breite und Hoehe im selben Durchgang). SVG steht absichtlich NICHT in der Liste -- das ist XML, das Skripte enthalten darf, ein als Bild getarntes Programm. Ausgeliefert wird spaeter genau der ERKANNTE Typ, nie der eingeschickte, dazu nosniff und "default-src 'none'; sandbox". Auf der Platte bekommt jede Datei einen erzeugten Zufallsnamen, in einem Ordner ausserhalb des Repos. Zuruecknehmen loescht die Datei wirklich -- sonst waere sie im Verlauf weg und ueber die Adresse noch da, also nur der Anschein einer Ruecknahme. --- WEGRAEUMEN --- Zwei Spalten in chat_teilnehmer statt einer, wegen eines Randfalls: Bei einem Gespraech ohne jede Nachricht waere die Grenze 0 -- und 0 heisst sonst "nie geloescht". geloescht_am unterscheidet die beiden. Die Grenze wirkt in der Liste, im Verlauf, in der Vorschau, in der SUCHE, bei den Anhaengen und in der Ungelesen-Zahl. Die Suche ist die Stelle, an der so etwas typischerweise durchrutscht: aus der Liste weg, ueber die Suche noch da. --- DER FUND: 323 PIXEL --- Ein Bild ist spaeter fertig als der Rest der Seite. Beim Oeffnen springt der Verlauf ans Ende, dann kommt das Foto an und schiebt alles darunter weg -- man steht ploetzlich mittendrin, ohne etwas angeklickt zu haben. Das width/height-Attribut am <img>, wie man es ueberall liest, hilft hier NICHT: Es gibt nur das Seitenverhaeltnis. Damit daraus eine Hoehe wird, muss die Breite feststehen -- bei "width: auto" und einem nicht geladenen Bild ist sie 0. Der Platz wird jetzt am KASTEN reserviert (aspect-ratio + max-width aus den gemessenen Massen). Und die Pruefung dazu hatte denselben Fehler wie das Auge: Sie mass in einem Fenster, in dem das Bild laengst im Zwischenspeicher lag, und meldete tadellose 0 px. Sie misst jetzt in einem FRISCHEN Browser -- der Fall, um den es geht, ist der erste. --- SICHERUNG --- chat-anhaenge/ ist im selben Zug in tools/sicherung-holen.sh eingetragen, nicht spaeter, wenn es auffaellt: Genau so ist die Luecke bei den Profilbildern entstanden. tools/wiederherstellung-proben.mjs bestaetigt: "der Server benutzt 4 Datenordner", keiner fehlt. --- Pruefung --- server/pruef-chat-anhaenge.mjs, neu, 60 Pruefungen, alle gruen. Abschnitt 3 versucht ausdruecklich, hereinzukommen: HTML als bild.png, SVG als foto.png, SVG als svg, eine Textdatei, ein PNG-Kopf ohne Inhalt -- alle fuenf mit 415 abgewiesen, ein echtes JPEG danach mit 201 angenommen (sonst bewiese der Block nur, dass gar nichts durchkommt). 13 MB geben 413 mit lesbarem Text, nicht 500. Konsolenfehler werden nach ZEITPUNKT getrennt, nicht nach Statusnummer: Was waehrend eines absichtlichen Versuchs entsteht, ist gewollt. Nach Nummer zu filtern waere bequem und wuerde ab morgen echte 404 mit verschlucken. Ausserdem gruen: pruef-chat, pruef-chat-optik, pruef-chat-ausbau, pruef-css-klassen. Angesehen bei 1440 px und bei 390 px. Co-Authored-By: Claude Opus 5 <[email protected]>
149 lines
6.9 KiB
Bash
149 lines
6.9 KiB
Bash
#!/usr/bin/env bash
|
|
# ===================================================================
|
|
# Holt die Server-Sicherungen auf diesen Rechner, prueft sie und meldet
|
|
# sich beim Server zurueck.
|
|
#
|
|
# Eine Sicherung, die auf derselben Platte liegt wie das Original, ist
|
|
# keine -- sie hilft gegen versehentliches Loeschen, aber nicht gegen
|
|
# einen Plattenausfall. Deshalb dieser Schritt.
|
|
#
|
|
# Laeuft normalerweise von allein (Windows-Aufgabenplanung, taeglich,
|
|
# siehe tools/sicherung-einrichten.ps1). Von Hand geht auch:
|
|
# bash tools/sicherung-holen.sh
|
|
# ===================================================================
|
|
set -uo pipefail
|
|
|
|
ZIEL="$HOME/Documents/Obelix/Sicherungen/workspace"
|
|
PROTOKOLL="$ZIEL/lauf-protokoll.txt"
|
|
SCHLUESSEL_DATEI="$HOME/.dogfather-kopie-schluessel"
|
|
mkdir -p "$ZIEL"
|
|
|
|
# Jeder Lauf wird mitgeschrieben -- auch der gescheiterte. Ohne das
|
|
# waere hinterher nicht zu klaeren, ob die Aufgabe gar nicht lief oder
|
|
# ob sie lief und scheiterte. Das sind zwei sehr verschiedene Fehler.
|
|
notiere() { echo "$(date '+%Y-%m-%d %H:%M') $*" >> "$PROTOKOLL"; }
|
|
|
|
scheitern() {
|
|
echo "FEHLGESCHLAGEN: $*" >&2
|
|
notiere "FEHLER: $*"
|
|
exit 1
|
|
}
|
|
|
|
echo "Hole Sicherungen vom Server ..."
|
|
scp -q dogfather-server:/home/dogiweb/workspace-daten/sicherungen/*.db "$ZIEL/" \
|
|
|| scheitern "Server nicht erreichbar oder keine Sicherung vorhanden"
|
|
|
|
# Die Datenbank kennt hochgeladene Dateien und PDFs nur ueber ihren
|
|
# Namen -- die Dateien selbst liegen daneben im Dateisystem und stecken
|
|
# NICHT in der Datenbanksicherung. Ohne diesen Schritt haette man nach
|
|
# einem Plattenausfall eine Bibliothek voller Verweise auf PDFs, die es
|
|
# nicht mehr gibt.
|
|
#
|
|
# scp loescht nie etwas. Wer auf dem Server versehentlich ein PDF
|
|
# entfernt, hat es hier weiterhin -- das ist Absicht, nicht Nachlaessigkeit.
|
|
# PROFILBILDER GEHOEREN DAZU (ergaenzt 06.09.2026).
|
|
#
|
|
# Hier standen nur dateien/ und wissen/. Die Profilbilder liegen aber in
|
|
# einem dritten Ordner (workspace-steckbrief.js: profilbilder/) -- und
|
|
# der wurde nie mitgeholt. Nach einem Plattenausfall waeren alle Bilder
|
|
# weg gewesen, obwohl "die Sicherung" jeden Tag gruen gemeldet hat.
|
|
#
|
|
# Gefunden hat das nicht das Nachdenken, sondern die Probe: Beim ersten
|
|
# Lauf von tools/wiederherstellung-proben.mjs blieb genau eine von
|
|
# achtzehn Seiten rot -- der Steckbrief, mit einem 404 fuer ein Bild.
|
|
# Genau dafuer gibt es sie.
|
|
#
|
|
# CHAT-ANHAENGE GEHOEREN DAZU (ergaenzt 09.09.2026).
|
|
#
|
|
# Seit diesem Tag koennen im Chat Fotos und PDFs geschickt werden
|
|
# (workspace-chat.js: chat-anhaenge/). Der Ordner wurde hier im selben
|
|
# Zug eingetragen -- nicht spaeter, wenn es auffaellt: Genau so ist die
|
|
# Luecke bei den Profilbildern entstanden.
|
|
#
|
|
# Wer hier einen Ordner ergaenzt, traegt ihn auch in ORDNER unten ein;
|
|
# die Probe vergleicht die Liste gegen die Ordner, die der Server
|
|
# wirklich benutzt, und schlaegt bei einem vergessenen an.
|
|
echo "Hole hochgeladene Dateien, Bilder, Anhaenge und die Bibliothek ..."
|
|
ORDNER="dateien wissen profilbilder chat-anhaenge"
|
|
for o in $ORDNER; do
|
|
mkdir -p "$ZIEL/$o"
|
|
scp -qr "dogfather-server:/home/dogiweb/workspace-daten/$o/." "$ZIEL/$o/" 2>/dev/null || true
|
|
done
|
|
|
|
DATEIEN=$(find $(for o in $ORDNER; do echo "$ZIEL/$o"; done) -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
echo " $DATEIEN Datei(en) aus Ablage und Bibliothek"
|
|
|
|
# Geprueft wird mit Node, nicht mit dem sqlite3-Programm: Node bringt
|
|
# SQLite seit Fassung 22 selbst mit, und auf diesem Windows-Rechner ist
|
|
# sqlite3 gar nicht vorhanden -- eine erste Fassung gab dort nur
|
|
# Fragezeichen aus und meldete trotzdem "fertig".
|
|
ERGEBNIS=$(node -e '
|
|
const { DatabaseSync } = require("node:sqlite");
|
|
const { readdirSync, statSync } = require("node:fs");
|
|
const { join } = require("node:path");
|
|
const ziel = process.argv[1];
|
|
const dateien = readdirSync(ziel).filter((n) => n.endsWith(".db")).sort();
|
|
if (!dateien.length) { console.error("Keine Sicherung gefunden!"); process.exit(1); }
|
|
let schlecht = 0, bytes = 0;
|
|
for (const name of dateien) {
|
|
const pfad = join(ziel, name);
|
|
const groesse = statSync(pfad).size;
|
|
bytes += groesse;
|
|
let zeile;
|
|
try {
|
|
const d = new DatabaseSync(pfad, { readOnly: true });
|
|
const heil = Object.values(d.prepare("PRAGMA integrity_check").get())[0];
|
|
const p = d.prepare("SELECT COUNT(*) AS n FROM personen").get().n;
|
|
d.close();
|
|
const gut = heil === "ok" && p > 0;
|
|
if (!gut) schlecht++;
|
|
zeile = `${gut ? "ok " : "FEHL"} ${heil}, ${p} Personen`;
|
|
} catch (f) { schlecht++; zeile = "FEHL nicht lesbar: " + f.message; }
|
|
console.error(` ${name.padEnd(28)} ${String(Math.round(groesse/1024) + " KB").padStart(8)} ${zeile}`);
|
|
}
|
|
/* Nur diese eine Zeile geht nach stdout -- das Skript liest sie aus. */
|
|
console.log(JSON.stringify({ anzahl: dateien.length, bytes, schlecht }));
|
|
process.exit(schlecht ? 1 : 0);
|
|
' "$ZIEL") || scheitern "eine oder mehrere Sicherungen sind unbrauchbar"
|
|
|
|
ANZAHL=$(node -e 'process.stdout.write(String(JSON.parse(process.argv[1]).anzahl))' "$ERGEBNIS")
|
|
BYTES=$(node -e 'process.stdout.write(String(JSON.parse(process.argv[1]).bytes))' "$ERGEBNIS")
|
|
|
|
# --- Rueckmeldung an den Server ------------------------------------------
|
|
#
|
|
# Das ist der Teil, der verhindert, dass die Sache STILL aufhoert zu
|
|
# laufen. Ohne ihn glaubt man, man haette eine Kopie ausserhalb -- und
|
|
# merkt erst im Ernstfall, dass der Rechner seit Wochen aus war oder die
|
|
# geplante Aufgabe klemmt. Auf der Automationen-Seite steht danach, wie
|
|
# alt die Kopie ist.
|
|
if [ -f "$SCHLUESSEL_DATEI" ]; then
|
|
SCHLUESSEL=$(tr -d ' \r\n' < "$SCHLUESSEL_DATEI")
|
|
ANTWORT=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Kopie-Schluessel: $SCHLUESSEL" \
|
|
-d "{\"dateien\":$DATEIEN,\"sicherungen\":$ANZAHL,\"bytes\":$BYTES,\"rechner\":\"$(hostname)\"}" \
|
|
https://workspace.dogfather-universe.com/workspace/api/zustand/kopie || echo "000")
|
|
if [ "$ANTWORT" = "200" ]; then
|
|
echo " Dem Server zurueckgemeldet."
|
|
elif [ "$ANTWORT" = "410" ]; then
|
|
# 410 heisst hier etwas Bestimmtes: Die ADRESSE stimmt nicht mehr.
|
|
# Genau das war am 06.09.2026 der Fall -- der Workspace ist auf
|
|
# workspace.dogfather-universe.com umgezogen, die alte Adresse
|
|
# antwortet seither mit 410 "Gone". Die Rueckmeldung lief seitdem
|
|
# ins Leere, und auf der Automationen-Seite waere die Kopie mit
|
|
# jedem Tag aelter erschienen, obwohl sie taeglich lief.
|
|
echo " FEHLER: Der Server sagt 410 -- die Adresse in diesem Skript ist veraltet."
|
|
echo " Aktuell: https://workspace.dogfather-universe.com"
|
|
notiere "Rueckmeldung 410: Adresse veraltet, Kopie selbst ist in Ordnung"
|
|
else
|
|
echo " Hinweis: Rueckmeldung an den Server ging nicht (HTTP $ANTWORT)."
|
|
notiere "Rueckmeldung fehlgeschlagen (HTTP $ANTWORT), Kopie selbst ist in Ordnung"
|
|
fi
|
|
else
|
|
echo " Hinweis: kein Schluessel unter $SCHLUESSEL_DATEI -- keine Rueckmeldung."
|
|
fi
|
|
|
|
echo
|
|
echo "Fertig: $ANZAHL Sicherung(en), $DATEIEN Datei(en), alle lesbar."
|
|
notiere "ok: $ANZAHL Sicherungen, $DATEIEN Dateien, $((BYTES/1024)) KB"
|