Plataformas
Python
O Runlight roda em Python 3.11 ou mais recente, dentro de um app Django, Flask ou FastAPI ou de qualquer app WSGI ou ASGI, ou como servidor independente.
O pacote Python é uma versão do Runlight para Python. Ele serve o mesmo painel e a mesma API, e responde a cada requisição exatamente como a biblioteca TypeScript. Os números vão para as mesmas tabelas, então qualquer um dos dois consegue ler um banco de dados que o outro escreveu.
Ele precisa do Python 3.11 ou mais recente e não tem dependências próprias.
Instalação
Adicione o pacote com o pip.
bash
pip install runlightO SQLite vem com o Python. Para Postgres, instale o extra postgres, que adiciona o psycopg 3, e para MySQL ou MariaDB, instale o extra mysql, que adiciona o PyMySQL.
bash
pip install "runlight[postgres]"
pip install "runlight[mysql]"Os fusos horários vêm do banco de dados de fusos horários do sistema. O Windows não tem um, então adicione o extra tzdata nele.
Crie a instância
Crie um módulo que cria a instância e importe-o onde você precisar do Runlight.
myproject/analytics.py
from runlight import Runlight
from runlight.store import Stores
rl = Runlight(
store=Stores.sqlite("data/runlight.db"),
site={"name": "example.com", "hostnames": ["example.com"], "timezone": "Europe/London"},
)As opções têm os mesmos nomes que em Configuração. Passe-as como argumentos nomeados em snake case, como trust_proxy=False e rate_limit=60, ou como um único dict com os nomes do TypeScript, como Runlight({"store": ..., "trustProxy": False}). As tabelas são criadas na primeira requisição. update_agent_ranges baixa uma vez por dia as listas de endereços que as empresas de IA publicam, para conferir os acessos de agentes de IA. Fica desligada por padrão, e então é usada a cópia que veio com a versão.
Stores
As tabelas do Runlight começam com rl_. Todo store aceita prefix para usar um prefixo diferente, como Stores.sqlite(path, prefix="wp_rl_"), de modo que dois apps que compartilham um banco de dados mantêm cada um as suas próprias tabelas.
Um prefixo tem até 32 letras, dígitos e sublinhados e não começa com dígito. store.tables() lista as tabelas do store pelo nome, para um app que remove o Runlight.
SQLite
python
Stores.sqlite("data/runlight.db")Mantenha o arquivo fora de qualquer pasta que o seu servidor web sirva, em uma pasta em que o app possa escrever, já que o SQLite grava um journal ao lado dele. A pasta é criada se não existir.
Postgres
python
Stores.postgres("postgres://runlight:password@127.0.0.1:5432/runlight")statement_timeout interrompe qualquer comando depois do número de milissegundos que você informar, e o padrão é 120000. schema mantém as tabelas em um schema próprio, como em Stores.postgres(url, schema="analytics"). Em vez de uma URL, você pode passar uma conexão psycopg que o seu app já tenha, que o Runlight usa como está e nunca fecha.
MySQL e MariaDB
python
Stores.mysql("mysql://runlight:password@127.0.0.1:3306/runlight")Este store funciona com MySQL 8.0.20 ou mais recente e MariaDB 11.4 ou mais recente. Uma URL mariadb:// também funciona. Ele aceita statement_timeout como o Postgres, que o MySQL aplica às leituras e o MariaDB a todos os comandos, como em Configuração.
Stores.url() escolhe o store a partir de uma DATABASE_URL que comece com postgres://, mysql://, mariadb:// ou sqlite:.
Django
Adicione o app do Runlight e o middleware dele às suas configurações, e indique a instância em RUNLIGHT. O middleware vem primeiro, para que nenhum outro middleware altere as requisições do Runlight nem as respostas dele.
settings.py
INSTALLED_APPS = [
# ...
"runlight.django",
]
MIDDLEWARE = [
"runlight.django.RunlightMiddleware",
# ...
]
RUNLIGHT = "myproject.analytics.rl"RUNLIGHT guarda a própria instância ou o caminho pontuado até ela, e um caminho para uma função que cria uma também funciona. RUNLIGHT_ROUTES passa opções para as rotas com os nomes do TypeScript, como {"accounts": True}.
O middleware serve o painel e a API sob /runlight, os links curtos em /go e todos os domínios de links que você adiciona em Configurações, antes das URLs do próprio Django. Cada domínio de links também precisa estar em ALLOWED_HOSTS.
Rode a verificação agendada pelo cron com o comando de gerenciamento.
bash
*/5 * * * * cd /srv/myproject && python manage.py runlight_checkFlask
Passe o app e a instância para init_app, que coloca o Runlight na frente das views do próprio app. Ele responde sob /runlight, em /go e em todos os domínios de links que você adiciona em Configurações, e passa todo o resto para o seu app.
app.py
from flask import Flask
import runlight.flask
from myproject.analytics import rl
app = Flask(__name__)
runlight.flask.init_app(app, rl)As opções das rotas entram como argumentos nomeados, como runlight.flask.init_app(app, rl, accounts=True). init_app também adiciona um comando que roda a verificação agendada, para o cron.
bash
*/5 * * * * cd /srv/myproject && flask --app app runlight checkFastAPI
init_app adiciona o Runlight como middleware na frente das rotas do app, então ele responde sob /runlight, em /go e em todos os domínios de links que você adiciona em Configurações. Funciona também para apps Starlette.
main.py
from fastapi import FastAPI
import runlight.fastapi
from myproject.analytics import rl
app = FastAPI()
runlight.fastapi.init_app(app, rl)O trabalho do próprio Runlight roda em threads de worker, então ele nunca bloqueia o event loop. Para a verificação agendada, chame rl.check() a partir de um script que o cron executa, ou defina CRON_SECRET e chame a verificação por HTTP.
Qualquer app WSGI ou ASGI
Bottle, Pyramid e qualquer outro app WSGI usam o middleware WSGI.
python
from runlight.wsgi import RunlightMiddleware
application = RunlightMiddleware(application, rl)Quart, Django sob ASGI e qualquer outro app ASGI usam o middleware ASGI, que recebe a instância como runlight=.
python
from runlight.asgi import RunlightMiddleware
application = RunlightMiddleware(application, runlight=rl)Os dois respondem sob /runlight, em /go e em todos os domínios de links, e passam todo o resto para o seu app. As opções das rotas entram como argumentos nomeados, como no Flask.
Para rodar o Runlight como um app próprio, sem um app seu por trás, use runlight.wsgi.app(rl) com um servidor WSGI como o gunicorn, ou runlight.asgi.app(rl) com um servidor ASGI como o uvicorn. Os caminhos fora dos do Runlight respondem então com 404.
Um link curto em /go/<name> sem nenhum link com esse nome segue para o seu app, então a sua própria página 404 responde, seja a do Django, a do Flask, a do FastAPI ou a do seu app WSGI ou ASGI. runlight.wsgi.app() e runlight.asgi.app() não têm nenhum app por trás e respondem eles mesmos com um 404. Num domínio de links, /go/<name> é um 404 como qualquer outro caminho que não seja um dos links desse domínio.
Seja qual for o jeito como você o adiciona, coloque o script em todas as páginas, logo antes de </head>.
html
<script defer src="/runlight/s.js"></script>Depois defina RUNLIGHT_TOKEN como uma string longa e aleatória e abra /runlight/?token= seguido dessa string uma vez para entrar, como descreve Primeiros passos. Com accounts=True e um RUNLIGHT_SECRET, as pessoas entram com o próprio e-mail e senha, como em Contas.
Agentes de IA
O middleware registra as leituras de agentes de IA enquanto o seu app serve as páginas, e deixa passar intacta qualquer outra requisição. O Runlight compara o endereço de cada acesso com a lista publicada do agente e depois o descarta. Passe observe=False para init_app ou para o middleware para desativar isso, ou defina "observe": False em RUNLIGHT_ROUTES no Django. Sem middleware, chame rl.observe(request) com um runlight.http.Request para cada requisição de página.
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. Rode-a a cada poucos minutos com python manage.py runlight_check no Django, flask runlight check no Flask ou rl.check() a partir de qualquer script.
Quando nada consegue rodar um comando em um timer, defina CRON_SECRET e faça um agendador chamar /runlight/api/check com ele, como mostra Verificação agendada.
O servidor independente
O pacote inclui o servidor independente, que roda o Runlight em um domínio próprio, como stats.example.com, com o painel na raiz, sites adicionados pelo painel, contas com login e links curtos em qualquer domínio que você apontar para ele. Instale o pacote e rode o comando runlight.
bash
pip install runlight
runlightO servidor escuta na porta 3000 e guarda os dados em uma pasta chamada runlight-data no diretório de onde você o inicia. Na primeira inicialização, ele mostra um link de configuração com um código de uso único. Abra esse link e crie sua conta, como descreve Crie sua conta.
O servidor roda a verificação agendada sozinho a cada cinco minutos, e baixa os dados de localização do mês como o servidor Node faz. Coloque-o atrás de um proxy que adicione HTTPS, como em Coloque na internet.
Configurações
O servidor lê as configurações de variáveis de ambiente.
| Variável | O que faz |
|---|---|
PORT |
A porta em que escutar. O padrão é 3000. |
HOST |
O endereço em que escutar. O padrão é 0.0.0.0. |
DATA_DIR |
A pasta do arquivo SQLite, do segredo e dos dados de localização. O padrão é ./runlight-data. |
DATABASE_URL |
Um endereço postgres:// guarda os dados no Postgres em vez do SQLite, e um endereço mysql:// ou mariadb:// os guarda no MySQL ou no MariaDB. |
RUNLIGHT_SECRET |
A chave que assina os logins e criptografa as chaves salvas. Sem ela, o servidor cria uma e a guarda em DATA_DIR, legível só pelo usuário que o roda. |
RUNLIGHT_URL |
O endereço público do servidor, como https://stats.example.com, que nunca pode virar um domínio de links. Os e-mails de convite e de relatório apontam para ele. |
RUNLIGHT_TOKEN |
Um token que scripts podem enviar como bearer, além dos tokens de API criados no painel. |
RUNLIGHT_OBSERVE_KEY |
Uma única chave com que qualquer site pode informar agentes de IA. A chave de agentes própria de cada site, mostrada em Configurações, Instalação, é a melhor escolha. |
TRUST_PROXY |
Quais cabeçalhos de encaminhamento aceitar, para o endereço de cada visitante e os cabeçalhos de localização da hospedagem. Sem definir, eles só contam numa conexão vinda deste computador ou de uma rede privada. true (ou 1, yes, on) os aceita de qualquer lugar, para uma CDN que conecta de endereços públicos, e false (ou 0, no, off) nunca os aceita. x-forwarded-for, x-real-ip ou cf-connecting-ip lê o endereço só desse cabeçalho. Qualquer outro valor é recusado. |
RUNLIGHT_GEO |
De onde vêm as localizações quando nenhum cabeçalho as informa, como em Localizações. O padrão é city, e pode ser country, off ou o caminho de um arquivo MMDB. |
RUNLIGHT_AGENT_RANGES |
on por padrão, o que baixa uma vez por dia as listas de endereços que as empresas de IA publicam, para conferir os acessos de agentes de IA. Defina como off para usar a cópia que veio com a versão. |
CRON_SECRET |
Permite que um agendador rode a verificação em POST /api/check. |
Comandos
runlight check roda a verificação agendada uma vez e baixa os dados de localização do mês. runlight migrate cria ou atualiza as tabelas na hora.
As tabelas também se atualizam sozinhas quando o servidor inicia, então uma atualização só precisa de pip install --upgrade runlight e um reinício. Faça backup da pasta de dados, já que as chaves salvas não podem ser lidas sem o segredo que está nela.
Senhas esquecidas
Rode o comando password onde o servidor roda para dar uma nova senha a uma conta, que ele mostra. Ele também desativa o login em duas etapas dessa conta, e cria a conta se ela não existir, como proprietário em um servidor ainda sem ninguém e como administrador nos outros casos.
bash
runlight password someone@example.comAgentes de IA a partir de um log
Agentes de IA como o GPTBot e o ClaudeBot leem páginas sem rodar JavaScript, então a tag do script nunca os vê. Um app Python com o Runlight dentro os conta onde a página é servida, como mostra Agentes de IA. Para qualquer outro site que você hospeda, o log de acesso do servidor web os registra, e o comando agents lê esse log.
bash
runlight agents --log /var/log/nginx/access.log --to https://stats.example.com --key rlo_... --site https://example.com --followA chave é a chave de agentes do site, mostrada no Runlight em Configurações, Instalação, e ela só pode informar leituras desse site. Quando --to ou --key ficam de fora, o comando lê RUNLIGHT_URL ou RUNLIGHT_OBSERVE_KEY do ambiente.
O comando funciona como no servidor independente, com --follow para continuar rodando e --state para continuar de onde a última execução parou. Só as leituras de página bem-sucedidas de agentes de IA conhecidos saem da máquina, e os endereços dos visitantes ficam onde estão. O endereço IP do agente vai junto com cada uma, e o Runlight o compara com a lista publicada do agente e depois o descarta.
O log precisa registrar o endereço real do agente, senão todo acesso aparece como não verificado. Atrás de uma CDN ou de um proxy reverso, configure o servidor web como agentes de IA a partir de um log no servidor independente descreve.
Diferenças em relação à biblioteca TypeScript
O pacote Python passa nos testes de conformidade a que a biblioteca TypeScript é submetida, em SQLite, Postgres, MySQL e MariaDB. As diferenças vêm do jeito como o Python roda.
- O trabalho do Runlight é síncrono e roda em threads. O middleware ASGI entrega cada uma das requisições do Runlight a uma thread de worker, então o event loop fica livre para o resto do seu app.
- O SQLite mantém uma conexão, que todas as threads compartilham sob um lock. O Postgres e o MySQL abrem uma conexão para cada thread, então um servidor com dezesseis threads mantém até dezesseis conexões.
- O que a biblioteca TypeScript faz em segundo plano, como apagar visitas depois que uma retenção menor é escolhida, roda depois que a resposta é enviada.
- O limite de requisições do script de rastreamento e as listas em cache de domínios de links e de ícones dos sites ficam na memória, então cada processo mantém as suas. Um servidor com vários processos de worker permite a cada endereço o limite uma vez em cada processo. Os limites de senha errada ficam no banco de dados, então todos os processos os compartilham.
- As senhas recebem hash com scrypt pelo
hashlibdo Python. Os hashes batem com os da biblioteca TypeScript, então as contas continuam valendo.