Gesamte Doku

Start

  1. 01Erste Schritte
  2. 02Installation
  3. 03Tracking

Funktionen

  1. 04Das Dashboard
  2. 05Ziele
  3. 06Kurzlinks
  4. 07KI-Quellen und Agenten
  5. 08E-Mail-Berichte
  6. 09Stündlicher Check
  7. 10Frag deine KI

Referenz

  1. 11Konfiguration
  2. 12HTTP-API

Plattformen

  1. 13Eigenständiger Server
  2. 14PHP
  3. 15WordPress
  4. 16Drupal
  5. 17Craft CMS
  6. 18Python
  7. 19Rails
  8. 20Ruby
  9. 21Go
  10. 22Java
  11. 23.NET
  12. 24Elixir
  13. 25Rust

Vertrauen

  1. 26Datenschutz

Referenz

Konfiguration

Diese Seite behandelt jede Option für runlight() und routes(), einschließlich Stores und Websites.

ts

import { runlight } from "@runlight/sdk";

export const rl = runlight({ store, site, /* ...options */ });
export const routes = rl.routes({ /* ...route options */ });

Diese Seite verwendet die Namen der TypeScript-Bibliothek. Jede andere Sprache nimmt dieselben Optionen, geschrieben so, wie diese Sprache Namen schreibt, wie ihre Seite zeigt, von PHP bis Rust.

Stores

Runlight bewahrt alles in einer Handvoll Tabellen mit dem Präfix rl_ auf, die bei der ersten Nutzung angelegt und an Ort und Stelle aktualisiert werden. Jeder Store nimmt eine Option prefix, die anstelle von rl_ verwendet wird, etwa wp_rl_, damit zwei Apps, die sich eine Datenbank teilen, jeweils ihre eigenen Tabellen behalten. Das Präfix besteht aus bis zu 32 Buchstaben, Ziffern und Unterstrichen und beginnt nicht mit einer Ziffer, und tables() eines Stores listet seine Tabellen mit Namen auf, für eine App, die Runlight entfernt.

SQLite

ts

import { sqlite } from "@runlight/sdk/sqlite";

sqlite({ path: "./data/runlight.db" });

SQLite braucht better-sqlite3 und passt zu einem einzelnen Server, auf dem die Datei neben der eigenen Datenbank deiner App liegen kann. Auf einem Serverless-Host ohne dauerhafte Festplatte nimmst du Postgres. Eine Datei, die Runlight anlegt, ist nur für ihren Besitzer und ihre Gruppe les- und schreibbar (0660), damit ein geplanter Job, der als anderer Benutzer derselben Gruppe läuft, sie trotzdem nutzen kann.

Postgres

ts

import { postgres } from "@runlight/sdk/postgres";

postgres({ url: process.env.DATABASE_URL });

Postgres braucht pg. Übergib url, oder übergib pool, um einen Pool zu teilen, den deine App schon hat (Runlight beendet nie einen Pool, den es nicht selbst angelegt hat). Ein Pool braucht mindestens 2 Verbindungen. Wenn Runlight den Pool anlegt, legt max seine Verbindungen fest, standardmäßig 10. Eine Anfrage wartet höchstens 10 Sekunden auf eine freie (ein Tracker-Aufruf versucht es noch zweimal, bevor er aufgegeben wird), und statementTimeout stoppt jede einzelne Abfrage nach so vielen Millisekunden, standardmäßig 120000 (0 schaltet das aus). Wenn mehrere Prozesse gleichzeitig starten, werden die Tabellen trotzdem nur einmal angelegt. Ein Prozess wartet höchstens 30 Sekunden auf einen anderen, der die Tabellen anlegt oder aktualisiert. Danach antwortet eine Anfrage mit 503 und dem Code tables_busy, und ein Tracker-Aufruf wartet höchstens 10 Sekunden und wird dann verworfen.

MySQL und MariaDB

ts

import { mysql } from "@runlight/sdk/mysql";

mysql({ url: process.env.DATABASE_URL });

Dieser Store braucht mysql2 und funktioniert mit MySQL 8.0.20 oder neuer und MariaDB 11.4 oder neuer. Übergib eine URL mit mysql:// oder mariadb:// als url, oder übergib pool, um einen Pool aus mysql2/promise zu teilen, den deine App schon hat (Runlight beendet nie einen Pool, den es nicht selbst angelegt hat). Ein Pool braucht mindestens 2 Verbindungen. Wenn Runlight den Pool anlegt, legt max seine Verbindungen fest, standardmäßig 10. Eine Anfrage wartet höchstens 10 Sekunden auf eine freie (ein Tracker-Aufruf versucht es noch zweimal, bevor er aufgegeben wird), und statementTimeout stoppt jede einzelne Abfrage nach so vielen Millisekunden, standardmäßig 120000 (0 schaltet das aus). Unter MySQL gilt diese Grenze für Lesezugriffe, unter MariaDB für jede Anweisung. Wenn mehrere Prozesse gleichzeitig starten, werden die Tabellen trotzdem nur einmal angelegt. Ein Prozess wartet höchstens 30 Sekunden auf einen anderen, der die Tabellen anlegt oder aktualisiert. Danach antwortet eine Anfrage mit 503 und dem Code tables_busy, und ein Tracker-Aufruf wartet höchstens 10 Sekunden und wird dann verworfen.

Die Tabellen nutzen utf8mb4 mit der Kollation utf8mb4_0900_bin. Text wird nach Codepoint verglichen und sortiert, Groß- und Kleinschreibung und Leerzeichen am Ende bleiben erhalten, sodass jeder Bericht genauso aussieht wie unter SQLite oder Postgres.

libSQL und Turso

ts

import { libsql } from "@runlight/sdk/libsql";
import { createClient } from "@libsql/client"; // or "@libsql/client/web" on the edge

libsql({ client: createClient({ url: process.env.TURSO_URL, authToken: process.env.TURSO_TOKEN }) });

Dieser Store braucht @libsql/client. Turso über HTTP funktioniert überall, wo fetch funktioniert, auch auf Vercel Edge und Deno Deploy, und eine file:-URL zeigt auf eine lokale SQLite-Datei.

Cloudflare D1

ts

import { d1 } from "@runlight/sdk/d1";

d1({ database: env.DB });

Übergib das D1-Binding aus der Umgebung deines Workers. D1 hat keine interaktiven Transaktionen, daher schreibt ein Link-Import jeden Link einzeln. Scheitert einer mittendrin, überspringt ein erneuter Import die schon geschriebenen Links.

Bun

ts

import { bunSqlite } from "@runlight/sdk/bun";

bunSqlite({ path: "./data/runlight.db" });

Dieser Store nutzt das eingebaute SQLite von Bun, für Apps auf Bun, wo better-sqlite3 nicht lädt.

Optionen von runlight()

Option Standard Was sie tut
store (Pflicht) Wo die Zahlen liegen, eines von sqlite, postgres, mysql, libsql, d1 oder bunSqlite.
site {} Die Website, die diese Installation zählt.
sites Mehrere Websites in einer Installation, jede mit ihren Hostnamen, anstelle von site.
geo Deine eigene Standortsuche, für Hosts, die keine Standort-Header senden.
trustProxy true Die Adresse des Besuchers aus Proxy-Headern lesen.
linkPath "/go" Wo Kurzlinks auf der eigenen Domain deiner App liegen.
rateLimit 120 Erlaubte Tracker-Anfragen von einer Adresse pro Minute, oder false oder 0 für keine Grenze.
mail Der E-Mail-Dienst für E-Mail-Berichte, im Code festgelegt, oder der eigene Mailer deiner App, den ein im Dashboard gespeicherter Dienst ersetzen kann.
mailServices jeder Dienst Die E-Mail-Dienste, die die Einstellungen anbieten, nach ID, etwa alle außer smtp. Einen anderen zu speichern wird mit dem Code mail_service abgelehnt, und ein früher gespeicherter Dienst, der nicht aufgeführt ist, zählt als keiner, sodass der eigene Mailer der App sendet.
managedSites false Websites in der Datenbank halten und sie im Dashboard verwalten, wie es der eigenständige Server tut, anstelle von site und sites.
secret RUNLIGHT_SECRET, dann RUNLIGHT_TOKEN Verschlüsselt die in der Datenbank gespeicherten Schlüssel für den E-Mail-Dienst, den KI-Assistenten und verbundene Installationen.

Websites

Feld Standard Was es tut
id "default" für die erste Website Stabile ID, die mit jeder Zeile gespeichert wird, aus Buchstaben, Ziffern, Punkten, Bindestrichen und Unterstrichen.
name der erste Hostname Wird im Dashboard angezeigt und lässt sich in den Einstellungen ändern.
hostnames [] (beliebig) Welche Hostnamen zu dieser Website zählen, ohne Rücksicht auf www.. Bei mehreren Websites braucht jede mindestens einen.
timezone "UTC" IANA-Zeitzone für Tage, Wochen und die Stunden im Dashboard, etwa "America/Toronto", und in den Einstellungen änderbar.

Ein Besuch von einem Hostnamen, den keine Website beansprucht, wird ignoriert. Eine Staging-Kopie deiner Website zählt also nicht, solange du ihren Hostnamen nicht hinzufügst.

Rate-Limit

Jeder Prozess zählt Tracker-Anfragen pro Besucheradresse über eine Minute und verwirft den Rest stillschweigend, damit ein Skript eine Website nicht mit erfundenen Besuchen fluten kann. Ein echter Besucher sendet ein paar Anfragen pro Minute, weit unter dem Standard von 120. Die Zählung liegt unter einer gehashten Adresse im Speicher und wird jede Minute geleert, es wird also nie eine Adresse aufbewahrt. Auf Hosts, die viele kurzlebige Kopien deiner App ausführen, etwa Cloudflare Workers, zählt jede Kopie für sich, dort ist die Grenze also lockerer.

Standort

Land, Region und Stadt kommen auf Vercel, Cloudflare und Netlify ohne Einrichtung aus den Headern deines Hosts. Anderswo übergibst du geo, eine Funktion, die eine IP-Adresse in einen Standort umwandelt. Dieses Beispiel stützt sich auf eine MMDB-Datei.

ts

runlight({
  store,
  geo: async (ip) => {
    const found = reader.get(ip); // your MMDB reader
    return found ? { country: found.country?.iso_code, city: found.city?.names?.en } : null;
  },
});

Die Adresse wird nach der Suche verworfen, nur der Ort bleibt.

Optionen von routes()

Option Standard Was sie tut
basePath "/runlight" Wo die Routen eingebunden werden, mit dem Skript unter {basePath}/s.js.
token RUNLIGHT_TOKEN Schützt Dashboard und API. Sende es als Authorization: Bearer <token>, oder öffne das Dashboard einmal mit ?token=, um ein Cookie zu bekommen. null lässt alles offen, etwa hinter deiner eigenen Authentifizierung.
accounts false Anmeldekonten für das Dashboard, mit Einladungen nach Rolle. Siehe Konten unten.
authorize Deine eigene Prüfung, anstelle eines Tokens. Gib true für vollen Zugriff zurück, "member" für jemanden, der alles ändern darf außer dem E-Mail-Dienst, den Einstellungen des Assistenten und dem Löschen einer Website, "read" für jemanden, der nur lesen darf, oder false, aus einer Funktion oder einem Promise.
cronSecret CRON_SECRET Ein zweites Secret, das der stündliche Check neben dem Token annimmt.
origin Die öffentliche Adresse deiner App, etwa https://example.com. Eine Link-Domain kann nie ihr Host sein, und Links in E-Mail-Berichten zeigen darauf, egal welchen Host-Header eine Anfrage nennt. Ohne sie springt der eigene Host der Anfrage ein, und ein verbundener Hub kann keine Link-Domains oder E-Mail-Berichte hinzufügen.
ownHosts Eine Funktion, die weitere Namen zurückgibt, unter denen deine App antwortet, etwa ["app.example.com"], und die ebenfalls nie Link-Domains werden können.
signIn Deine eigene Anmeldeseite. Eine App, die sich über OAuth verbindet, schickt einen abgemeldeten Eigentümer mit ?next= dorthin, und das Dashboard verlinkt darauf, wenn eine Sitzung endet.
signOut Ein Link zum Abmelden, angezeigt in der Fußzeile des Dashboards.
geoCredit false Nennt DB-IP in der Fußzeile des Dashboards, wie es seine kostenlosen Standortdaten verlangen.
cms Das CMS, das das Skript zu jeder Seite hinzufügt, etwa "WordPress", wenn Runlight darin läuft. Das Dashboard öffnet sich dann schon vor dem ersten Besuch mit seinen Zahlen, Einstellungen, Einbinden sagt, dass das CMS das Skript hinzufügt, und Seiten mit demselben Origin, etwa der Adminbereich des CMS, dürfen das Dashboard in einem Frame zeigen. Die Plugins für WordPress, Drupal und Craft setzen es.
observeKey RUNLIGHT_OBSERVE_KEY Ein Schlüssel für die ganze Installation, mit dem eine Website mit WordPress, Drupal oder Craft Abrufe von KI-Agenten für jede Website an POST /api/observe melden kann. Jede Website hat außerdem ihren eigenen Agenten-Schlüssel, angezeigt unter Einstellungen, Einbinden, der nur für diese Website meldet und die bessere Wahl ist.

Die Optionen accountOf und tokenMade verbinden die Routen mit den eigenen Konten des eigenständigen Servers und sind intern.

Ohne Token antworten Dashboard und API mit 503, bis du eines setzt, außer wenn NODE_ENV gleich development ist. Das Erfassen von Besuchen, das Skript, Kurzlinks, Freigabelinks und Abbestell-Links brauchen das Token nie.

Konten

Schalte Konten ein, damit sich mehrere Personen im Dashboard anmelden können, jede mit eigener E-Mail, eigenem Passwort und eigener Rolle, genau wie beim eigenständigen Server.

ts

export const { GET, POST, PUT, PATCH, DELETE } = rl.routes({ accounts: true });

Das Dashboard bittet dann alle, sich unter /runlight/login anzumelden. Um das erste Konto anzulegen, öffne /runlight/setup und gib dein RUNLIGHT_TOKEN zusammen mit deiner E-Mail und deinem Passwort ein, sodass das nur kann, wer die App betreibt. Dieses Konto ist der Eigentümer. In der Entwicklung ohne Token, oder mit token: null, was alles offen lässt, braucht das erste Konto kein Token. Leg es also an, bevor jemand anderes die Seite erreichen kann.

Der Eigentümer und die Admins laden unter Einstellungen, Personen weitere Personen ein, als Admin, Mitglied oder Betrachter, und die Einladung geht per E-Mail raus, wenn ein E-Mail-Dienst eingerichtet ist. Alle können unter Konto die Zwei-Faktor-Anmeldung einschalten. Das Token funktioniert für Skripte weiterhin als Bearer-Token.

Sitzungen werden mit RUNLIGHT_SECRET signiert, oder mit dem Token, wenn das nicht gesetzt ist. Halte es also über Neustarts und Deployments hinweg gleich. Passwörter werden mit scrypt gehasht, wo die Runtime es hat, wie Node, Bun und Deno, und mit PBKDF2 auf Edge-Runtimes wie Cloudflare Workers.

Wenn jemand sein Passwort vergisst, kann der Eigentümer oder ein Admin die Person entfernen und neu einladen. Wenn der Eigentümer seines vergisst, gib dem Konto ein neues aus einem Skript, das dein Runlight nutzt.

ts

import { Accounts } from "@runlight/sdk";
import { rl } from "./lib/runlight";

const accounts = new Accounts(rl.store, process.env.RUNLIGHT_SECRET ?? process.env.RUNLIGHT_TOKEN!);
const user = await accounts.setPassword("you@example.com", "a new long password", Date.now());
await accounts.disableTwoFactor(user.id);

Umgebungsvariablen

Variable Wofür
RUNLIGHT_TOKEN Das Token für Dashboard und API.
RUNLIGHT_SECRET Verschlüsseln gespeicherter Schlüssel für E-Mail, den KI-Assistenten und verbundene Installationen (fällt auf das Token zurück).
CRON_SECRET Aufrufen des stündlichen Checks.
RUNLIGHT_OBSERVE_KEY CMS-Plugins, die Abrufe von KI-Agenten melden.