Plattformen
PHP
Runlight läuft mit PHP 8.2 oder neuer, in einer Laravel-, Symfony- oder reinen PHP-App oder auf einer eigenen Domain auf gewöhnlichem PHP-Hosting.
Das PHP-Paket ist Runlight, noch einmal in PHP geschrieben. Es liefert dasselbe Dashboard und dieselbe API aus und gibt auf jede Anfrage die Antwort, die auch die TypeScript-Bibliothek gibt. Seine Zahlen landen in denselben Tabellen, deshalb kann jede der beiden eine Datenbank lesen, die die andere geschrieben hat. Es braucht PHP 8.2 oder neuer mit PDO und dem Treiber für deine Datenbank.
Installieren
Füge das Paket mit Composer hinzu.
bash
composer require runlight/runlightRunlight nutzt die Erweiterungen curl, intl, sodium und APCu, wenn sie geladen sind, und weicht für jede fehlende auf reines PHP aus.
Die Instanz erstellen
Leg eine Datei an, die die Instanz zurückgibt, und binde sie überall ein, wo du Runlight brauchst.
runlight.php
<?php
use Runlight\Runlight;
use Runlight\Store\Stores;
return new Runlight([
'store' => Stores::sqlite(__DIR__ . '/data/runlight.db'),
'site' => ['name' => 'example.com', 'hostnames' => ['example.com'], 'timezone' => 'Europe/London'],
]);Die Optionen heißen genauso wie unter Konfiguration, deshalb funktionieren sites, trustProxy, rateLimit, mail und managedSites alle so wie dort. Die Tabellen werden bei der ersten Anfrage angelegt.
Stores
Runlights Tabellen beginnen mit rl_. Die Stores für SQLite, MySQL und Postgres nehmen eine Option prefix, die stattdessen verwendet wird, etwa Stores::sqlite(['path' => $path, 'prefix' => 'wp_rl_']) oder Stores::mysql($url, ['prefix' => 'wp_rl_']), damit zwei Apps in einer gemeinsamen Datenbank jeweils ihre eigenen Tabellen behalten, und Stores::fromDb($db, 'wp_rl_') macht dasselbe über ein eigenes Db. Ein Präfix besteht aus bis zu 32 Buchstaben, Ziffern und Unterstrichen und beginnt nicht mit einer Ziffer. $store->tables() listet die Tabellen des Stores mit Namen auf, für eine App, die Runlight entfernt.
SQLite
php
Stores::sqlite(__DIR__ . '/data/runlight.db');SQLite braucht die Erweiterung pdo_sqlite, die die meisten PHP-Installationen haben. Leg die Datei außerhalb deines Web-Roots ab, in einem Ordner, in den der Webserver schreiben darf, weil SQLite daneben ein Journal schreibt. Fehlt der Ordner, wird er angelegt.
MySQL und MariaDB
php
Stores::mysql('mysql://runlight:password@127.0.0.1:3306/runlight');Dieser Store braucht pdo_mysql und funktioniert mit MySQL 8.0.20 oder neuer und MariaDB 11.4 oder neuer. Eine mariadb://-URL funktioniert auch. Ein zweites Argument wie ['statementTimeout' => 30000] bricht jede einzelne Abfrage nach so vielen Millisekunden ab, Standard ist 120000, wie unter Konfiguration.
Postgres
php
Stores::postgres('postgres://runlight:password@127.0.0.1:5432/runlight');Dieser Store braucht pdo_pgsql. Er nimmt statementTimeout ebenfalls im zweiten Argument, und ['schema' => 'analytics'] legt die Tabellen in ein eigenes Schema. Stores::url() wählt den Store anhand einer DATABASE_URL, die mit postgres://, mysql://, mariadb:// oder sqlite: beginnt.
Reines PHP
In einer App mit einer einzigen index.php, durch die jede Anfrage läuft, schickst du Pfade unter /runlight und /go ganz oben an Runlight.
public/index.php
<?php
require __DIR__ . '/../vendor/autoload.php';
use Runlight\Http\Request;
use Runlight\Server\FrontController;
$rl = require __DIR__ . '/../runlight.php';
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($path === '/runlight' || str_starts_with($path, '/runlight/') || str_starts_with($path, '/go/')) {
FrontController::serve($rl, $rl->routes());
return;
}
// AI agents reading your pages; optional.
$rl->observe(Request::fromGlobals());
// The rest of your app.FrontController::serve() liest die Anfrage aus den Globals von PHP und sendet die Antwort. observe() zeichnet die Abrufe von KI-Agenten auf und kehrt für alle anderen sofort zurück. Füge das Skript in jede Seite ein, direkt vor </head>.
html
<script defer src="/runlight/s.js"></script>Setze dann RUNLIGHT_TOKEN auf eine lange zufällige Zeichenkette und öffne einmal /runlight/?token=, gefolgt von dieser Zeichenkette, um dich anzumelden, wie es Erste Schritte beschreibt. Mit routes(['accounts' => true]) und einem RUNLIGHT_SECRET melden sich Leute mit ihrer eigenen E-Mail-Adresse und ihrem Passwort an, wie unter Konten.
Eine Link-Domain beantwortet Kurzlinks an der Wurzel eines eigenen Namens, ihre Anfragen passen also nie zu den Pfaden oben. Um eine aus der App heraus zu bedienen, frag zuerst linkDomainResponse(). Es antwortet nur auf deinen Link-Domains und gibt für jeden anderen Host null zurück, sodass der Rest der App wie bisher weiterläuft.
php
$request = Request::fromGlobals();
$linked = $rl->linkDomainResponse($request, ['ip' => $request->remoteAddress]);
if ($linked !== null) {
$linked->emit();
return;
}Laravel und Symfony erledigen das mit Runlights Klasse ShortLinks, wie ihre Abschnitte zeigen.
Request::fromServer($server) liest eine Anfrage aus einem Array, das wie $_SERVER aufgebaut ist, etwa aus der entslashten Kopie davon, die ein CMS anlegt. Runlight\Log::to(fn (string $line) => ...) schickt Runlights Hinweise statt ins Fehlerlog von PHP in ein eigenes Log.
Laravel
Runlights Routen gehören außerhalb der Middleware-Gruppe web von Laravel. Diese Gruppe verlangt bei jedem POST ein CSRF-Token, das der Tracker nicht senden kann, und sie verschlüsselt Cookies, die Runlight dann nicht mehr zurücklesen könnte. Runlight prüft Anfragen auf seine eigene Weise.
Registriere die Instanz in AppServiceProvider.
app/Providers/AppServiceProvider.php
use Runlight\Runlight;
use Runlight\Store\Stores;
public function register(): void
{
$this->app->singleton(Runlight::class, fn () => new Runlight([
'store' => Stores::sqlite(storage_path('app/runlight.db')),
'site' => ['name' => 'example.com', 'hostnames' => ['example.com'], 'timezone' => 'Europe/London'],
]));
}Füge einen Controller hinzu.
app/Http/Controllers/RunlightController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Runlight\Bridge\HttpFoundation;
use Runlight\Runlight;
use Symfony\Component\HttpFoundation\Response;
class RunlightController
{
public function __invoke(Request $request, Runlight $rl): Response
{
$routes = $rl->routes(['token' => (string) config('services.runlight.token')]);
return HttpFoundation::handle($rl, $routes, $request);
}
}Gib ihm eine eigene Routendatei.
routes/runlight.php
<?php
use App\Http\Controllers\RunlightController;
use Illuminate\Support\Facades\Route;
Route::any('/runlight/{path?}', RunlightController::class)->where('path', '.*');
Route::get('/go/{slug}', RunlightController::class);Lade diese Datei in bootstrap/app.php mit then, das Routen außerhalb der Gruppe web registriert.
bootstrap/app.php
use Illuminate\Support\Facades\Route;
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
then: fn () => Route::group([], base_path('routes/runlight.php')),
)Für Link-Domains setzt du in derselben Datei Runlights Middleware ShortLinks vor jede Route. Sie beantwortet eine Anfrage auf einer Link-Domain, bevor Laravel sie routet, ebenso /go/<slug>, wenn der Link existiert, und reicht jede andere Anfrage unverändert weiter.
bootstrap/app.php
use Illuminate\Foundation\Configuration\Middleware;
use Runlight\Bridge\ShortLinks;
->withMiddleware(function (Middleware $middleware) {
$middleware->prepend(ShortLinks::class);
})Lies das Token über die Konfiguration, damit es auch nach php artisan config:cache noch funktioniert, und setze RUNLIGHT_TOKEN in .env.
config/services.php
'runlight' => [
'token' => env('RUNLIGHT_TOKEN'),
],Das (string) im Controller macht aus einem fehlenden Token ein leeres, und das hält das Dashboard geschlossen. Mit null stünde es allen offen. Führe den stündlichen Check über den Scheduler von Laravel aus.
routes/console.php
use Illuminate\Support\Facades\Schedule;
use Runlight\Runlight;
Schedule::call(fn () => app(Runlight::class)->check())->hourly();Symfony
Ein einziger Controller bedient alles unter /runlight und die Kurzlinks unter /go. Seine Methode shortLinks hört auf jede Anfrage, bevor Symfony sie routet, damit auch eine Link-Domain Runlight erreicht, und jede andere Anfrage läuft unverändert weiter.
src/Controller/RunlightController.php
<?php
namespace App\Controller;
use Runlight\Bridge\HttpFoundation;
use Runlight\Bridge\ShortLinks;
use Runlight\Runlight;
use Runlight\Store\Stores;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Attribute\Route;
final class RunlightController
{
private ?Runlight $rl = null;
public function __construct(
#[Autowire('%kernel.project_dir%/var/runlight.db')] private readonly string $database,
#[Autowire('%env(RUNLIGHT_TOKEN)%')] private readonly string $token,
) {
}
#[Route('/runlight/{path}', name: 'runlight', requirements: ['path' => '.*'], defaults: ['path' => ''])]
#[Route('/go/{slug}', name: 'runlight_link', methods: ['GET'])]
public function __invoke(Request $request): Response
{
$rl = $this->runlight();
return HttpFoundation::handle($rl, $rl->routes(['token' => $this->token]), $request);
}
#[AsEventListener(event: KernelEvents::REQUEST, priority: 64)]
public function shortLinks(RequestEvent $event): void
{
(new ShortLinks($this->runlight()))->onKernelRequest($event);
}
private function runlight(): Runlight
{
return $this->rl ??= new Runlight([
'store' => Stores::sqlite($this->database),
'site' => ['name' => 'example.com', 'hostnames' => ['example.com'], 'timezone' => 'Europe/London'],
]);
}
}Setze RUNLIGHT_TOKEN in .env.local. Symfony bricht mit einem Fehler ab, wenn es fehlt, deshalb bleibt das Dashboard nie versehentlich offen. Für den stündlichen Check rufst du $rl->check() aus einem Konsolenbefehl auf, den Cron ausführt, oder du setzt CRON_SECRET und rufst den Check über HTTP auf.
Runlight\Bridge\HttpFoundation macht aus einer Anfrage von Symfony oder Laravel eine von Runlight und aus seiner Antwort wieder eine dortige, und Runlight\Bridge\ShortLinks beantwortet Kurzlinks vor den Routen der App. Beide brauchen symfony/http-foundation, das beide Frameworks schon mitbringen.
ShortLinks beantwortet /go/<name> nur, wenn es den Link gibt, und reicht jeden anderen Namen an deine Routen weiter. Dort wirft die Route /go/{slug} von oben für einen Namen ohne Link den Not-found-Fehler des Frameworks, sodass in jedem Fall deine eigene 404-Seite antwortet. FrontController und das eigenständige Drop-in haben keine App dahinter und senden dieses 404 selbst. Auf einer Link-Domain ist /go/<name> überall ein 404, wie jeder andere Pfad, der keiner der Links dieser Domain ist.
Andere PHP-Apps
Jede PHP-App kann Runlight eine Anfrage übergeben und seine Antwort zurückschicken. Baue aus URL, Methode, Headern, Body und der Adresse der Verbindung einen Runlight\Http\Request und übergib ihn an FrontController::answer().
php
use Runlight\Http\Request;
use Runlight\Server\FrontController;
$request = new Request($url, $method, $headers, $body, $_SERVER['REMOTE_ADDR'] ?? '');
$answer = FrontController::answer($rl, $rl->routes(), $request);
// $answer->status, $answer->headers->all(), and $answer->text() are what to send.Übergib die Adresse, von der die Verbindung kam. Weitergeleitete Header liest Runlight selbst, wenn trustProxy es erlaubt.
Eine Website auf WordPress, Drupal oder Craft braucht nichts von diesem Code. Ihr Plugin verbindet sie mit einem Runlight, das du anderswo betreibst, etwa dem Drop-in unten, wie die Seiten zu WordPress, Drupal und Craft zeigen.
Das eigenständige Drop-in
Das Drop-in ist das PHP-Gegenstück zum eigenständigen Server. Es betreibt Runlight auf einer eigenen Domain, etwa stats.example.com, mit dem Dashboard an der Wurzel, im Dashboard hinzugefügten Websites, Konten mit Anmeldung und Kurzlinks auf jeder Domain, die du darauf zeigen lässt. Es passt zu Hosting, das PHP ohne Node ausführt.
Einrichten
Leg neben deinem Web-Root einen Projektordner an, installiere das Paket dort und kopiere das Drop-in in den Ordner, den die Domain ausliefert.
bash
mkdir stats && cd stats
composer require runlight/runlight
cp -r vendor/runlight/runlight/standalone publicLass die Domain auf public zeigen. Auf Apache und LiteSpeed schickt die mitgelieferte .htaccess-Datei jede Anfrage an index.php. Auf nginx fügst du einen Server-Block wie diesen hinzu.
nginx.conf
server {
server_name stats.example.com;
root /var/www/stats/public;
index index.php;
location / {
try_files $uri /index.php$is_args$args;
}
location = /index.php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.ht {
deny all;
}
# In case the drop-in folder is also the project folder: never serve the data, the code, or the config.
location ~ ^/(vendor|runlight-data|config\.php|composer\.(json|lock))(/|$) {
deny all;
}
}Die Daten liegen in runlight-data im Projektordner, außerhalb des Web-Roots, und werden bei der ersten Anfrage angelegt. Dort liegen die SQLite-Datei und das Geheimnis, das Anmeldungen signiert, die Ortsdaten in einem eigenen Ordner. Der Benutzer des Webservers muss in den Projektordner schreiben dürfen oder in einen Ordner runlight-data, den du für ihn anlegst.
Dein Konto anlegen
Die erste Anfrage schreibt einen Einrichtungslink mit einem Einmalcode in setup.txt im Datenordner. Öffne diesen Link und gib deine E-Mail-Adresse und ein Passwort mit mindestens zehn Zeichen ein, zweimal getippt. Ohne RUNLIGHT_URL beginnt der Link mit einer Platzhalteradresse, setze also deine eigene vor /setup. Der Link funktioniert nur, solange es kein Konto gibt, und der Cron-Befehl löscht die Datei, sobald es eines gibt. Um den Link noch einmal auszugeben, führe das hier im Projektordner aus.
bash
vendor/bin/runlight setupIst RUNLIGHT_TOKEN gesetzt, fragt die Einrichtungsseite stattdessen nach dem Token, und es wird keine Datei geschrieben. Du kannst das erste Konto auch auf der Kommandozeile mit vendor/bin/runlight password you@example.com anlegen, was sein Passwort ausgibt. Rollen, Einladungen, Zwei-Faktor-Anmeldung und das Hinzufügen von Websites funktionieren wie beim eigenständigen Server.
Einstellungen
Die Einstellungen kommen aus Umgebungsvariablen oder aus einer config.php im Projektordner, die dieselben Namen zurückgibt. Sind beide gesetzt, gewinnen die Umgebungsvariablen. Kopiere das Beispiel, um damit anzufangen.
bash
cp vendor/runlight/runlight/config.example.php config.phpconfig.php
<?php
return [
'RUNLIGHT_URL' => 'https://stats.example.com',
'DATABASE_URL' => 'mysql://runlight:password@localhost:3306/runlight',
];| Einstellung | Was sie tut |
|---|---|
RUNLIGHT_URL |
Die öffentliche Adresse des Dashboards, etwa https://stats.example.com. Sie kann nie zu einer Link-Domain werden, und E-Mails mit Einladungen und Berichten verlinken darauf. |
DATABASE_URL |
Eine postgres://-, mysql://- oder mariadb://-Adresse speichert die Daten in dieser Datenbank statt in SQLite. |
DATA_DIR |
Der Datenordner, standardmäßig runlight-data im Projektordner. Ein relativer Pfad wird vom Projektordner aus gelesen. |
RUNLIGHT_SECRET |
Der Schlüssel, der Anmeldungen signiert und gespeicherte Schlüssel verschlüsselt. Ohne ihn erzeugt das Drop-in selbst einen und legt ihn im Datenordner ab. |
RUNLIGHT_TOKEN |
Ein Bearer-Token für Skripte, mit dem auch statt über den Einrichtungslink das erste Konto angelegt wird. |
TRUST_PROXY |
Auf false setzen, wenn nichts vor dem Webserver steht, oder auf x-real-ip oder cf-connecting-ip, wenn dieser Header die Adresse des Besuchers enthält. |
RUNLIGHT_GEO |
Woher Orte kommen, wie beim eigenständigen Server. Standard ist city, möglich sind auch country, off oder der Pfad zu einer MMDB-Datei. |
CRON_SECRET |
Erlaubt einem Scheduler, den Check unter POST /api/check auszuführen, wenn er den Cron-Befehl nicht ausführen kann. |
RUNLIGHT_OBSERVE_KEY |
Ein einzelner Schlüssel für die KI-Agenten-Meldungen aller Websites. Der eigene Agenten-Schlüssel jeder Website, angezeigt unter Einstellungen, Einbinden, ist die bessere Wahl. |
RUNLIGHT_CONFIG |
Der Pfad einer Konfigurationsdatei, die woanders als im Projektordner liegt. |
TRUST_PROXY ist auf PHP-Hosting wichtig, wo oft Apache oder nginx Besuchern direkt antwortet, ohne Proxy davor. Runlight liest die Adresse jedes Besuchers standardmäßig aus dem letzten Eintrag von X-Forwarded-For, und wenn nichts davorsteht, das diesen Eintrag anhängt, kann ein Besucher ihn selbst schreiben. Setze auf einem solchen Server TRUST_PROXY=false.
Cron
Füge der Crontab des Benutzers, unter dem die Website läuft, eine Zeile hinzu. Alle fünf Minuten führt sie den stündlichen Check aus, und einmal im Monat lädt sie die neuen Ortsdaten herunter.
bash
*/5 * * * * cd /var/www/stats && vendor/bin/runlight cronDer Befehl gibt nichts aus, wenn alles in Ordnung ist, abgesehen von einer Zeile, wenn die Ortsdaten eines neuen Monats bereitliegen. Läuft ein Durchlauf noch, wenn der nächste startet, überlässt der zweite ihm die Arbeit. Hat der Host keinen Cron, setze CRON_SECRET und lass einen beliebigen Scheduler damit /api/check aufrufen, wie Stündlicher Check zeigt. Für Orte musst du dann allerdings RUNLIGHT_GEO auf eine eigene Datei setzen.
Vergessene Passwörter
Führe im Projektordner den Befehl password aus, um einem Konto ein neues Passwort zu geben, das er ausgibt. Er schaltet außerdem die Zwei-Faktor-Anmeldung für dieses Konto aus und legt das Konto an, wenn es noch keines gibt.
bash
vendor/bin/runlight password someone@example.comUpgrades und Backups
Führe im Projektordner composer update runlight/runlight aus. Die Tabellen aktualisieren sich bei der nächsten Anfrage selbst, oder du führst vendor/bin/runlight migrate aus, um sie sofort zu aktualisieren. Sichere den Datenordner und config.php, denn ohne das Geheimnis lassen sich gespeicherte Schlüssel nicht lesen.
KI-Agenten aus einem Log
KI-Agenten wie GPTBot und ClaudeBot rufen Seiten ab, ohne JavaScript auszuführen, deshalb sieht das Script-Tag sie nie. Eine PHP-App mit Runlight darin zählt sie dort, wo die Seite ausgeliefert wird, mit observe(), wie Reines PHP zeigt. Für jede andere Website, die du selbst hostest, stehen sie im Zugriffslog des Webservers, und der Befehl agents liest es.
bash
vendor/bin/runlight agents --log /var/log/nginx/access.log --to https://stats.example.com --key rlo_... --site https://example.com --followDer Schlüssel ist der Agenten-Schlüssel der Website, der in Runlight unter Einstellungen, Einbinden angezeigt wird, und er kann nur Abrufe für diese Website melden. Fehlt --to oder --key, liest der Befehl RUNLIGHT_URL oder RUNLIGHT_OBSERVE_KEY aus der Umgebung oder aus config.php, so wie die anderen Befehle ihre Einstellungen lesen. --site ist die Adresse der Website, für Logs im üblichen Format von nginx oder Apache, die den Host weglassen. Die JSON-Logs von Caddy enthalten den Host, dort ist es also nicht nötig. Der Benutzer, der den Befehl ausführt, muss das Log lesen dürfen.
Mit --follow läuft der Befehl weiter, sendet Abrufe, sobald sie passieren, und macht weiter, wenn das Log rotiert wird, wobei er die alte Datei zu Ende liest, bevor er zur neuen wechselt. Ein systemd-Dienst oder ein Prozessmanager hält ihn am Laufen. Füge --state agents.json hinzu, damit er nach einem Neustart dort weitermacht, wo er aufgehört hat. Ohne --follow liest er das Log einmal und hört auf, und mit --state beginnt der nächste Lauf dort, wo der letzte endete, was gut zu Cron passt.
bash
*/5 * * * * cd /var/www/stats && vendor/bin/runlight agents --log /var/log/nginx/access.log --to https://stats.example.com --key rlo_... --site https://example.com --state agents.jsonNur ein Lauf zur Zeit kann eine Zustandsdatei benutzen. Er hält eine Sperre neben der Datei (agents.json.lock), deshalb hört ein zweiter Lauf, etwa ein Cron-Job, der startet, während der letzte noch sendet, mit einer Meldung auf und sendet nichts doppelt. Eine Sperre, die ein abgestürzter Lauf hinterlassen hat, wird übernommen.
Nur erfolgreiche Seitenabrufe bekannter KI-Agenten verlassen die Maschine, jeweils mit der Adresse der Seite, dem User-Agent des Agenten und dem Zeitpunkt der Auslieferung. Die Adressen der Besucher und alles andere im Log bleiben, wo sie sind. Runlight zählt nur Seitenabrufe aus der letzten Woche, deshalb überspringt der erste Lauf über ein langes Log die alten.
Was der Node-Server kann und das Drop-in nicht
PHP hält zwischen Anfragen nichts am Laufen, deshalb funktionieren ein paar Dinge anders als bei npx runlight.sh.
- Der stündliche Check und der monatliche Download von DB-IP laufen über Cron. Bis zum ersten
runlight cronbekommen Besuche einen Ort nur aus Headern wie denen von Cloudflare. - Der Einrichtungslink wird einmal in eine Datei geschrieben und bleibt dort, bis es ein Konto gibt.
- Es gibt kein Docker-Image, und nichts lauscht auf einem eigenen Port. Der Webserver führt das Drop-in aus.
Mehrere Webserver können sich eine Datenbank teilen, so wie Instanzen des Node-Servers. Jede Anfrage liest die Liste der Websites neu, deshalb erscheint eine auf einem Server hinzugefügte Website sofort auch auf den anderen.
Was anders ist als bei der TypeScript-Bibliothek
Das PHP-Paket besteht dieselben Konformitätstests wie die TypeScript-Bibliothek, auf SQLite, Postgres, MySQL und MariaDB. Die Unterschiede kommen daher, wie PHP läuft.
- Es gibt keinen Connection-Pool. Jede Anfrage öffnet ihre eigene Datenbankverbindung und schließt sie am Ende, eine stark besuchte Website hält also eine Verbindung pro PHP-Worker.
- Was die TypeScript-Bibliothek im Hintergrund erledigt, etwa das Löschen von Besuchen, nachdem eine kürzere Aufbewahrungsdauer gewählt wurde, läuft, nachdem die Antwort gesendet ist. PHP-FPM und LiteSpeed schließen vorher die Verbindung, andere Server halten sie offen, bis die Arbeit erledigt ist. Der stündliche Check läuft über Cron oder den Scheduler eines Frameworks, weil in PHP nichts auf einem Timer läuft.
- Passwörter werden in reinem PHP mit scrypt gehasht, was ohne den JIT-Compiler von OPcache etwa 0,7 Sekunden pro Anmeldung dauert und mit ihm etwa 0,2 Sekunden. Die Hashes stimmen mit denen der TypeScript-Bibliothek überein, Konten lassen sich also übernehmen.
- Die TypeScript-Bibliothek hält manche Zähler im Arbeitsspeicher, die PHP zwischen Anfragen vergisst. Die Limits für falsche Passwörter liegen in der Datenbank, deshalb teilen sich alle Worker sie. Das Rate-Limit des Trackers liegt in APCu, wenn es geladen ist, und sonst in kleinen Dateien im temporären Ordner des Systems, außer du übergibst einen eigenen
Runlight\Cachealscache, der dann auch das Symbol der Website aufbewahrt. - Runlight liest
RUNLIGHT_TOKENund die anderen Variablen ausgetenv(), dann aus$_ENV, dann aus$_SERVER, weil Hosts sie an unterschiedlichen Stellen setzen.