Shortlink — Administrator-Handbuch

Für Betreiber einer Installation: Einrichtung, Konfiguration, Betrieb.

Für die tägliche Arbeit mit dem System gibt es das Benutzerhandbuch. Für Entwicklung und Konventionen: CONVENTIONS.md, ACCESSIBILITY.md.

Stand: 4.0.0.


Inhalt

  1. Was Shortlink ist — und was nicht
  2. Voraussetzungen
  3. Installation
  4. Konfiguration in der .env
  5. Systemeinstellungen in der Oberfläche
  6. Wie ein Aufruf abläuft
  7. Ziel-Prüfung, Vorschauseite und Aufsicht
  8. Konten und Rollen
  9. Redaktionelle Seiten und Betreiberpflichten
  10. Sicherheit: was das System tut und warum
  11. Datenhaltung und Aufbewahrung
  12. Backup und Wiederherstellung
  13. Wartung
  14. Aktualisierung
  15. Störungssuche
  16. Grenzen und bekannte Lücken

Shortlink ist ein selbst betriebener Dienst zum Kürzen von Adressen. Ein angemeldeter Nutzer trägt eine lange Ziel-Adresse ein und erhält eine kurze, die er weitergeben kann. Der Besucher braucht kein Konto.

Die zwei Anwendungsfälle:

Fall Wer handelt Weg
Kurz-Adresse anlegen angemeldeter Nutzer Ziel eintragen → Kurz-Adresse erhalten → weitergeben
Kurz-Adresse aufrufen Besucher ohne Konto Adresse öffnen → ggf. Passwort → ggf. Vorschauseite → Weiterleitung

Der eigentliche Grund, einen solchen Dienst selbst zu betreiben, sind nicht die kurzen Adressen. Es sind drei Dinge, die ein öffentlicher Kürzungsdienst nicht bietet: Die Kurz-Adresse liegt auf deiner Domain, das Ziel bleibt änderbar (ein gedruckter Link bleibt gültig, während die Zielseite umzieht), und die Aufrufzahlen bleiben im Haus.

Was Shortlink nicht ist: keine Statistik-Plattform (es gibt Zähler, aber keine Auswertung nach Herkunft, Gerät oder Kampagne), kein Marketing-Werkzeug, kein Werbe-Tracker, keine Dateiablage.

Bewusste Entscheidungen, die den Zuschnitt erklären:

  • Keine öffentliche Registrierung. Konten legt ausschließlich ein Administrator an.
  • Eine Kurz-Adresse wird nie ein zweites Mal vergeben. Auch nicht, nachdem der Link abgelaufen oder gelöscht wurde. Der Datensatz bleibt, der Slug bleibt belegt. Würde er frei, könnte ein später angelegter Link ihn erben — und ein alter, längst verschickter oder gedruckter Shortlink führte plötzlich auf ein fremdes Ziel.
  • Weiterleitung immer mit 302, niemals 301. Eine dauerhaft zwischengespeicherte Weiterleitung erreicht bei regelmäßigen Besuchern die Anwendung nie wieder: Ein geändertes Ziel greift bei ihnen nicht, und der Zähler sieht sie nicht mehr.
  • Alle Wege der Nutzer funktionieren ohne JavaScript. Mit JavaScript wird es bequemer (Live-Prüfung der gewünschten Adresse, Detailansicht ohne Seitenwechsel), aber kein Anwendungsfall hängt davon ab.
  • Deutschsprachige Oberfläche. Es gibt derzeit keine Mehrsprachigkeit.

2. Voraussetzungen

Pflicht

Bereich Anforderung
PHP 8.3 oder neuer
PHP-Erweiterungen openssl, pdo, mbstring, fileinfo, json, zip, gd, intl, sodium
Datenbank MySQL 8 / MariaDB 10.6 oder neuer (SQLite und PostgreSQL laufen technisch, sind aber nicht geprüft)
Webserver nginx oder Apache mit PHP-FPM
Composer 2.x
Node.js 20 oder neuer — nur zum Bauen der Oberfläche, nicht im Betrieb
Verzeichnisrechte storage/ und bootstrap/cache/ müssen für den PHP-Prozess schreibbar sein

Speicherplatz ist hier kein Thema: Diese Anwendung speichert keine Dateien von Nutzern. Was wächst, sind Datenbankzeilen — Links, Klick- und Versandprotokolle.

Die geladenen Erweiterungen und Verzeichnisrechte prüfst du vor der Installation auf der Kommandozeile:

php artisan shortlink:check

Nach der Installation stehen dieselben Werte in der Oberfläche: Einstellungen → Systemdaten → Ausführliche Diagnose.

Dringend empfohlen

  • HTTPS. Ohne TLS gehen Passwörter und Sitzungs-Cookies im Klartext über die Leitung. Außerdem funktioniert „In die Zwischenablage kopieren" in Browsern nur in einem sicheren Kontext.
  • Zwei Cron-Einträge — Aufräumen und Ziel-Prüfung (siehe Wartung). Ohne den zweiten wird kein Ziel jemals geprüft.
  • Ein Mail-Versand. Ohne ihn lassen sich keine Konten einrichten, keine Passwörter zurücksetzen und keine Adressen ändern. Das System ist dann nur eingeschränkt nutzbar.

Kein Queue-Worker nötig. Es gibt in dieser Anwendung keine Hintergrundarbeit; QUEUE_CONNECTION=sync genügt. Wer aus einer älteren Fassung oder aus dem Quellprojekt kommt: Der Worker war für Prüfsummen und Dateiverschlüsselung da, beides gibt es hier nicht.

Keine Anpassung von Upload-Grenzen. upload_max_filesize, post_max_size und client_max_body_size spielen keine Rolle — es wird nichts hochgeladen außer einem Logo und einer Theme-Datei durch Administratoren.


3. Installation

Es gibt drei Wege. Alle drei kommen am selben Punkt heraus: eine geschriebene .env, eine migrierte Datenbank und ein erster Administrator.

Davor steht in allen drei Fällen derselbe Schritt: die Dateien auf den Server bringen. Dafür gibt es ein Skript, das auf dem Zielserver läuft:

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

Es holt das neueste Release, prüft die Prüfsumme, entpackt, legt ab und installiert die Abhängigkeiten — und es sieht nach, was am Ziel steht: leer heißt Erstinstallation, eine erkennbare Installation heißt Aktualisierung (dann übergibt es an das update.sh aus dem neu geladenen Paket), und etwas anderes heißt Abbruch, ohne etwas zu überschreiben. Derselbe Aufruf ist damit auch der ganze Vorgang einer Aktualisierung.

Es endet vor shortlink:install, weil Datenbank- und Administrator-Passwort sonst in der Prozessliste und der Shell-Historie stünden. Der Weg von Hand, der Umgang mit einem nicht öffentlichen Projekt (Deploy-Token) und die Prüfung der SHA256-Summe stehen in INSTALL.md, Abschnitt 1.

Lies das Skript, bevor du es ausführst. Deshalb steht dort curl -fLO und kein curl … | sh: Gepipet führt es Code aus, den niemand gesehen hat — und bei einer abgebrochenen Verbindung führt es ihn halb aus.

Die Prüfsumme gehört zum Download, nicht zur Kür. Ein abgebrochener Download ergibt ein Archiv, das sich entpacken lässt und dem Dateien fehlen — und das fällt erst im Betrieb auf, an einer Stelle, die mit dem Download nichts zu tun zu haben scheint.

Im Browser (Assistent)

Der bequemste Weg für eine einzelne Installation. Quellen ablegen, Abhängigkeiten installieren, Oberfläche bauen — dann die Adresse im Browser öffnen. Solange keine Installation erkannt wird, führt jeder Aufruf auf den Assistenten:

  1. Voraussetzungen — PHP-Version, Erweiterungen, Schreibrechte.
  2. Datenbank — Zugangsdaten; der Assistent schreibt die .env und migriert.
  3. Administrator — Name, Adresse, Passwort des ersten Kontos.
  4. Fertig.

Nach der Installation ist der Assistent gesperrt (Middleware installer-open); er lässt sich nicht versehentlich ein zweites Mal aufrufen.

Auf der Kommandozeile

Für automatisierte oder wiederholte Installationen. Derselbe Ablauf, ohne Browser:

php artisan shortlink:install \
  --db-host=127.0.0.1 --db-port=3306 \
  --db-database=shortlink --db-username=shortlink --db-password='…' \
  --url=https://go.example.org --name='Kurzlinks Beispiel AG' \
  --admin-name='Vorname Nachname' --admin-email=admin@example.org \
  --admin-password='<langes Passwort>'

Fehlende Angaben fragt der Befehl interaktiv nach. Weitere Optionen: --db-prefix (wenn die Datenbank mit anderen Anwendungen geteilt wird), --update-check, --update-source, --force (weitermachen, obwohl eine Installation erkannt wurde).

Ob eine Installation eingerichtet ist, beantwortet ein eigener Befehl über den Exit-Code — brauchbar in Deployment-Skripten:

php artisan shortlink:installed   # 0 = eingerichtet, 1 = noch nicht

Von Hand

# 1. Quellen holen und Abhängigkeiten installieren
git clone <repository> shortlink && cd shortlink
composer install --no-dev --optimize-autoloader
npm ci && npm run build          # baut CSS und JS nach public/build

# 2. Konfiguration anlegen
cp .env.example .env
php artisan key:generate         # setzt APP_KEY

# 3. .env bearbeiten — mindestens:
#    APP_ENV=production, APP_DEBUG=false, APP_URL=https://…
#    DB_CONNECTION=mysql und die Zugangsdaten

# 4. Datenbank aufbauen
php artisan migrate --force

# 5. Öffentlichen Symlink für das Logo setzen
php artisan storage:link

# 6. Für den Betrieb zwischenspeichern
php artisan config:cache && php artisan route:cache && php artisan view:cache

Den ersten Administrator legst du danach mit php artisan shortlink:install oder über den Browser-Assistenten an.

Der Webserver muss auf public/ zeigen, nicht auf das Projektverzeichnis.

Mit GitLab CI/CD

Die Pipeline in .gitlab-ci.yml kann ein leeres Zielverzeichnis anlegen und beim ersten Lauf Quellcode, PHP-Abhängigkeiten und Frontend-Assets installieren. Sie legt aber absichtlich weder eine Datenbank noch Geheimnisse an. Diese Werte gehören nicht ins Repository und werden wegen .rsync-exclude auch nicht an den Server übertragen.

Vor dem ersten Pipeline-Lauf daher einmalig:

  1. Datenbank und Datenbankbenutzer beim Hoster bzw. Datenbankserver anlegen.

  2. Das Zielverzeichnis für den Deployment-Benutzer zugänglich machen; es darf leer sein.

  3. Im Zielverzeichnis eine produktive .env anlegen. Mindestens nötig sind:

    APP_ENV=production
    APP_DEBUG=false
    APP_URL=https://go.example.org
    APP_KEY=base64:<zufaelliger-32-byte-schluessel>
    DB_CONNECTION=mysql
    DB_HOST=<datenbank-host>
    DB_PORT=3306
    DB_DATABASE=<datenbankname>
    DB_USERNAME=<datenbankbenutzer>
    DB_PASSWORD=<datenbankpasswort>
    
  4. Die GitLab-Variablen SSH_PRIVATE_KEY (maskiert, Base64-kodiert), SSH_KNOWN_HOSTS (File-Variable) sowie SERVER_USER, SERVER_IP, SERVER_PATH und gegebenenfalls SERVER_PORT setzen. Der Host-Key wird vorab lokal ermittelt und geprüft, nie erst in der Pipeline per ssh-keyscan bezogen.

Der erste erfolgreiche Lauf führt Migrationen aus und setzt den Symlink public/storage, wenn er noch fehlt. Für spätere Deployments bleiben .env, storage/ und vendor/ erhalten.

APP_KEY nach dem ersten Produktiveinsatz nicht neu erzeugen: Ohne den bisherigen Wert sind gespeicherte Sitzungen und das in der Datenbank abgelegte SMTP-Passwort nicht mehr lesbar.

Nach der Installation, in dieser Reihenfolge

Der erste Administrator ist zunächst unbestätigt, und das ist Absicht. Der Installer erklärt keine Adresse für bestätigt — bewiesen ist sie erst, wenn jemand einen an sie geschickten Link angeklickt hat. Beim Assistenten wird sie getippt, über die Pipeline kommt sie aus einer Variablen; ein Tippfehler wäre nirgends zu sehen, denn das Konto funktioniert und nur die Mails gehen ins Nichts.

Bis zur Bestätigung kann dieses Konto alles außer Shortlinks anlegen: Einstellungen, Nutzer, Seiten, Aufsicht. Deshalb steht der Mailversand hier an erster Stelle — ohne ihn lässt sich die Bestätigung nicht verschicken.

  1. Einstellungen → E-Mail ausfüllen und Test-Mail senden.
  2. Eigene Adresse bestätigen: Konto → „Bestätigungsmail senden", dann den Link in der Mail anklicken. Kommt sie nicht an, ist entweder die Adresse falsch geschrieben oder der Versand nicht in Ordnung — und beides jetzt zu bemerken ist der eigentliche Gewinn dieses Schritts. Dieselbe Adresse trägt später den Passwort-Reset und die Nachricht über automatisch stillgelegte Links.
  3. Zwei-Faktor-Anmeldung einrichten (Konto → Sicherheit). Die Wiederherstellungs-Codes an einem anderen Ort ablegen als das Passwort.
  4. Einstellungen → Allgemein: Name der Installation, gegebenenfalls Logo.
  5. Einstellungen → Links durchgehen — das ist der Bereich, der über das Verhalten dieses Dienstes entscheidet (siehe Kapitel 7).
  6. Einstellungen → Funktionen durchgehen.
  7. Public Suffix List holen: php artisan shortlink:suffix-list. Die mitgelieferte Kopie funktioniert, veraltet aber.
  8. Seiten füllen: Impressum, Datenschutz, Barrierefreiheit.
  9. Beide Cron-Einträge einrichten (siehe Wartung).
  10. Backup einrichten und eine Wiederherstellung testen.

4. Konfiguration in der .env

Grundlagen

Schlüssel Bedeutung
APP_ENV Im Betrieb production
APP_DEBUG Im Betrieb false. true zeigt bei Fehlern interne Pfade und Konfigurationswerte
APP_KEY Wird von key:generate gesetzt. Verlust macht verschlüsselte Sitzungen und das gespeicherte Mail-Passwort unlesbar
APP_URL Vollständige öffentliche Adresse mit https://. Steht in allen Links in Mails und ist die Grundlage jeder ausgegebenen Kurz-Adresse
APP_TIMEZONE Zeitzone für Anzeige und gespeicherte Zeitstempel. Standard Europe/Berlin. Nur vor dem ersten Produktivdatensatz ändern — siehe unten
APP_NAME Nur noch Präfix für Cache-, Session- und Queue-Schlüssel — nicht der angezeigte Name
SHORTLINK_NAME Ausgangswert des angezeigten Namens; überschreibbar in Einstellungen → Allgemein
DB_* Datenbank-Zugang
SESSION_DRIVER database (Standard)
SESSION_SECURE_COOKIE Auf einer Installation mit HTTPS true. Ohne diesen Schalter kann das Session-Cookie im Klartext übertragen werden
SESSION_SAME_SITE lax (Standard) — Schutz gegen Cross-Site-Requests
QUEUE_CONNECTION sync genügt. Es gibt keine Hintergrundarbeit
MAIL_* Nur Ausgangswerte. Sobald unter Einstellungen → E-Mail ein Zugang gepflegt ist, hat dieser Vorrang

Die kurze Domain gehört in APP_URL, und diese Entscheidung ist schwer zurückzunehmen. Jede ausgegebene Kurz-Adresse setzt sich aus APP_URL und dem Slug zusammen. Wird die Domain später gewechselt, sind alle bereits verschickten und gedruckten Adressen ungültig — der Slug allein hilft dann nichts. Wähle die Domain vor dem ersten produktiven Link, und wähle sie kurz: Bei einem Kürzungsdienst ist die Länge der Domain der Teil, den niemand mehr ändern kann.

Mehrere Domains auf derselben Installation: erlaubt, aber nur eine steht in den Adressen. Zeigen weitere Domains auf diesen Server — Altbestand, eine Testdomain, www. davor, die nackte IP —, dann funktioniert die Anwendung über jede von ihnen vollständig: Jede löst jeden Shortlink auf, zeigt jede Vorschauseite und lässt jede Anmeldung zu. Ausgegeben wird aber immer die Adresse aus APP_URL — im Feld „Kurz-Adresse", im QR-Code, im CSV-Export und in den System-Mails.

Das ist Absicht und nicht zu umgehen: Vorher bekam ein Link die Domain, über die jemand die Oberfläche zufällig gerade geöffnet hatte. Auf dem Bildschirm fällt so ein Link nicht auf — auf dem Plakat steht er dann für immer.

Das gilt für alles, was das Haus verlässt, nicht nur für Kurz-Adressen: Auch die Links in System-Mails — Passwort zurücksetzen, Einladung, Adresse bestätigen, Zwei-Faktor neu einrichten, „Link ansehen" — tragen die Adresse aus APP_URL. Eine Mail wird Stunden später in einem anderen Browser geöffnet, weitergeleitet, ausgedruckt, in ein Ticket kopiert; ab dem Absenden korrigiert diesen Link niemand mehr. Das ist dieselbe Lage wie bei einem Shortlink und deshalb dieselbe Regel.

Nicht betroffen sind Links und Formulare INNERHALB einer geöffneten Seite. Sie bleiben auf dem Host, über den zugegriffen wird — dort liegt die Sitzung des Nutzers. Ein Passwort-Formular, das auf die festgelegte Domain abschickt, vermerkte die Freischaltung auf einem Host, den der Besucher nie zu sehen bekommt, und er stünde danach wieder vor der Passwortmaske.

Wer die Oberfläche über eine andere Domain öffnet, sieht angemeldet einen dauerhaften Hinweis unter der Kopfzeile, der beide Adressen nennt. Er lässt sich nicht wegklicken, weil der Zustand fortbesteht, solange dort gearbeitet wird. Besucher sehen ihn nie.

Fehlt APP_URL oder steht dort kein https:// davor, gilt wieder der Host der Anfrage — geraten wird nichts. Einstellungen → Systemdaten meldet diesen Zustand als Warnung; Einstellungen → Links zeigt oben, welche Adresse gerade gilt.

Zwei .env-Syntaxregeln, die stumm zuschlagen. Ein # in einem nicht quotierten Wert schneidet ihn ab — aus Ge#heim wird Ge, ohne Fehlermeldung. Und ein Leerzeichen in einem nicht quotierten Wert ist ein Parse-Fehler, nach dem die Anwendung gar nicht mehr startet. Passwörter, Schlüssel und alles mit Sonderzeichen deshalb immer in doppelte Anführungszeichen setzen. Im Zweifel nachsehen statt annehmen: php artisan tinker --execute="var_dump(env('MAIL_PASSWORD'));"

Schlüssel Standard Bedeutung
SHORTLINK_EXPIRED_RETENTION_DAYS 3 Karenz, bis ein abgelaufener Link stillgelegt wird. 0 = beim nächsten Lauf, -1 = nie. Stillgelegt, nicht gelöscht — der Slug bleibt dauerhaft belegt
SHORTLINK_CLICK_LOG_RETENTION_DAYS 90 Aufbewahrung der Klick-Protokolle (gekürzte IP, Zeitpunkt, Browserkennung). -1 = nie löschen. Der Zähler am Link bleibt
SHORTLINK_SEND_LOG_RETENTION_DAYS 90 Aufbewahrung des Versandprotokolls (Empfängeradresse, Zeitpunkt, Ergebnis). -1 = nie löschen. Sinnvollerweise derselbe Wert wie die Zeile darüber — sonst zeigt die Zeitleiste Aufrufe ohne den zugehörigen Versand
SHORTLINK_SUFFIX_LIST_SOURCE https://publicsuffix.org/list/public_suffix_list.dat Woher shortlink:suffix-list eine frische Liste holt. Auf einen internen Spiegel umstellbar
SHORTLINK_UPDATE_CHECK true Einmal täglich beim Öffnen der Systemdaten nachsehen, ob es eine neuere Fassung gibt. Auf false setzen, wenn diese Installation keine Verbindung nach außen aufbauen soll — die Abfrage verrät dem Anbieter, dass es sie gibt
SHORTLINK_UPDATE_SOURCE GitLab-API des Projekts Woher die Release-Liste kommt. Nur ändern, wenn du eine eigene Fassung pflegst

Zwei Pfade sind bewusst nicht über die .env einstellbar, sondern stehen in config/shortlink.php (suffix_list_file, version_file, env_file, Vorgabe jeweils null = „der echte Ort"). Der Ort dieser Dateien ist keine Betriebseinstellung; die Einstellbarkeit existiert, damit der Testlauf nicht ins Projektverzeichnis greift.

Nach jeder Änderung an der .env:

php artisan config:cache

Zeitzone

Standard ist Europe/Berlin — bewusst nicht Laravels Vorgabe UTC. Alle Zeitangaben dieser Anwendung sind für Menschen gedacht: das Ablaufdatum auf der Vorschauseite, das „gültig bis" in der Mail an den Empfänger, die Zeitstempel in Übersicht und Systembericht. Eine Uhrzeit, die im Sommer zwei Stunden danebenliegt, ist für den Empfänger schlicht falsch — und niemand rechnet sie um.

Für Installationen in anderen Zonen: APP_TIMEZONE=Europe/Vienna (oder Europe/Zurich und so weiter).

Diese Entscheidung gehört an den Anfang. Zeitstempel werden in dieser Zone geschrieben und gelesen. Wird die Zone später gewechselt, verschieben sich die bereits gespeicherten Werte in ihrer Deutung: Ein Link, der „bis 23:59" gilt, gilt plötzlich bis 22:59 oder 00:59. Bei einer laufenden Installation ist das kein Konfigurations-, sondern ein Datenthema.


5. Systemeinstellungen in der Oberfläche

Erreichbar über das Zahnrad im Kopfbereich → Einstellungen, nur für Administratoren. Sieben Bereiche mit eigener Adresse:

Bereich Inhalt
Allgemein Name der Installation, Logo
Funktionen ein systemweiter Schalter: darf ein Link dauerhaft gelten
Links vertrauenswürdige Ziel-Domains, Vorschauseite, Prüf-Frist, zwei Beschränkungen
Erscheinungsbild eigene tokens.css mit Farben und Schrift
E-Mail Versandweg, Absender, SMTP-Zugang, Test-Mail
Wartung Aufräumen von Hand anstoßen, Testlauf
Systemdaten Fassung und Aktualisierung, Betriebswerte, Diagnose, Bericht zum Kopieren

Der Bereich Links ist der wichtigste und steht deshalb in einem eigenen Kapitel: siehe Kapitel 7.

Allgemein

Name erscheint als Wortmarke (wenn kein Logo hinterlegt ist), im Browser-Titel, in allen System-Mails und als Aussteller in der Authenticator-App. Hier darf der Unternehmensname stehen. Leer lassen setzt den Standard.

Logo erscheint im Kopfbereich und im Kopf der Mails. Empfohlen: SVG oder PNG mit durchsichtigem Hintergrund, Höhe etwa 32 Pixel. Ohne Logo steht dort der Name.

Bereits eingerichtete Zwei-Faktor-Anmeldungen behalten den alten Namen in der Authenticator-App. Der Aussteller steckt im QR-Code, nicht auf dem Server.

Funktionen

Eine Karte je Entscheidung: wem die Links gehören, die Gültigkeit von Shortlinks, eigene Kurz-Adressen, QR-Codes und der Passwortschutz von Links. Alle Werte stehen ab Werk so, wie sich das System ohne sie verhält — eine Aktualisierung nimmt einer laufenden Installation nichts weg, ohne dass jemand es entscheidet. Neben jeder Entscheidung steht, wie viele Links heute davon betroffen wären.

Eine Ausnahme von „nimmt nichts weg": der Schalter ganz oben. Ein Wechsel vom gemeinsamen Bestand in den Benutzer-Modus löscht alle Bereiche — die Links bleiben, die Gruppierung nicht. Die Karte nennt die Zahl vorher; rückgängig machen lässt es sich nicht. Warum das so sein muss, steht unter Was der Schalter mit den Bereichen macht.

„Kein Ablauf" zur Auswahl stellen. Ohne diesen Schalter muss bei jedem Link ein Zeitraum gewählt werden. Bestehende dauerhafte Links laufen weiter — ihre Empfänger können nichts dafür. Die Option verschwindet nur aus allen Masken; beim nächsten Speichern eines solchen Links ist ein Zeitraum fällig, und die Maske sagt das vorher.

Warum das überhaupt ein Schalter ist: Ein dauerhafter Link hat kein berechenbares Ende. Das Aufräumen kann ihn nie stilllegen, also bleibt er, bis jemand ihn löscht. Wer eine Installation aufräumbar halten will, verbietet die Option — dann hat jeder Link ein Ende.

Höchstdauer. Vorgabe: ohne Begrenzung. Längere Zeiträume verschwinden aus allen Masken und aus der serverseitigen Prüfung — eine Grenze, die nur die Auswahl kürzt, wäre mit einem gefälschten Formularwert umgangen.

Eine Höchstdauer schließt „Kein Ablauf" mit aus, sonst wäre sie mit einem Griff zu umgehen. Beides zusammen wird beim Speichern abgelehnt und nicht stillschweigend aufgelöst: Ein Formular, das eine Angabe kommentarlos umdeutet, hinterlässt einen Betreiber, der eine Erfolgsmeldung gelesen und ein anderes Verhalten bekommen hat.

Bestehende Links werden nicht gekürzt. Beim Bearbeiten bleibt „Unverändert" wählbar, auch wenn die Restlaufzeit über der Höchstdauer liegt. Andernfalls verkürzte schon das Speichern eines geänderten Titels die Laufzeit eines Links im Umlauf — und das trifft dessen Empfänger, nicht den Bearbeitenden. Die Grenze gilt für neu gewählte Zeiträume.

Vorbelegung beim Anlegen. Womit die Auswahl steht, wenn niemand sie anfasst — und das ist der Regelfall: Die meisten Links entstehen, ohne dass jemand das Feld öffnet. Ohne Eintrag bleibt es bei „Kein Ablauf", also beim bisherigen Verhalten. Eine Vorbelegung, die es unter den geltenden Regeln nicht gibt („Kein Ablauf" bei verbotenen Dauerlinks, ein Zeitraum über der Höchstdauer), wird abgelehnt.

Eigene Kurz-Adressen

Eigene Kurz-Adressen zulassen. Vorgabe: ja. Ohne Häkchen bekommt jeder neue Link eine gewürfelte Adresse, und das Feld verschwindet aus der Anlegen-Maske — kein gesperrtes Feld, das eine Möglichkeit erklärt, die es hier nicht gibt.

Warum das eine Entscheidung ist, die vorher fällt: Eine Wunsch-Adresse ist bequem und in einer Installation mit mehreren Konten zugleich ein Verteilungsproblem. Wer als Erster shop, kontakt oder 2026 nimmt, hat sie für immer — eine vergebene Adresse wird nie wieder frei. Zurückdrehen lässt sich das nicht.

Der Schalter wirkt auch serverseitig: Ein trotzdem geschickter Wunsch wird abgelehnt, und die Live-Prüfung antwortet nicht mehr mit „frei". Ohne das wäre er ein Vorschlag, den ein selbst gebautes Formular oder eine noch offene alte Seite übergeht.

Bestehende Wunsch-Adressen bleiben unangetastet und funktionieren weiter. Die Karte nennt eine Schätzung, wie viele es sind — ob eine Adresse gewünscht oder gewürfelt war, wurde nie festgehalten, deshalb ist die Zahl eine Untergrenze.

QR-Codes

QR-Codes bereitstellen. Vorgabe: ja. Ohne Häkchen verschwinden sie aus allen Masken, und die Download-Adresse antwortet 404 — nicht 403. Eine 403 hieße „dir fehlt das Recht" und schickte den Nutzer zu Ihnen, um etwas zu erbitten, das es in dieser Installation gar nicht gibt.

Der Code führt auf die Kurz-Adresse, nicht auf das Ziel. Das ist der ganze Zweck: Ein gedruckter Code auf das Ziel gäbe alles auf, was dieser Dienst kann — Ablauf, Klick-Limit, Passwortschutz und ein später korrigiertes Ziel. Angeboten werden zwei Fassungen: PNG für Bildschirm, Präsentation und E-Mail, SVG für den Druck.

Warum die Vorgabe hier „an" ist, anders als bei einem Schalter, der etwas wegnimmt: Die Funktion ist neu, es gibt nichts wegzunehmen — und ihr Zweck ist gerade, den Nutzern einen Weg zu ersparen. Ein neuer Schalter, der auf „aus" steht, ist eine Funktion, die niemand findet.

Ausschalten nimmt nichts weg, was im Umlauf ist. Ein bereits heruntergeladener oder gedruckter Code funktioniert weiter — er ist nichts anderes als die Kurz-Adresse in einem anderen Gewand. Es lassen sich nur keine neuen mehr laden.

Was das System nicht kann: Logo in der Mitte, Farben, gerundete Module. Jede dieser Verzierungen kostet Lesbarkeit, und ein Code, der im Halbdunkel nicht erkannt wird, ist als Aushang wertlos. Wer gestaltete Codes braucht, erzeugt sie aus der Kurz-Adresse mit einem eigenen Werkzeug.

Technische Voraussetzung: die PHP-Erweiterung zlib — sie steckt in jedem üblichen PHP-Bau und steht deshalb nicht unter den Voraussetzungen. Fehlt sie doch, sagt die Karte es, das Häkchen bleibt trotzdem gespeichert, und die Codes tauchen erst auf, wenn die Erweiterung da ist. Bewusst nicht ins Häkchen zurückgeschrieben: Nach einem Serverumzug stünde die Funktion sonst auf „aus", ohne dass jemand sie abgeschaltet hätte.

Weder GD noch Imagick werden gebraucht. Die Bibliothek bacon/bacon-qr-code erzeugt nur SVG, EPS und Imagick; das PNG entsteht deshalb in der Anwendung selbst (App\Support\QrCode) — ein Schwarz-Weiß-Bild mit Farbtiefe 1, für das es keine Bildbibliothek braucht. Eine neue Pflicht-Erweiterung für eine Funktion, die Wege abnehmen soll, wäre der falsche Tausch: composer install bräche beim nächsten Update auf jedem Server ohne sie.

In welcher Fassung ein Konto den Code im Dialog nach dem Anlegen bekommt, entscheidet es selbst unter Nutzereinstellungen (Vorgabe PNG). Auf der Detailseite und im seitlichen Detailbereich stehen immer beide.

Mindestlänge, 4 bis 64 Zeichen, Vorgabe 8. Sie war lange fest auf 4 und damit deutlich unter den zwölf Zeichen, die dieselbe Anwendung für ein Konto-Passwort verlangt.

Ein Link-Passwort ist etwas anderes als ein Konto-Passwort: Es schützt eine öffentlich erreichbare Adresse und wird einem Empfänger über einen zweiten Kanal mitgeteilt und dort abgetippt. Eine Länge, die niemand weitergeben mag, führt dazu, dass der Schutz gar nicht erst gesetzt wird — deshalb 8 und nicht 12.

Bestehende Links bleiben unangetastet. Ein gespeichertes Passwort lässt sich nicht nachmessen, und ein Link im Umlauf würde für seine Empfänger unbrauchbar. Geprüft wird, sobald jemand ein Passwort setzt; der Hilfetext am Feld nennt die geltende Zahl.

Der Eingabe-Endpunkt ist auf zehn Versuche je zehn Minuten gedrosselt. Durchprobieren ist damit teuer — ein kurzes Passwort bleibt gegenüber jemandem, der den Link und den Anlass kennt, trotzdem ein schwaches Versprechen.

Erscheinungsbild

Farben und Schrift der gesamten Installation stecken in einer einzigen Datei, tokens.css. Der Weg ist bewusst: herunterladen — im Editor ändern — hochladen.

  1. Gerüst herunterladen. Ist noch kein eigenes Theme hinterlegt, kommt die mitgelieferte Datei; sonst die derzeit aktive. Sie ist durchgehend kommentiert, es steht also an jeder Variablen, was sie bewirkt.
  2. Ändern. Geändert werden die Werte, nicht die Namen. Ein Variablenname, den es nicht gibt, bleibt wirkungslos — die Seite bleibt heil, die Änderung passiert nur nicht. Ebenso wichtig ist der Platz: Es wirkt nur, was in :root, in .dark oder in einem anderen gewöhnlichen Selektor steht. :root gilt im hellen Modus (und im dunklen, sofern .dark nichts anderes sagt), .dark nur im dunklen.
  3. Hochladen. Nur .css. Die Datei wird vollständig übernommen und als <style>-Block in den Kopf jeder Seite gesetzt.

Die zwei Ebenen — hier entscheidet sich der Aufwand

Die Datei ist in zwei Abschnitte geteilt, und welchen Sie anfassen, entscheidet, wie viel sich ändert.

Ebene 1 — Grundpalette (--c-…). Acht Werte, und die ganze Installation sieht anders aus. --c-ink ist die Textfarbe, --c-surface-50 der Seitengrund, --c-border die Linienfarbe, --c-brand der Akzent — je einmal für hell (:root) und dunkel (.dark). Alles, was nichts Eigenes sagt, folgt diesen Werten.

Ebene 2 — Element-Tokens (--e-…). Ein Wert je Element. Auszugsweise:

Element Token
Fließtext --e-body
Überschriften --e-heading (h1/h2), --e-subheading (h3/h4)
Feld-Beschriftungen --e-label, Hilfetexte --e-help
Fließtext-Links --e-link, Unterstrich beim Zeigen --e-link-hover
Kopfleiste --e-nav (Fläche), --e-on-nav (Links und Symbole), --e-nav-line
Menüs --e-menu, --e-on-menu, --e-menu-line
Buttons --e-button (Fläche), --e-on-button (Beschriftung), --e-button-line
Buttons, zweite Stufe --e-button-secondary, --e-on-button-secondary, --e-button-secondary-line
Karten --e-card, --e-card-line
Tabellen --e-table, --e-table-head, --e-table-line
Dialoge und Toasts --e-dialog, --e-toast samt -line
Formularfelder --e-input, --e-on-input, --e-input-line, --e-input-hint
Trennlinien allgemein --e-line
Fußzeile --e-footer, --e-on-footer, --e-footer-line
Zeigen mit der Maus (alle Bedienelemente) --e-hover und --e-on-hover, je Element zusätzlich --e-button-hover, --e-nav-hover, …

Die vollständige Liste steht kommentiert in der Datei; jeder Token trägt dort seinen Zweck. Jeder dieser Werte zeigt per Vorgabe auf Ebene 1 und erbt, solange Sie ihn nicht anfassen.

Das ist der praktische Unterschied: Überschriften in Ihrer Hausfarbe bei schwarzem Fließtext sind eine Zeile —

:root {
    --e-heading: #1d4ed8;
}

— und nicht der Versuch, --c-ink zu ändern, was Fließtext, Überschriften, Kartenrahmen und Menütexte in einem Zug erwischt hätte.

Ein fester Farbwert gilt in beiden Modi. Sobald ein Element-Token auf eine Farbe statt auf die Palette zeigt, ist die Vererbung unterbrochen — auch im dunklen Modus. Soll er dort anders sein, wiederholen Sie ihn im Block .dark. Prüfen Sie in diesem Fall den Kontrast in beiden Modi.

Es gibt keinen Farbwähler. Dreißig Farbfelder in einem Formular könnten weniger und würden das Wichtigste nicht mitliefern: die Erklärung, welche Variable wohin wirkt.

Zurücksetzen stellt das mitgelieferte Theme wieder her. Die hochgeladene Datei wird dabei gelöscht — wer sie behalten will, lädt sie vorher herunter.

Wo die Datei liegt: storage/app/private/theme/tokens.css. Das Verzeichnis storage/ ist vom Deployment ausgenommen, eine Aktualisierung überschreibt das Theme also nicht. Ins Backup gehört es trotzdem (siehe Kapitel 12).

Wer Farben ändert, ändert Kontraste. Die Statusfarben dieser Anwendung sind auf knapp über 4,5:1 ausgelegt, in hell und dunkel getrennt. Der Abstand ist bewusst knapp (kräftige Farben lesen sich besser als blasse) und damit empfindlich. ACCESSIBILITY.md enthält die gemessenen Werte als Ausgangspunkt.

@theme wirkt hier nicht. Wer die Datei resources/css/tokens.css direkt aus dem Quelltext nimmt statt das Gerüst herunterzuladen, findet dort @theme-Blöcke. Die gehören zum Bauvorgang von Tailwind; ein Browser kennt diese Anweisung nicht und überliest sie samt Inhalt — ohne Fehlermeldung. Das Ergebnis sieht aus wie „halb übernommen": Die Farben greifen, Schrift, Radien und Statusfarben nicht. Das Gerüst aus dieser Oberfläche hat die Blöcke bereits an den richtigen Platz gerückt; wird trotzdem eine Datei mit @theme hochgeladen, meldet die Seite die betroffenen Zeilen — und übernimmt die Datei dennoch.

Hinweis auf fremde Server. Enthält die Datei ein @import oder ein url() auf eine fremde Adresse — typisch bei aus dem Netz kopierten Themes, die ihre Schrift von einem CDN holen —, wird das nach dem Hochladen mit Zeilennummer angezeigt. Das Theme ist trotzdem aktiv: Die Entscheidung gehört dem Betreiber. Zu bedenken ist nur, dass dann bei jedem Seitenaufruf die IP-Adresse jedes Besuchers an diesen Server geht. Das gehört in die Datenschutzerklärung — oder die Dateien werden lokal abgelegt.

Schriften werden nicht über die Oberfläche hochgeladen. Die Dateien (.woff2) werden von Hand nach public/fonts/ gelegt, der passende @font-face-Block gehört in die eigene tokens.css. Grund: Web-Schriften sind lizenzpflichtig, und wer eine ablegt, muss die Lizenz haben. Das ist eine Entscheidung am Server, keine in einem Browser-Formular.

E-Mail

Ohne Mail-Versand lassen sich keine Konten einrichten. Zwei Wege:

  • SMTP (empfohlen): Server, Port, Verschlüsselung, Benutzername, Passwort. Zustellbarkeit und Fehler sind nachvollziehbar. Port 587 für STARTTLS, 465 für SSL.
  • sendmail: nutzt das Mailsystem des Servers. Ohne korrekte SPF- und DKIM-Einträge landen solche Nachrichten oft im Spam-Ordner.

Das SMTP-Passwort wird verschlüsselt in der Datenbank abgelegt (mit APP_KEY) und nie wieder angezeigt. Ein leeres Passwortfeld bedeutet „unverändert lassen"; zum Entfernen gibt es eine eigene Auswahl.

Werte aus der Datenbank haben Vorrang vor der .env. Leere Felder bedeuten: es gilt der .env-Wert.

Die Test-Mail prüft den gespeicherten Zugang, nicht die Eingaben im Formular — also erst speichern, dann testen. Scheitert der Versand, steht die Meldung des Servers im Klartext da; sie ist hier die eigentliche Information.

Das Postfach der Absenderadresse muss jemand ansehen. Nutzer sehen bei jedem Link-Versand, ob der Mailserver die Nachricht angenommen hat — mehr kann diese Installation nicht wissen. Stellt der empfangende Server sie anschließend nicht zu, geht die Fehlermeldung an den Envelope-Absender, also an die unter Absenderadresse eingetragene Systemadresse, und nicht an den Nutzer.

Der Mail-Versand ist außerdem Voraussetzung für eine Einstellung im Bereich Links. Ist „Betroffene Links automatisch stilllegen" eingeschaltet, geht bei jedem Stilllegen eine Nachricht an den Eigentümer. Ohne Versand legt die Anwendung still, ohne dass es jemand erfährt — der Eigentümer merkt es erst an der Rückfrage seiner Empfänger. Die Einstellungsseite warnt in diesem Fall.

Systemdaten

Ganz oben steht die Fassung dieser Installation und, sofern die Prüfung eingeschaltet ist, ob es eine neuere gibt.

Offene Punkte holen den Administrator ab — sie warten nicht auf dieser Seite. Findet die Anwendung etwas, das dem ordnungsgemäßen Betrieb entgegensteht, steht das unter der Kopfzeile auf jeder Seite, für Administratoren und nur für sie. Derselbe Text, dieselbe Liste wie hier oben.

Der Grund ist die Eigenschaft, die alle diese Punkte teilen: Sie fallen im Alltag nicht auf. Ein fehlender Cron-Eintrag sieht aus wie ein System, in dem gerade nichts abzuräumen war. Ein aktiver Debug-Modus sieht aus wie gar nichts. Eine fehlende Public Suffix List sieht aus wie eine Anwendung, die eben gern Vorschauseiten zeigt. Wer diese Seite von sich aus aufruft, hat den Verdacht bereits — und genau der fehlt.

Die Meldung lässt sich nicht schließen. Das ist Absicht: Ein Schließen-Knopf machte aus jedem dieser Punkte eine Sache, die man auch wegklicken kann. Sie endet auf genau einem Weg, nämlich indem der Punkt behoben wird — danach verschwindet sie von selbst. Einzige Ausnahme beim Zeitpunkt: Ob die Public Suffix List vorliegt, wird aus Geschwindigkeitsgründen bis zu eine Minute lang gemerkt (php artisan shortlink:suffix-list setzt das sofort zurück). Alles andere — .env, Einstellungen, Cron — wirkt beim nächsten Seitenaufruf. Diese Seite hier prüft immer frisch.

Nutzer ohne Betriebsrechte sehen die Meldung nie. Ein fehlender Cron-Eintrag ist nichts, was sie beheben können, und eine dauerhafte Meldung ohne Handlungsmöglichkeit nutzt nur die Aufmerksamkeit ab, auf die es beim Administrator ankommt.

Auf neue Fassungen hinweisen ist ein Schalter auf dieser Seite. SHORTLINK_UPDATE_CHECK in der .env ist dabei nur der Ausgangswert — er gilt, solange hier niemand etwas entschieden hat; danach gilt die Entscheidung auf der Seite, in beide Richtungen. Die Seite nennt daneben, worauf der Ausgangswert steht.

Die Abfrage verrät dem Server hinter SHORTLINK_UPDATE_SOURCE IP-Adresse und Zeitpunkt dieser Installation — sonst nichts: keine Kennung, keine Nutzungsdaten, keine Rückmeldung über das Ergebnis. In einem abgeschotteten Netz ist sie ohnehin nur ein Timeout. Aktualisiert wird dadurch nichts.

Wo nachgesehen wird, bleibt in der .env (SHORTLINK_UPDATE_SOURCE, wirksam nach php artisan config:cache). Auf welchen Server diese Installation hört, ist eine Vertrauensentscheidung und gehört nicht in ein Formular, das jeder Administrator absenden kann — anders als das reine Ein und Aus, das nur eine Verbindung nach draußen abstellt oder wieder zulässt.

Die installierte Fassung steht in der Datei VERSION im Projektverzeichnis. Paket und Deployment schreiben sie; wer aus dem Quellcode installiert hat, sieht „unbekannt" — dann fehlt der Maßstab, und die Seite sagt das, statt zu raten.

Gibt es eine neuere Fassung, erscheint ein Knopf Paket bereitstellen. Er lädt das Release-Archiv, hält es gegen die veröffentlichte SHA256-Prüfsumme und entpackt es nach storage/app/private/updates/. Eingespielt wird dabei nichts. Danach zeigt die Seite den einen Befehl, der noch fehlt — mit dem Pfad dieser Installation schon eingesetzt:

sh storage/app/private/updates/<fassung>/update.sh /pfad/zu/dieser/installation

Mit --dry-run angehängt zeigt er vorher, was sich ändern würde.

Warum kein Knopf, der die Aktualisierung durchführt? Damit der Webserver den Programmcode überschreiben könnte, den er selbst ausführt, müsste app/, config/ und vendor/ für ihn beschreibbar sein. Jede Schreiblücke irgendwo im System wäre damit ein Weg, eigenen Code auszuführen. Diese Installation hält den Code bewusst unbeschreibbar — die Oberfläche nimmt deshalb den mühsamen Teil ab (URL suchen, Prüfsumme vergleichen, entpacken), der riskante bleibt ein Befehl im Terminal.

Die Prüfung ist abschaltbar (SHORTLINK_UPDATE_CHECK=false). Sie fragt einmal täglich beim Anbieter nach und verrät ihm dabei, dass es diese Installation gibt — samt IP-Adresse und Zeitpunkt. Nach außen geht eine GET-Anfrage und sonst nichts: keine Kennung dieser Installation, keine Nutzungsdaten. In einem abgeschotteten Netz läuft sie ohnehin nur in einen Timeout. Abgeschaltet erscheint statt der Fassungsangabe ein entsprechender Hinweis; alles andere funktioniert unverändert.

Bereitgestellte Pakete räumt shortlink:cleanup wieder ab, sobald sie eingespielt sind — Maßstab ist die Fassung, nicht das Alter.

Darunter: Webserver, PHP, Laravel, Datenbank, belegter und freier Speicher, Arbeitsspeicher sowie Queue, Cache und Sitzungen.

Oben stehen Warnungen, die Handeln erfordern. Es sind genau vier:

  • APP_ENV steht auf local — dann sind mehrere Schutzmaßnahmen abgeschaltet, unter anderem die Sperre gegen destruktive Datenbankbefehle (migrate:fresh, db:wipe).
  • APP_DEBUG=true — zeigt bei Fehlern interne Pfade und Konfigurationswerte.
  • Das Klick-Protokoll wird nie automatisch gelöscht (SHORTLINK_CLICK_LOG_RETENTION_DAYS=-1). Das ist eine gültige Wahl, aber eine, die personenbezogene Einträge unbefristet stehen lässt.
  • Die Public Suffix List fehlt oder ist nicht lesbar. Folge: jedes Ziel bekommt eine Vorschauseite.

Darunter die ausführliche Diagnose auf einen weiteren Klick und ein Bericht als Klartext zum Kopieren. Der Bericht enthält bewusst keine Zugangsdaten und keine Schlüssel — auch nicht gekürzt —, damit er ohne Prüfung in ein Ticket oder eine Nachricht eingefügt werden kann.


6. Wie ein Aufruf abläuft

Diese Reihenfolge zu kennen erspart die meisten Rückfragen, weil sie erklärt, warum ein Besucher etwas anderes sieht als der Eigentümer erwartet. Sie ist fest und wird bei jedem Aufruf von vorn durchlaufen:

  1. Redaktionelle Seite vor Shortlink. /{slug} löst ein Controller auf, und er sieht zuerst nach, ob es eine veröffentlichte Seite mit dieser Adresse gibt. Erst danach die Links. Beide teilen sich denselben Namensraum; ohne feste Reihenfolge hinge das Ergebnis von der Reihenfolge der Routendatei ab.
  2. Gibt es den Link? Nein → 404.
  3. Ist er verfügbar? Stillgelegt, abgelaufen oder Klick-Limit erreicht → die neutrale Hinweisseite mit 410 Gone. Nicht 404: Der Link hat existiert, er ist nur nicht mehr gültig.
  4. Ist er passwortgeschützt? Ja und nicht freigeschaltet → Passwortmaske.
  5. Braucht es eine Vorschauseite? Ja → Vorschauseite mit dem Ziel und dem Grund. Der Aufruf zählt hier noch nicht.
  6. Weiterleitung mit 302, und der Aufruf wird gezählt.

Warum das Passwort vor der Vorschauseite kommt: Die Vorschauseite nennt den Ziel-Host. Stünde sie vorher, wäre das Passwort nur eine Verzögerung — wer es nicht hat, wüsste trotzdem, wohin der Link führt.

Was auf der Hinweisseite steht und was nicht: Bei Ablauf und erschöpftem Limit wird der Grund benannt, mit Datum — das erspart eine Rückfrage beim Absender. Bei einem stillgelegten Link bleibt es neutral. Dieser Zustand ist die Antwort des Betreibers auf ein Ziel, das sich als bösartig erwiesen hat; stünde dort „wegen Missbrauch gesperrt", wäre die Seite eine Rückmeldung an genau den, der den Missbrauch betreibt. In keinem Fall steht dort das Ziel.

Was gezählt wird und was nicht

Der Zähler an einem Link ist eine Zahl, an der Nutzer Entscheidungen treffen („verschickt und seitdem nichts — nachfassen"). Deshalb steht dahinter mehr als ein ++:

  • Link-Vorschauen zählen nicht. Slack, WhatsApp, Signal, Teams, Discord und viele Mailclients rufen jede Adresse in einer Nachricht von sich aus ab. Ungefiltert stünde der Zähler auf 3, bevor ein Mensch den Link gesehen hat — und bei einem Klick-Limit von 1 wäre er verbraucht, während er beim Empfänger noch ungelesen im Chat liegt.
  • Solche Aufrufe werden trotzdem protokolliert, mit dem Merkmal is_bot. Die Erkennung stützt sich auf die Kennung, die der Aufrufer selbst schickt, und die kann fehlen, lügen oder sich morgen ändern. Ein stillschweigend weggeworfener Aufruf wäre nicht mehr nachvollziehbar — und dann streitet man mit dem Betreiber über eine Zahl, die niemand prüfen kann.
  • Der Blick auf die Vorschauseite zählt nicht, erst der Klick auf „Weiter". Jeder Eintrag im Protokoll trägt deshalb auch, ob er über die Weiterleitung oder über die Vorschauseite kam.

Die Liste der erkannten Dienste steht an einer Stelle im Code (app/Support/BotSignature.php) und ist absichtlich nicht vollständig — sie kann es nicht sein.


7. Ziel-Prüfung, Vorschauseite und Aufsicht

Hier entscheidet sich, wie dieser Dienst sich verhält. Der Hintergrund ist einfach: Eine Kurz-Adresse verbirgt ihr Ziel. Genau das ist ihr Zweck und genau das macht sie missbrauchbar — ein Link auf deiner Domain, der auf eine Phishing-Seite führt, trägt deinen guten Namen.

Shortlink beantwortet das nicht mit einer Sperre, sondern mit Sichtbarkeit: Wer nicht direkt weitergeleitet werden darf, bekommt eine Vorschauseite mit dem Ziel und dem Grund. Der Betreiber entscheidet, wo die Grenze liegt.

Vorschauseite immer zeigen. Ab Werk aus. Der grobe Schalter dieses Bereichs: Ist er an, wird vor jeder Weiterleitung das Ziel genannt — ausnahmslos. Er steht deshalb ganz oben: Wer ihn setzt, braucht alles Weitere auf der Seite nicht mehr.

Es ist die einzige Einstellung dieses Bereichs mit einer belastbaren Zusage. Alle anderen hängen an der Domain-Liste und an der Ziel-Prüfung, und beide können danebenliegen: eine versehentlich freigegebene Domain, ein Ziel, das erst nach der letzten Prüfung umgebaut wurde.

Was er kostet, offen gesagt: Der Kurzlink verliert seinen Hauptnutzen. Aus einem Klick werden zwei, und beim Regelfall dieser Anwendung — ein Ziel auf der Domain des Kunden — steht dann eine Zwischenseite vor etwas, das niemanden gefährdet. Eine Vorschauseite, die immer kommt, wird gewohnheitsmäßig weggeklickt und schützt dann niemanden mehr.

Die Begründung auf der Vorschauseite bleibt die genauere: Leitet ein Ziel selbst weiter oder liegt es auf einer fremden Domain, steht weiterhin das dort. Der Haken am einzelnen Link („Ziel immer vorher anzeigen") wird wirkungslos — er kann nichts mehr hinzufügen —, und die Anlegen-Maske weist ihn dann als wirkungslos aus.

Vertrauenswürdige Ziel-Domains. Eine Domain je Zeile. Ziele auf diesen Domains werden direkt weitergeleitet, alle anderen bekommen eine Vorschauseite. Solange nichts gespeichert wurde, steht hier die Domain aus APP_URL.

Verglichen wird die registrierbare Domain, nicht der vollständige Host. Ein Eintrag kunde.de deckt damit auch www.kunde.de und shop.kunde.de ab. Das ist gewollt — sonst bekäme der Regelfall (go.kunde.dekunde.de/…) eine Vorschauseite. Die Kehrseite: Einer einzelnen Subdomain zu vertrauen und dem Rest nicht, ist damit nicht möglich.

Eingaben werden auf die registrierbare Domain gekürzt (https://www.kunde.de/pfadkunde.de). Ein Domain-Suffix wie co.uk oder github.io fällt heraus und wird gemeldet: Ihn einzutragen würde jeder Domain darunter vertrauen.

Ein leeres Feld ist gültig und bedeutet: keine Domain gilt als vertrauenswürdig — dann erscheint bei jedem Link eine Vorschauseite.

Ziele, die sich nicht prüfen ließen. Ein Schalter, ab Werk aus. „Nicht prüfbar" heißt meist Zeitüberschreitung, TLS-Fehler oder ein Server, der HEAD-Anfragen ablehnt — in der Regel also keine Gefahr. Eingeschaltet erscheint die Vorschauseite auch für die eigenen Ziele des Kunden, sobald dessen Server einmal langsam war; eine Vorschauseite, die man gewohnt wegklickt, schützt niemanden mehr.

Fremde Ziel-Domains („Nur Ziele auf freigegebenen Domains zulassen"). Ab Werk aus. Dieser Schalter wirkt an einer anderen Stelle als die übrigen: Er entscheidet nicht, wie weitergeleitet wird, sondern ob ein Link überhaupt entstehen darf. Eingeschaltet lehnt die Anlegen-Maske jedes Ziel außerhalb der Liste ab.

Eine Installation, die ausschließlich Kunden-Adressen kürzt, macht sich damit als Missbrauchswerkzeug unbrauchbar. Der Preis: Niemand kann mehr auf eine fremde Seite verlinken — auch nicht harmlos. Bestehende Links bleiben unangetastet.

Automatisch stilllegen. Ab Werk aus, und das mit Grund: Ein Befund ist keine Gewissheit. Eine Wartungsseite, ein Login-Rücksprung oder eine Tracking-Weiche sieht für die Prüfung genauso aus wie eine Umleitung mit Absicht. Eingeschaltet wird der Kampagnenlink eines Kunden mitten in der Aktion abgeschaltet.

Damit dieser Tausch — Verfügbarkeit gegen Sicherheit — nicht heimlich passiert, geht bei jedem automatischen Stilllegen eine Nachricht an den Eigentümer des Links, mit Ziel, tatsächlicher Endadresse und dem Hinweis, dass der Betreiber ihn wieder freigeben kann.

Frist der Ziel-Prüfung. Nach wie vielen Tagen ein Ziel erneut geprüft wird, Vorgabe 7. 0 bedeutet: nach der ersten Prüfung nicht mehr wiederholen — eine gültige Wahl für abgeschottete Installationen, aber eine bewusste: Ein Ziel, das erst nach dem Anlegen zur Weiterleitung umgebaut wird, fällt dann nicht mehr auf.

Was die Prüfung tut

shortlink:check-targets folgt der Weiterleitungskette des eingetragenen Ziels selbst, über höchstens fünf Sprünge, mit fünf Sekunden Zeitgrenze je Anfrage. Drei Ergebnisse:

Ergebnis Bedeutung
ok Der Weg endet auf derselben registrierbaren Domain
foreign_redirect Das Ziel leitet selbst auf eine andere Domain weiter — immer Vorschauseite, auch bei vertrauenswürdiger Domain
unknown Nicht erreichbar. Kein Befund — ein Timeout ist keine Aussage

Geprüft wird ausschließlich von diesem Lauf. Nicht beim Anlegen: Das würde den Knopf bis zu fünf Sekunden an einem fremden Server hängen lassen, im Kettenfall länger. Ein neuer Link ist deshalb zunächst „noch nicht geprüft", und bis zum ersten Prüflauf entscheidet allein die Domain-Liste, ob eine Vorschauseite erscheint. Ohne den Cron-Eintrag wird nie geprüft.

Der Befund foreign_redirect ist der Weg, auf dem sich die Domain-Liste sonst umgehen ließe: Ein Ziel auf der eigenen Domain, das selbst weiterschickt, wäre ohne diese Prüfung der unauffälligste aller Fälle.

Public Suffix List

Die Frage „gehören zwei Hosts zur selben Domain" lässt sich nicht durch Abschneiden von Labels beantworten. Aus go.kunde.de das erste Label zu streichen ergibt richtig kunde.de — aus kunde.co.uk aber co.uk, und damit wäre jede Domain unter .co.uk vertrauenswürdig.

Deshalb liegt eine Kopie der Public Suffix List im Paket (resources/public-suffix-list.dat, MPL 2.0). Aktualisieren:

php artisan shortlink:suffix-list

Der Befehl schreibt nach storage/app/private/public-suffix-list.dat; diese Fassung hat Vorrang vor der mitgelieferten. Das Verzeichnis ist vom Deployment ausgenommen, eine Aktualisierung der Anwendung überschreibt sie also nicht.

Dies ist die einzige Stelle, an der diese Anwendung von sich aus nach draußen greift — und auch nur, wenn der Befehl aufgerufen wird. Für abgeschottete Installationen lässt sich die Quelle auf einen internen Spiegel umstellen (SHORTLINK_SUFFIX_LIST_SOURCE).

Fehlt die Liste, bekommt jedes Ziel eine Vorschauseite — die sichere Richtung. Die Einstellungsseite sagt das oben, sonst sucht man den Fehler bei den Links. Ist zusätzlich „Nur Ziele auf freigegebenen Domains zulassen" eingeschaltet, könnte gar kein Link mehr angelegt werden; die Maske lehnt ab und nennt diesen Grund.

Eine veraltete Liste ist kein Ausfall: Folge sind neue TLDs, die als unbekannt gelten, also eine Vorschauseite, die zu Unrecht erscheint oder zu Unrecht ausbleibt.

Der Ort, an dem du auf einen Befund reagieren kannst. Er zeigt alle Shortlinks dieser Installation — Befunde zuerst —, mit dem Konto, dem der Link gehört. Drei Sichten: alle, mit Befund, stillgelegt, jeweils mit Zähler.

Genau zwei Aktionen: stilllegen und wieder freigeben. Bearbeiten und Löschen bleiben beim Eigentümer. Du greifst ein, um Missbrauch zu stoppen, nicht um fremde Links zu pflegen.

Ein stillgelegter Link leitet nicht mehr weiter; Besucher sehen die neutrale Hinweisseite ohne Begründung. Der Eigentümer wird beim manuellen Stilllegen nicht benachrichtigt — anders als beim automatischen, wo eine Nachricht Pflicht ist. Jeder Eingriff wird protokolliert.

Das Freigeben setzt das Prüfergebnis nicht zurück. Ein Befund bleibt sichtbar, auch wenn du entschieden hast, dass er in Ordnung ist — sonst verschwände die Information, auf deren Grundlage du entschieden hast.


8. Konten und Rollen

Das entscheidet ein Schalter unter Einstellungen → Funktionen, ganz oben. Zwei Stellungen:

Gemeinsam (Vorgabe)

Der Installation, nicht dem Konto. Jedes angemeldete Konto sieht alle Shortlinks dieser Installation und darf sie ansehen, bearbeiten, verschicken und löschen. Wer einen Link angelegt hat, wird festgehalten und in Übersicht und Detailansicht angezeigt — als Auskunft darüber, wen man fragen kann, nicht als Besitzangabe.

Der Grund: Eine Kurz-Adresse auf der Domain des Hauses ist eine Sache des Hauses. Ein Kollege im Urlaub darf kein Grund sein, warum ein Ziel nicht umgezogen werden kann — und genau dafür gibt es diesen Dienst.

Der Preis, offen benannt: Es gibt keine abgestuften Rechte je Link. Wer bearbeiten darf, kann das Ziel eines Links im Umlauf auf etwas Beliebiges setzen — das ist folgenreicher als löschen, denn ein gelöschter Link leitet gar nicht mehr weiter, ein umgebogener aber woandershin. Wer das nicht möchte, vergibt keine Konten an Personen, denen er das nicht zutraut; ein Zwischenzustand ist nicht vorgesehen.

Die eine Stufe, die dem Administrator vorbehalten bleibt, ist die Aufsicht: einen Link stilllegen und wieder freigeben (Verwaltung → Links). Löschen kann ohnehin jeder.

Links ohne Anleger. Nach dem Löschen eines Kontos stehen dessen Links ohne Anleger da — in der Übersicht mit dem Abzeichen ohne Anleger. Das ist ein Betriebsproblem, kein Schönheitsfehler: An den Anleger geht die Nachricht, wenn die Ziel-Prüfung einen Link von selbst stilllegt. Ohne ihn geht sie an niemanden, und der Link fällt aus, ohne dass es jemand erfährt (im Log steht es).

Jedes Konto kann einen solchen Link in der Detailansicht übernehmen und ist damit der Adressat. Bewusst ein Knopf und keine Automatik: Die Anwendung könnte den nächsten Bearbeiter eintragen — sie hätte damit jemanden zum Zuständigen gemacht, der nur ein Ziel korrigiert hat. Ein Link, der bereits einen Anleger hat, lässt sich nicht übernehmen.

Nach dem Löschen eines Kontos lohnt ein Blick in die Übersicht. Was dort ohne Anleger trägt, sollte jemand übernehmen — sonst ist der Link zwar in Betrieb, aber unbeaufsichtigt.

Zuweisen — nur der Administrator. Auf der Detailseite eines Links steht für Administratoren die Karte Zuständigkeit: eine Auswahl über alle Konten und ein Knopf. Sie setzt den Anleger, sonst nichts — Adresse, Ziel, Ablauf und Zahlen bleiben.

Der Unterschied zum Übernehmen ist nicht die Bequemlichkeit, sondern wer entscheidet: Beim Übernehmen holt sich jemand einen herrenlosen Link selbst, und das darf jedes Konto, weil es niemandem etwas wegnimmt. Beim Zuweisen bestimmt jemand über ein anderes Konto — deshalb Administrator, deshalb auch bei einem Link, der schon einen Anleger hat, und deshalb geht eine E-Mail an den Zugewiesenen. Sie nennt den Link, das Ziel und den Namen dessen, der zugewiesen hat; ohne diesen Namen wäre es eine Anweisung aus dem Nichts.

Der eigentliche Anlass ist ein ausgeschiedener Kollege, dessen Links jemand weiterführen soll. Zurückweisen lässt sich eine Zuweisung nicht — ein Administrator kann sie aber jederzeit wieder ändern.

Die Zuweisung hängt nicht am Mailversand. Sie ist gespeichert, bevor etwas verschickt wird — sonst entschiede die Erreichbarkeit eines Mailservers darüber, ob eine Zuständigkeit gesetzt werden kann. Ist kein Versand eingerichtet, sagt die Karte das vorher, und die Rückmeldung sagt es noch einmal. Dann selbst Bescheid geben.

Rückwirkung auf eine bestehende Installation: Niemand verliert etwas, aber jeder sieht mehr — nach dem Update steht in der Übersicht jedes Kontos, was bisher nur sein Anleger sah. Wer das nicht will, muss vor dem Update entscheiden, welche Konten bestehen bleiben.

Je Konto (Benutzer-Modus)

Jedes Konto sieht und verwaltet nur seine eigenen Links. Neue Adressen tragen den Namensraum des Kontos: /xy/sommeraktion statt /sommeraktion.

Beides hängt zusammen und lässt sich nicht trennen: Getrennte Bestände ohne getrennte Namensräume hießen, dass zwei Konten um dieselben Wunsch-Adressen streiten, ohne die Links des anderen sehen zu können. Erst der Namensraum erlaubt es, dass zwei Konten beide sommeraktion haben.

Der Schalter prägt, er migriert nicht. Eine einmal ausgegebene Adresse ändert sich nie und löst in beiden Stellungen weiter auf — auch nach dem Hin- und Zurückschalten. Alles andere wäre kein Schalter, sondern eine Falle: Er bräche beim Umschalten jeden gedruckten Link und beim Zurückschalten ein zweites Mal.

Beim Umschalten in den Benutzer-Modus werden Links ohne Anleger dem Administrator zugeordnet, der umschaltet. Sonst wären sie in Betrieb und für niemanden sichtbar: Übernehmen gibt es nur im gemeinsamen Bestand, und in der eigenen Übersicht taucht ein Link ohne Anleger dann bei keinem Konto mehr auf. Die Karte nennt vorher, wie viele es sind.

Was der Modus nicht ändert: wer bearbeiten und löschen darf (der Anleger und der Administrator), die Aufsicht, das Zuweisen und die Adressen im globalen Namensraum.

Was der Schalter mit den Bereichen macht

Bereiche gruppieren die Übersicht (siehe Bereiche weiter unten). Wem sie gehören, hängt an derselben Stellung — und die beiden Richtungen tun nicht dasselbe. Das ist die einzige Stelle in den Einstellungen, an der ein Schalter Daten löscht.

Richtung Was mit den Bereichen passiert
Gemeinsam → je Konto Alle Bereiche werden gelöscht. Die Links bleiben und leiten weiter; sie stehen danach unter Ohne Bereich. Nicht umkehrbar.
Je Konto → gemeinsam Alle Bereiche bleiben und gelten ab dann für alle. In den Namen wird geschrieben, von welchem Konto sie stammen: aus Kampagne wird Kampagne (Muster GmbH, xy).

Warum gelöscht wird. Ein gemeinsamer Bereich ist eine gemeinsame Sache. Bliebe er im Benutzer-Modus stehen, hätte Konto A darin zwei Links und Konto B drei — und keines sähe die des anderen. Jeder sähe einen Ausschnitt, hielte ihn für den ganzen Bereich und handelte danach: doppelte Links, doppelte Absprachen, und niemandem fällt es auf, weil nichts abstürzt. Ein leerer Neuanfang ist verständlich, ein halber Bereich nicht.

Warum in der Gegenrichtung nichts verloren geht. Aus vielen persönlichen Ordnungen lässt sich eine gemeinsame machen. Nur müssen zwei Konten, die ihren Bereich beide Kampagne genannt haben, unterscheidbar bleiben — sie sind und bleiben zwei getrennte Bereiche. Deshalb der Zusatz im Namen. Er steht im Namen und nicht daneben, weil der Bereich danach der Installation gehört: Eine Spalte stammte von wäre nach dem ersten Umbenennen falsch und ließe sich nie wieder loswerden. Als Text lässt er sich unter Bereiche kürzen oder entfernen.

Die Karte in den Einstellungen nennt beides vorher, mit Anzahl.

Bereiche

Was sie sind: eine Beschriftung, die ein Link tragen kann — Kampagne Herbst, Messe, Vertrieb. Die Übersicht macht daraus ein Akkordeon: ein aufklappbarer Abschnitt je Bereich, darin dieselbe Tabelle wie bisher. Links ohne Bereich stehen oben unter Ohne Bereich. Solange kein Bereich angelegt ist, sieht die Übersicht genau aus wie vorher — eine flache Tabelle.

Was sie nicht sind: eine Berechtigung. Ein Bereich ändert nichts daran, wer einen Link sieht oder bearbeiten darf; das entscheidet allein der Schalter oben. Wer einen vertraulichen Link in einen Bereich einsortiert, hat ihn nicht versteckt. Die Oberfläche sagt das auf der Seite Bereiche ausdrücklich, weil das Wort die andere Lesart nahelegt.

Wer sie verwaltet: jedes angemeldete Konto, unter Bereiche in der Navigation — bewusst kein Administratorbereich. Im Benutzer-Modus gehören die Bereiche den einzelnen Konten; wer seine eigene Ordnung nicht aufräumen dürfte, müsste für jeden Tippfehler den Betreiber fragen. Im gemeinsamen Bestand gehören sie der Installation, und dort darf jedes Konto auch die Links aller bearbeiten — dieselbe Linie.

Löschen nimmt keinen Link mit. Ein Bereich ist eine Beschriftung, kein Behälter: Seine Links bleiben bestehen, leiten weiter und stehen danach unter Ohne Bereich.

Umbenennen bricht nichts. Ein Bereich steht in keiner ausgegebenen Adresse und in keiner verschickten Nachricht. Genau darin unterscheidet er sich von der Kurz-Adresse, die unveränderlich ist.

Ein zugewiesener Link im Benutzer-Modus trägt weiterhin den Bereich seines vorigen Kontos — den der neue Eigentümer nicht sehen darf. Er steht bei ihm deshalb unter Ohne Bereich und lässt sich beim nächsten Speichern in einen eigenen Bereich einsortieren.

Jedes angemeldete Konto kann die Links, die es sieht, als CSV-Datei herunterladen — im Kopf der Übersicht den ganzen sichtbaren Bestand, unter Bereiche je Zeile den einzelnen Bereich. Zwei Spalten: die vollständige Kurz-Adresse (mit Schema, Host und Namensraum) und die Ziel-URL. Einschränkungen am Link spielen keine Rolle — abgelaufene, stillgelegte, passwortgeschützte und ausgeschöpfte Links stehen mit drin. Gelöschte nicht.

Der Export geht durch dieselbe Abfrage wie die Übersicht (App\Support\LinkAccess). Es kommt also nie mehr heraus, als am Bildschirm ohnehin steht: im gemeinsamen Bestand alles, im Benutzer-Modus nur die eigenen Links. Auch der Bereichs-Export ist zusätzlich so eingeschränkt — der zugewiesene Link aus dem Kasten oben taucht in der Datei seines alten Bereichs nicht auf.

Es gibt keinen Schalter, mit dem sich der Export abstellen lässt — und keinen, der ihn auf Administratoren begrenzt. Wer die Liste am Bildschirm sehen darf, darf sie mitnehmen. Wenn das für eine Installation nicht gewollt ist, ist der Hebel die Betriebsart (Einstellungen → Funktionen: Benutzer-Modus), nicht der Export.

Das Format ist CSV nach RFC 4180: Komma, CRLF, UTF-8 mit Signatur. Numbers, LibreOffice und OpenOffice öffnen es per Doppelklick; Excel richtet sich nach dem Listentrennzeichen von Windows (in Deutschland das Semikolon) und braucht daher den Weg Daten → Aus Text/CSV.

Namensräume (Präfixe)

Jedes Konto bekommt beim Anlegen einen Namensraum aus zwei Zeichen — bestehende Konten bei der Aktualisierung. Der Zeichenvorrat ist derselbe wie bei den gewürfelten Kurz-Adressen (ohne 0, 1, l und o, weil eine Adresse vorgelesen und abgetippt wird); 32 Zeichen ergeben 1024 Namensräume. Wird es eng, vergibt das System drei und dann vier Zeichen. Längen stehen problemlos nebeneinander, eine gedruckte Adresse bricht dabei nie.

Vergeben wird beim Anlegen des Kontos und nicht beim ersten Link: Ein Namensraum ist eine Reservierung, und wer sie erst vergibt, wenn sie gebraucht wird, vergibt sie unter Zeitdruck — mitten in einem Web-Request, in dem der Nutzer eigentlich einen Link anlegen wollte.

Woraus das Kürzel entsteht

Das Kürzel wird aus dem Kontonamen abgeleitet und nicht gewürfelt. Es steht in jeder Adresse, die das Konto ausgibt: md für Muster Design ist vorlesbar und merkbar, k7 ist es nicht.

Name Regel Kürzel
John Doe Anfangsbuchstaben beider Wörter jd
Kanzlei ein Wort: erster und letzter Buchstabe ki
Max von Mustermann mehr als zwei Wörter: erstes und letztes mm
Änne Gauß fremde Zeichen werden übersetzt (äa, ßs) ag
Lisa Meyer l fehlt dem Alphabet — der nächste Buchstabe rückt nach im

Ein Namenszusatz oder eine Rechtsform in der Mitte fällt damit heraus; gezählt werden das erste und das letzte Wort, weil der Nachname bzw. der kennzeichnende Schluss das ist, wonach jemand das Konto sucht. Ein Titel wie Dr. zählt allerdings als Wort — bei Dr. John Doe entsteht dd. Wo das störend ist, hilft die Neuvergabe (unten).

l und o haben kein Ersatzzeichen. Sie fehlen dem Alphabet, weil sie mit 1 und 0 verwechselt werden — und die fehlen ebenfalls, es gibt also nichts, was ihnen ähnlich sähe. Statt zu würfeln, rückt darum der nächste verwendbare Buchstabe desselben Wortes nach: Ein Kürzel, bei dem ein Buchstabe noch stimmt, ist näher am Namen als zwei zufällige.

Gewürfelt wird nur noch als Notausgang — und dann genau wie früher. Das passiert in drei Fällen: Der Name gibt keine zwei verwendbaren Zeichen her (Loop etwa besteht nach der Filterung aus einem einzigen), das passende Kürzel steht auf der Sperrliste (unten), oder es gehört schon einem anderen Konto. Mit dem Namen wird nur EINMAL versucht: Ein Konto hat genau einen Namen, und wenn dessen Kürzel belegt ist, gibt es kein zweites, das noch dazu passen würde.

Ein umbenanntes Konto behält sein Kürzel. Abgeleitet wird beim Anlegen; danach hängen ausgegebene Adressen daran. Wer das Kürzel dem neuen Namen anpassen will, nimmt die Neuvergabe — mit allem, was dort steht.

Unglückliche Kürzel

Ein Kürzel kann sich als Aussage lesen — ein Parteikürzel, ein Kürzel aus dem rechtsextremen Umfeld, eine Beleidigung. Das trifft gewürfelte Kürzel, aber auch abgeleitete: Sarah Schmidt ergäbe ss. Das System hält dagegen eine Sperrliste (App\Support\Prefixes::UNFORTUNATE), die nie vergeben wird; ein gesperrtes Kürzel führt zum Würfel.

Eine offizielle Liste dafür gibt es nicht, und die hier ist ausdrücklich unvollständig. Schimpfwort-Listen kennen fu und fck, aber kein Parteikürzel; § 86a StGB betrifft Kennzeichen verfassungswidriger Organisationen im Verwendungskontext und nicht eine gewürfelte Kennung. Die Liste ist eine Abwägung — sie zu erweitern ist der Normalfall, und ein Eintrag wirkt auf künftige Vergaben.

Neu vergeben lässt sich ein Kürzel unter Verwaltung → Konten → Bearbeiten, Karte Namensraum für Kurz-Adressen. Das System schlägt freie Alternativen vor — Vorschläge statt eines Würfels, weil ein blindes neues Kürzel dasselbe Problem wiederholen kann.

Ist das zum Namen passende Kürzel frei, steht es dort an erster Stelle und ist mit passt zum Namen ausgezeichnet; die übrigen Vorschläge sind gewürfelt und bei jedem Aufruf der Seite andere. Das ist auch der Weg für Konten aus einer älteren Fassung: Ihre Kürzel wurden gewürfelt, bevor es die Ableitung aus dem Namen gab — die Aktualisierung tauscht sie nicht aus (ausgegebene Adressen hängen daran), über diesen Vorschlag lässt sich das aber Konto für Konto nachholen.

Was mit dem alten Kürzel passiert, entscheidet eine einzige Frage: Liegt eine Adresse darunter?

keine Adresse darunter Das Kürzel wird freigegeben und kann künftig wieder vergeben werden. Es stand nirgends, also gibt es nichts zu schützen — und einen der 1024 kurzen Namensräume für immer stillzulegen, kostet nur. Das ist der Regelfall: Wer ein gewürfeltes Kürzel gegen das passende tauscht, hat meist noch keinen Link.
Adressen darunter Das Kürzel bleibt gesperrt und wird nie neu vergeben. Es muss: Sonst erbte irgendwann ein zweites Konto xy und gäbe /xy/sommeraktion ein zweites Mal aus. Auch ein gelöschter Link zählt — seine Adresse ist im Umlauf.

Und das gibt etwas auf. War das alte Kürzel unglücklich — ein Parteikürzel, eine Beleidigung, die die Sperrliste nicht kennt —, dann liegt es nach der Freigabe wieder im Topf und kann das nächste Konto treffen. Die Freigabe kann das nicht unterscheiden; sie sieht nur, dass keine Adresse daran hängt. Wer aus diesem Grund austauscht, trägt das Kürzel zusätzlich in die Sperrliste ein (App\Support\Prefixes::UNFORTUNATE). Das ist die Stelle, die „nie wieder" sagt — und sie wirkt auf jede Installation, nicht nur auf diese eine Zeile.

Bereits ausgegebene Adressen ändern sich nicht. Links unter dem alten Kürzel behalten es; das neue gilt für alles, was danach entsteht. Ein Konto kann dadurch Adressen in zwei Namensräumen haben, und beide lösen weiter auf — genau dafür steht der Namensraum am Link und nicht am Konto.

Ein Namensraum überlebt sein Konto. Wird das Konto gelöscht, bleibt die Reservierung stehen. Sie muss stehenbleiben, solange auch nur ein Link darunter liegt — auch ein gelöschter, dessen Adresse ja dauerhaft belegt bleibt. Gäbe man ihn frei, könnte ein neues Konto xy erben und eine bereits verschickte Adresse ein zweites Mal ausgeben.

Der Aufräumlauf gibt herrenlose Namensräume wieder frei, sobald kein Link mehr darunter liegt (shortlink:cleanup, Kategorie Freigegebene Namensräume ohne Konto und ohne Adressen; im Testlauf sichtbar, ohne dass etwas passiert). Er nimmt beide Herkünfte: gelöschte Konten und ausgetauschte Kürzel. In der Praxis betrifft das fast nur Namensräume, unter denen nie ein Link entstand — und genau dafür gibt es den Lauf: Ein eingeladenes Konto, das nie benutzt und später gelöscht wurde, hielte sonst für immer einen der kurzen Namensräume belegt.

Für ausgetauschte Kürzel ist der Lauf die Nachholung. Bis 3.1.0 hat ein Austausch ausnahmslos gesperrt; solche Kürzel liegen in bestehenden Installationen herum. Der erste Aufräumlauf nach der Aktualisierung gibt sie frei, soweit keine Adresse daran hängt. Wenn darunter eines ist, das nie wieder vergeben werden soll, gehört es vor diesem Lauf in die Sperrliste.

Es gibt keine öffentliche Registrierung. Konten legt ein Administrator unter Nutzer an.

Zwei Rollen:

Rolle Darf
Nutzer alle Shortlinks der Installation anlegen, bearbeiten, verschicken und löschen, eigenes Konto pflegen
Administrator zusätzlich: Nutzer verwalten, Systemeinstellungen, redaktionelle Seiten, Links stilllegen und freigeben

Der Unterschied zwischen den Rollen ist beim Link-Bestand kleiner geworden: Auch ein Nutzer sieht in seiner Übersicht alle Links (siehe Wem die Links gehören oben). Was dem Administrator vorbehalten bleibt, ist die Aufsicht unter Verwaltung → Links — stilllegen und wieder freigeben (siehe Kapitel 7). Die Aufsichtsliste verlinkt die Detailseite jetzt auch: Sie ist für jedes angemeldete Konto erreichbar, und der Betreiber trifft seine Entscheidung dort auf besserer Grundlage.

Konto anlegen

Adresse, Name und Rolle eintragen. Das System verschickt eine Einladung; die eingeladene Person setzt ihr Passwort selbst über einen Link, der 7 Tage gültig ist. Der Administrator vergibt kein Passwort und erfährt es nicht. Das ist Absicht: Ein Passwort, das zwei Personen kennen, ist kein Nachweis für eine Person mehr.

Läuft die Einladung ab, kann sie erneut verschickt werden.

Wer die Einladung annimmt, hat damit seine Adresse bestätigt. Der Link kam ja dort an. Ein Tippfehler in der Adresse führt deshalb nicht zu einem halb funktionierenden Konto, sondern zu gar keinem: Die Einladung kommt nirgends an, niemand kann sie annehmen. Das ist der günstigere Fehler.

Die bestätigte Adresse als Voraussetzung

Ein Konto, dessen Adresse nicht bestätigt ist, kann keine neuen Shortlinks anlegen. Sonst ist nichts gesperrt: Bestehende Links bearbeiten, verschicken und löschen geht weiter, ebenso jede Einstellungsseite — auch die der Verwaltung.

Der Grund für genau diesen Zuschnitt: Ein Shortlink ist eine Adresse auf deiner Domain. Stellt sich später heraus, dass etwas mit ihm nicht stimmt, ist die Mail an den Eigentümer der einzige Weg, ihn zu erreichen — bei automatisch stillgelegten Links ist sie das eingebaute Verfahren (siehe Kapitel 7). Eine Adresse, die nie jemand nachgewiesen hat, ist dafür kein Weg.

Ein laufender Adresswechsel sperrt nicht: Dort bleibt die bisherige Adresse aktiv und bestätigt, der Nutzer also erreichbar — und der Wechsel lässt sich jederzeit verwerfen.

Wie ein Konto aus dem Zustand herauskommt: Konto → „Bestätigungsmail senden", dann den Link anklicken. Derselbe Knopf schickt auch die Mail eines hängenden Adresswechsels erneut. Jeder Aufruf erzeugt einen neuen Link und entwertet den vorigen — der häufigste Anlass ist ein abgelaufener Link, und dieselbe Mail noch einmal zu schicken hülfe da nicht.

In der Nutzerliste steht je Konto, ob die Adresse bestätigt ist. Ein Konto, das dort lange unbestätigt bleibt, hat entweder die Einladung nie angenommen oder eine Adresse, die niemand liest.

Was ein Administrator nicht kann

  • Fremde E-Mail-Adressen ändern. Sonst könnte er ein fremdes Konto auf seine eigene Adresse umleiten und über „Passwort vergessen" übernehmen. Die Adresse ändert nur der Inhaber selbst, und die neue Adresse muss über einen Link bestätigt werden (24 Stunden gültig).
  • Passwörter setzen oder lesen.
  • Den letzten Administrator herabstufen oder löschen. Sonst wäre die Installation ohne Verwaltung.
  • Fremde Links bearbeiten oder löschen. Nur stilllegen und freigeben.

Zwei-Faktor-Anmeldung

Jeder Nutzer richtet sie selbst ein (Konto → Sicherheit) mit einer Authenticator-App. Bei der Einrichtung erscheinen 8 Wiederherstellungs-Codes, jeder genau einmal verwendbar. Sie werden nur dieses eine Mal angezeigt.

Verliert jemand Gerät und Codes, kann ein Administrator die Zwei-Faktor-Anmeldung für dieses Konto zurücksetzen. Das wird protokolliert und dem Betroffenen per Mail mitgeteilt. Prüfe vorher auf einem anderen Weg, wer da anfragt — dieser Vorgang entfernt eine Sicherheitsmaßnahme.

Konto löschen

Das eigene Konto löscht der Inhaber unter Konto; dafür ist das aktuelle Passwort nötig, eine offene Sitzung genügt nicht. Ein Administrator kann fremde Konten löschen, außer dem letzten Administrator.

Die Shortlinks des Kontos bleiben bestehen und leiten weiter. Sie gehören der Installation, nicht dem Konto (siehe Wem die Links gehören weiter oben). Was wegfällt, ist der Verweis: In der Übersicht steht bei diesen Links danach „—" statt eines Namens. Klick- und Versandprotokolle bleiben ebenfalls, weil die Links in Benutzung sind.

Das war früher anders, und die alte Regel steht bis heute in manchem Kopf: Ein gelöschtes Konto sollte keine wirksamen Weiterleitungen hinterlassen. Das war richtig, solange ein Link seinem Anleger gehörte. In einem gemeinsamen Bestand wäre es die teuerste Fassung des falschen Verhaltens — ein ausscheidender Mitarbeiter nähme die Kampagnenlinks des ganzen Hauses mit, Adressen, die gedruckt sind und die seine Kollegen benutzen.

Ein Konto zu löschen entfernt also keine Links. Wer einen Link loswerden will, löscht ihn in der Übersicht; wer ihn nur sperren will, legt ihn unter Verwaltung → Links still. Die Adresse bleibt in jedem Fall dauerhaft belegt und wird nie neu vergeben — sonst könnte ein später angelegter Link sie erben, und ein alter, längst gedruckter Shortlink führte plötzlich auf ein fremdes Ziel.


9. Redaktionelle Seiten und Betreiberpflichten

Unter Seiten pflegst du die Inhalte, die in der Fußzeile erscheinen. Die Installation legt Impressum, Datenschutz und Barrierefreiheit als Platzhalter an — aber nur, wenn es noch gar keine Seiten gibt, damit ein zweiter Lauf nichts überschreibt. Diese Texte sind nicht rechtsverbindlich und müssen vor dem Produktivgang ersetzt werden.

Platzhalter statt fertiger Texte ist Absicht: Alle drei sind Aussagen des Betreibers über seine eigene Installation. Ein vorformulierter Text wäre entweder falsch oder eine Konformitätsbehauptung, die niemand geprüft hat.

Die folgenden Punkte gehören inhaltlich hinein. Das ist eine Erinnerungsliste, keine Rechtsberatung — im Zweifel juristisch prüfen lassen:

Impressum — Anbieterangaben nach § 5 DDG.

Datenschutzerklärung — mindestens:

  • dass bei jedem Aufruf eines Shortlinks eine gekürzte IP-Adresse, der Zeitpunkt und die Browserkennung protokolliert werden, und wie lange (Standard 90 Tage),
  • dass beim Versand eines Links durch das System die Empfängeradresse protokolliert wird, und wie lange (Standard 90 Tage),
  • dass Ziel-Adressen gespeichert werden und dass die Anwendung sie regelmäßig selbst abruft, um Weiterleitungen zu erkennen,
  • dass Ziele nicht auf Schadsoftware oder Rechtswidrigkeit geprüft werden,
  • Empfänger von E-Mails (der eigene Mailserver oder der Provider),
  • Rechte der Betroffenen und der Kontaktweg.

Ein Punkt, der bei einem Kürzungsdienst leicht übersehen wird: Die Vorschauseite und die Weiterleitung geben nichts an das Ziel weiter — die Antwort trägt Referrer-Policy: no-referrer, und die Vorschauseite enthält bewusst keinen anklickbaren Verweis auf das Ziel. Aber die Weiterleitung selbst führt den Besucher zu einem fremden Server, und dort greift dessen Datenschutzerklärung. Wenn deine Nutzer auf fremde Ziele verlinken dürfen, gehört das erwähnt.

Erklärung zur Barrierefreiheit (BFSG) — Konformitätsstatus, bekannte Einschränkungen, Datum der Bewertung und ein erreichbarer Feedback-Weg.

Der Feedback-Weg ist keine eingebaute Funktion, sondern eine redaktionelle Seite, die du selbst anlegst (Slug üblicherweise kontakt). Es genügt ein mailto:-Link auf eine Adresse, die du auch liest — ein Formular verlangt das Gesetz nicht. Setze bei dieser Seite den Haken „im Footer zeigen", damit der Weg von jeder Seite aus erreichbar ist, und verlinke ihn zusätzlich aus der Erklärung heraus.

Behaupte keine Konformität, die nicht geprüft wurde. Was am System selbst umgesetzt ist, steht in ACCESSIBILITY.md — das ist die Grundlage für die Erklärung, aber kein Prüfbericht. Die öffentlich sichtbaren Seiten dieses Dienstes sind Weiterleitung, Passwortmaske, Vorschauseite und Hinweisseite; sie sind der Teil, den beliebige Empfänger zu sehen bekommen.

Weitere Seiten (AGB, Nutzungsbedingungen, FAQ) lassen sich anlegen und in der Fußzeile sortieren.

Reservierte Adressen sind gesperrt, damit sie nicht mit den Programmwegen kollidieren: account, admin, install, invitation, links, login, logout, password, two-factor-challenge, build, storage, up, preview, favicon.ico, robots.txt. Die Sperre gilt in beide Richtungen — für Shortlinks und für redaktionelle Seiten.


10. Sicherheit: was das System tut und warum

Die Kurz-Adresse selbst

Ein zufälliger Slug ist 7 Zeichen aus einem Vorrat von 32 (23456789abcdefghijkmnpqrstuvwxyz) — rund 3,4 · 10¹⁰ Möglichkeiten. Ausgeschlossen sind 0, 1, l, o und Großschreibung: Ein Shortlink wird vorgelesen, abgetippt und gedruckt, und das sind die Verwechslungen, die tatsächlich passieren.

Ein Slug ist keine Zugangsbeschränkung. 7 Zeichen sind gegen gezieltes Durchprobieren nicht viel, und ein selbst gewählter Slug (sommeraktion) ist erratbar. Wer den Zugang beschränken will, setzt ein Passwort — das ist der dafür gebaute Weg.

Aufgelöst wird case-insensitive: /ABC23DE erreicht /abc23de. Ein Empfänger, der die Adresse von einem Plakat abtippt, achtet nicht auf Groß- und Kleinschreibung.

Schutz vor Missbrauch der Weiterleitung

  • Nur http und https. Andere Schemata werden abgelehnt.

  • Zugangsdaten in der Adresse werden abgelehnt, nicht entfernt. https://kunde.de@boese.example/ führt zu boese.example — ein Mensch liest den Teil davor als Host. Solche Eingaben sind kein Versehen.

  • Höchstlänge 2000 Zeichen, keine Steuerzeichen.

  • Die Ziel-Prüfung greift auf IP-Ebene, nicht auf Namensebene. Bei jedem Sprung der Weiterleitungskette werden die aufgelösten Adressen gegen private und reservierte Bereiche geprüft: 10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, dazu CGNAT 100.64/10, 192.0.0.0/24, 198.18/15, Multicast 224/4 und ff00::/8. IPv4-in-IPv6 wird ausgepackt und in beiden Formen geprüft.

    Der Grund: Ein öffentlicher Hostname darf völlig legitim auf 10.0.0.5 zeigen. Wer den Namen prüft statt der Adresse, hinterlässt einen Portscanner für das eigene Netz — mit einer Kurz-Adresse als Steuerung.

  • Die Prüfung schickt eine eigene Kennung ohne die Adresse dieser Installation. Ein fremder Server soll nicht erfahren, wo er nachgesehen wurde.

Anmeldung und Drosselung

Fünf Fehlversuche pro Adresse und IP-Adresse sperren die Anmeldung für eine Minute. Passwörter werden mit bcrypt gespeichert. „Passwort vergessen" antwortet immer gleich, unabhängig davon, ob es die Adresse gibt — sonst wäre die Maske ein Werkzeug zum Aufspüren vorhandener Konten.

Gedrosselt sind alle öffentlichen schreibenden Wege, aber nicht die Weiterleitung selbst: Eine Drosselung darauf träfe genau das, was der Dienst tun soll. Deshalb sind die Freischaltung eines passwortgeschützten Links (10 Versuche in 10 Minuten) und der Weiter-Knopf der Vorschauseite (30 in 10 Minuten) eigene, gedrosselte Routen.

Die Vorschauseite

Sie nimmt die Verschleierung zurück, die ein Kürzungsdienst zwangsläufig erzeugt — und zwar dann, wenn sie nicht gewollt war. Vier Eigenschaften sind Absicht:

  • Kein anklickbarer Verweis auf das Ziel, sondern ein POST-Knopf. Ein Anker liefe an der Zählung vorbei und gäbe Crawlern etwas zu folgen. Die vollständige Adresse steht als auswählbarer Text da: lesbar, markierbar, kopierbar — aber nicht versehentlich anklickbar.
  • Keine automatische Weiterleitung, kein Zähler, kein Meta-Refresh. Eine Seite, die von selbst weitergeht, ist keine Warnung, sondern eine Verzögerung.
  • Referrer-Policy: no-referrer auf der Antwort.
  • Der Grund steht dabei. Eine Vorschauseite ohne Begründung ist eine Hürde, die man wegklickt. Der Ton ist bewusst neutral: Der häufigste Grund („liegt auf einer anderen Domain") trifft völlig harmlose Links, und wer dort warnt, wo nichts zu warnen ist, erzieht seine Besucher zum Wegklicken.

Alle öffentlichen Seiten tragen X-Robots-Tag: noindex, nofollow, noarchive — global über eine Middleware, also auch auf Fehlerseiten.

Was protokolliert wird

  • Jeder Aufruf: gekürzte IP-Adresse, Zeitpunkt, Browserkennung, ob als Link-Vorschau erkannt, und ob über Weiterleitung oder Vorschauseite.
  • Jeder Versand eines Links durch das System: wer, an welche Adresse, welcher Link, Ergebnis.
  • Jedes Zurücksetzen einer Zwei-Faktor-Anmeldung durch einen Administrator.
  • Jeder Eingriff der Aufsicht (stilllegen, freigeben).

Die IP-Adresse wird beim Schreiben gekürzt, nicht bei der Anzeige: IPv4 ohne letztes Oktett, IPv6 mit den letzten 80 Bit auf Null. Eine Adresse, die erst später gekürzt würde, wäre vorher vollständig gespeichert gewesen.

Das gehört in die Datenschutzerklärung.

Was das System bewusst nicht tut

  • Keine Prüfung der Ziel-Inhalte. Kein Abgleich mit Sperrlisten, keine Malware-Prüfung, keine Inhaltsanalyse. Geprüft wird ausschließlich, ob das Ziel auf eine andere Domain weiterleitet.
  • Keine Auswertung der Aufrufe. Es gibt einen Zähler und ein Protokoll, aber keine Statistik nach Herkunft, Gerät, Referrer oder Kampagne. Das ist kein fehlendes Feature, sondern der Verzicht, aus einem Weiterleitungsdienst ein Messwerkzeug zu machen.
  • Kein Cookie, kein Zählpixel, keine Kennung des Besuchers.
  • Keine echte Löschung von Links. Sie werden stillgelegt und als gelöscht markiert, damit die Adresse belegt bleibt.

11. Datenhaltung und Aufbewahrung

Was Wo Wie lange
Links, Konten, Einstellungen, Seiten Datenbank bis zur Löschung — Links bleiben als Datensatz bestehen, damit der Slug belegt bleibt
Klick-Protokoll (gekürzte IP, Zeitpunkt, Browserkennung) Datenbank SHORTLINK_CLICK_LOG_RETENTION_DAYS, Standard 90 Tage
Versandprotokoll (Empfängeradresse, Absender, Zeitpunkt, Ergebnis) Datenbank SHORTLINK_SEND_LOG_RETENTION_DAYS, Standard 90 Tage
Sitzungen Datenbank SESSION_LIFETIME, Standard 120 Minuten
Logo storage/app/public/branding/ bis zum Ersetzen oder Entfernen
Eigenes Theme storage/app/private/theme/tokens.css bis zum Ersetzen oder Zurücksetzen
Aktualisierte Public Suffix List storage/app/private/public-suffix-list.dat bis zur nächsten Aktualisierung
Bereitgestellte Update-Pakete storage/app/private/updates/ bis zum Aufräumlauf, nachdem sie eingespielt wurden

Der Lebenszyklus eines Links endet halb von selbst. Ein abgelaufener Link leitet sofort nicht mehr weiter; nach der Karenzzeit legt das Aufräumen ihn still. Die Karenz ist dafür da, dass ein um einen Tag verpasster Termin kein Verlust wird — in dieser Zeit kann der Eigentümer den Link verlängern, statt einen neuen Slug ausgeben zu müssen.

Wird ein Link stillgelegt oder gelöscht, gehen seine Klick- und Versandprotokolle mit. Sie sind personenbezogen (gekürzte IP-Adresse, Empfängeradresse) und haben ohne wirksamen Link keinen Zweck mehr. Der Klick-Zähler am Link bleibt: eine Zahl ohne Personenbezug.

Zwei Fälle bleiben bewusst außen vor, weil sie kein berechenbares Ende haben:

  • Links ohne Ablaufdatum (sofern in den Funktionen erlaubt),
  • Links, die nur ihr Klick-Limit erreicht haben.

Beide bleiben aktiv gelistet, bis jemand sie löscht. Wer das nicht will, verbietet „Kein Ablauf" in den Funktionen — dann hat jeder Link ein Ende.

Was hier NICHT steht, ist Absicht: Es gibt keinen Wert und keinen Befehl, der einen Link endgültig aus der Tabelle entfernt. Der Slug muss dauerhaft belegt bleiben. Wer Datensätze wirklich loswerden muss (etwa auf Betroffenen-Verlangen), tut das in der Datenbank und muss dann selbst dafür sorgen, dass der Slug nicht neu vergeben wird.

Die geltenden Fristen stehen in der Oberfläche unter Einstellungen → Wartung, Karte Geltende Fristen — mit den Werten, die tatsächlich wirken, nicht nur mit den Namen der Schlüssel. Zwei davon befristen personenbezogene Daten, und wer für eine Auskunft nach DSGVO geradesteht, muss den geltenden Wert nachsehen können, ohne sich auf den Server zu verbinden. Geändert werden sie weiterhin nur in der .env; die Karte ist eine Anzeige.

Damit das Aufräumen greift, muss shortlink:cleanup --force regelmäßig laufen (Cron). Ohne diesen Eintrag bleibt alles liegen. Prüfe nach der Einrichtung einmal den Testlauf, damit du siehst, was der erste echte Lauf entfernen würde.


12. Backup und Wiederherstellung

Ein vollständiges Backup braucht drei Bestandteile. Fehlt einer, ist die Wiederherstellung unvollständig:

  1. Datenbank — Konten, Links, Protokolle, Einstellungen, Seiten. Das ist der wesentliche Teil: Hier stecken die Slugs.
  2. storage/app/ — Logo, eigenes Theme, aktualisierte Public Suffix List.
  3. .env — enthält APP_KEY.
# Datenbank
mysqldump --single-transaction --quick -u <user> -p <db> | gzip > db-$(date +%F).sql.gz

# Ablagen
tar czf storage-$(date +%F).tar.gz -C /var/www/shortlink storage/app

Bei einem Kürzungsdienst ist das Datenbank-Backup die eigentliche Sicherung. Geht die Datenbank verloren, sind nicht „Daten" weg, sondern jede ausgegebene Adresse ist ungültig — jeder gedruckte Flyer, jeder QR-Code, jeder Link in einer alten Mail. Ein Kürzungsdienst hat keine Kopie beim Nutzer, aus der man nachladen könnte: Der Nutzer hat nur die kurze Adresse, und was dahinter stand, wusste ausschließlich dieser Server.

Und schlimmer als „weg" ist „teilweise wiederhergestellt": Ein Backup, das einen Teil der Slugs nicht enthält, gibt sie zur Neuvergabe frei. Der nächste angelegte Link kann einen alten erben.

Ohne APP_KEY sind das gespeicherte SMTP-Passwort und verschlüsselte Sitzungen unlesbar. Beides lässt sich neu setzen — die Slugs nicht.

Der Knopf in der Oberfläche: Einstellungen → Wartung

Unter Einstellungen → Wartung liegt oben Datenbank herunterladen. Er liefert je Treiber:

Treiber Datei Inhalt
SQLite shortlink-datenbank-2026-09-01-1530.sqlite die Datenbankdatei selbst, über VACUUM INTO als abgeschlossener Stand
MySQL / MariaDB shortlink-datenbank-2026-09-01-1530.sql SQL-Text: DROP TABLE + CREATE TABLE + INSERT, gelesen in einem Snapshot (REPEATABLE READ)

Wofür er gedacht ist — und wofür nicht. Er ist der Handgriff vor einem Eingriff: vor dem Aufräumen, vor einem Update, vor dem Umschalten des Link-Modus. Er ist kein Ersatz für die regelmäßige Sicherung oben — die läuft ohne Klick und liegt nicht im Download-Ordner eines Arbeitsplatzrechners.

Ohne mysqldump und ohne Shell-Aufruf. Der Dump wird in PHP erzeugt. Der Grund ist nicht Vorliebe: Auf gehärteten und geteilten Hosts sind exec/shell_exec gesperrt und mysqldump liegt dort oft gar nicht. Ein Knopf, der bei der Hälfte der Installationen eine leere Datei liefert, ist schlimmer als keiner — man merkt es erst, wenn man die Sicherung braucht.

  • Nur die Tabellen dieser Installation. Ist DB_PREFIX gesetzt, werden ausschließlich Tabellen mit diesem Präfix gesichert. In einer geteilten Datenbank bleiben fremde Anwendungen unberührt. Ohne Präfix ist diese Grenze nicht zu erkennen — dann werden alle Tabellen des Schemas gesichert.
  • Gelesen wird in Blöcken (500 Zeilen) und der Text als Strom ausgeliefert. Der Speicherbedarf bleibt damit unabhängig von der Größe von click_logs.
  • PostgreSQL und SQL Server stehen in config/database.php, werden hier aber nicht bedient. Der Knopf erscheint dann nicht; stattdessen steht ein Hinweis auf die Werkzeuge des Datenbanksystems.

Die Datei ist selbst ein personenbezogener Datenbestand. Sie enthält Passwort-Hashes, Sitzungen, Klick-Protokolle mit gekürzten IP-Adressen und Versandprotokolle mit Empfängeradressen. Verschlüsselt aufbewahren, nicht in einen geteilten Ordner legen, nach Gebrauch löschen.

Sie enthält die .env NICHT — und damit nicht den APP_KEY. Die Spalten two_factor_secret und two_factor_recovery_codes sind mit ihm verschlüsselt. Wer die Datenbank ohne die passende .env zurückspielt, hat eine Installation, in die niemand mit zweitem Faktor mehr hineinkommt: Die Daten sind da, der Schlüssel fehlt. Sichere die .env immer zusammen mit der Datenbank.

Scheitert die Sicherung, steht der Grund im Protokoll (storage/logs). Die häufigsten Ursachen sind fehlender Platz und fehlendes Schreibrecht im Temp-Verzeichnis des Servers.

Wiederherstellung

gunzip < db-2026-08-14.sql.gz | mysql -u <user> -p <db>
tar xzf storage-2026-08-14.tar.gz -C /var/www/shortlink
php artisan storage:link
php artisan config:cache

Danach prüfen: Anmeldung, ein Aufruf eines bestehenden Shortlinks (leitet er weiter?), ein passwortgeschützter Link, Test-Mail, Einstellungen → Systemdaten (Schreibrechte), und Einstellungen → Links (steht die Domain-Liste noch?).

Teste die Wiederherstellung, bevor du sie brauchst. Ein Backup, das nie zurückgespielt wurde, ist eine Annahme, keine Sicherung.


13. Wartung

Zwei Cron-Einträge

# 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 /var/www/shortlink && /usr/bin/php artisan shortlink:cleanup --force >> storage/logs/cleanup.log 2>&1

# 2) Ziel-Prüfung: nachsehen, ob gespeicherte Ziel-URLs inzwischen selbst auf eine andere
#    Domain weiterleiten.
#    OHNE diesen Eintrag wird ein Ziel NIE geprüft — auch nicht beim Anlegen.
15 * * * * cd /var/www/shortlink && /usr/bin/php artisan shortlink:check-targets >> /dev/null 2>&1

Beide stehen auch in der Oberfläche unter Einstellungen → Systemdaten, Karte „Zeitsteuerung (Cron)" — mit dem Pfad dieser Installation und einem Knopf zum Kopieren. Dort steht ebenfalls, wann jeder der beiden Befehle zuletzt gelaufen ist. Ist einer nie gelaufen oder länger her, als sein Takt erwarten lässt, erscheint oben auf derselben Seite eine Warnung.

Was die Anzeige dabei NICHT kann: die crontab lesen — dazu müsste die Anwendung eine Shell aufrufen, und das tut sie nirgends. Sie kennt nur den letzten tatsächlichen Lauf, und daran ist nicht zu erkennen, wer ihn gestartet hat: Ein Aufruf von Hand zählt genauso. Nicht mitgezählt werden der Testlauf ohne --force und das Aufräumen über den Knopf unter Einstellungen → Wartung — sonst meldete ein einziger Probelauf einen Cron-Eintrag, den es gar nicht gibt.

Warum die Prüfung stündlich und das Aufräumen nächtlich: Die Prüfung arbeitet mit Limit und Frist (Vorgabe 200 Links, 120 Sekunden) und nimmt beim nächsten Lauf dort weiter, wo sie 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. Das Aufräumen ist eine Datenbank-Sache und gehört in die ruhige Zeit.

Die vier Befehle

Befehl Was er tut
shortlink:cleanup Aufbewahrungsfristen durchsetzen. Ohne --force verändert er nichts (Testlauf)
shortlink:check-targets Ziele erneut prüfen. --all, --limit=200, --deadline=120
shortlink:suffix-list Frische Public Suffix List holen
shortlink:check Voraussetzungen prüfen, ohne etwas zu ändern

Dazu shortlink:install und shortlink:installed für die Einrichtung (siehe Kapitel 3).

Aufräumen: die fünf Kategorien

Kategorie Was
Abgelaufene Links Ablauf liegt länger zurück als die Karenzzeit → stillgelegt, nicht gelöscht. Die Adresse bleibt belegt
Klick-Protokolle älter als die eingestellte Frist (der Zähler am Link bleibt)
Versandprotokoll älter als die eingestellte Frist
Eingespielte Update-Pakete Maßstab ist die Fassung, nicht das Alter
Freigegebene Namensräume Kürzel ohne Konto, unter denen keine Adresse liegt — gelöschte Konten und ausgetauschte Kürzel (siehe Kapitel 5)

Plattenplatz gibt allein „Eingespielte Update-Pakete" frei. Alles andere sind Datenbankzeilen; bei den Namensräumen ist der Ertrag ein freies Kürzel.

Dasselbe steht in der Oberfläche unter Einstellungen → Wartung: Testlauf, dann bestätigte Ausführung mit Bericht. Der Testlauf ist dort der eigentliche Zweck — er zeigt, was passieren würde, und die ersten drei Kategorien betreffen echte Daten.

Ist eine Frist auf -1 gesetzt, erscheint die Kategorie im Bericht mit dem Hinweis, dass das automatische Löschen abgeschaltet ist. Sie fehlt nicht stillschweigend.

Regelmäßig ansehen

  • Verwaltung → Links, Sicht „Mit Befund". Der Zähler steht oben und immer, auch bei 0: „Keine Befunde" ist eine Auskunft, eine fehlende Meldung ist keine.
  • Einstellungen → Systemdaten: Warnungen, freier Speicher.
  • storage/logs/laravel.log auf wiederkehrende Fehler. Hier landet auch, wenn eine Benachrichtigung an einen Link-Eigentümer nicht verschickt werden konnte — das Stilllegen selbst läuft trotzdem durch.
  • Ob die beiden Cron-Einträge laufen: Einstellungen → Systemdaten, Karte „Zeitsteuerung (Cron)". Dort steht je Auftrag der letzte Lauf. Gegenprobe für die Ziel-Prüfung: In der Aufsichtsliste steht je Link, wann zuletzt geprüft wurde — stehen dort überall alte Daten oder „noch nicht geprüft", fehlt der Eintrag.

14. Aktualisierung

Der empfohlene Weg läuft über Einstellungen → Systemdaten: Paket bereitstellen (die Oberfläche prüft die Prüfsumme), dann den angezeigten Befehl im Terminal ausführen.

Von Hand, aus dem Quellcode:

php artisan down                              # Wartungsmodus
git pull
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan up

Vor jeder Aktualisierung ein Backup, mindestens von der Datenbank. Migrationen sind nicht immer rückwärts anwendbar.

queue:restart ist nicht nötig — es gibt keine Worker.

Was eine Aktualisierung nicht anfasst: .env, storage/ (also Logo, eigenes Theme und die aktualisierte Public Suffix List) und die Datenbank über die Migrationen hinaus.


15. Störungssuche

Beobachtung Wahrscheinliche Ursache Vorgehen
Jeder Link zeigt eine Vorschauseite Public Suffix List fehlt, oder die Domain-Liste ist leer php artisan shortlink:suffix-list; Einstellungen → Links prüfen. Die Seite sagt oben, wenn die Liste fehlt
Ein Link zeigt eine Vorschauseite, obwohl das Ziel auf der Liste steht Das Ziel leitet selbst auf eine andere Domain weiter (foreign_redirect) Detailseite des Links: dort steht die tatsächliche Endadresse. Das ist ein Befund, kein Fehler
Prüfung sagt bei allen Links „noch nicht geprüft" Cron-Eintrag für shortlink:check-targets fehlt Eintrag setzen; einmalig von Hand laufen lassen
Ein Ziel gilt dauerhaft als „nicht prüfbar" Der Zielserver lehnt HEAD und GET ab, blockiert die Kennung oder antwortet zu langsam Kein Befund und kein Ausfall. Wenn es stört: das Ziel auf die Domain-Liste setzen
Anlegen wird abgelehnt: „Public Suffix List" „Nur Ziele auf freigegebenen Domains zulassen" ist an, aber die Liste fehlt Liste holen oder den Schalter ausschalten
Links werden still abgeschaltet „Betroffene Links automatisch stilllegen" ist an Verwaltung → Links, Sicht „Stillgelegt", dort freigeben. Schalter überdenken
Eigentümer erfahren nichts vom Stilllegen Kein Mail-Versand eingerichtet Einstellungen → E-Mail; die Seite Links warnt in diesem Fall
Der erste Administrator kann keine Shortlinks anlegen Seine Adresse ist noch nicht bestätigt — so legt der Installer sie an Einstellungen → E-Mail einrichten, dann Konto → „Bestätigungsmail senden" und den Link anklicken
Ein Nutzer kann nichts anlegen, alles andere geht Adresse unbestätigt Nutzerliste prüfen. Hat er die Einladung je angenommen? Sonst Einladung erneut senden
Die Bestätigungsmail kommt nicht an Adresse falsch geschrieben, oder der Versand ist nicht in Ordnung Test-Mail in Einstellungen → E-Mail. Bei falscher Adresse: Der Nutzer ändert sie selbst unter Konto (ein Administrator kann fremde Adressen nicht ändern)
Keine Mails Zugang falsch oder Provider blockiert Einstellungen → E-Mail → Test-Mail; die Fehlermeldung steht im Klartext da
Mails landen im Spam SPF/DKIM fehlen, Absender passt nicht zur Domain DNS-Einträge setzen, Absenderadresse anpassen
Zähler steht auf 0, obwohl der Link im Chat liegt Link-Vorschauen zählen nicht mit Kein Fehler. Der Aufruf steht im Protokoll, als Vorschau markiert
Zähler ist verbraucht, bevor jemand geklickt hat Ein nicht erkannter Vorschau-Dienst Klick-Limit höher setzen. Wer den Dienst benennen kann: app/Support/BotSignature.php
Logo erscheint nicht storage:link fehlt php artisan storage:link
Oberfläche ohne Gestaltung Oberfläche nicht gebaut npm ci && npm run build
Änderung an .env wirkt nicht Konfiguration zwischengespeichert php artisan config:cache
Neue Seite oder neuer Slug zeigt 404 Adresse ist reserviert oder Seite nicht veröffentlicht anderen Slug wählen; Häkchen „veröffentlicht" prüfen
Ein Slug lässt sich nicht anlegen, ist aber nirgends zu sehen Er war schon einmal vergeben — Slugs werden nie neu vergeben anderen wählen
„Kopieren" funktioniert nicht Kein HTTPS (kein sicherer Kontext) TLS einrichten; der Text lässt sich derweil markieren
Uhrzeiten liegen ein bis zwei Stunden daneben APP_TIMEZONE weicht von der Zone der Nutzer ab Passende Zone setzen. Achtung: nur vor dem ersten Produktivdatensatz
Niemand kommt mehr in ein Konto (2FA verloren) Gerät und Codes weg Nutzer → Zwei-Faktor zurücksetzen, nach Rückfrage auf anderem Weg

Für Support-Anfragen: Einstellungen → Systemdaten → Bericht kopieren. Er enthält alle Betriebswerte und keine Geheimnisse.


16. Grenzen und bekannte Lücken

Offen und bewusst — damit niemand etwas voraussetzt, was nicht da ist:

  • Keine Prüfung der Ziel-Inhalte. Kein Abgleich mit Sperrlisten (Google Safe Browsing oder ähnlich), keine Malware-Prüfung. Erkannt wird ausschließlich, ob ein Ziel auf eine andere Domain weiterleitet.
  • Die Erkennung von Link-Vorschauen ist eine Heuristik. Sie stützt sich auf die Kennung, die der Aufrufer selbst schickt. Ein unbekannter Dienst zählt mit, ein fälschlich erkannter Browser nicht.
  • QR-Codes ohne Gestaltung. Schwarz auf Weiß, PNG oder SVG — kein Logo in der Mitte, keine Farben, keine gerundeten Module.
  • Kein Anlegen mehrerer Links auf einmal und kein Import.
  • Keine Auswertung der Aufrufe — nur Zähler und Protokoll, keine Statistik nach Herkunft, Gerät oder Zeitverlauf.
  • Der Slug eines bestehenden Links ist nicht änderbar. Absicht: Er würde jede verschickte und gedruckte Kopie brechen, und der alte würde frei.
  • Links ohne Ablaufdatum und Links mit erreichtem Klick-Limit werden nicht automatisch stillgelegt. Sie haben kein berechenbares Ende.
  • Kein endgültiges Löschen. Datensätze bleiben, damit Adressen belegt bleiben.
  • Nur deutschsprachig. Keine Mehrsprachigkeit, keine englische Oberfläche.
  • Zwei Rollen. Keine feineren Rechte, keine Gruppen, keine Mandanten.
  • Barrierefreiheit ist umgesetzt und im Markup geprüft, aber nicht an einem echten Screenreader und nicht extern (siehe ACCESSIBILITY.md).

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