Plataformas
Python
Runlight funciona con Python 3.11 o posterior, dentro de una aplicación Django, Flask o FastAPI o de cualquier aplicación WSGI o ASGI, o como servidor independiente.
El paquete de Python es Runlight escrito de nuevo en Python. Sirve el mismo panel y la misma API, y da a cada petición la misma respuesta que da la biblioteca de TypeScript. Sus cifras van en las mismas tablas, así que cualquiera de los dos puede leer una base de datos que haya escrito el otro. Necesita Python 3.11 o posterior y no tiene dependencias propias.
Instalación
Añade el paquete con pip.
bash
pip install runlightSQLite viene con Python. Para Postgres, instala el extra postgres, que añade psycopg 3, y para MySQL o MariaDB, instala el extra mysql, que añade PyMySQL.
bash
pip install "runlight[postgres]"
pip install "runlight[mysql]"Las zonas horarias salen de la base de datos de zonas horarias del sistema. Windows no tiene ninguna, así que ahí añade el extra tzdata.
Crea la instancia
Crea un módulo que cree la instancia, e impórtalo donde necesites 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"},
)Las opciones tienen los mismos nombres que en Configuración. Pásalas como argumentos con nombre en snake case, como trust_proxy=False y rate_limit=60, o como un solo dict con los nombres de TypeScript, como Runlight({"store": ..., "trustProxy": False}). Las tablas se crean en la primera petición.
Almacenes
Las tablas de Runlight empiezan por rl_. Cada almacén acepta prefix para usar otro en su lugar, como Stores.sqlite(path, prefix="wp_rl_"), así que dos aplicaciones que comparten una base de datos mantienen cada una sus propias tablas. Un prefijo tiene como máximo 32 letras, dígitos y guiones bajos, y no empieza por un dígito. store.tables() lista las tablas del almacén por nombre, para una aplicación que quiera quitar Runlight.
SQLite
python
Stores.sqlite("data/runlight.db")Guarda el archivo fuera de cualquier carpeta que sirva tu servidor web, en una carpeta en la que la aplicación pueda escribir, ya que SQLite escribe un journal a su lado. La carpeta se crea si no existe.
Postgres
python
Stores.postgres("postgres://runlight:password@127.0.0.1:5432/runlight")statement_timeout detiene cualquier sentencia pasados esos milisegundos, y el valor por defecto es 120000. schema guarda las tablas en un schema propio, como en Stores.postgres(url, schema="analytics"). En lugar de una URL puedes pasar una conexión de psycopg que tu aplicación ya tenga, que Runlight usa tal cual y nunca cierra.
MySQL y MariaDB
python
Stores.mysql("mysql://runlight:password@127.0.0.1:3306/runlight")Este almacén funciona con MySQL 8.0.20 o posterior y MariaDB 11.4 o posterior. También vale una URL mariadb://. Acepta statement_timeout igual que Postgres, que MySQL aplica a las lecturas y MariaDB a todas las sentencias, como en Configuración.
Stores.url() elige el almacén a partir de un DATABASE_URL que empiece por postgres://, mysql://, mariadb:// o sqlite:.
Django
Añade la app de Runlight y su middleware a tus ajustes, y nombra la instancia en RUNLIGHT. El middleware va el primero, para que ningún otro middleware cambie las peticiones de Runlight ni sus respuestas.
settings.py
INSTALLED_APPS = [
# ...
"runlight.django",
]
MIDDLEWARE = [
"runlight.django.RunlightMiddleware",
# ...
]
RUNLIGHT = "myproject.analytics.rl"RUNLIGHT contiene la propia instancia o la ruta con puntos hasta ella, y también vale la ruta a una función que la cree. RUNLIGHT_ROUTES pasa opciones a las rutas con sus nombres de TypeScript, como {"accounts": True}. El middleware sirve el panel y la API bajo /runlight, los enlaces cortos en /go y cada dominio de enlaces que añadas en Ajustes, antes que las URLs propias de Django. Cada dominio de enlaces tiene que estar también en ALLOWED_HOSTS.
Ejecuta la comprobación programada desde cron con el comando de gestión.
bash
*/5 * * * * cd /srv/myproject && python manage.py runlight_checkFlask
Pasa la aplicación y la instancia a init_app, que pone Runlight delante de las vistas propias de la aplicación. Responde bajo /runlight, en /go y en cada dominio de enlaces que añadas en Ajustes, y pasa todo lo demás a tu aplicación.
app.py
from flask import Flask
import runlight.flask
from myproject.analytics import rl
app = Flask(__name__)
runlight.flask.init_app(app, rl)Las opciones de las rutas se pasan como argumentos con nombre, como runlight.flask.init_app(app, rl, accounts=True). init_app también añade un comando que ejecuta la comprobación programada, para cron.
bash
*/5 * * * * cd /srv/myproject && flask --app app runlight checkFastAPI
init_app añade Runlight como middleware delante de las rutas de la aplicación, así que responde bajo /runlight, en /go y en cada dominio de enlaces que añadas en Ajustes. También funciona con aplicaciones Starlette.
main.py
from fastapi import FastAPI
import runlight.fastapi
from myproject.analytics import rl
app = FastAPI()
runlight.fastapi.init_app(app, rl)El trabajo propio de Runlight se ejecuta en hilos de trabajo, así que nunca bloquea el event loop. Para la comprobación programada, llama a rl.check() desde un script que ejecute cron, o define CRON_SECRET y llama a la comprobación por HTTP.
Cualquier aplicación WSGI o ASGI
Bottle, Pyramid y cualquier otra aplicación WSGI usan el middleware WSGI.
python
from runlight.wsgi import RunlightMiddleware
application = RunlightMiddleware(application, rl)Quart, Django bajo ASGI y cualquier otra aplicación ASGI usan el middleware ASGI, que recibe la instancia como runlight=.
python
from runlight.asgi import RunlightMiddleware
application = RunlightMiddleware(application, runlight=rl)Ambos responden bajo /runlight, en /go y en cada dominio de enlaces, y pasan todo lo demás a tu aplicación. Las opciones de las rutas se pasan como argumentos con nombre, igual que con Flask. Para ejecutar Runlight como una aplicación propia, sin ninguna tuya detrás, usa runlight.wsgi.app(rl) con un servidor WSGI como gunicorn, o runlight.asgi.app(rl) con un servidor ASGI como uvicorn. Las rutas fuera de las de Runlight responden entonces con un 404.
Un enlace corto en /go/<name> sin ningún enlace con ese nombre sigue hacia tu aplicación, así que responde tu propia página 404, sea la de Django, la de Flask, la de FastAPI o la de tu aplicación WSGI o ASGI. runlight.wsgi.app() y runlight.asgi.app() no tienen ninguna aplicación detrás y responden ellos mismos con un 404. En un dominio de enlaces, /go/<name> es un 404 como cualquier otra ruta que no sea uno de los enlaces de ese dominio.
Lo añadas como lo añadas, pon el script en cada página, justo antes de </head>.
html
<script defer src="/runlight/s.js"></script>Después define RUNLIGHT_TOKEN como una cadena larga y aleatoria y abre una vez /runlight/?token= seguido de esa cadena para iniciar sesión, como describe Primeros pasos. Con accounts=True y un RUNLIGHT_SECRET, cada persona inicia sesión con su propio correo y contraseña, como en Cuentas.
Agentes de IA
El middleware registra los accesos de los agentes de IA mientras tu aplicación sirve sus páginas, y deja pasar intacta cualquier otra petición. Pasa observe=False a init_app o al middleware para desactivarlo, o define "observe": False en RUNLIGHT_ROUTES en Django. Sin middleware, llama a rl.observe(request) con un runlight.http.Request en cada petición de página.
La comprobación programada
La comprobación programada rota las sales diarias, envía los informes por email pendientes, aplica cuánto tiempo conserva cada sitio sus visitas y construye los resúmenes que mantienen rápidos los rangos largos. Ejecútala cada pocos minutos con python manage.py runlight_check en Django, flask runlight check en Flask o rl.check() desde cualquier script. Cuando nada puede ejecutar un comando con un temporizador, define CRON_SECRET y haz que un programador de tareas llame a /runlight/api/check con él, como muestra Comprobación programada.
El servidor independiente
El paquete incluye el servidor independiente, que ejecuta Runlight en un dominio propio, como stats.example.com, con el panel en la raíz, sitios añadidos desde el panel, cuentas con inicio de sesión y enlaces cortos en cualquier dominio que apuntes hacia él. Instala el paquete y ejecuta el comando runlight.
bash
pip install runlight
runlightEl servidor escucha en el puerto 3000 y guarda sus datos en una carpeta llamada runlight-data dentro del directorio desde el que lo arrancas. En su primer arranque muestra un enlace de configuración con un código de un solo uso. Abre ese enlace y crea tu cuenta, como describe Crea tu cuenta. El servidor ejecuta él mismo la comprobación programada cada cinco minutos, y descarga los datos de ubicación del mes igual que el servidor de Node. Ponlo detrás de un proxy que añada HTTPS, como en Publícalo en internet.
Ajustes
El servidor lee sus ajustes de variables de entorno.
| Variable | Qué hace |
|---|---|
PORT |
El puerto en el que escucha. Por defecto, 3000. |
HOST |
La dirección en la que escucha. Por defecto, 0.0.0.0. |
DATA_DIR |
La carpeta para el archivo SQLite, el secreto y los datos de ubicación. Por defecto, ./runlight-data. |
DATABASE_URL |
Una dirección postgres:// guarda los datos en Postgres en lugar de SQLite, y una dirección mysql:// o mariadb:// los guarda en MySQL o MariaDB. |
RUNLIGHT_SECRET |
La clave que firma los inicios de sesión y cifra las claves guardadas. Sin ella, el servidor crea una y la guarda en DATA_DIR, legible solo por el usuario que lo ejecuta. |
RUNLIGHT_URL |
La dirección pública del servidor, como https://stats.example.com, que nunca puede convertirse en dominio de enlaces. Los correos de invitación y de informes enlazan a ella. |
RUNLIGHT_TOKEN |
Un token que los scripts pueden enviar como bearer, además de los tokens de API creados en el panel. |
RUNLIGHT_OBSERVE_KEY |
Una sola clave para los informes de agentes de IA de todos los sitios. La clave de agentes propia de cada sitio, que aparece en Ajustes, Instalación, es la mejor opción. |
TRUST_PROXY |
Defínela como false cuando no haya ningún proxy delante del servidor, o como x-real-ip o cf-connecting-ip cuando esa cabecera lleve la dirección del visitante. |
RUNLIGHT_GEO |
De dónde salen las ubicaciones cuando ninguna cabecera las da, como en Ubicaciones. Por defecto es city, y puede ser country, off o la ruta a un archivo MMDB. |
CRON_SECRET |
Permite que un programador de tareas ejecute la comprobación en POST /api/check. |
Comandos
runlight check ejecuta la comprobación programada una vez y obtiene los datos de ubicación del mes. runlight migrate crea o actualiza las tablas al momento. Las tablas también se actualizan solas cuando arranca el servidor, así que para actualizar basta con pip install --upgrade runlight y un reinicio. Haz copia de seguridad de la carpeta de datos, ya que las claves guardadas no se pueden leer sin el secreto que contiene.
Contraseñas olvidadas
Ejecuta el comando password donde se ejecuta el servidor para darle a una cuenta una contraseña nueva, que el comando muestra. También desactiva el inicio de sesión en dos pasos de esa cuenta, y crea la cuenta si no existe, como propietario en un servidor que aún no tiene a nadie y como administrador en los demás casos.
bash
runlight password someone@example.comAgentes de IA desde un log
Los agentes de IA como GPTBot y ClaudeBot acceden a las páginas sin ejecutar JavaScript, así que la etiqueta de script nunca los ve. Una aplicación Python con Runlight dentro los cuenta donde se sirve la página, como muestra Agentes de IA. Para cualquier otro sitio que alojes tú, el log de acceso del servidor web los tiene, y el comando agents lo lee.
bash
runlight agents --log /var/log/nginx/access.log --to https://stats.example.com --key rlo_... --site https://example.com --followLa clave es la clave de agentes del sitio, que aparece en Runlight en Ajustes, Instalación, y solo puede informar de accesos a ese sitio. Cuando falta --to o --key, el comando lee RUNLIGHT_URL o RUNLIGHT_OBSERVE_KEY del entorno. El comando funciona igual que en el servidor independiente, con --follow para seguir en marcha y --state para continuar donde se quedó la última ejecución. Solo salen de la máquina los accesos correctos a páginas de agentes de IA conocidos, y las direcciones de los visitantes se quedan donde están.
En qué se diferencia de la biblioteca de TypeScript
El paquete de Python pasa las pruebas de conformidad que se exigen a la biblioteca de TypeScript, en SQLite, Postgres, MySQL y MariaDB. Las diferencias vienen de cómo se ejecuta Python.
- El trabajo de Runlight es síncrono y se ejecuta en hilos. El middleware ASGI pasa cada una de sus peticiones a un hilo de trabajo, así que el event loop queda libre para el resto de tu aplicación.
- SQLite mantiene una sola conexión, que todos los hilos comparten con un bloqueo. Postgres y MySQL abren una conexión por hilo, así que un servidor con dieciséis hilos mantiene hasta dieciséis conexiones.
- Lo que la biblioteca de TypeScript hace en segundo plano, como borrar visitas después de elegir una retención más corta, se ejecuta una vez enviada la respuesta.
- El límite de peticiones del tracker y las listas en caché de dominios de enlaces y de iconos de sitios se guardan en memoria, así que cada proceso tiene las suyas. Un servidor con varios procesos de trabajo permite a cada dirección el límite una vez en cada proceso. Los límites de contraseñas incorrectas se guardan en la base de datos, así que todos los procesos los comparten.
- El hash de las contraseñas se calcula con scrypt a través de
hashlibde Python. Los hashes coinciden con los de la biblioteca de TypeScript, así que las cuentas se mantienen.