Files
DogFatherGitandClaude Opus 5 5e503463f5 Chat: die GIF-Kiste des Rudels -- und eine Luecke in der Sicherung
Filipe: "dan will ich auch dass man im chat auch sprachnachrichten und
gifts reinschicken kann. also nur die modis rechte linke hand und
dogfather."

ICH LESE "gifts" ALS GIFs -- die bewegten Bildchen -- und sage das
ausdruecklich, statt es stillschweigend anzunehmen. Im Zusammenhang
("im chat reinschicken", direkt neben Sprachnachrichten) passt nichts
anderes. Sollte er Geschenke gemeint haben, sagt er es, und dann baue
ich das.

EINE EIGENE KISTE STATT GIPHY ODER TENOR
----------------------------------------
Beide brauchen ein Konto und einen Schluessel und bekommen bei jedem
Tastendruck mit, wonach hier gesucht wird -- an eine fremde Firma, aus
einem Arbeitsplatz heraus, in dem sonst nichts nach aussen geht. Dazu
kosten sie ab einer Menge Geld. Beides steht quer zu dem, wie dieses
Haus gebaut ist.

Die Kiste laeuft auf demselben Server wie alles andere, kostet nichts
und wird mit der Zeit besser statt schlechter: Was darin liegt, hat das
Team ausgesucht. Zehn gute GIFs, die alle kennen, sind im Alltag mehr
wert als zehn Millionen fremde -- ein Katalog mit allem ist nicht mehr
Auswahl, sondern weniger.

HABEN WIR DAS SCHON? WIRD AM INHALT BEANTWORTET
-----------------------------------------------
Der Schluessel ist der SHA-256 der Datei. Ueber den NAMEN zu
vergleichen waere die naheliegende Abkuerzung -- und sie versagt genau
dort, wo Dateien "giphy.gif", "giphy(1).gif" und "download.gif"
heissen, also fast immer. Dasselbe Bild waere dreimal dieselbe Kachel.

Dasselbe GIF zweimal hineinzulegen ist deshalb KEIN Fehler: Es ist
drin, und genau das wollte man. Eine Absage waere formal richtig und im
Erleben falsch.

DAS WICHTIGSTE STUECK: ES WIRD KOPIERT, NICHT VERWIESEN
-------------------------------------------------------
Eine Nachricht zurueckzunehmen entfernt ihre Datei von der Platte. Laege
ein Kachel-GIF in demselben Ordner und mehrere Nachrichten zeigten
darauf, naehme das Zuruecknehmen EINER Nachricht das GIF fuer alle
anderen mit -- und in drei Gespraechen stuende ab da ein kaputtes Bild.
Das ist die Sorte Fehler, die erst Wochen spaeter auffaellt und dann
nicht mehr zu reparieren ist.

Deshalb liegt die Kiste in einem eigenen Ordner (chat-gifs/), und beim
Verschicken wird kopiert. Danach ist es ein ganz normaler Anhang: im
Verlauf, in der Suche, zuruecknehmbar, unter derselben Nachtruhe. Keine
zweite Sorte Nachricht mit eigenen Rechten und eigenem Loeschen.

Geprueft wird genau das: GIF hineinlegen, verschicken, aus der Kiste
nehmen -- und danach steht das verschickte immer noch im Gespraech.

"ALSO NUR" GILT AUCH HIER
-------------------------
Ansehen, hineinlegen, verschicken: alle drei nur fuer Team Dogi und
DogFather (istTeamDogi). Der Satz stand im selben Atemzug wie die
Sprachnachrichten; es gibt keinen Grund, warum er fuer das eine gelten
sollte und fuer das andere nicht. Gemessen auch am Knopf vorbei:
403/403/403.

AUGENSCHONEND -- HIER EINE ECHTE FRAGE, KEINE FORMSACHE
--------------------------------------------------------
Zwoelf gleichzeitig laufende Bildchen sind das Gegenteil von ruhig. Wer
"Bewegung reduzieren" gesetzt hat, bekommt deshalb STEHENDE Bilder: das
erste Einzelbild, im Browser auf eine Zeichenflaeche gemalt (kostet
keine zweite Datei), und es laeuft erst, wenn man es anfasst oder mit
der Tastatur darauf steht.

Fuer alle anderen laufen sie. Eine GIF-Auswahl, in der sich nichts
bewegt, ist eine, in der man nicht sieht, was man verschickt -- deshalb
steht dazu eine Gegenprobe in der Pruefung.

NEBENBEFUND, UND ER WIEGT SCHWERER ALS DIE GIFS
-----------------------------------------------
Weil die Kiste einen neuen Datenordner braucht, lief
tools/wiederherstellung-proben.mjs -- und meldete, dass der Ordner
"material" seit dem 22.09. NICHT gesichert wird. 2,4 MB
Materialbibliothek auf dem Server, nach einem Plattenausfall weg, ohne
dass die taegliche Meldung je etwas gesagt haette. Genau die Luecke,
wegen der diese Probe ueberhaupt gebaut wurde -- und sie hat sie
gefunden, weil sie die Ordner aus dem QUELLTEXT liest statt aus einer
gepflegten Liste.

Eingetragen. Danach frisch geholt und zurueckgespielt: 18 von 18 gruen,
"DIE SICHERUNG LAESST SICH ZURUECKSPIELEN" (vorher 3 Fehler).

Dabei war die Gegenprobe daneben selbst kaputt: Sie prueft, ob ein
erfundener Ordner als fehlend erkannt wird, und verlangte dafuer
`=== 1`. Das setzt voraus, dass sonst nichts fehlt. An dem Tag, an dem
wirklich etwas fehlte, wurde sie rot und zeigte damit auf sich selbst
statt auf den Fund -- zwei Meldungen, eine Ursache, und die zweite
lenkt von der ersten ab. Jetzt zaehlt sie die Differenz.

GEPRUEFT
--------
pruef-chat-anhaenge: 109 Pruefungen, 0 Fehler (vorher 86, davor 60).
pruef-chat, pruef-chat-aufloesen (126), pruef-chat-optik: gruen.
tools/wiederherstellung-proben: 18 von 18.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-23 02:07:56 +02:00

171 lines
8.0 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.
#
# MATERIAL FEHLTE -- GEFUNDEN AM 23.09.2026, NICHT BEMERKT SEIT DEM
# 22.09.
#
# server/workspace-material.js legt seit dem 22.09. einen Ordner
# "material" an (2,4 MB auf dem Server). Er stand hier nicht. Nach
# einem Plattenausfall waere die Materialbibliothek weg gewesen, ohne
# dass die taegliche Meldung je etwas gesagt haette -- genau die
# Luecke, wegen der diese Probe ueberhaupt gebaut wurde.
#
# Gefunden hat es nicht das Lesen, sondern tools/wiederherstellung-
# proben.mjs: Sie liest die Ordner aus dem Quelltext (join(DATEN_ORDNER,
# "...")) und vergleicht sie mit dieser Zeile. Das ist der Unterschied
# zwischen einer Liste, die jemand pflegen muss, und einer, die
# nachgezaehlt wird.
#
# DIE GIF-KISTE GEHOERT DAZU (ergaenzt 23.09.2026, im selben Zug wie
# die Funktion -- nicht spaeter, wenn es auffaellt). Sie liegt in
# chat-gifs/ und ist das einzige, was NUR dort liegt: Verschickte GIFs
# sind Kopien in chat-anhaenge/, die Kiste selbst nicht. Ginge die
# Platte kaputt, waere ohne diesen Eintrag die gesammelte Auswahl des
# Teams weg, und die Nachrichten damit auch nicht mehr nachlegbar.
#
# 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 chat-gifs material"
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"