Toute la documentation

Démarrer

  1. 01Pour commencer
  2. 02Installation
  3. 03Suivi

Fonctionnalités

  1. 04Le tableau de bord
  2. 05Objectifs
  3. 06Liens courts
  4. 07Sources et agents IA
  5. 08Rapports par e-mail
  6. 09Vérification planifiée
  7. 10Interrogez votre IA

Référence

  1. 11Configuration
  2. 12API HTTP

Plateformes

  1. 13Serveur autonome
  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

Confiance

  1. 26Confidentialité

Plateformes

Go

Runlight tourne sous Go 1.25 ou plus récent, dans n’importe quelle application net/http, chi ou Echo, ou comme serveur à part entière.

Le module Go, c’est Runlight réécrit en Go. Il sert le même tableau de bord et la même API, et il donne à chaque requête la même réponse que la bibliothèque TypeScript. Ses chiffres vont dans les mêmes tables, donc chacun peut lire une base de données écrite par l’autre. Il lui faut Go 1.25 ou plus récent, et le module lui-même ne dépend d’aucun autre module.

Installation

Ajoutez le module avec go get, ainsi que le pilote de votre base de données.

bash

go get runlight.sh/go
go get modernc.org/sqlite

Runlight parle à sa base de données par database/sql, donc n’importe quel pilote fonctionne. Les tests tournent avec modernc.org/sqlite pour SQLite, github.com/jackc/pgx/v5/stdlib pour Postgres et github.com/go-sql-driver/mysql pour MySQL et MariaDB.

Créer l’instance

Ouvrez la base de données, enveloppez-la dans un stockage, et créez une instance pour toute l’application.

analytics.go

package main

import (
	"database/sql"

	_ "modernc.org/sqlite"
	runlight "runlight.sh/go"
)

func newRunlight() (*runlight.Runlight, error) {
	db, err := sql.Open("sqlite", "data/runlight.db")
	if err != nil {
		return nil, err
	}
	return runlight.New(runlight.Options{
		Store: runlight.NewStore(runlight.SQLite(db).Owned(0)),
		Site:  &runlight.SiteOptions{Name: "example.com", Hostnames: []string{"example.com"}, Timezone: "Europe/London"},
	})
}

Les options portent les mêmes noms que dans Configuration, écrits comme des champs Go, comme Sites, IgnoreProxy, RateLimit, Mail et ManagedSites. Chaque méthode qui lit ou écrit prend d’abord un context.Context, et les tables sont créées à la première requête.

Stockages

Owned confie la connexion à Runlight, qui la ferme quand le stockage est fermé. Omettez-le pour partager un *sql.DB que votre application a déjà, que Runlight ne ferme alors jamais.

Les tables de Runlight commencent par rl_. runlight.NewStoreWithPrefix(runlight.SQLite(db).Owned(0), "wp_rl_") utilise un autre préfixe à la place, pour que deux applications qui partagent une base gardent chacune leurs propres tables. Un préfixe compte jusqu’à 32 lettres, chiffres et tirets bas, et ne commence pas par un chiffre, et store.Tables() liste les tables du stockage par leur nom, pour une application qui retire Runlight.

SQLite

go

db, err := sql.Open("sqlite", "data/runlight.db")
store := runlight.NewStore(runlight.SQLite(db).Owned(0))

Runlight garde une seule connexion SQLite et la passe en mode WAL avec un délai d’attente en cas de verrou. Gardez le fichier hors de tout dossier que sert votre serveur web, dans un dossier où l’application peut écrire, puisque SQLite écrit un journal à côté.

Postgres

go

db, err := sql.Open("pgx", "postgres://runlight:password@127.0.0.1:5432/runlight?statement_timeout=120000")
store := runlight.NewStore(runlight.Postgres(db).Owned(0))

Importez github.com/jackc/pgx/v5/stdlib pour le pilote pgx. Le paramètre statement_timeout arrête toute instruction au bout de ce nombre de millisecondes, comme le fait la bibliothèque TypeScript après 120000. Pour garder les tables dans leur propre schéma, ajoutez search_path=analytics à l’URL. Le pool demande au moins deux connexions.

MySQL et MariaDB

go

db, err := sql.Open("mysql", "runlight:password@tcp(127.0.0.1:3306)/runlight?charset=utf8mb4&loc=UTC")
store := runlight.NewStore(runlight.MySQL(db).Owned(120000))

Ce stockage fonctionne avec MySQL 8.0.20 ou plus récent et MariaDB 11.4 ou plus récent. Le nombre passé à Owned est le délai maximal des instructions de chaque session, en millisecondes, et 0 le désactive. Le pool demande au moins deux connexions.

net/http

Les routes sont un http.Handler. Montez-les sur leur chemin de base, /runlight par défaut, et ajoutez les liens courts sur /go/{slug}.

main.go

rl, err := newRunlight()
if err != nil {
	log.Fatal(err)
}
routes, err := rl.Routes(runlight.RoutesOptions{})
if err != nil {
	log.Fatal(err)
}

mux := http.NewServeMux()
mux.Handle("/runlight", routes)
mux.Handle("/runlight/", routes)
mux.Handle("GET /go/{slug}", rl.LinksHTTP())
mux.Handle("/", yourApp)

log.Fatal(http.ListenAndServe(":8080", rl.LinkDomains(rl.Observer(mux))))

rl.Observer enregistre les requêtes des agents IA pendant que votre application sert ses pages, et transmet chaque requête. rl.LinkDomains répond aux liens courts sur vos domaines de liens, les noms à part que vous faites pointer vers l’application, avant que le mux ne voie la requête. Il laisse passer tous les autres hôtes sans y toucher, donc vos propres pages ne sont pas affectées. Dans une application qui fait passer chaque requête par un seul handler, routes.Middleware(next) répond aux domaines de liens et aux chemins de Runlight, et passe le reste à next sans lire leur corps. Il répond aussi aux documents OAuth que les clients MCP cherchent à la racine du site.

rl.LinksHTTP() répond à un lien court que personne n’a avec un simple 404. Pour afficher votre propre page 404 à cet endroit, utilisez rl.LinksHTTPOr(notFound), qui confie ces requêtes à votre handler notFound. Sur un domaine de liens, /go/{slug} est un 404 comme tout autre chemin sans lien.

Ajoutez le script à chaque page, juste avant </head>.

html

<script defer src="/runlight/s.js"></script>

Définissez ensuite RUNLIGHT_TOKEN avec une longue chaîne aléatoire et ouvrez une fois /runlight/?token= suivi de cette chaîne pour vous connecter, comme l’explique Pour commencer. RoutesOptions accepte les mêmes réglages que routes() dans Configuration, donc Token, Authorize, BasePath et Origin fonctionnent comme là-bas. Avec Accounts: true et un RUNLIGHT_SECRET, chacun se connecte avec son propre e-mail et son propre mot de passe, comme dans Comptes.

chi

L’adaptateur chi est un module à part, donc chi n’est jamais une dépendance du module principal.

bash

go get runlight.sh/go/chi

go

r := chi.NewRouter()
r.Use(rl.Observer)
runlightchi.Mount(r, rl, routes)

Mount ajoute les routes sous leur chemin de base, les documents OAuth et les liens courts, et il répond aux domaines de liens avant vos propres routes. Appelez-le sur le routeur principal avant vos propres routes, puisque les routes lisent le chemin complet de chaque requête et que chi n’accepte des middlewares que jusqu’à la première route. Appelé après une route, il panique avec ce conseil, car un domaine de liens perdrait sinon chaque lien court dont le chemin correspond à l’une de vos routes. Quand vos routes doivent venir en premier, entourez tout le routeur de rl.LinkDomains et appelez runlightchi.MountRoutes, qui ajoute le reste. Un lien court que personne n’a va au handler NotFound du routeur, donc votre propre page 404 lui répond.

Echo

L’adaptateur Echo est lui aussi un module à part, et il fonctionne avec Echo v5.

bash

go get runlight.sh/go/echo

go

e := echo.New()
e.Use(runlightecho.Observer(rl))
runlightecho.Register(e, rl, routes)

Register ajoute les routes, les documents OAuth et les liens courts, comme Mount le fait pour chi. Il répond aux domaines de liens avant qu’Echo ne route chaque requête, où qu’il soit appelé dans l’application. Un lien court que personne n’a renvoie echo.ErrNotFound, donc votre gestionnaire d’erreurs lui répond comme à toute page que l’application n’a pas.

Autres routeurs

Tout routeur qui accepte un http.Handler peut servir Runlight de la même façon que http.ServeMux, enveloppé dans rl.LinkDomains pour les domaines de liens. Pour répondre vous-même à une requête, transformez-la en requête Runlight avec runlight.FromHTTP, passez-la à routes.Handle, et renvoyez la réponse avec runlight.WriteHTTP. FromHTTP lit le corps avec les limites qu’utilise l’adaptateur Node, 16 Ko pour le traceur et 10 Mo pour tout le reste, et garde l’adresse d’où vient la connexion. Runlight lit lui-même les en-têtes transmis, sauf si IgnoreProxy est défini.

La vérification planifiée

La vérification planifiée renouvelle les sels quotidiens, envoie les rapports par e-mail arrivés à échéance, applique la durée de conservation des visites de chaque site et construit les agrégats qui gardent les longues périodes rapides. Appelez rl.Check(ctx) toutes les quelques minutes depuis une goroutine avec un time.Ticker. Quand rien dans l’application ne tourne à intervalles réguliers, définissez CRON_SECRET et faites appeler /runlight/api/check avec lui par un planificateur, comme le montre Vérification planifiée.

Le serveur autonome

La commande runlight est le serveur autonome en Go. Elle fait tourner Runlight sur son propre domaine, comme stats.example.com, avec le tableau de bord à la racine, des sites ajoutés depuis le tableau de bord, des comptes de connexion et des liens courts sur n’importe quel domaine que vous faites pointer vers elle. Installez-la et lancez-la.

bash

go install runlight.sh/go/cmd/runlight@latest
runlight

Le serveur écoute sur le port 3000 et garde ses données dans un dossier nommé runlight-data dans le répertoire depuis lequel vous le lancez. À son premier démarrage, il affiche un lien de configuration avec un code à usage unique. Ouvrez ce lien et créez votre compte, comme l’explique Créer votre compte. Le serveur lance lui-même la vérification planifiée toutes les cinq minutes, et il télécharge les données de localisation du mois comme le fait le serveur Node. Placez-le derrière un proxy qui ajoute HTTPS, comme dans Le mettre sur Internet.

Pour faire tourner le serveur dans un programme à vous, runlight.sh/go/server le fournit sous forme de http.Handler, avec server.New qui prend les mêmes réglages sous forme de champs.

Réglages

Le serveur lit ses réglages dans des variables d’environnement.

Variable Ce qu’elle fait
PORT Le port d’écoute. 3000 par défaut.
HOST L’adresse d’écoute. 0.0.0.0 par défaut.
DATA_DIR Le dossier du fichier SQLite, du secret et des données de localisation. ./runlight-data par défaut.
DATABASE_URL Une adresse postgres:// garde les données dans Postgres au lieu de SQLite, et une adresse mysql:// ou mariadb:// les garde dans MySQL ou MariaDB.
RUNLIGHT_SECRET La clé qui signe les connexions et chiffre les clés enregistrées. Sans elle, le serveur en crée une et la garde dans DATA_DIR, lisible uniquement par l’utilisateur qui le fait tourner.
RUNLIGHT_URL L’adresse publique du serveur, par exemple https://stats.example.com, qui ne peut jamais devenir un domaine de liens. Les e-mails d’invitation et de rapport pointent vers elle.
RUNLIGHT_TOKEN Un jeton que des scripts peuvent envoyer comme bearer token, en plus des jetons d’API créés dans le tableau de bord.
RUNLIGHT_OBSERVE_KEY Une seule clé pour les signalements d’agents IA de tous les sites. La clé des agents propre à chaque site, affichée dans Réglages, Installation, est le meilleur choix.
TRUST_PROXY Mettez false quand aucun proxy ne se trouve devant le serveur, ou x-real-ip ou cf-connecting-ip quand c’est cet en-tête qui contient l’adresse du visiteur.
RUNLIGHT_GEO D’où viennent les lieux quand aucun en-tête ne les donne, comme dans Lieux. city par défaut, et peut valoir country, off ou le chemin d’un fichier MMDB.

Les tables se mettent à jour toutes seules au démarrage du serveur, donc une mise à jour ne demande qu’une nouvelle compilation et un redémarrage. Sauvegardez le dossier de données, puisque les clés enregistrées ne peuvent pas être lues sans le secret qu’il contient.

Mots de passe oubliés

Lancez la commande password là où tourne le serveur pour donner à un compte un nouveau mot de passe, qu’elle affiche. Elle désactive aussi la connexion à deux facteurs pour ce compte, et elle crée le compte s’il n’existe pas, comme propriétaire sur un serveur encore vide et comme administrateur sinon.

bash

runlight password someone@example.com

Agents IA depuis un journal

Les agents IA comme GPTBot et ClaudeBot récupèrent les pages sans exécuter de JavaScript, donc la balise script ne les voit jamais. Une application Go qui contient Runlight les compte là où la page est servie, avec rl.Observer. Pour tout autre site que vous hébergez vous-même, le journal d’accès du serveur web les contient, et la commande agents le lit.

bash

runlight agents --log /var/log/nginx/access.log --to https://stats.example.com --key rlo_... --site https://example.com --follow

La clé est la clé des agents du site, affichée dans Runlight dans Réglages, Installation, et elle ne peut signaler que les requêtes de ce site. Quand --to ou --key est omis, la commande lit RUNLIGHT_URL ou RUNLIGHT_OBSERVE_KEY dans l’environnement. La commande fonctionne comme sur le serveur autonome, avec --follow pour continuer de tourner et --state pour reprendre là où la dernière exécution s’est arrêtée. Seules les requêtes de pages réussies des agents IA connus quittent la machine, et les adresses des visiteurs restent où elles sont.

Différences avec la bibliothèque TypeScript

Le module Go passe les tests de conformité imposés à la bibliothèque TypeScript, sur SQLite, Postgres, MySQL et MariaDB. Les différences viennent de la façon dont Go s’exécute.

  • Les requêtes tournent dans des goroutines, et l’instance peut en servir beaucoup à la fois. SQLite garde une seule connexion, que toutes les requêtes partagent sous un verrou, et Postgres et MySQL utilisent le pool que gère database/sql.
  • Ce que la bibliothèque TypeScript fait en arrière-plan, comme supprimer des visites après le choix d’une durée de conservation plus courte, s’exécute dans une goroutine à côté de la réponse. rl.Idle() l’attend, ce qu’un programme peut appeler avant de se terminer.
  • Les fuseaux horaires viennent de la base de fuseaux horaires de Go. Un programme compilé pour un système qui n’en a pas importe time/tzdata.
  • Le passage en majuscules ou en minuscules utilise des tables tirées de celles de Node, donc les noms se comparent de la même façon des deux côtés. Les sites sont triés par nom selon les règles principales de l’ordre par défaut d’Unicode, donc de temps en temps un nom dans une écriture plus rare se retrouve une place plus loin que là où Node le met.
  • La limite de débit du traceur et les listes en cache des domaines de liens et des icônes de sites sont gardées en mémoire, donc chaque processus a les siennes. Les limites de mots de passe erronés sont gardées dans la base, donc tous les processus les partagent.
  • Les mots de passe sont hachés avec scrypt en Go. Les hachages sont identiques à celles de la bibliothèque TypeScript, donc les comptes se transfèrent.