Todas as docs

Início

  1. 01Primeiros passos
  2. 02Instalação
  3. 03Rastreamento

Recursos

  1. 04O painel
  2. 05Metas
  3. 06Links curtos
  4. 07Origens e agentes de IA
  5. 08Relatórios por email
  6. 09Verificação agendada
  7. 10Pergunte à sua IA

Referência

  1. 11Configuração
  2. 12API HTTP

Plataformas

  1. 13Servidor independente
  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

Confiança

  1. 26Privacidade

Plataformas

Rust

O Runlight roda dentro de um serviço Rust sobre tokio, atrás do axum, do hyper ou de qualquer servidor que aceite um serviço tower, com as tabelas no próprio banco de dados do app por meio do sqlx.

O crate Rust é o Runlight reescrito em Rust. Ele serve o mesmo painel e a mesma API, e dá a cada requisição a mesma resposta que a biblioteca TypeScript dá. Os números ficam nas mesmas tabelas, então qualquer um dos dois consegue ler um banco de dados que o outro escreveu. Ele precisa do Rust 1.88 ou mais recente e roda sobre tokio. O store vem de um segundo crate, runlight-sqlx, que precisa do Rust 1.94 por causa do sqlx 0.9.

Instalação

Adicione os dois crates, com o banco de dados que você usa e o adapter de framework que você quer.

toml

[dependencies]
runlight = { version = "0.1", features = ["axum"] }
runlight-sqlx = { version = "0.1", features = ["sqlite"] }

A feature transport do crate runlight vem ligada por padrão e dá a ele um cliente para os serviços de e-mail, as instalações conectadas, os ícones dos sites e o Assistente de IA. A feature tower transforma as rotas em um serviço tower, e axum adiciona os routers abaixo. As features do runlight-sqlx são sqlite, postgres e mysql.

Crie a instância

Crie um Runlight quando o seu serviço iniciar e guarde-o no seu state. Cloná-lo custa pouco.

rust

use runlight::{Runlight, RunlightOptions, SiteOptions};

let store = runlight_sqlx::connect("sqlite:data/runlight.db").await?;
let mut options = RunlightOptions::new(store);
options.site = Some(SiteOptions {
    name: Some("example.com".into()),
    hostnames: Some(vec!["example.com".into()]),
    timezone: Some("Europe/London".into()),
    ..SiteOptions::default()
});
let rl = Runlight::new(options)?;

As opções têm os mesmos nomes de Configuração, em snake case, então sites, trust_proxy, rate_limit, mail e managed_sites funcionam como lá. As tabelas são criadas na primeira requisição.

Stores

runlight_sqlx::connect abre um pool próprio a partir de uma URL. Para usar o pool que o seu app já tem, passe-o, e o Runlight guarda as tabelas ao lado das suas.

As tabelas do Runlight começam com rl_. sqlite_with_prefix, postgres_with_prefix, mysql_with_prefix e connect_with_prefix aceitam um prefixo para usar no lugar, como runlight_sqlx::sqlite_with_prefix(pool, "wp_rl_")?, para que dois apps que dividem um banco de dados mantenham cada um suas próprias tabelas. Um prefixo tem até 32 letras, dígitos e sublinhados, e não começa com um dígito. store.tables() lista as tabelas do store pelo nome, para um app que está removendo o Runlight.

SQLite

rust

let store = runlight_sqlx::sqlite(pool); // a sqlx::SqlitePool

O store reserva uma conexão do pool para as suas instruções, uma de cada vez, em modo WAL com um busy timeout de cinco segundos. A feature sqlite compila o SQLite a partir do código-fonte, então o seu build precisa de um compilador C.

MySQL e MariaDB

rust

let store = runlight_sqlx::mysql(pool); // a sqlx::MySqlPool

Este store funciona com MySQL 8.0.20 ou mais recente e MariaDB 11.4 ou mais recente, e o pool precisa de pelo menos duas conexões. A autenticação padrão do MySQL 8 em uma conexão sem TLS precisa da feature mysql-rsa do sqlx, então ligue-a no seu próprio Cargo.toml ou conecte por TLS.

Postgres

rust

let store = runlight_sqlx::postgres(pool); // a sqlx::PgPool

O pool precisa de pelo menos duas conexões. Cada instrução roda sozinha, então as escritas do Runlight nunca entram em uma transação que o seu app tenha aberto.

axum

Junte os routers ao seu app. O painel e a API respondem em /runlight, e os links curtos em /go/{slug}.

rust

use runlight::{RoutesOptions, TokenOption};

let routes = rl.routes(RoutesOptions {
    token: TokenOption::Given(std::env::var("RUNLIGHT_TOKEN")?),
    ..RoutesOptions::default()
})?;
let app = axum::Router::new()
    .merge(routes.router())
    .merge(rl.link_router())
    .layer(axum::middleware::from_fn_with_state(rl.clone(), runlight::adapters::axum_support::middleware));

O middleware responde a uma requisição em um domínio de links com o link curto antes que as suas próprias rotas a vejam, e registra as leituras das suas páginas feitas por agentes de IA. Uma requisição para qualquer outro host segue para o seu app sem alteração. Mantenha a layer, já que um router do axum só vê os caminhos que recebeu e os links curtos de um domínio de links ficam na raiz do próprio nome. Sirva o app com into_make_service_with_connect_info::<SocketAddr>() para que o Runlight veja o endereço da conexão quando nenhum cabeçalho de proxy indicar o visitante. Em seguida, adicione o script a todas as páginas, logo antes de </head>.

html

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

Depois defina RUNLIGHT_TOKEN como uma string aleatória longa e abra uma vez /runlight/?token= seguido dessa string para entrar, como descreve Primeiros passos. RoutesOptions aceita as configurações que routes() aceita em Configuração, em snake case, então base_path, accounts e origin funcionam como lá.

link_router() responde a toda requisição que o axum roteia para /go/{slug}. Uma requisição num domínio de links vem primeiro, então ali /go/<name> é um 404 como qualquer outro caminho que não seja um dos links desse domínio. No seu próprio host ele redireciona quando o link existe, e um nome sem link recebe o 404 simples do Runlight, já que o axum já casou a rota e não consegue passar a requisição para o fallback do seu app. rl.link_service() responde do mesmo jeito.

Outros servidores

Routes é um tower::Service para qualquer http::Request, então o hyper e qualquer outra coisa feita sobre tower pode servi-lo como está. Cada requisição que ele recebe em um domínio de links é respondida com o link curto, e o serviço de links curtos de rl.link_service() faz o mesmo. Sem a feature tower, monte você mesmo um runlight::http::Request e chame routes.handle(request).await, que responde com um runlight::http::Response.

A verificação agendada

A verificação agendada troca os salts diários, envia os relatórios por email que estão na hora, aplica por quanto tempo cada site guarda as visitas e soma os totais diários que deixam os períodos longos rápidos. Chame rl.check().await a cada poucos minutos a partir de uma task sua. Quando nada no serviço roda com timer, defina CRON_SECRET e faça um agendador chamar /runlight/api/check com ele, como mostra Verificação agendada.

rust

let checker = rl.clone();
tokio::spawn(async move {
    loop {
        if let Err(error) = checker.check().await {
            eprintln!("Runlight check failed: {error}");
        }
        tokio::time::sleep(std::time::Duration::from_secs(300)).await;
    }
});

Localização

O Runlight lê o país, a região e a cidade dos cabeçalhos que a Vercel, a Cloudflare e a Netlify adicionam. Atrás de qualquer outra coisa, dê a ele um banco de dados de cidades da MaxMind ou do DB-IP por meio de runlight::mmdb::Mmdb, que lê o arquivo uma página por vez, conforme as consultas precisam.

rust

options.geo = Some(std::sync::Arc::new(runlight::mmdb::Mmdb::open("data/dbip-city-lite.mmdb")?));

Diferenças em relação à biblioteca TypeScript

O crate Rust passa nos testes de conformidade aos quais a biblioteca TypeScript é submetida, no SQLite, Postgres, MySQL e MariaDB. As diferenças vêm do jeito como o Rust roda.

  • O crate faz as requisições de saída, para serviços de e-mail, instalações conectadas, ícones dos sites e o Assistente de IA, por meio de um Fetcher que você pode substituir, para que testes e redes restritas possam passar o seu próprio.
  • Ele lê os fusos horários do banco de dados do sistema, então um fuso cujas regras mudaram depois da cópia da biblioteca TypeScript segue as regras mais novas.
  • Não existe servidor independente em Rust. Para um único painel com vários sites, rode o servidor Node ou o servidor de outra linguagem, e conecte o site deste serviço a ele.