Suchfeld im Designbereich, Sicherung als Arbeitspaket O
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Neunzig Eigenschaften in acht zugeklappten Gruppen: "wo ist die
Rahmenstaerke" ist eine berechtigte Frage. Jetzt filtert ein Suchfeld die
Eigenschaften, oeffnet die passenden Gruppen und sagt, wie viele Treffer es
gibt - und wenn keine, wo eigenes CSS zu finden ist. Getestet: "rahmen" laesst
die Gruppen Rahmen und Bild stehen, 14 Treffer.

Neu im Ausbauplan, Paket O: Sicherung und Wiederherstellung als Funktion statt
als Disziplin. Heute entstehen Sicherungen nur als Nebenprodukt des Updaters
und weil ich vor jeder Migration von Hand eine Kopie lege. Geplant sind
Datenbank per VACUUM INTO statt copy (eine Kopie waehrend eines
Schreibvorgangs kann unbrauchbar sein), Medienordner, ein Manifest mit
Fassung, Migrationsstand und Pruefsummen - und ohne .env, weil darin
APP_SECRET steht und eine Sicherung den Server verlaesst. Dazu Rotation, damit
die Platte nicht vollaeuft und die naechste Sicherung nicht genau dann
scheitert, wenn man sie braucht.

Die Abnahme ist die Wiederherstellung, nicht die Sicherung: eine Seite
loeschen, zurueckspielen, pruefen dass sie wieder da ist. Eine
Sicherungsfunktion, deren Wiederherstellung nie gelaufen ist, ist eine
Vermutung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 20:43:19 +02:00
1 parent 806df4b5df
commit 464f52b147
3 files changed
+61 -1

No files matched your search

+31 -1
View File
@@ -151,7 +151,7 @@ Abnahme: Templatewechsel erhält die Inhalte. Das Referenzplugin lässt sich akt
*Braucht: nichts; teils vorziehbar.*
~~Benutzerverwaltung und Passwortänderung in der Oberfläche.~~ — **erledigt am 12.09.2026** (ADR 030). Offen bleibt der Rest des Pakets: Inhaltscache mit gezielter Erneuerung beim Veröffentlichen. Bildformate und Ressourcenpakete prüfen. Sicherung und Wiederherstellung erproben. Updateverfahren mit Datenbankmigration und Rückfallstrategie dokumentieren — **geplant am 12.09.2026 in `docs/UPDATE.md`**: neue Version über einen Git-Tag ziehen, mit Vorprüfung, Sicherung, Wartungszustand, Migration, Rauchtest und Rückweg. Umsetzung offen; zwei Entscheidungen hängen am Hosting (Composer auf dem Server oder `vendor/` im Tag, und ob es Shell-Zugriff gibt).
~~Benutzerverwaltung und Passwortänderung in der Oberfläche.~~ — **erledigt am 12.09.2026** (ADR 030). Offen bleibt der Rest des Pakets: Inhaltscache mit gezielter Erneuerung beim Veröffentlichen. Bildformate und Ressourcenpakete prüfen. Sicherung und Wiederherstellung erproben — **als eigenes Arbeitspaket O geführt**, weil Umut daraus eine Funktion haben will und nicht eine Übung. Updateverfahren mit Datenbankmigration und Rückfallstrategie dokumentieren — **geplant am 12.09.2026 in `docs/UPDATE.md`**: neue Version über einen Git-Tag ziehen, mit Vorprüfung, Sicherung, Wartungszustand, Migration, Rauchtest und Rückweg. Umsetzung offen; zwei Entscheidungen hängen am Hosting (Composer auf dem Server oder `vendor/` im Tag, und ob es Shell-Zugriff gibt).
Abnahme: Veröffentlichen macht die Änderung sofort sichtbar und erneuert nur die betroffenen Einträge. Eine Wiederherstellung aus dem Backup ist nachgewiesen.
@@ -208,6 +208,36 @@ Umut: „Einen Installer brauche ich noch wie wordpress schritt für schritt all
Abnahme: eine leere Installation ist im Browser in sechs Schritten betriebsbereit, ohne Konsole. Ein zweiter Aufruf von `/install` liefert 404. Die Systemprüfung schlägt bei fehlenden Schreibrechten fehl und sagt, welcher Pfad fehlt. Getestet gegen ein leeres Verzeichnis und eine leere Datenbank, nicht gegen die Entwicklungsinstallation.
### O – Sicherung und Wiederherstellung als Funktion (Nutzerauftrag vom 12.09.2026)
*Braucht: nichts. Für MySQL zusätzlich die Hostingentscheidung aus Arbeitspaket J.*
Umut: „Todo aufnehmen. Standard backup funktion.“ Heute gibt es Sicherungen nur als Nebenprodukt: der Updater kopiert die Datenbank vor einem Update, und ich habe vor jeder Migration von Hand eine Kopie in `var/backups/` gelegt. Das ist keine Sicherungsfunktion, das ist Disziplin – und Disziplin fällt aus, sobald jemand anderes arbeitet.
**Was in eine Sicherung gehört**, und was nicht:
- **Die Datenbank.** Bei SQLite die Datei, und zwar konsistent: eine Kopie während eines Schreibvorgangs kann unbrauchbar sein, deshalb `VACUUM INTO` statt `copy()`. Bei MySQL `mysqldump`.
- **Der Medienordner.** Ohne ihn ist die Sicherung wertlos: die Dokumente verweisen auf Bild-IDs, die Dateien liegen im Medienordner. Das ist auch der Teil, der groß wird.
- **Ein Manifest**: Zeitpunkt, Fassung aus `VERSION`, höchste angewandte Migration, Anzahl Seiten, Fassungen und Medien, und eine Prüfsumme je Teil. Ohne das weiß man beim Zurückspielen nicht, ob die Sicherung zum Code passt.
- **Nicht `.env`.** Darin steht `APP_SECRET`; eine Sicherung, die man herunterlädt und weitergibt, darf das nicht enthalten. Die Datei wird beim serverseitigen Aufbewahren mitkopiert (der Updater tut das schon), aber nicht in ein Archiv gelegt, das den Server verlässt. Beim Zurückspielen wird sie deshalb auch nicht überschrieben.
**Ablauf einer Sicherung:** Wartungszustand ist *nicht* nötig – `VACUUM INTO` liefert einen konsistenten Stand, und Medien werden nur hinzugefügt, nie verändert. Geschrieben wird nach `var/backups/sicherung-<zeitstempel>/` mit Manifest; auf Wunsch als ein Archiv. `var/` liegt außerhalb des Dokumentenstamms (`public/`), also ist keine Sicherung über das Netz erreichbar – das muss die Systemprüfung nachweisen, nicht annehmen.
**Aufbewahrung mit Rotation:** die letzten sieben täglichen, vier wöchentlichen, drei monatlichen. Ohne Rotation läuft die Platte voll und dann scheitert die nächste Sicherung genau dann, wenn man sie braucht.
**Herunterladen:** in der Verwaltung, als Archiv, gestreamt. Mit zwei ehrlichen Hinweisen: ohne `.env`, und bei einem großen Medienordner bricht ein Browserdownload womöglich ab – dann ist die Konsole oder ein Dateizugriff der Weg. Kein Archiv im Speicher aufbauen.
**Wiederherstellung** ist der Teil, der zählt, und der Teil, den niemand übt:
1. `cms:restore <verzeichnis-oder-archiv>`, und in der Verwaltung nur die Anzeige, welche Sicherungen es gibt. Ein Knopf, der die Datenbank überschreibt, gehört nicht neben einen Knopf, der eine Seite speichert.
2. Manifest gegen die laufende Installation prüfen: eine Sicherung aus einer **neueren** Fassung wird abgewiesen – deren Migrationen kennt der alte Code nicht. Eine ältere ist erlaubt, dann läuft danach die Migration.
3. Wartungszustand an, **Sicherung des aktuellen Stands** anlegen (auch eine Wiederherstellung kann die falsche sein), Datenbank ersetzen, Medien ergänzen statt löschen, Migration, Cache, Rauchtest, Wartungszustand aus.
4. Protokoll nach `var/backups.log`.
**Was gebaut wird:** `src/Ops/Backup.php`, `cms:backup` (mit `--rotate`, `--archive`), `cms:restore` (mit `--force`), eine Übersicht im Bildschirm „System“ mit Größe, Zeitpunkt, Fassung und Prüfsummenstatus je Sicherung, und ein Eintrag im Cronjob-Abschnitt des README.
**Abnahme – und hier liegt der offene Punkt aus Arbeitspaket I:** eine Wiederherstellung ist *nachgewiesen*. Nicht „die Funktion existiert“, sondern: eine Sicherung wird angelegt, die Datenbank wird nachweislich verändert (eine Seite gelöscht, ein Bild entfernt), die Sicherung wird zurückgespielt, und danach ist beides wieder da – geprüft im Test gegen eine wegwerfbare Installation und einmal von Hand auf der Entwicklungsinstallation. Eine Sicherungsfunktion, deren Wiederherstellung nie gelaufen ist, ist eine Vermutung.
### K – Migration von kant-it.de
*Braucht: B, C, D, E, F, H, J und Backendzugang.*
+29
View File
@@ -265,6 +265,35 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
panel.append(scope);
panel.append(element('p', 'field-help', 'Leere Felder erben vom größeren Gerät und zuletzt vom Theme. Werte mit eigenem Wert sind hervorgehoben und lassen sich einzeln zurücksetzen.'));
// A filter over the properties. With more than ninety of them across eight collapsed groups,
// "where is the border thickness" is a fair question - so it can be typed instead of hunted.
const search = element('input', 'design-search');
search.type = 'search';
search.id = 'design-search';
search.placeholder = 'Eigenschaft suchen, z. B. Rahmen …';
const searchLabel = element('label', 'search-label', 'Eigenschaft suchen');
searchLabel.htmlFor = search.id;
const found = element('p', 'field-help');
search.addEventListener('input', () => {
const term = search.value.trim().toLowerCase();
let matches = 0;
for (const groupElement of panel.querySelectorAll('.design-group')) {
let visible = 0;
for (const entry of groupElement.querySelectorAll('.design-row, .side-wrap, .design-group-label')) {
// A per-side control is a heading plus a block of fields; they travel together.
const text = (entry.classList.contains('side-wrap') ? entry.previousElementSibling?.textContent : entry.textContent) ?? '';
const hit = '' === term || text.toLowerCase().includes(term);
entry.hidden = !hit;
if (hit) visible++;
}
groupElement.hidden = '' !== term && 0 === visible;
if (!groupElement.hidden && '' !== term) groupElement.open = true;
matches += visible;
}
found.textContent = '' === term ? '' : (0 === matches ? 'Nichts gefunden. Unter „Erweitert“ gibt es außerdem eigenes CSS für dieses Element.' : `${matches} Treffer.`);
});
panel.append(searchLabel, search, found);
for (const [group, groupLabel] of Object.entries(GROUPS)) {
const properties = Object.entries(schema.properties).filter(([, p]) => p.group === group);
if (properties.length === 0) continue;
+1
View File
@@ -208,3 +208,4 @@ dialog p{font-size:14px;line-height:1.7;color:var(--muted)}
.row-library button{display:flex;align-items:center;gap:10px;min-height:38px;padding:8px 12px;border:1px solid var(--line);border-radius:7px;background:#f7f9f2;color:var(--ink);font-size:11px}
.row-library button:hover:not(:disabled){border-color:var(--accent)}
.row-library span{letter-spacing:-2px;font-size:13px}
#design-search{font-size:12px;min-height:36px;padding:9px}