Installer: sechs Bildschirme, und eine Datei davor
/install fuehrt von einer leeren Installation zu einer Website: Systempruefung, Datenbank (SQLite oder MySQL, Verbindung sofort geprueft), Migration im Prozess ohne Konsole, Website mit Startseite, Bereichen, Menue sowie Impressum und Datenschutzgeruest, erstes Konto, Abschlussliste. Jeder Schritt schreibt sein Ergebnis sofort, damit ein Neuladen nichts verliert. Gesperrt wird ueber den Zustand der Datenbank - gibt es ein Konto, antwortet /install mit 404. Eine Datei koennte jemand loeschen. Solange nichts installiert ist, fuehrt jede Adresse zum Installer statt zu einem 500 aus einer fehlenden Tabelle. Und weil ohne APP_SECRET nicht einmal die Seite bootet, die das erklaeren wuerde, erzeugt der erste Start das Geheimnis selbst. scripts/install.php ist die einzelne Datei davor: Adresse und SHA-256 stehen darin (vom Buildskript eingetragen), ohne passende Pruefsumme wird nicht entpackt, jeder Archiveintrag wird gegen das Zielverzeichnis geprueft, und danach loescht sie sich selbst. Geprueft: 379 Tests unter PHP 8.2 und 8.4, darunter der ganze Ablauf durch die HTTP-Schicht, neun ZIP-Slip-Varianten, axe ohne Verstoss auf allen Installationsbildschirmen, und das entpackte Releasearchiv startet in den Installer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c3bff7d936
commit
e34b4dca67
25 files changed
+1937
-7
No files matched your search
@@ -440,6 +440,26 @@ Vom Verweis bleibt nur der Host. Die vollständige Adresse, aus der jemand kam,
|
||||
|
||||
**Aufbewahrung:** nach 60 Tagen werden die Stunden eines Tages zu einer Zeile verschmolzen, nach 425 Tagen wird gelöscht. Gut ein Jahr, damit ein Vergleich mit dem Vorjahresmonat möglich bleibt. `cms:stats:prune` macht beides und ist für einen Cronjob gedacht.
|
||||
|
||||
## ADR 041 – Der Installer: sechs Bildschirme, und danach ist er weg (12.09.2026)
|
||||
|
||||
Umut: „Einen Installer brauche ich noch wie wordpress schritt für schritt alles einrichten migration ausführen usw.“ und, später, „einen installer das eine php ist und alle dateien herunterladet“. Beides ist gebaut, und beides hängt an derselben Frage: was darf eine Datei dürfen, die auf einem leeren Webspace liegt und von jedem aufrufbar ist?
|
||||
|
||||
**Der Ablauf schreibt jeden Schritt sofort**, statt Antworten in einer Sitzung zu sammeln. Nach dem Datenbankschritt gibt es `.env.local`, nach dem Migrationsschritt Tabellen, nach dem Websiteschritt Inhalte. Ein Neuladen, ein Zeitüberlauf, ein geschlossener Tab verliert damit nichts – der Installer sieht, was schon da ist, und macht an der nächsten fehlenden Stelle weiter. Wer eine spätere Adresse rät, wird zurückgeschickt, und zwar vom Server, nicht von einem Link.
|
||||
|
||||
**Das Ende ist das erste Konto, und es ist ein Zustand der Datenbank, keine Datei.** Eine Datei, die den Installer sperrt, ist eine Datei, die jemand löschen kann – und dann steht auf einer laufenden Website wieder ein Formular, das ein Administratorkonto anlegt. Gibt es ein Konto, antwortet jede Adresse unter `/install` mit 404.
|
||||
|
||||
**Kein Sitzungstoken, sondern eine Unterschrift.** Das Sitzungscookie dieser Anwendung gilt nur für `/admin` – eine bewusste Entscheidung, die öffentliche Seiten cachebar hält –, und für sechs Bildschirme wird sie nicht aufgeweicht. Jedes Formular trägt Schritt und Zeitpunkt, mit `APP_SECRET` unterschrieben, zwei Stunden gültig. Ehrlich dazugesagt: das ist kein CSRF-Schutz, denn wer den Installer erreicht, bekommt auch eine gültige Unterschrift. Der Schutz ist, dass es den Installer nach dem ersten Konto nicht mehr gibt – deshalb steht auf dem letzten Bildschirm, dass sofort nach dem Hochladen installiert werden soll.
|
||||
|
||||
**Migrationen laufen im Prozess, nicht über die Konsole.** Auf geteiltem Hosting gibt es oft keine Shell, und `proc_open` ist ein häufiges Opfer einer gehärteten `php.ini`. Der Installer benutzt dieselbe `migrations.php` und dieselbe `DependencyFactory` wie das Konsolenkommando. Bricht der Browser ab, ist nichts kaputt: angewandte Migrationen sind vermerkt, ein erneuter Aufruf macht weiter.
|
||||
|
||||
**Ein Huhn-Ei-Problem und seine Lösung:** ohne `APP_SECRET` bootet Symfony nicht – also nicht einmal die Seite, die erklären würde, warum. Deshalb erzeugt der erste Start das Geheimnis selbst und schreibt es nach `.env.local`. Geht das nicht, läuft die Anfrage trotzdem, und die Systemprüfung sagt in einem Satz, dass im Projektstamm das Schreibrecht fehlt. Eine weiße Seite wäre die Alternative gewesen.
|
||||
|
||||
**Und eine Weiterleitung statt eines Fehlers:** solange keine Installation existiert, führt jede Adresse zu `/install`. Der erste Besuch auf einem frisch entpackten Webspace ist sonst ein 500 aus einer fehlenden Tabelle – genau der Moment, in dem jemand einen Satz braucht und keinen Stacktrace. Sobald ein Konto existiert, wird eine Marke unter `var/installed` geschrieben und keine Anfrage fragt die Datenbank noch einmal; die Marke schweigt nur diesen Subscriber, der Installer selbst fragt immer die Datenbank.
|
||||
|
||||
**Die einzelne Datei davor** (`install.php`) lädt das Releasearchiv, prüft dessen SHA-256, entpackt es und löscht sich danach selbst. Adresse und Prüfsumme stehen **in der Datei**, eingetragen beim Bauen des Releases: ein Installer, der eine URL aus einem Formular entgegennimmt, ist eine Einladung, fremden Code auf den Server zu legen. Stimmt die Prüfsumme nicht, wird nicht entpackt – es gibt kein Häkchen daran vorbei. Jeder Archiveintrag wird gegen das Zielverzeichnis geprüft, bevor er geschrieben wird; geprüft wird nicht durch Suchen nach „..“, sondern indem der zusammengesetzte Zielpfad unterhalb des Ziels liegen muss. In ein Verzeichnis, in dem schon eine Installation liegt, arbeitet sie gar nicht erst.
|
||||
|
||||
**Was der Installer anlegt, ist nicht die Demoseite.** Eine Startseite mit dem Namen der Website, Kopf- und Fußbereich mit Menü – und Impressum und Datenschutzerklärung als Gerüst mit markierten Lücken. Das Letzte ist kein Entgegenkommen: eine deutsche Unternehmenswebsite ohne beides ist ab dem ersten Tag ein Problem, und die Installation ist der einzige Moment, in dem mit Sicherheit jemand hinsieht.
|
||||
|
||||
## ADR 040 – Bildgrößen, und ein Release für Hosting ohne Composer (12.09.2026)
|
||||
|
||||
Zwei Fragen von Umut in einem Satz: „werden Bilder in verschiedenen Größen angelegt und in WebP umgewandelt?“ und „auf einem Hostingpaket ist kein Composer, meistens, oder?“ – die erste war zur Hälfte schon beantwortet, die zweite ist berechtigt und hatte bis heute keine Antwort.
|
||||
|
||||
+3
-3
@@ -191,11 +191,11 @@ Umut: „alle inhalte prüfen immer pagination und minimal laden“. Als durchge
|
||||
|
||||
Abnahme: keine Liste in der Verwaltung, die mehr Einträge hat als sie zeigt, ohne das zu sagen und ohne Blättern. Ein automatischer Test, der die Größe der Startseite und die Anzahl der Datenbankabfragen prüft. **Beides erfüllt.** Offen bleibt aus diesem Paket: `srcset` ist für Bilder aus der Mediathek vorhanden, aber eine Seite mit vielen Bildern ist nie gegen das Budget gemessen worden, und die Benutzerliste blättert noch nicht (sie ist naturgemäß kurz).
|
||||
|
||||
### N – Installer (Nutzerauftrag vom 12.09.2026)
|
||||
### N – Installer (Nutzerauftrag vom 12.09.2026) — **erledigt am 12.09.2026** (ADR 041), außer dem Download aus einer veröffentlichten Releaseadresse
|
||||
|
||||
**Teilweise erledigt am 12.09.2026:** der *Updater* ist gebaut (ADR 035), der *Installer* nicht. Beide teilen die Systemprüfung, die jetzt existiert und dort wiederverwendet wird.
|
||||
**Gebaut:** der Browserinstaller unter `/install` mit sechs Bildschirmen und die einzelne `install.php`, die ein Buildskript mit Adresse und Prüfsumme versieht. Der Schritt „Datenbank“ fragt SQLite oder MySQL ab und prüft die Verbindung sofort; damit brauchte er die Hostingentscheidung aus Arbeitspaket J nicht mehr abzuwarten.
|
||||
|
||||
*Braucht: die Hostingentscheidung aus Arbeitspaket J für den Schritt „Datenbank“.*
|
||||
**Offen bleibt:** ein echter Download (dazu muss ein Release unter seiner Adresse liegen), MySQL gegen einen echten Server, und eine Installation auf echtem Hosting.
|
||||
|
||||
Umut: „Einen Installer brauche ich noch wie wordpress schritt für schritt alles einrichten migration ausführen usw.“ Heute braucht eine Installation `bin/setup.php` und Konsolenzugriff – auf geteiltem Hosting ist das genau die Hürde, an der es scheitert.
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Qualitätsbericht – erster ausführbarer Prototyp
|
||||
|
||||
## Ergänzung: Installer, 12.09.2026
|
||||
|
||||
- **379 PHP-Tests / 1606 Assertions** unter PHP 8.2.12 und 8.4.22, 17 JavaScript-Modelltests, Playwright-Editortest, öffentliche Browserprüfung.
|
||||
- **Die Abnahme des Pakets ist ein Test, kein Bericht:** ein Test geht die sechs Bildschirme durch die echte HTTP-Schicht, mit einem Kernel in einem wegwerfbaren Projektverzeichnis – leere Adresse → Weiterleitung auf `/install`, Datenbank, Migration, Website, Konto, fertig. Danach antwortet `/install` mit **404** und `/` mit **200**, und auf der Startseite steht der Name, den das Formular bekommen hat. Die Migrationen darin sind die echten.
|
||||
- **Von Hand im Browser nachgestellt**, gegen eine Kopie der Anwendung in einem leeren Verzeichnis mit leerer Datenbank: erste Anfrage 302 auf `/install`, Systemprüfung, Datenbank (SQLite), 13 Migrationen, Website „Tischlerei Berg“. Den letzten Schritt – das Konto – habe ich nicht im Browser ausgefüllt, sondern dem Test überlassen; ich tippe keine Passwörter in Formulare.
|
||||
- **axe auf allen vier erreichbaren Installationsbildschirmen: null Verstöße** (wcag2a, wcag2aa, wcag21a, wcag21aa).
|
||||
- **Das Releasearchiv wurde entpackt und gestartet**, nicht nur gebaut: `kantsites-1.3.0.zip` in ein leeres Verzeichnis, Server darauf, erste Anfrage → Installer. Damit ist belegt, dass `vendor/` vollständig ist und auf dem Zielsystem weder Composer noch eine Konsole gebraucht wird.
|
||||
- Die einzelne `install.php` ist dort geprüft, wo sie gefährlich wäre: neun Archiveinträge, die aus dem Zielverzeichnis ausbrechen wollen (`../`, absolut, Windows-Laufwerk, Rückwärtsschrägstrich, Nullbyte), landen nirgends – und ein Archiv mit einem solchen Eintrag bricht ab, ohne die Datei außerhalb anzulegen. Der Eintrag im Testarchiv ist echt: ZIP speichert `kantsites-1.2.0/../entwischt.txt` unverändert.
|
||||
- **Nicht geprüft:** der Download selbst. Solange das Archiv nicht unter seiner Adresse liegt, ist die Kette „Datei hochladen → lädt → entpackt“ nur bis zum Entpacken belegt. Ebenso ungeprüft: MySQL als Zieldatenbank im Installer (der Verbindungstest ist da, ein echter MySQL-Server war es nicht), echtes Hosting, und ein Screenreader.
|
||||
|
||||
## Ergänzung: Bildgrößen und Releasearchiv, 12.09.2026
|
||||
|
||||
- **352 PHP-Tests / 1479 Assertions** unter PHP 8.2.12 und 8.4.22, 17 JavaScript-Modelltests, Playwright-Editortest, öffentliche Browserprüfung.
|
||||
|
||||
@@ -27,6 +27,7 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt.
|
||||
- Adresshierarchie: neue Spalten `path`, `parent_id`, `type`, `created_at` über Migration `20260912000400`; websiteweit eindeutiger Pfad statt eindeutigem Slug, Tiefe bis vier Ebenen, Beiträge ohne Unterseiten, reservierte Adressen und Adressableitung aus dem Titel. Öffentliche Route bedient verschachtelte Pfade wie `/leistungen/webdesign`; ein Lookahead hält `/admin`, `/media` und `/assets` aus dem Auffangmuster.
|
||||
- **Ladeverhalten gemessen statt vermutet** (ADR 039, Arbeitspaket M): ein Zähler liefert die Datenbankabfragen je Anfrage im Kopf `X-Kant-Queries` (außerhalb der Produktion). Die erste Messung zeigte 18 Abfragen auf der Startseite, weil Einstellungen und Theme je Aufrufer erneut gelesen wurden; beide merken sich ihren Stand jetzt für die Dauer einer Anfrage – **18 → 8**. Die Browserprüfung misst je Seite HTML, CSS, Anfragen, Skripte und Abfragen gegen Obergrenzen: Startseite 7,4 KB HTML, 22,7 KB CSS, 2 Anfragen, 0 Skripte.
|
||||
- **Das erste Bild einer Seite lädt sofort**, alles darunter weiterhin verzögert. Es ist meistens das größte Ding über der Falz.
|
||||
- **Installer (Arbeitspaket N, ADR 041):** `/install` führt in sechs Schritten von einer leeren Installation zu einer Website – Systemprüfung, Datenbank (SQLite oder MySQL, Verbindung sofort geprüft), Migration im Prozess ohne Konsole, Website mit Startseite, Kopf-/Fußbereich, Menü sowie Impressum und Datenschutzgerüst, erstes Administratorkonto, Abschlussliste. Solange keine Installation existiert, führt jede Adresse dorthin; sobald ein Konto existiert, antwortet `/install` mit 404. Davor liegt `scripts/install.php`: eine einzelne Datei, die das Releasearchiv mit fester Adresse und fester SHA-256 lädt, gegen ZIP-Slip gesichert entpackt und sich danach selbst löscht.
|
||||
- **Fassungsverlauf, Einsendungen und Weiterleitungen blättern** statt bei 30, 50 beziehungsweise 2000 Einträgen stillschweigend aufzuhören.
|
||||
- **Sicherung und Wiederherstellung** (ADR 038, Arbeitspaket O): `cms:backup` schreibt Datenbank (über `VACUUM INTO`, nicht als Dateikopie) und Medienordner nach `var/backups/`, mit Manifest samt Prüfsumme, und rotiert die alten. In der Verwaltung gibt es die Übersicht mit Zustand je Sicherung und einen Knopf zum Anlegen. **Zurückspielen nur auf der Konsole** (`cms:restore`): Prüfsumme, Fassungsvergleich, Sicherheitskopie des aktuellen Stands, Wartungszustand, Datenbank ersetzen, Medien **ergänzen** statt ersetzen. Nicht enthalten: `.env`, `vendor/`, Quellcode – steht so im Manifest.
|
||||
- **Seiten ohne Menü, andere Köpfe je Seite** (ADR 037, Dokumentschema v16): jede Seite wählt Kopf- und Fußbereich – Standard, ein anderer Bereich oder **keiner**, für eine Landingpage ohne Navigation. Dafür sind mehrere Kopf- und Fußbereiche erlaubt, jeder mit eigener Adresse; der erste bleibt `default`. Ein Verweis auf einen gelöschten Bereich fällt auf den Standard zurück statt die Navigation stillschweigend zu verlieren.
|
||||
|
||||
Reference in new issue
Block a user