Plateformes
Elixir
Runlight tourne sous Elixir 1.18 ou plus récent sur Erlang/OTP 27 ou plus récent, dans une application Phoenix ou Plug, avec ses tables dans votre propre repo Ecto.
Le paquet Elixir, c’est Runlight réécrit en Elixir. 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 Elixir 1.18 ou plus récent sur Erlang/OTP 27 ou plus récent.
Installation
Ajoutez le paquet à mix.exs, avec l’adaptateur Ecto de votre base de données si votre application n’en a pas encore.
mix.exs
def deps do
[
{:runlight, "~> 0.1"},
{:ecto_sqlite3, "~> 0.17"}
]
endRunlight garde ses tables dans le repo Ecto de votre application, par ecto_sqlite3, postgrex ou myxql. Son tableau de bord est un Plug, que toute application Phoenix a déjà. Les fuseaux horaires viennent du paquet tz, compilé avec, donc une release fonctionne sur n’importe quelle image.
Démarrer l’instance
Runlight est un enfant de l’arbre de supervision de votre application, démarré après votre repo.
lib/my_app/application.ex
children = [
MyApp.Repo,
{Runlight,
store: {Runlight.Store, repo: MyApp.Repo},
site: [name: "example.com", hostnames: ["example.com"], timezone: "Europe/London"],
check_every: :timer.hours(1)}
]Les options portent les mêmes noms que dans Configuration, écrits en snake case, donc sites, trust_proxy, rate_limit, mail et managed_sites fonctionnent tous comme là-bas. Les tables sont créées au démarrage de l’instance, et un stockage injoignable empêche l’application de démarrer. check_every lance la vérification planifiée à intervalles réguliers. Elle 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. Omettez cette option quand un planificateur appelle plutôt /runlight/api/check avec CRON_SECRET, comme le montre Vérification planifiée, ou appelez Runlight.check(Runlight.instance()) depuis une tâche à vous.
Stockages
Le stockage est votre repo, et son adaptateur détermine le SQL qu’écrit Runlight. Les tables de Runlight commencent toutes par rl_, donc elles se rangent à côté des vôtres. prefix: "wp_rl_", comme dans {Runlight.Store, repo: MyApp.Repo, prefix: "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 Runlight.Store.tables(store) liste les tables du stockage par leur nom, pour une application qui retire Runlight.
Sur SQLite, gardez le journal en écriture anticipée (WAL) d’ecto_sqlite3 et donnez au repo un busy_timeout d’au moins 5000. Une base en mémoire existe une fois par connexion, donc un repo avec database: ":memory:" demande pool_size: 1.
Postgres fonctionne tel quel. Runlight construit ses index en concurrence, donc une grosse table continue d’accepter des écritures pendant une mise à jour.
MySQL doit être en version 8.0.20 ou plus récente, et MariaDB en 11.4 ou plus récente, puisque les tables utilisent la collation binaire utf8mb4_0900_bin pour que le texte se trie de la même façon sur toutes les bases.
Phoenix
Faites suivre /runlight vers le Plug en dehors du pipeline :browser. Ce pipeline demande un jeton CSRF à chaque POST, que le traceur ne peut pas envoyer, et Runlight vérifie les requêtes à sa façon.
lib/my_app_web/router.ex
scope "/" do
forward "/runlight", Runlight.Plug, token: System.get_env("RUNLIGHT_TOKEN", "")
forward "/go", Runlight.Plug.Links
endLe jeton est lu une seule fois, à la compilation du routeur. Dans une release, omettez l’option et définissez RUNLIGHT_TOKEN là où tourne l’application, puisque Runlight le lit à la première requête. Un jeton vide garde le tableau de bord fermé, et token: false le laisse ouvert pour une application qui contrôle elle-même les accès.
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. Avec accounts: true et un RUNLIGHT_SECRET, chacun se connecte avec son propre e-mail et son propre mot de passe, comme dans Comptes.
Pour compter les agents IA qui lisent vos pages, ajoutez l’observateur au pipeline :browser. Il enregistre leurs requêtes et rend la main aussitôt pour tous les autres.
lib/my_app_web/router.ex
pipeline :browser do
plug :accepts, ["html"]
plug Runlight.Plug.Observer
endUn domaine de liens répond aux liens courts sur son propre nom. Chacun des Plugs de Runlight répond à une requête sur un domaine de liens avant toute autre chose et laisse passer toutes les autres requêtes sans y toucher. Un routeur Phoenix ne les exécute que pour les chemins qu’il reconnaît, donc placez le Plug des domaines de liens dans votre endpoint, avant le routeur, et chaque requête sur ce nom y reçoit sa réponse.
lib/my_app_web/endpoint.ex
plug Runlight.Plug.LinkDomains
plug MyAppWeb.RouterUn nom qu’aucun lien ne porte revient à votre application. Transmis depuis un routeur Phoenix, Runlight.Plug.Links lève la NoRouteError de Phoenix, donc votre page 404 s’affiche, et transmis depuis un simple Plug.Router, il répond 404. Vous pouvez aussi placer plug Runlight.Plug.Links dans votre endpoint avant le routeur, où il remplace à la fois le forward et le Plug des domaines de liens. Il y répond aux domaines de liens et à /go/<name> pour les liens qui existent, et passe toutes les autres requêtes à votre application, un nom inconnu compris. Sur un domaine de liens, /go/<name> est toujours un 404, puisque les liens d’un domaine de liens se trouvent à la racine de son nom.
Plug.Router
Un Plug.Router fait suivre de la même façon. Le Plug des domaines de liens se place avant :match, pour qu’il voie chaque requête.
lib/my_app/router.ex
defmodule MyApp.Router do
use Plug.Router
plug Runlight.Plug.LinkDomains
plug :match
plug :dispatch
forward "/runlight", to: Runlight.Plug
forward "/go", to: Runlight.Plug.Links
endDerrière un proxy
Runlight lit l’adresse du visiteur dans la dernière entrée de X-Forwarded-For, écrite par le proxy le plus proche, donc un visiteur compte une fois par jour. Sans rien devant l’application, mettez trust_proxy: false et il lit l’adresse de la connexion elle-même. Derrière Cloudflare et un autre proxy, trust_proxy: "cf-connecting-ip" ne lit que cet en-tête.
Plusieurs instances
Donnez un nom à chacune et passez-le aux Plugs.
elixir
{Runlight, name: MyApp.Stats, store: {Runlight.Store, repo: MyApp.Repo}, site: [name: "Docs"]}
forward "/stats", Runlight.Plug, instance: MyApp.StatsDifférences avec la bibliothèque TypeScript
Le paquet Elixir 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 Elixir s’exécute.
- Chaque requête tourne dans son propre processus, et l’instance vit dans votre arbre de supervision. Son état, comme la limite de débit du traceur et les listes en cache des domaines de liens, se trouve dans une table ETS, donc tous les processus d’un nœud le partagent et chaque nœud garde le sien.
- 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 sa propre tâche une fois la réponse envoyée.
- Les nouveaux mots de passe sont hachés avec PBKDF2-SHA-256, que le module crypto d’Erlang calcule nativement. Les hachages que la bibliothèque TypeScript a créés avec scrypt permettent toujours de se connecter, donc les comptes se transfèrent.
- Il n’y a pas de serveur autonome en Elixir. Pour un seul tableau de bord sur plusieurs sites, faites tourner le serveur Node ou le serveur d’un autre langage, et connectez-y le site de cette application.