Shortlink installieren

Eine Seite, ein Weg. Rechne mit 15 Minuten, wenn Datenbank und Domain schon stehen.


Was du vorher brauchst

Hosting SSH-Zugang, PHP 8.3 oder neuer, Composer, MySQL oder MariaDB, Apache oder nginx
Datenbank Eine Datenbank und ein Benutzer mit vollen Rechten darauf — beides legst du selbst an, Shortlink tut es nicht
Domain Auf das Verzeichnis public/ zeigend, mit TLS-Zertifikat
Cron Zugang zur Zeitsteuerung des Servers (zwei Einträge, siehe unten)

node und npm brauchst du nicht. Das Distributionspaket bringt CSS und JavaScript fertig gebaut mit.

Die Datenbank muss nicht leer sein. Teilst du sie dir mit anderen Anwendungen — bei manchen Hostern gibt es nur eine —, vergib beim Einrichten ein Tabellen-Präfix.


1. Dateien auf den Server bringen

Drei Wege. Der erste nimmt dir die anderen ab.

Mit dem Setup-Skript (empfohlen)

Ein Skript, das auf dem Zielserver läuft. Es holt das neueste Release, prüft die Prüfsumme, entpackt es, legt die Dateien ab und installiert die Abhängigkeiten:

curl -fLO https://gitlab.com/mankeldor/shortlink/-/raw/main/shortlink-setup.sh
sh shortlink-setup.sh /pfad/zur/installation

Lies das Skript, bevor du es ausführst — deshalb steht hier curl -fLO und kein curl … | sh. Ein Skript in die Shell zu pipen führt Code aus, den niemand gesehen hat, und bei einer abgebrochenen Verbindung führt es ihn halb aus.

Das Skript sieht sich das Zielverzeichnis an und entscheidet danach:

Zustand des Ziels Was passiert
nicht vorhanden oder leer Erstinstallation
erkennbare Shortlink-Installation Aktualisierung über das update.sh aus dem neu geladenen Paket
vorhanden, nicht leer, aber keine Shortlink-Installation Abbruch. Es wird nichts überschrieben und nichts heruntergeladen

Der letzte Fall ist der Grund für die Prüfung: Ein vertippter Pfad soll kein fremdes Projekt überschreiben.

Es endet vor php artisan shortlink:install und sagt, wie es weitergeht. Absicht: Datenbank-Passwort und Administrator-Passwort würden in einer Skript-Befehlszeile in der Prozessliste und in der Shell-Historie stehen.

Nützliche Optionen: --dry-run (zeigt nur, was passieren würde), --version vX.Y.Z (eine bestimmte Fassung statt der neuesten), --token-file (nicht öffentliches Projekt), --yes. sh shortlink-setup.sh --help listet alle.

Für eine Aktualisierung ist derselbe Aufruf der ganze Vorgang. Kein Download von Hand, kein Entpacken, kein Nachdenken darüber, welches Skript das richtige ist.

Warum das Skript keine Prüfsumme von sich selbst hat

Die Frage liegt nahe — Composer macht es vor. Dort kommt der Hash von der Download-Seite und die Datei von einer anderen Adresse: zwei getrennte Handlungen, und ein späterer Tausch der Datei fällt auf.

Diese Trennung gibt es hier nicht. Der einzige Ort für einen Hash wäre dasselbe Repository, aus dem auch das Skript kommt — wer das eine ändern kann, ändert das andere im selben Commit. Dazu müsste der Hash bei jeder Änderung des Skripts mitgepflegt werden, und ein vergessener Hash meldet „korrupt" für eine völlig heile Datei. Das ist der Zustand, in dem Leute anfangen, die Prüfung zu überspringen.

Was stattdessen trägt:

Die Klammer im Skript Der ganze Rumpf steht in { … }. Ein halb übertragenes Skript führt damit nichts aus statt seiner ersten Zeilen — genau der Schaden, gegen den eine Prüfsumme bei einem Skript schützt. Nachgemessen: ohne Klammer läuft der Anfang, mit Klammer gibt es einen Syntaxfehler und sonst nichts.
Die Prüfsumme des Pakets Die erzwingt das Skript, und das Paket wird die laufende Anwendung. Es ist das lohnendere Ziel und die größere Datei.
Lesen Deshalb curl -fLO und kein curl … | sh. Vierhundert Zeilen sind in zwei Minuten gelesen — eine echte Prüfung, keine, die man blind mitkopiert.

Wer eine kryptographische Zusicherung über einen zweiten Kanal will: Das leistet ein signierter Tag, nicht eine Datei mit einer Zahl darin, die neben ihr liegt.

Das Release-Paket von Hand holen

Das Paket liegt in der Paket-Registry des Projekts und lässt sich direkt auf dem Server per Terminal holen — nichts muss über den eigenen Rechner laufen:

VERSION=v4.0.0   # die zu installierende Fassung — aktuelle siehe Release-Seite
BASE=https://gitlab.com/api/v4/projects/mankeldor%2Fshortlink/packages/generic/shortlink/$VERSION

curl -fL -o "shortlink-$VERSION.tar.gz"        "$BASE/shortlink-$VERSION.tar.gz"
curl -fL -o "shortlink-$VERSION.tar.gz.sha256" "$BASE/shortlink-$VERSION.tar.gz.sha256"

sha256sum -c "shortlink-$VERSION.tar.gz.sha256"   # MUSS "OK" sagen
tar -xzf "shortlink-$VERSION.tar.gz" && cd "shortlink-$VERSION"

Die Prüfsumme ist kein Beiwerk. Ein abgebrochener Download ergibt ein Archiv, das sich entpacken lässt und dem Dateien fehlen — und das fällt erst im Betrieb auf. sha256sum -c kostet eine Sekunde. Auf macOS heißt der Befehl shasum -a 256 -c.

Ist das Projekt nicht öffentlich, antwortet curl mit 404 statt mit dem Archiv (die API verrät nicht, dass es das Projekt gibt). Dann braucht der Aufruf ein Token:

curl -fL --header "DEPLOY-TOKEN: <token>" -o "shortlink-$VERSION.tar.gz" "$BASE/shortlink-$VERSION.tar.gz"

Nimm dafür ein Deploy-Token mit dem Recht read_package_registry (Settings → Repository → Deploy tokens), kein persönliches Zugangs-Token: Es gilt nur für dieses Projekt, nur zum Lesen, und es hängt nicht an einem Menschen, der irgendwann das Konto wechselt. Der Header heißt bei einem persönlichen oder Projekt-Token PRIVATE-TOKEN, im CI-Job JOB-TOKEN.

Ein Token auf einem Server steht in der Prozessliste und in der Shell-Historie, wenn es im Befehl steht. Für mehr als einen einmaligen Download gehört es in eine Datei mit chmod 600 und wird per --header "DEPLOY-TOKEN: $(cat /pfad/token)" gelesen.

Oder das Repository klonen (für Entwicklung)

git clone https://gitlab.com/mankeldor/shortlink.git
cd shortlink

Aus dem Repository heraus fehlen die gebauten Assets. Dann einmalig auf deinem Rechner npm ci && npm run build ausführen und public/build/ mit hochladen. Deshalb ist das Paket der bequemere Weg: Dort ist das Frontend fertig gebaut, und node braucht der Server nicht.

2. Abhängigkeiten installieren

composer install --no-dev --optimize-autoloader

2a. Verzeichnisse und Rechte prüfen

php artisan shortlink:check

Der Befehl ändert nichts. Er prüft PHP-Version und -Erweiterungen, ob die Betriebsverzeichnisse vorhanden und beschreibbar sind und ob die .env nicht für alle lesbar ist. Er braucht weder .env noch Datenbank, läuft also auch vor der Installation.

Beschreibbarkeit wird mit einem echten Schreibversuch geprüft und nicht über die Rechte-Bits — die sagen bei ACLs, NFS-Freigaben oder SELinux gern etwas anderes als die Wirklichkeit.

Fehlen Verzeichnisse — der Regelfall nach einem rsync, das storage/ ausspart:

mkdir -p storage/framework/views storage/framework/cache/data \
         storage/framework/sessions storage/logs \
         storage/app/private storage/app/public bootstrap/cache

Fehlt das Schreibrecht, gehören die Verzeichnisse dem Nutzer, unter dem PHP läuft (www-data, nginx, bei Shared Hosting meist dein eigener Benutzer):

chown -R <web-user>:<web-group> storage bootstrap/cache
chmod -R u+rwX storage bootstrap/cache

Der Befehl nennt in beiden Fällen die betroffenen Pfade und den passenden Aufruf.

3. Einrichten

Zwei gleichwertige Wege — nimm den, der zu dir passt.

Weg A: Kommandozeile

php artisan shortlink:install

Der Befehl prüft die Voraussetzungen, fragt Datenbank, Adresse und den ersten Administrator ab und richtet alles ein. Die Kommandozeile ist englisch — sie benutzt, wer deployt. Vorhandene Werte aus einer bestehenden .env sind vorbelegt; ein zweiter Lauf ist damit ein Bestätigen statt einer Neueingabe. Für automatisiertes Deployment ohne Rückfragen:

php artisan shortlink:install --no-interaction \
    --db-database=shortlink --db-username=shortlink --db-password='…' \
    --url=https://beispiel.de \
    --admin-name='Vorname Nachname' --admin-email=admin@beispiel.de --admin-password='…' \
    --update-check=false

Zwei der Fragen betreffen die Prüfung auf neue Fassungen:

Option Bedeutung
--update-check=true|false Einmal täglich nachsehen, ob es ein neueres Release gibt, und einen Hinweis in den Systemdaten zeigen. Aktualisiert wird dadurch nichts
--update-source=… Woher die Release-Liste kommt. Wird nur gefragt, wenn geprüft werden soll — nur nötig, wenn du eine eigene Fassung pflegst

Beides landet in der .env (SHORTLINK_UPDATE_CHECK, SHORTLINK_UPDATE_SOURCE) und ist dort jederzeit änderbar. Vorgabe ist true. Die Abfrage baut eine Verbindung nach außen auf und verrät dem Anbieter, dass es diese Installation gibt — in abgeschotteten Netzen also false.

Weg B: Browser

Rufe https://beispiel.de/install auf und folge den vier Schritten.

Diese Adresse ist ohne Anmeldung erreichbar, solange kein Administrator existiert — anders ginge es nicht, es gibt ja noch kein Konto. Sobald der erste Administrator angelegt ist, antwortet sie mit 404. Richte deshalb zügig ein, statt eine frisch hochgeladene Installation tagelang offen stehen zu lassen.

Im Schritt „Datenbank" steht neben den Zugangsdaten eine Auswahl „Auf neue Fassungen prüfen". Sie ist vorbelegt und jederzeit abwählbar; die Quelle der Release-Liste lässt sich nur in der .env ändern (SHORTLINK_UPDATE_SOURCE) — sie ist eine Expertenangabe und hätte auf dieser Seite nur Fragen aufgeworfen.

4. Die zwei Cron-Einträge

Beide sind nötig, und sie tun Verschiedenes. Pfad anpassen:

# 1) Aufräumen: abgelaufene Links stilllegen, alte Klick- und Versandprotokolle löschen,
#    eingespielte Update-Pakete entfernen.
#    OHNE diesen Eintrag greift KEINE der Aufbewahrungsfristen.
0 3 * * * cd /pfad/zu/shortlink && php artisan shortlink:cleanup --force >> /dev/null 2>&1

# 2) Ziel-Prüfung: nachsehen, ob gespeicherte Ziel-URLs inzwischen selbst auf eine andere
#    Domain weiterleiten. Betroffene Links zeigen dann eine Vorschauseite statt direkt
#    weiterzuleiten.
#    OHNE diesen Eintrag wird ein Ziel NIE geprüft. Auch nicht beim Anlegen: Das würde den
#    Knopf bis zu fünf Sekunden an einem fremden Server hängen lassen, deshalb übernimmt
#    ausschließlich dieser Lauf die Prüfung.
#    Wer einen Shortlink missbrauchen will, trägt ein harmloses Ziel ein und baut es
#    später um.
15 * * * * cd /pfad/zu/shortlink && php artisan shortlink:check-targets >> /dev/null 2>&1

Warum die Prüfung stündlich und das Aufräumen nächtlich: Der Befehl arbeitet mit Limit und Frist (Vorgabe 200 Links, 120 Sekunden) und nimmt beim nächsten Lauf dort weiter, wo er aufgehört hat. Stündlich in kleinen Portionen belastet die geprüften Server weniger als einmal täglich alles auf einmal — und ein umgebautes Ziel fällt schneller auf. Wie alt ein Prüfergebnis werden darf, steht unter Einstellungen → Links (Vorgabe 7 Tage); der Cron-Takt entscheidet nur, wie fein die Arbeit verteilt wird.

Die Prüfung braucht die Public Suffix List. Sie liegt im Paket bei; frisch holen mit php artisan shortlink:suffix-list (die einzige Stelle, an der diese Anwendung von selbst nach draußen greift, und nur wenn du den Befehl aufrufst).

Ein Queue-Worker ist NICHT nötig. Diese Anwendung hat keine Hintergrundarbeiten: keine Jobs, keine in die Warteschlange gestellten Nachrichten. QUEUE_CONNECTION darf auf sync stehen bleiben. (Bei einem Dateidienst war das anders — dort liefen Prüfsummen und Verschlüsselung im Hintergrund. Wer diese Anleitung von dort kennt: ein queue:work-Eintrag ist hier gegenstandslos.)

Dass php artisan queue:restart weiter im Aktualisierungsweg steht, ist Absicht: Der Befehl kostet nichts, wenn kein Worker läuft — er setzt nur eine Marke, auf die ein Worker beim nächsten Durchlauf achten würde. Ihn aus einem funktionierenden Ausliefer-Prozess zu entfernen wäre mehr Risiko als Nutzen, und käme je eine Hintergrundarbeit dazu, müsste er wieder hinein.

shortlink:cleanup ohne --force ist ein Testlauf: Er zeigt, was gelöscht würde, und verändert nichts. Führe ihn einmal von Hand aus, bevor du den Cron scharf schaltest. Er zählt bewusst nicht als Lauf (siehe nächster Absatz) — sonst meldete die Oberfläche nach diesem einen Aufruf einen Eintrag, den es noch gar nicht gibt.

Kontrolle im Browser: Beide Zeilen stehen nach der Installation auch unter Einstellungen → Systemdaten, Karte „Zeitsteuerung (Cron)" — mit dem Pfad dieser Installation und einem Knopf zum Kopieren. Dort steht ebenfalls, wann jeder Befehl zuletzt gelaufen ist; fehlt ein Lauf oder ist er überfällig, erscheint oben auf der Seite eine Warnung. Gelesen wird dafür nicht die crontab (die Anwendung ruft keine Shell auf), sondern ein Zeitstempel, den die Befehle selbst hinterlassen.

5. Nach der Installation

Die Reihenfolge der ersten zwei Schritte ist nicht beliebig. Der Installer legt den ersten Administrator mit einer unbestätigten Adresse an — bewiesen ist eine Adresse erst, wenn jemand einen an sie geschickten Link angeklickt hat, und beim Installieren wird sie getippt oder aus einer Pipeline-Variablen gesetzt. Bis zur Bestätigung kann dieses Konto alles (Einstellungen, Nutzer, Seiten), nur keine Shortlinks anlegen. Und um sich zu bestätigen, braucht es den Mailversand. Also erst Mail, dann bestätigen.

  1. Mailversand einrichten (Einstellungen → E-Mail). Ohne ihn lassen sich keine weiteren Konten einladen und keine Passwörter zurücksetzen. Es gibt dort eine Test-Mail-Schaltfläche, die die Serverantwort im Klartext zeigt.
  2. Eigene Adresse bestätigen (Konto → „Bestätigungsmail senden", dann den Link in der Mail anklicken). Kommt die Mail nicht an, ist die Adresse falsch geschrieben oder der Versand nicht in Ordnung — beides ist jetzt zu bemerken die halbe Miete, denn dieselbe Adresse trägt später den Passwort-Reset und die Benachrichtigung über automatisch stillgelegte Links.
  3. Pflichtseiten füllen (Seiten). Impressum, Datenschutzerklärung und Barrierefreiheitserklärung liegen als Platzhalter bereit. Die Barrierefreiheitserklärung braucht einen erreichbaren Feedback-Weg — ein mailto:-Link genügt, ein Formular ist nicht vorgeschrieben.
  4. Zwei-Faktor-Anmeldung aktivieren (Konto → Sicherheit).
  5. Public Suffix List holen (php artisan shortlink:suffix-list). Die mitgelieferte Kopie funktioniert, veraltet aber — und ohne Liste bekommt jedes Ziel eine Vorschauseite.
  6. Verhalten bei Zielen festlegen (Einstellungen → Links): vertrauenswürdige Domains, Vorschauseite, Prüf-Frist.

Deployment über die GitLab-Pipeline

Die Pipeline erkennt selbst, ob das Ziel schon eingerichtet ist (php artisan shortlink:installed, Exit-Code 0 oder 1) — von außen ließe sich das nicht zuverlässig sagen, denn eine vorhandene .env verrät nicht, ob je migriert wurde.

Zustand des Ziels Was die Pipeline tut
eingerichtet Wartungsmodus, Dateien übertragen, composer install, migrieren, Caches schreiben, Queue neu starten, Wartungsmodus beenden
frisch Dateien übertragen, composer installkeine Migration, kein Cache. Der Job endet mit einer Handlungsaufforderung und wird als Warnung (orange) angezeigt

Der frische Fall ist bewusst keine Fehlermeldung: Die Übertragung hat funktioniert, es fehlt nur noch die Einrichtung. Danach genügt ein Durchlauf über php artisan shortlink:install auf dem Server oder /install im Browser; jedes weitere Deployment läuft dann automatisch den vollständigen Weg.

Migrationen und Caches bleiben im frischen Fall bewusst aus: Erstere hätten keine Datenbank, und ein Config-Cache würde die .env festschreiben, die der Installer erst noch schreibt.

Vor dem Übertragen prüft die Pipeline zusätzlich, ob das Zielverzeichnis beschreibbar ist und ob php (mindestens 8.3) und composer im PATH liegen. Ein rsync auf einen Server ohne diese Voraussetzungen hinterlässt sonst eine halbfertige Installation.

Aktualisieren

Das Release-Archiv taugt für beides — neue Installation und Aktualisierung einer bestehenden. Für die Aktualisierung gilt aber: nicht einfach über die vorhandene Installation entpacken. Drei Dinge gehen dabei schief, und keines davon meldet sich.

Falle Was passiert
Entfernte Dateien bleiben liegen Ein Update fügt hinzu und überschreibt, es löscht nicht. Klassen und Templates, die es in der neuen Fassung nicht mehr gibt, bleiben im Verzeichnis stehen — und config/-Dateien, die entfallen sind, werden weiterhin geladen
storage/ und bootstrap/cache/ werden angefasst Beide liegen im Archiv, weil eine NEUE Installation sie braucht. Über eine bestehende gelegt, setzen sie die Zugriffsrechte dieser Verzeichnisse zurück — und als root entpackt auch den Eigentümer
Gecachte Konfiguration überlebt bootstrap/cache/config.php stammt noch von der alten Fassung und ignoriert alles, was sich geändert hat

Der kürzeste Weg: das Setup-Skript

Für eine bestehende Installation ist es derselbe Aufruf wie bei der Erstinstallation (Abschnitt 1) — das Skript erkennt den Zustand des Ziels selbst:

curl -fLO https://gitlab.com/mankeldor/shortlink/-/raw/main/shortlink-setup.sh
sh shortlink-setup.sh /pfad/zu/deiner/installation --dry-run
sh shortlink-setup.sh /pfad/zu/deiner/installation

Es holt das neueste Release, prüft die Prüfsumme und übergibt dann an genau das update.sh aus dem neu geladenen Paket — nicht an eine ältere Fassung, die auf dem Server herumliegt. Alles, was unten über update.sh steht, gilt also unverändert; das Skript spart nur den Download und das Entpacken von Hand.

Wer den Weg von Hand gehen will oder ein Paket schon liegen hat, liest weiter.

Der Weg: update.sh

Im Paket liegt ein Skript, das die Aktualisierung durchführt. Es läuft aus dem entpackten Paket heraus und bekommt den Pfad zu deiner Installation — es gibt also keinen fest verdrahteten Ort, an dem eine Installation zu liegen hätte.

tar -xzf shortlink-v2.0.1.tar.gz          # erzeugt ./shortlink-v2.0.1/, überschreibt nichts
cd shortlink-v2.0.1

sh update.sh /pfad/zu/deiner/installation --dry-run    # zeigt, was passieren würde
sh update.sh /pfad/zu/deiner/installation

Was es tut, in dieser Reihenfolge:

  1. Prüft, dass das Ziel wirklich eine Shortlink-Installation ist (artisan, composer.json, app/Support/Installer.php, .env). Gleich wird mit --delete abgeglichen; ein vertippter Pfad würde sonst ein fremdes Verzeichnis ausräumen.
  2. Sichert Datenbank, .env und Programmstand nach ../shortlink-backup-<zeitstempel>/. Scheitert die Sicherung, bricht es ab, bevor irgendetwas angefasst wurde — mit --skip-backup lässt sich das überstimmen, wenn du eine eigene Sicherung hast.
  3. Schaltet den Wartungsmodus ein — noch mit der alten Fassung, deren vendor/ vollständig ist.
  4. Ersetzt die Dateien und entfernt dabei die, die es in der neuen Fassung nicht mehr gibt. Unangetastet bleiben .env, storage/, public/storage, bootstrap/cache/ und vendor/. Mit rsync, wenn vorhanden — sonst mit einem Kopiervorgang und anschließendem Aufräumen anhand der Dateiliste des Pakets.
  5. composer install, shortlink:check, migrate --force, Caches, queue:restart, Wartungsmodus aus.

Scheitert etwas nach Schritt 4, bleibt die Installation absichtlich im Wartungsmodus — ein halb ausgetauschter Stand darf nicht ausgeliefert werden. Das Skript schreibt dann hin, wie du zurückkommst.

Die Reihenfolge in Schritt 5 ist der Grund, warum es das Skript überhaupt gibt: Zwischen dem Dateiaustausch und composer install steht neuer Code auf altem vendor/. Jeder Artisan-Aufruf in diesem Fenster kann mit „class not found" abbrechen.

Von Hand

Wer es lieber selbst steuert — dieselben Schritte:

mysqldump --single-transaction --quick -u <user> -p <db> | gzip > db-$(date +%F).sql.gz
cp .env ~/env-$(date +%F).bak

cd /pfad/zu/deiner/installation
php artisan down --retry=60
rsync --archive --delete \
      --exclude='/.env' --exclude='/storage/' \
      --exclude='/public/storage' --exclude='/bootstrap/cache/' --exclude='/vendor/' \
      ~/shortlink-v2.0.1/ ./
composer install --no-dev --optimize-autoloader
php artisan shortlink:check
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan queue:restart
php artisan up

Migrationen laufen vorwärts. Es gibt keinen Weg zurück außer der Sicherung aus der ersten Zeile.

Danach

Die .env bleibt unangetastet. Die mitgelieferte .env.example lohnt trotzdem einen Blick — dort stehen neue Schlüssel drin, bevor du sie vermisst:

diff <(grep -oE '^[A-Z0-9_]+' .env | sort -u) \
     <(grep -oE '^[A-Z0-9_]+' .env.example | sort -u)

Fehlt ein Schlüssel in deiner .env, gilt der Vorgabewert aus config/ — die Anwendung läuft also. Angesehen haben solltest du ihn trotzdem.


Wenn etwas nicht läuft

Symptom Ursache
composer install bricht mit einem Datenbankfehler ab Ältere Fassung als 1.1. Der Bootvorgang setzte eine Datenbank voraus, die es beim ersten Lauf noch nicht gibt
/install antwortet mit 404 Es existiert bereits ein Administrator. Der Installer schließt sich selbst — das ist Absicht
Weiße Seite, keine Gestaltung public/build/ fehlt. Assets bauen und hochladen
500er beim ersten Seitenaufruf, failed to open stream: Permission denied Ein Verzeichnis unter storage/ fehlt oder ist nicht beschreibbar. php artisan shortlink:check nennt es
.env ist für alle lesbar Auf einem geteilten Server liest jeder Nachbar Datenbank-Passwort und Schlüssel mit: chmod 640 .env
Logo wird nicht angezeigt public/storage fehlt: php artisan storage:link, oder von Hand verknüpfen
Zugangsdaten stimmen, werden aber abgewiesen Ein # im Passwort in der .env. Wert in doppelte Anführungszeichen setzen — sonst schneidet der Parser ihn dort ab
Änderungen wirken nicht php artisan config:clear — eine gecachte Konfiguration ignoriert die .env vollständig

Ausführlich: docs/ADMIN-HANDBUCH.md.

Quelle dieser Seite: INSTALL.md im Repository. Sie wird beim Bauen daraus erzeugt — Änderungen gehören in die Markdown-Datei, nicht in diese Seite.