Rustango docs
← Guías

Referencia de la CLI manage

Esta es la herramienta de línea de comandos de Rustango, comparable al artisan de Laravel o al comando rails de Rails. En un proyecto generado mediante cargo rustango new, un único binario ejecuta cada comando («verbo»):

cargo run                          # runserver (no args = boot the HTTP server)
cargo run -- migrate               # any other verb
cargo run -- --help                # full subcommand list

One binary runs every manage verb — server, migrations, scaffolders, database utilities, and system commands

Fuente: rustango::manage (Cli, el despachador de verbos) — detrás de la característica manage (activada por defecto).

Versión ejecutable: cada verbo aquí se ejecuta en un proyecto generado; el ejemplo getting_started_blog se maneja con cargo run -- migrate y compañía.

¿Nuevo con algún término aquí? scaffold, migration, tenant — consulta el glosario.

El enrutador de comandos vive en rustango::manage::Cli; tu src/main.rs lo conecta así:

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    rustango::manage::Cli::new().api(urls::api()).run().await
}

Los proyectos multi-tenant añaden .tenancy() a la cadena. Eso cambia el enrutador a rustango::tenancy::manage y desbloquea los comandos multi-tenant.

Forma más antigua — los proyectos generados por manage startapp --with-manage-bin (o los anteriores a v0.16) todavía incluyen src/bin/manage.rs. Estos usan cargo run --bin manage -- <verb>. Ambas formas aceptan los mismos verbos.

Cada comando imprime en stdout y termina con un código distinto de cero ante errores de validación o de E/S. Ejecuta cargo run -- --help (o <verb> --help) para obtener la ayuda de uso en línea.


Tabla de contenidos


Migraciones

makemigrations [name]

Genera un archivo de migración a partir de los cambios en tus modelos. Compara tus modelos registrados con la última instantánea de esquema guardada en migrations/ y escribe un nuevo archivo JSON con lo que haya cambiado.

cargo run -- makemigrations                          # auto-name (e.g. 0004_add_slug_to_posts)
cargo run -- makemigrations rename_status_to_state   # custom suffix

Cambios detectados automáticamente:

  • CreateTable / DropTable
  • AddColumn / DropColumn
  • AlterColumnType / AlterColumnNullable / AlterColumnDefault / AlterColumnMaxLength
  • AlterColumnUnique
  • CreateIndex / DropIndex
  • AddCheckConstraint / DropCheckConstraint
  • CreateM2MTable / DropM2MTable

NO detectados automáticamente (renombrar vs. eliminar+añadir es ambiguo):

  • RenameTable, RenameColumn — usa --empty y edita el JSON.

makemigrations --app <app>

Limita la migración a una sola app. Escribe en el propio directorio <project_root>/<app>/migrations/ de esa app y solo mira los modelos que pertenecen a ella.

cargo run -- makemigrations --app blog
cargo run -- makemigrations --app blog backfill_slugs

makemigrations --scope <registry|tenant>

Solo multi-tenant. Escribe una única migración solo para los modelos de un scope — aquellos cuyo atributo #[rustango(scope = "...")] coincide. (Las tablas «registry» se comparten entre todos los tenants; las tablas «tenant» viven por tenant.) Sin este flag, un makemigrations simple en un proyecto de tenancy divide automáticamente los cambios en DOS archivos — uno para los modelos de registry, otro para los modelos de tenant — de modo que las tablas compartidas del framework (Org, Operator) no se filtren en las migraciones por tenant que ejecuta migrate-tenants.

cargo run -- makemigrations                       # tenancy: writes 0NN_<auto>.json (registry) + 0MM_<auto>.json (tenant) as needed
cargo run -- makemigrations --scope tenant        # explicit single-scope diff
cargo run -- makemigrations --scope registry      # explicit single-scope diff

Por qué importa la división: antes de v0.24.2, un makemigrations simple en un proyecto de tenancy agrupaba operaciones sobre rustango_operators (una tabla de registry) dentro de una migración de tenant. Cuando migrate-tenants ejecutaba ese archivo, rustango_operators se resolvía vía search_path a la copia de registry y chocaba con la restricción que ya estaba allí.

makemigrations --empty <name>

Crea una migración en blanco (sin operaciones forward) para que la rellenes a mano. Úsala cuando necesites escribir operaciones de datos u operaciones de renombrado que el autodetector no puede generar. Edita el JSON resultante tú mismo.

cargo run -- makemigrations --empty rename_status_to_state
# Then edit migrations/0005_rename_status_to_state.json:
#   "forward": [
#     {"schema": {"RenameColumn": {"table": "posts", "old_column": "status", "new_column": "state"}}}
#   ]

makemigrations --merge

Corrige un historial de migraciones que se ha dividido en dos ramas (issue #346). Esto ocurre cuando dos personas ejecutan cada una makemigrations en su propia rama de característica, de modo que ambos archivos nuevos apuntan al mismo padre. Tras fusionar ambas ramas, el historial tiene dos «hojas» (puntos finales), y el siguiente makemigrations elegiría arbitrariamente una como su padre.

--merge detecta esto y escribe un NNNN_merge.json vacío cuyo padre apunta a la última hoja en orden alfabético, reunificando el historial en una sola cadena. Su instantánea de esquema refleja el estado combinado, leído del registro de modelos vivo — los modelos de ambas ramas están compilados en este punto, así que la instantánea es precisa.

cargo run -- makemigrations --merge
# wrote migrations/0004_merge.json
#     merge node — empty `forward`, anchors the chain after divergent leaves
  • Ya es una sola cadena → imprime no merge needed y termina limpiamente. Seguro de ejecutar en un historial sano.
  • Historiales realmente separados (no una colisión de ramas) → da un error en lugar de inventar un padre.
  • No se puede combinar con --empty, --app, --scope ni un nombre posicional.

migrate

Aplica todas las migraciones pendientes a la base de datos, en orden — el equivalente del php artisan migrate de Laravel. Este es el comando que ejecutas después de makemigrations para cambiar realmente tu esquema.

cargo run -- migrate
cargo run -- migrate --dry-run                       # print SQL without writing

Cada archivo se ejecuta dentro de una transacción por defecto, de modo que un fallo revierte todo el archivo. Establece "atomic": false en el JSON para no usarla — la necesitas para sentencias como CREATE INDEX CONCURRENTLY que no pueden ejecutarse dentro de una transacción.

En modo tenancy (Cli::tenancy()), migrate es consciente del scope: primero aplica las migraciones de registry a la base de datos de registry compartida, luego aplica las migraciones de tenant a través de cada tenant activo. Para un control más fino, usa migrate-registry / migrate-tenants.

migrate <target>

Migra a un punto concreto del historial, hacia adelante o hacia atrás. Nombra una migración para moverte a ella; el objetivo especial zero deshace todo.

cargo run -- migrate 0003_add_slug      # forward to 0003
cargo run -- migrate 0001_initial       # roll back to 0001 (unapply 0002+)
cargo run -- migrate zero               # unapply EVERY migration

migrate --squash

Colapsa cada migración pendiente (sin aplicar) en un único diff recién generado — la escotilla de escape para la iteración de desarrollo, para cuando una pila de migraciones a medio terminar es más fácil de regenerar que de arreglar. Se niega a tocar cualquier cosa ya aplicada.

cargo run -- migrate --squash

El archivo regenerado registra los nombres que colapsó en su lista replaces. Eso importa en el momento en que hay otra base de datos involucrada: el checkout de tu colega, staging o CI puede que ya hayan aplicado algunos de los archivos que acabas de borrar. Sin replaces, el CREATE TABLE del archivo nuevo chocaría allí; con él, el runner reconcilia en su lugar (ver más abajo).

Reconciliación de squash

Un squash recrea el estado final de las migraciones que reemplaza, así que lo que el runner debería hacer depende por completo de lo que la base de datos de destino ya contenga. Lo decide automáticamente:

estado de la base de datosqué ocurre
nueva — sin historial, sin tablasel squash se ejecuta de verdad
cada migración reemplazada está en el ledgerregistrado, predecesores marcados como obsoletos, sin DDL
las tablas existen pero el ledger no tiene historialregistrado, sin DDL — el ledger se alinea a posteriori con las tablas existentes
solo están presentes algunas filas / tablas reemplazadasrechazado, nombrando lo que falta

El caso parcial es deliberadamente un error rotundo: ninguna elección automática es segura ahí, así que el runner se detiene y te dice lo que encontró en lugar de adivinar. Resuélvelo con migrate --fake (abajo).

Las migraciones sustituidas por un squash aplicado se tratan como aplicadas, así que puedes dejar los archivos viejos en disco durante una o dos releases — los despliegues que nunca las ejecutaron migran hacia adelante correctamente de todas formas.

Las migraciones ordinarias (que no son squash) no se ven afectadas: una migración simple cuya tabla ya existe sigue fallando ruidosamente, porque eso es un conflicto real y no un historial conocido-equivalente.

migrate --fake <name>

Marca una migración como aplicada sin ejecutar su SQL — la escotilla de escape del operador para cuando la base de datos ya está en el estado de destino pero el ledger no lo sabe (una BD configurada fuera de banda, una tabla de ledger eliminada, una migración parcialmente exitosa, un squash parcial rechazado). Repite el flag para reparar varias filas de una vez.

cargo run -- migrate --fake 0004_add_indexes
cargo run -- migrate --fake 0004_add_indexes --system        # framework's own chain
cargo run -- migrate --fake 0004_add_indexes --all-tenants   # every active tenant

El nombre se valida primero contra el directorio de migraciones, de modo que un error de tipeo no puede colar una fila falsa. Marcar es idempotente.

--system apunta a la propia cadena de migraciones del framework (system/migrations/, registrada en __rustango_system_migrations__) en lugar de la de tu proyecto. --all-tenants extiende la marca a través de cada tenant activo, informando de cada uno y continuando más allá de los fallos — las tablas del framework viven por tenant, así que repararlas es un trabajo por tenant.

downgrade [N]

Revierte las últimas N migraciones aplicadas (por defecto 1) — el migrate:rollback de Laravel. Cada migración debe ser reversible: los cambios de esquema se revierten automáticamente, pero las operaciones de datos necesitan un reverse_sql definido o el rollback falla.

cargo run -- downgrade                  # one step
cargo run -- downgrade 3                # three steps

showmigrations / status

Lista cada migración y si se ha aplicado. [X] significa aplicada, [ ] significa aún pendiente.

cargo run -- showmigrations
cargo run -- status                     # alias

Salida:

[X] 0001_initial
[X] 0002_add_status
[ ] 0003_add_slug

Migraciones de datos

add-data-op

Añade un paso de datos en SQL crudo a una migración sin editar el JSON a mano. Recurre a esto cuando necesites transformar filas existentes — rellenar una columna, limpiar datos — como parte de una migración. El resultado es una migración de datos que ejecuta SQL crudo, generada para ti desde la línea de comandos.

# New migration with up + down
cargo run -- add-data-op \
    --sql "UPDATE posts SET slug = lower(title)" \
    --reverse-sql "UPDATE posts SET slug = NULL" \
    --name backfill_post_slugs

# Append to an existing migration
cargo run -- add-data-op \
    --to 0003_add_slug \
    --sql "UPDATE posts SET slug = id::text"

# Irreversible (no rollback)
cargo run -- add-data-op \
    --sql "DELETE FROM legacy_data" \
    --name purge_legacy
FlagRequeridoDescripción
--sql <SQL>síSQL hacia adelante que se ejecuta en migrate
--reverse-sql <SQL>noSQL de rollback en unapply; omítelo para hacerla irreversible
--name <name>noSufijo del nombre de la nueva migración; por defecto data_op
--to <migration>noAñadir a una migración existente en lugar de crear una

Omite --reverse-sql y el paso se marca como reversible: false — cualquier intento de revertirlo falla de inmediato.


Generadores de proyecto / app

cargo rustango new <name> (binario aparte)

Crea un proyecto Rustango completamente nuevo — como laravel new o rails new. Esta es una herramienta aparte, así que instálala primero con cargo install cargo-rustango. Elige entre tres plantillas:

cargo rustango new myblog                          # default = fullstack (ORM + admin)
cargo rustango new myapi --template api            # JSON-only, no admin
cargo rustango new shop --template tenant          # multi-tenancy

Escribe:

<name>/
  Cargo.toml
  .env.example
  .gitignore
  rust-toolchain.toml
  docker-compose.yml
  Dockerfile                                (deploy image — multi-stage, release, non-root)
  Dockerfile.dev                            (cargo-watch image docker-compose.yml builds)
  .dockerignore
  README.md
  config/default.toml                       (shared knobs)
  config/{dev,staging,prod}_settings.toml   (per-tier overrides)
  migrations/                               (your app's migrations)
  system/migrations/                        (tenant template — framework tables, generated)
  src/{lib,main,models,views,urls}.rs

El nivel config/ es la fuente de los ajustes: default.toml guarda lo que comparten todos los entornos y un <env>_settings.toml lo sobrescribe, seleccionado en tiempo de ejecución por RUSTANGO_ENV (dev por defecto), así que un cargo run recién sacado funciona sin editar ningún TOML. Entre ellos llevan el ajuste del pool [database], [admin], [mcp] y la política secure_cookies — y son lo que lee la auditoría de ajustes de check --deploy. El contenido por nivel está en Scaffolding.

La plantilla de tenant incluye una carpeta system/migrations/ vacía. Las propias tablas del framework (rustango_orgs, rustango_users, roles/permisos, …) se generan en ella a partir de los modelos compilados en el primer cargo run -- migrate — no hay un JSON de bootstrap incluido a mano. Consulta migrate / migrate-registry.

startapp <name> [flags]

Crea una nueva app (un módulo de característica) bajo src/<name>/. Úsalo para mantener agrupados los modelos, vistas y URLs de una parte de tu proyecto.

cargo run -- startapp blog
cargo run -- startapp shop --with-manage-bin             # also writes src/bin/manage.rs
cargo run -- startapp shop --into apps                   # write under src/apps/shop/ instead

Crea:

src/<name>/
  mod.rs
  models.rs
  views.rs
  urls.rs

Seguro de volver a ejecutar — los archivos existentes se dejan intactos. Un paso manual: añade pub mod <name>; a src/lib.rs para que Rust compile el nuevo módulo.


Generadores de archivos (make:*)

Estos crean archivos de inicio para bloques de construcción comunes — muy parecido a los comandos make:* de Laravel (make:controller, make:model, …). Cada generador escribe en src/<snake_name>.rs (o tests/<snake_name>.rs para make:test) y:

  • Comprueba que el nombre es válido (PascalCase, letras/dígitos/guion bajo).
  • Lo convierte a snake_case para el nombre del archivo (PostViewSet → post_view_set.rs).
  • No sobrescribe un archivo existente.
  • Te recuerda que añadas pub mod X; a tu lib.rs.

make:viewset <Name> [--model <Model>] [--tenant | --no-tenant] [--crate <path>]

Genera un recurso REST CRUD completo para un modelo.

Dos plantillas, elegidas por ti. El #[derive(ViewSet)] de pool único captura un solo pool al montar, lo cual es incorrecto para un proyecto con tenancy — así que el generador elige entre dos formas. Orden de resolución: --no-tenant gana, luego --tenant, luego la detección automática — tenancy en la lista de features de la dependencia rustango en Cargo.toml — y si no, la plantilla de pool. Cuando la detección automática elige el modo tenant, lo indica por stdout y nombra --no-tenant como override.

cargo run -- make:viewset PostViewSet --model Post
cargo run -- make:viewset PostViewSet --model Post --no-tenant   # forzar la forma de pool

Plantilla de pool — src/post_view_set.rs generado:

#[derive(ViewSet)]
#[viewset(model = Post, fields = "id, ", filter_fields = "", search_fields = "", page_size = 20)]
pub struct PostViewSet;

Móntalo con: .merge(PostViewSet::router("/api/posts", pool.clone())).

Plantilla de tenant — no es un derive. Emite un pub fn router() -> Router<()> construido a partir de ViewSet::for_model(Post::SCHEMA).tenant_router("/api/posts"), con toda la cadena del builder (fields / filter_fields / search_fields / ordering / page_size / permissions_for_model / read_only) esbozada en líneas comentadas. La conexión se resuelve por petición mediante el extractor Tenant en lugar de capturarse una sola vez al montar, así que un único router() sirve a todos los tenants.

Móntalo con: .merge(crate::viewsets::post::router()) — no con la línea PostViewSet::router(path, pool) de arriba.

--crate <path> renombra el crate del framework en las líneas use generadas, para proyectos que importan rustango con otro nombre.

make:serializer <Name> [--model <Model>]

Genera una estructura #[derive(Serializer)] — controla cómo un modelo se convierte hacia y desde JSON.

cargo run -- make:serializer PostSerializer --model Post

make:form <Name>

Genera una estructura #[derive(Form)] para validar y procesar la entrada de un formulario.

cargo run -- make:form ContactForm

make:job <Name>

Genera un jobs::Job — una struct de payload más la implementación del trait, con NAME, MAX_ATTEMPTS y async fn run(&self). Es trabajo que encolas desde un handler y que un worker ejecuta después.

run recibe solo la payload: ni pool, ni tenant, ni contexto de request. Lleva en sus campos todo lo que el job necesite.

cargo run -- make:job EmailDigestJob

make:scheduled <Name>

Genera una tarea de intervalo fijo para scheduler::Scheduler — la forma que make:job emitía antes de generar un job de verdad.

cargo run -- make:scheduled NightlySweep

make:worker <Name>

Genera un binario de worker independiente para src/bin/ — un proceso que vacía la cola de trabajos y no sirve HTTP. Ejecútalo junto al proceso web, o como su propio contenedor.

La forma es corta y fácil de escribir mal de un modo que solo aparece en producción: un worker que espera tokio::signal::ctrl_c() atiende SIGINT pero no SIGTERM, que es lo que envían docker stop, Kubernetes y systemd. Entonces el vaciado nunca se ejecuta, el contenedor muere al agotarse su periodo de gracia, y los trabajos en vuelo se pierden sin que se registre nada. El worker generado espera shutdown::shutdown_signal(), que atiende ambas señales.

cargo run -- make:worker JobsWorker

make:notification <Name>

Genera una estructura de notificación que construye un correo electrónico — como el make:notification de Laravel.

cargo run -- make:notification WelcomeEmail

make:middleware <Name>

Genera una función de middleware — código que se ejecuta antes y después de cada request (comprobaciones de auth, logging, etc.). «axum» es el framework web sobre el que está construido Rustango, así que el stub coincide con la forma del middleware de axum.

cargo run -- make:middleware AuditLog

make:test <Name>

Genera un test de integración en tests/ que usa TestClient para hacer requests contra tu app.

cargo run -- make:test post_smoke

Utilidades de base de datos

db:info

Muestra con qué base de datos está configurada esta build para hablar, sin conectarse. Imprime la versión del framework, qué drivers de base de datos (características de Cargo postgres/mysql) están compilados, la URL de conexión con la contraseña oculta y el backend detectado. Como nunca abre una conexión, resulta práctico en CI o contenedores donde la base de datos aún no está levantada pero quieres confirmar que la configuración es correcta.

cargo run -- db:info

db:dump [--out <path>] [--data-only|--schema-only] [--no-owner]

Respalda tu base de datos ejecutando pg_dump contra DATABASE_URL — como php artisan db:dump. Por defecto el SQL va a stdout (para que puedas canalizarlo); pasa --out <path> (-o) para escribir un archivo en su lugar. --data-only y --schema-only se corresponden directamente con los flags de pg_dump, y --no-owner omite las líneas OWNER. Necesitas pg_dump instalado y en tu PATH.

cargo run -- db:dump > backups/before-migrate.sql    # stdout → file
cargo run -- db:dump --out backups/before-migrate.sql

La línea de estado running: pg_dump … va a stderr, así que se queda fuera de la redirección y fuera de una tubería. Hasta #1404 iba a stdout, lo que la dejaba en la primera línea del archivo .sql.

db:restore <path> [--clean]

Carga un archivo de dump de vuelta en tu base de datos — la contraparte de db:dump. Pasa el archivo por psql contra DATABASE_URL con ON_ERROR_STOP=1, de modo que se detiene ante el primer error. Añade --clean para borrar primero el esquema existente (antepone DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;) para que la restauración aterrice en una base de datos vacía. Necesitas psql en tu PATH.

cargo run -- db:restore backups/before-migrate.sql
cargo run -- db:restore backups/before-migrate.sql --clean

Comandos del sistema

version / --version

Imprime la versión del framework Rustango.

$ cargo run -- version
rustango 0.57.11

about

Imprime una instantánea de tu entorno: versión del framework, modelos y apps registrados, si la base de datos es accesible, y variables de entorno clave. Incluye esto en los tickets de soporte cuando algo va mal.

$ cargo run -- about
rustango
  version:        0.57.11
  models:         3 registered
  apps:           1 (blog)
  RUSTANGO_ENV:   local
  DATABASE_URL:   postgres://***@localhost:5433/myblog
  db_connect:     ok

check [--deploy]

Ejecuta comprobaciones de salud en tu proyecto. Añade --deploy para las comprobaciones más estrictas de preparación para producción.

Comprobaciones siempre activas:

  • ≥ 1 modelo registrado vía inventory
  • BD accesible (SELECT 1)
  • Modelos registrados pero ninguna migración en disco (no compara recuentos — un directorio migrations/ existente se reporta como info, sea cual sea el número)

Con --deploy:

  • RUSTANGO_ENV es prod o production
  • RUSTANGO_SESSION_SECRET establecido y ≥ 32 bytes (la clave HMAC para cookies + JWTs; el framework nunca lee SECRET_KEY)
  • DATABASE_URL establecido
  • RUSTANGO_APEX_DOMAIN establecido — el aviso salta en todos los proyectos cuando falta o es localhost, y así lo dice; los proyectos de un solo tenant pueden ignorarlo
  • DATABASE_URL apuntando a localhost / 127.0.0.1 (aviso — en producción suele ser un host gestionado)
  • RUSTANGO_BIND empezando por 127.0.0.1 (aviso — solo loopback no acepta tráfico externo)
  • Una auditoría del nivel de settings sobre tu TOML, señalando valores de desarrollo dejados en un nivel de producción (requiere la característica config)
  • Meta.required_db_vendor / required_db_features de cada modelo registrado, comprobados contra el dialecto realmente conectado
$ cargo run -- check --deploy
running rustango system check (deploy mode)...
  [info]    3 models registered via inventory
  [info]    database reachable
  [info]    4 migration(s) on disk
  [info]    RUSTANGO_SESSION_SECRET length OK
all checks passed

Termina con un código distinto de cero si falla cualquier comprobación de nivel de error. Las advertencias por sí solas no provocan un fallo.

docs

Abre la documentación de Rustango (https://docs.rs/rustango) en tu navegador. Siempre imprime también la URL, de modo que sigue funcionando en un servidor sin interfaz gráfica.

cargo run -- docs

--help / help

Lista cada comando con una descripción de una línea. En modo tenancy, también se añaden los comandos multi-tenant listados abajo.


Comandos de tenancy

Estos comandos existen solo en proyectos multi-tenant (una app que sirve a muchos clientes/orgs aislados). Aparecen solo cuando el proyecto se compila con features = ["tenancy"] Y Cli::new() se encadena con .tenancy().

Todo lo que puede hacer la consola de operador lo pueden hacer también estos comandos — para que una acción pueda ejecutarse en un hook de despliegue, en un cron o en una máquina donde nadie puede abrir un navegador.

menu / actions

Hay más de cuarenta verbos de tenancy. --help te dice que existen; el menú te ayuda a ejecutar uno que nunca has ejecutado.

cargo run -- menu
  rustango manage — pick an action

  TENANTS
     1) list-tenants             every tenant in the registry
     2) create-tenant            provision a new tenant
     3) edit-tenant              change routing and display config
     …
  HOSTNAMES
     7) list-hosts               every hostname a tenant answers on
     …
     q  quit
     ?  every other verb: cargo run -- --help

Elige un número (o escribe el verbo) y solo te pregunta lo que el verbo no preguntará por sí mismo, muestra la línea de comandos que está a punto de ejecutar, la ejecuta y vuelve para la siguiente acción. Pasa por el mismo dispatcher que los flags, así que no puede desviarse de ellos.

Relacionado pero distinto: wizard es una puesta en marcha única; menu es la lista permanente de lo que puedes hacer después.

wizard / init

Lleva un proyecto recién creado desde «acabo de generarlo» hasta un tenant funcionando, un operador y un superusuario de tenant. Cada paso es opcional — pulsa n para saltarte uno.

cargo run -- wizard

init-tenancy

No-op — conservado por compatibilidad. El framework ya no incluye migraciones de bootstrap construidas a mano. Sus propias tablas (rustango_orgs, rustango_operators, rustango_users, roles/permisos, …) se generan en system/migrations/ a partir de los modelos compilados — el flujo normal (modelos → makemigrations → migrate) — y las aplica migrate / migrate-registry, que las generan bajo demanda si los archivos faltan.

cargo run -- init-tenancy   # does nothing now; kept so old scripts don't break

Las versiones más antiguas escribían aquí 0001_rustango_*_initial.json; ese flujo codificado a mano ya no existe. Para aprovisionar, simplemente ejecuta cargo run -- migrate. Un modelo de usuario personalizado (.user_model::<AppUser>()) fluye por las mismas system/migrations/ generadas — consulta Modelo de usuario personalizado.

migrate-registry

Aplica solo las migraciones de registry — las tablas compartidas entre tenants. El registry contiene rustango_orgs y rustango_operators más cualquier tabla con scope de registry que definas. Las tablas de tenant quedan intactas.

cargo run -- migrate-registry

migrate-tenants

Aplica las migraciones de tenant a cada tenant activo, uno tras otro. Cada tenant usa su propia conexión (su propio esquema o base de datos), y si un tenant falla, el resto se ejecuta igualmente — el comando informa del resultado por tenant al final.

cargo run -- migrate-tenants

Para el caso común, un migrate simple ya hace primero el registry, luego los tenants — recurre a migrate-tenants solo cuando necesites ese paso por sí solo.

runserver / run-server

Arranca el servidor web multi-tenant. En un proyecto de tenancy esto es lo mismo que un cargo run pelado; la forma con nombre existe para que los binarios personalizados que parsean sus propios argumentos aún puedan activarlo.

cargo run                        # implicit
cargo run -- runserver           # explicit

create-tenant <slug> [options]

Configura un nuevo tenant (cliente/org) y le aplica las migraciones de tenant. El <slug> es su identificador corto. Seguro de volver a ejecutar — llamarlo de nuevo sobre un slug existente se rechaza de entrada con tenant slug `<slug>` already exists (tenancy/provision.rs:599), antes de hacer nada más.

cargo run -- create-tenant acme --display-name "ACME Corp"
cargo run -- create-tenant beta --mode database --database-url postgres://...
FlagDescripción
--display-name <name>Etiqueta legible por humanos que se muestra en las barras laterales del admin
--mode schema | databaseModo de almacenamiento (por defecto: schema)
--database-url <url>URL de BD específica del tenant (requerida para el modo database)
--host-pattern <pattern>Anula el patrón de host usado por SubdomainResolver
--no-migrateOmite aplicar las migraciones con scope de tenant tras el aprovisionamiento
--backend postgres | mysql | sqliteDriver para un tenant en modo base (por defecto: postgres). Se valida contra --mode
--schema-name <s>Sobrescribe el nombre de esquema generado en modo esquema
--port <n>Puerto en el que el tenant es accesible, para el enrutado
--path-prefix <s>Prefijo de ruta bajo el que el tenant es accesible, para el enrutado

edit-tenant <slug> [options]

Cambia la configuración de enrutado y de presentación de un tenant — los mismos campos que ofrece la página de edición de la consola de operador.

cargo run -- edit-tenant acme --host-pattern shop.example.com
cargo run -- edit-tenant acme --display-name "ACME Inc" --deactivate
cargo run -- edit-tenant acme --clear host-pattern
FlagDescripción
--display-name <name>Etiqueta legible por humanos
--host-pattern <host>Nombre de host desnudo al que responde el tenant
--path-prefix <path>Un segmento con barra inicial, p. ej. /acme
--port <n>Puerto por el que se empareja el tenant
--database-url <url>Rota la URL de conexión del tenant
--activate / --deactivatePone el tenant en servicio o fuera de servicio
--clear <field>Vacía host-pattern, path-prefix o port

Solo se tocan los campos que nombras. «Dejar como está» y «vaciarlo» son instrucciones distintas — para eso está --clear, que evita depender de --host-pattern "", que algunas shells y runners de CI se comen.

Los valores se validan como los valida create-tenant: un patrón de host que lleve puerto, o un prefijo de ruta que el resolver nunca podría producir, se rechaza en lugar de guardarse para luego no emparejar nunca en silencio.

Rotar --database-url desaloja el pool cacheado del tenant, de modo que la siguiente petición reconecta con la nueva credencial; los demás cambios dejan en paz las conexiones calientes.

test-tenant-connection <url> [flags]

Sondea una URL de base de datos antes de comprometerte con ella — conecta y, por defecto, escribe y revierte, así que una credencial de solo lectura se detecta aquí y no en la primera petición del tenant.

cargo run -- test-tenant-connection postgres://user:pw@host/db
cargo run -- test-tenant-connection "$URL" --no-write-probe --timeout 5

drop-tenant <slug> [--confirm <slug>]

Desactiva un tenant estableciendo active = false. Esta es la opción suave y reversible — los datos del tenant permanecen en disco, y reactívalo con edit-tenant <slug> --activate. Volver a ejecutar create-tenant no funciona: la fila Org sigue existiendo, así que el slug se rechaza por duplicado. Cuando no estás ejecutando de forma interactiva (sin terminal adjunto), debes pasar --confirm <slug> con el slug tecleado de nuevo para confirmar.

cargo run -- drop-tenant acme --confirm acme

purge-tenant <slug> [--confirm <slug>] [--purge-database]

Elimina un tenant permanentemente. Borra el esquema del tenant y elimina su fila de rustango_orgs, sin deshacer posible. Cuando no estás ejecutando de forma interactiva (sin terminal adjunto), debes pasar --confirm <slug> con el slug tecleado de nuevo. Para los tenants en modo database, la base de datos subyacente se deja en su sitio a menos que también pases --purge-database. Sin esa bandera el comando se niega por completo para tenants en modo base — no borra la fila Org dejando la base, no hace absolutamente nada (tenancy/manage/tenants.rs:479).

cargo run -- purge-tenant acme --confirm acme
cargo run -- purge-tenant beta --confirm beta --purge-database   # database-mode: also DROP DATABASE

list-tenants

Lista cada tenant con su modo de almacenamiento y su estado activo/inactivo.

cargo run -- list-tenants

Nombres de host

Un tenant es alcanzable en su subdominio y, opcionalmente, en los nombres de host adicionales que le vincules. Un nombre de host lleva a exactamente un tenant.

cargo run -- list-hosts acme
cargo run -- add-host acme shop.example.com
cargo run -- set-host-enabled acme shop.example.com --off   # aparcarlo
cargo run -- remove-host acme shop.example.com
VerboQué hace
list-hosts <slug>Todos los nombres de host del tenant, el host base primero
add-host <slug> <hostname>Vincula uno. Se rechaza si otro tenant ya lo reclama
remove-host <slug> <hostname>Lo desvincula
set-host-enabled <slug> <hostname> --on|--offServirlo o aparcarlo

Aparcarlo (--off) conserva la fila y saca el host de servicio — útil mientras el DNS propaga, o al retirar un dominio que quizá quieras recuperar.

Los nombres de host se normalizan a la entrada (minúsculas, sin esquema, sin puerto, sin ruta), porque el valor guardado se compara byte a byte con la cabecera Host. Los verbos muestran lo que se guardó, no lo que escribiste.

El host base viene del host_pattern del tenant y no tiene fila propia, así que no puede quitarse aquí — cámbialo con edit-tenant --host-pattern. Deliberadamente no hay rename-host: quita y vuelve a añadir, para que el cambio llegue también a los demás pods en ejecución.

create-operator <username> --password <pwd>

Crea un operador — un admin global que puede gestionar cada tenant desde una consola entre tenants. Los operadores viven en el registry compartido, no dentro de un tenant concreto.

cargo run -- create-operator admin --password letmein
cargo run -- create-operator admin --generate     # mostrar una aleatoria en su lugar

Omite --password en un terminal y la pedirá sin eco.

list-operators

Cada operador, con si está activo y cuándo se creó, más un recuento de cuántos siguen activos.

cargo run -- list-operators

set-operator-active <username> --on|--off

Corta o restablece el acceso de un operador. La fila se mantiene en ambos casos, así que «¿quién hizo esto?» sigue resolviéndose más tarde — y la consola la relee en cada petición, de modo que una desactivación surte efecto en su siguiente clic y no cuando caduque la cookie.

cargo run -- set-operator-active grace --off   # salida de un compañero
cargo run -- set-operator-active grace --on

Dos cosas que rechaza, por la misma razón que las rechaza la consola:

  • Desactivar al último operador activo. Eso deja a todo el mundo fuera de la consola, y solo una shell sobre el registry podría deshacerlo.
  • Desactivarte a ti mismo, cuando la petición viene de la consola. La CLI no tiene sesión de la que dejarse fuera, así que allí solo aplica la primera regla.

La dirección es obligatoria — adivinar retiraría un acceso o lo concedería, y ambas cosas están mal si ocurren en silencio. Fijar el estado que ya tiene se informa y tiene éxito, para que un script de aprovisionamiento reejecutado no se ponga en rojo.

create-user <tenant> <username> --password <pwd> [--superuser]

Crea un usuario dentro de un tenant — con --superuser, también un administrador, pero siempre limitado a un solo tenant.

cargo run -- create-user acme alice --password hunter2 --superuser

--superuser establece is_superuser = true para ese usuario dentro del tenant. Eso lo convierte en admin del tenant (acceso de escritura completo en el admin del tenant), pero nunca otorga acceso a la consola de operador entre tenants.

create-role <tenant> <name>

Crea un rol (un paquete con nombre de permisos) dentro de un tenant.

cargo run -- create-role acme editor

list-roles <tenant>

Lista los roles definidos en un tenant dado.

cargo run -- list-roles acme

assign-role <tenant> <username> <role>

Otorga a un usuario uno de los roles del tenant.

cargo run -- assign-role acme alice editor

revoke-role <tenant> <username> <role>

Elimina un rol de un usuario — lo inverso de assign-role.

cargo run -- revoke-role acme alice editor

grant-perm <tenant> <role-name|username> <codename> [--role]

Otorga un único permiso. Por defecto, el segundo argumento es un nombre de usuario, así que el permiso va directamente a ese usuario; añade --role para otorgarlo a un rol en su lugar. Los codenames de permisos usan el formato <app>.<action>_<model> (blog.add_post, blog.change_post, …). La característica auto_create_permissions crea automáticamente los cuatro codenames CRUD estándar para cualquier modelo marcado con #[rustango(permissions)].

cargo run -- grant-perm acme alice blog.change_post           # grant to user alice
cargo run -- grant-perm acme editor blog.change_post --role   # grant to role editor

revoke-perm <tenant> <role-name|username> <codename> [--role]

Elimina un permiso — lo inverso de grant-perm. Apunta a un usuario por defecto; añade --role para revocarlo de un rol en su lugar.

cargo run -- revoke-perm acme alice blog.change_post
cargo run -- revoke-perm acme editor blog.change_post --role

create-api-key <tenant> <username> [--label <s>]

Emite una clave de API para un usuario de tenant. El token completo se imprime una vez y nunca más — cópialo ahora, porque solo se almacenan su prefijo y un hash.

cargo run -- create-api-key acme alice --label "ci-bot"

audit-cleanup

Poda las entradas antiguas del registro de auditoría (rustango_audit_log) para que no crezca indefinidamente. Recorta por antigüedad (--days) o por cantidad (--keep-last), y opcionalmente limítalo a un tenant.

cargo run -- audit-cleanup --days 90                       # delete > 90 days old
cargo run -- audit-cleanup --keep-last 50                  # keep most recent 50 per row
cargo run -- audit-cleanup --keep-last 50 --tenant acme    # scoped
cargo run -- audit-cleanup --registry --days 90            # solo el log del registry

Por defecto barre el log propio del registry y el de cada tenant activo. En el log del registry es donde la consola de operador anota lo que han hecho los operadores, así que crece con el uso de la consola. Nombrar un tenant (--tenant) pide ese tenant y deja el registry en paz. Un tenant roto se informa y se cuenta en lugar de terminar el barrido.

Ver qué ha pasado

Tres verbos de solo lectura que imprimen lo que la consola renderiza — las respuestas que necesitas durante un incidente, sin navegador ni cliente SQL.

cargo run -- list-runs                        # ejecuciones recientes de aprovisionamiento y migración
cargo run -- list-runs --kind migrate --limit 50
cargo run -- show-run 42                      # los pasos de una ejecución
cargo run -- audit-log                        # quién cambió qué, y cuándo
cargo run -- audit-log --pk acme --limit 100
VerboFlags
list-runs--limit <n>, --kind provision|migrate, --state <s>
show-run <id>—
audit-log--limit <n>, --table <t>, --pk <v>, --operation <o>, --source <s>

list-runs muestra primero lo más reciente, y sus filtros corren dentro de la consulta: pedir una ejecución migrate encuentra una aunque las más recientes sean todas aprovisionamientos. show-run imprime la cabecera de la ejecución y cada paso registrado, que es lo que te dice dónde ocurrió un fallo.

Los tenants creados desde la CLI también quedan registrados, marcados con requested_by = cli, así que el historial cubre ambas superficies.

prewarm-pools

Abre por adelantado una conexión para cada tenant activo en modo database. Vale la pena tras un despliegue, un reinicio del registry o una rotación de credenciales: convierte «la primera petición a cada tenant paga la conexión» en una única espera deliberada, y saca a la luz un tenant inalcanzable antes de que lo encuentre un usuario.

cargo run -- prewarm-pools

La consola de operador también tiene un botón para esto, en la lista de tenants.


Modelo de usuario personalizado (columnas extra en rustango_users)

Así añades tus propios campos a la tabla de usuarios. El User de tenant integrado tiene siete columnas fijas: id, username, password_hash, is_superuser, active, created_at, más una columna data JSONB (un blob JSON flexible) para cualquier metadato extra por usuario. Para la mayoría de las apps esa columna JSONB es todo lo que necesitas — sin migración, sin override, sin sorpresas.

Cuando quieras columnas tipadas e indexables en rustango_users en su lugar, hay dos enfoques. No son intercambiables; elige el que encaje con dónde está tu proyecto en su vida.

Opción 1 — Modelo de perfil hermano con FK (funciona en cualquier proyecto)

Lo mejor cuando el proyecto ya existe, o cuando prefieres dejar la tabla User del framework como única fuente de verdad.

#[derive(rustango::Model)]
pub struct UserProfile {
    #[rustango(primary_key)] pub id: rustango::sql::Auto<i64>,
    #[rustango(fk = "rustango_users")] pub user_id: i64,
    #[rustango(max_length = 128, default = "''")] pub display_name: String,
    #[rustango(max_length = 64, default = "'UTC'")] pub timezone: String,
}

Ejecuta cargo run -- makemigrations y luego cargo run -- migrate, y tendrás una tabla de extras tipada enlazada al usuario por clave foránea. Léela con el ORM:

let profile = UserProfile::objects()
    .where_(UserProfile::user_id.eq(user.id.get().copied().unwrap()))
    .first(&pool).await?;            // Option<UserProfile>

Contrapartida: una fila extra y un JOIN en cada acceso. Ventaja: cero riesgo de romper la auth del framework.

Opción 2 — Cli::user_model::<AppUser>() (solo greenfield)

Usa esto solo en un proyecto nuevo donde quieras los campos extra directamente en la propia tabla rustango_users. Como AppUser es el modelo rustango_users, sus columnas fluyen por el motor ordinario de makemigrations → migrate: las tablas del framework se generan en system/migrations/, así que las columnas de AppUser aterrizan en el CREATE TABLE rustango_users generado.

Paso 1. Define tu modelo. Tiene que declarar cada columna requerida por el framework exactamente (id, username, password_hash, is_superuser, active, created_at, data), más tus extras. Cada columna extra debe permitir NULL o tener un default = "…".

use rustango::sql::Auto;

#[derive(rustango::Model, Debug, Clone)]
#[rustango(table = "rustango_users")]
pub struct AppUser {
    #[rustango(primary_key)] pub id: Auto<i64>,
    #[rustango(max_length = 64, unique)] pub username: String,
    #[rustango(max_length = 255)] pub password_hash: String,
    pub is_superuser: bool,
    pub active: bool,
    pub created_at: chrono::DateTime<chrono::Utc>,
    #[rustango(default = "'{}'")] pub data: serde_json::Value,
    // extras —
    #[rustango(max_length = 128, default = "''")] pub display_name: String,
    #[rustango(max_length = 64, default = "'UTC'")] pub timezone: String,
}
impl rustango::tenancy::TenantUserModel for AppUser {}

Paso 2. Conecta el override en main.rs:

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    rustango::manage::Cli::new()
        .api(my_app::urls::router())
        .tenancy()
        .user_model::<AppUser>()
        .run().await
}

Paso 3. Registra AppUser en lugar del User del framework — solo un modelo puede reclamar table = "rustango_users". El scaffolder no incluye JSON de bootstrap estático (solo un system/migrations/ vacío), así que no hay nada que borrar; simplemente no registres también el User del framework.

Paso 4. Genera + aplica:

cargo run -- makemigrations       # generates system/migrations/ with AppUser's columns
cargo run -- migrate              # creates rustango_users with your extras

Advertencias:

  • Cambiar AppUser más adelante es un cambio de esquema normal: vuelve a ejecutar makemigrations para emitir la migración AddColumn, luego migrate.
  • Solo un modelo puede mapear a rustango_users. Registrar ambos, el User del framework y tu AppUser, hace que makemigrations sea ambiguo — registra AppUser solo. Esta es la razón principal por la que la Opción 2 es solo para proyectos nuevos; en un proyecto existente, la Opción 1 evita el problema.
  • El código de auth y admin del framework lee las siete columnas núcleo por nombre; tus columnas extra solo son accesibles a través de AppUser::objects().fetch(...).

Builder::user_model::<AppUser>() hace lo mismo para el código que construye el Builder del servidor directamente, sin pasar por Cli.


Subcomandos personalizados

Puedes añadir tus propios comandos y ejecutarlos junto a los verbos integrados. El truco es inspeccionar los argumentos tú mismo y manejar tu comando antes de pasar el resto a Cli::run. Dos formas de hacerlo:

En línea en src/main.rs (sin binario extra):

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    let args: Vec<String> = std::env::args().skip(1).collect();
    if matches!(args.first().map(String::as_str), Some("import-csv")) {
        let url = std::env::var("DATABASE_URL")?;
        let pool = rustango::sql::Pool::connect_postgres(&url).await?;
        return my_csv_importer::run(&pool, &args[1..]).await;
    }
    rustango::manage::Cli::new().api(urls::api()).run().await
}

Vía --with-manage-bin (src/bin/manage.rs aparte):

cargo run -- startapp app --with-manage-bin

Luego en src/bin/manage.rs:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    let args: Vec<String> = std::env::args().skip(1).collect();
    let url = std::env::var("DATABASE_URL")?;
    let pool = rustango::sql::Pool::connect_postgres(&url).await?;

    match args.first().map(String::as_str) {
        Some("import-csv") => my_csv_importer::run(&pool, &args[1..]).await,
        _ => rustango::migrate::manage::run(&pool, "./migrations".as_ref(), args)
            .await
            .map_err(Into::into),
    }
}

Ejecuta tus propios comandos igual que los integrados: cargo run -- import-csv path/to/file.csv (o cargo run --bin manage -- import-csv … cuando uses --with-manage-bin).


Flujos de trabajo comunes

Configuración inicial del proyecto (single-tenant)

cargo rustango new myapp
cd myapp
cp .env.example .env             # edit DATABASE_URL
docker compose up -d
cargo run -- migrate
cargo run                        # serve at :8080

Configuración inicial del proyecto (tenancy)

cargo rustango new myapp --template tenant
cd myapp
cp .env.example .env             # edit DATABASE_URL + RUSTANGO_APEX_DOMAIN
docker compose up -d
cargo run -- migrate                                      # registry + tenants
cargo run -- create-operator admin --password letmein
cargo run -- create-tenant acme --display-name "ACME Inc" \
                  --host-pattern acme.localhost
cargo run -- create-user acme alice --password tenantpw --superuser
cargo run                        # serve at :8080

Añadir tenants después de que la app ya está en marcha

Una app de tenancy real normalmente acumula modelos y migraciones mucho antes de que se registre su primer tenant. Este flujo funciona en cualquier punto de la vida del proyecto:

# 1. (any time) develop user models — define structs with #[derive(Model)],
#    add `pub mod ...;` to src/lib.rs.
# 2. Generate scope-aware migrations. In a tenancy project this writes
#    up to TWO files: one tagged registry-scope (touches Org/Operator),
#    one tagged tenant-scope (touches User + your models). Pre-v0.24.2
#    this used to dump everything into one tenant-scoped file and
#    crash on `create-tenant` — see the changelog.
cargo run -- makemigrations

# 3. Apply migrations. `migrate` is scope-aware: it runs registry-
#    scoped files once against the registry pool first, then fans
#    tenant-scoped files across every active tenant.
cargo run -- migrate

# 4. Provision a NEW tenant whenever (could be days, weeks, many
#    migrations later). The tenancy code applies every accumulated
#    tenant-scoped migration to the new tenant's schema in one pass —
#    the new tenant arrives at the same schema state as existing ones.
cargo run -- create-tenant acme --display-name "ACME Inc" \
                  --host-pattern acme.localhost
cargo run -- create-user acme alice --password tenantpw --superuser

Por qué esto es seguro:

  • #[rustango(scope = "registry")] en Org/Operator mantiene los cambios en las tablas compartidas fuera de las migraciones por tenant.
  • migrate-tenants visita cada tenant activo y aplica solo las migraciones de tenant — los archivos de registry se omiten.
  • create-tenant ejecuta ese mismo paso de migrate-tenants contra el esquema del nuevo tenant, así que arranca completamente al día sin arreglos manuales.

Añadir un modelo

cargo run -- startapp blog        # if not done yet
# Edit src/blog/models.rs — add #[derive(Model)]
# Add `pub mod blog;` to src/lib.rs
cargo run -- makemigrations
cargo run -- migrate

Añadir una API JSON para ese modelo

cargo run -- make:viewset PostViewSet --model Post
# Edit src/post_view_set.rs — fill in field lists
# Mount in src/urls.rs
cargo run                        # GET /api/posts now works

Añadir un backfill de datos

cargo run -- add-data-op \
    --sql "UPDATE posts SET slug = lower(title) WHERE slug IS NULL" \
    --reverse-sql "UPDATE posts SET slug = NULL" \
    --name backfill_post_slugs
cargo run -- migrate

Auditoría previa al despliegue

cargo run --release -- check --deploy

Revertir la última migración

cargo run -- downgrade 1

Aplicar una migración de tenancy a un scope específico

cargo run -- migrate-registry            # registry-scoped only
cargo run -- migrate-tenants             # tenant-scoped, fan-out across orgs

Dar de baja un tenant

cargo run -- drop-tenant acme            # soft (reversible)
cargo run -- purge-tenant acme           # hard (drops schema/db)

Ajuste fino del pool de tenants (v0.27.7+)

Los tenants en modo database obtienen su propio pool de conexiones (un PgPool — un conjunto de conexiones de base de datos reutilizadas), cacheado por slug en TenantPools. Por defecto un pool se construye de forma perezosa, en el primer request del tenant, a menos que actives el pre-calentamiento. Los ajustes viven en TenantPoolsConfig:

CampoPor defectoPropósito
max_cached_database_pools64Tope de la caché de pools. Una vez llena, el siguiente tenant no cacheado da error (sin desalojo silencioso).
database_pool_max_connections16max_connections por pool. Mantenlo pequeño para que un fan-out de tenants no agote el max_connections de PG.
database_pool_min_connections0Mantiene N conexiones calientes en todo momento. ≥1 reduce la latencia del primer request al pagar el round-trip de TCP/TLS/auth en el arranque.
database_pool_acquire_timeout30sCuánto espera pool.acquire() antes de dar error PoolTimedOut.
database_pool_idle_timeout10 minCierra las conexiones inactivas tras esta duración. Se defiende de los cortes por load-balancer / idle_in_transaction_session_timeout.
database_pool_max_lifetime30 minFuerza la rotación de conexiones para que las credenciales arrendadas por vault se refresquen.
prewarm_active_tenantsfalseCuando es true, Server::Builder::serve llama a prewarm_database_tenants() en el arranque.

Pre-calentar en el arranque

Dos formas de activarlo:

  1. Automática — establece prewarm_active_tenants = true en el TenantPoolsConfig que le pasas a TenantPools::new(...).config(...). Server::Builder::serve ejecuta el pre-calentamiento antes de hacer el bind.

  2. Verbo de CLI — cargo run -- prewarm-pools construye pools para cada tenant activo en modo database y termina. Útil como hook posterior al despliegue (p. ej., tras una rotación de credenciales), o para validar que cada tenant es accesible antes de conmutar un load-balancer.

El pre-calentamiento recorre Org::objects().where(active = true, storage_mode = "database") y hace corto-circuito cuando se alcanza el tope de la caché (reportado como skipped_cap en el [PrewarmReport]). Los fallos de construcción por tenant registran un tracing::warn! pero no abortan el bucle.

Tracing

tenant_pool_init es un tracing::info_span! que envuelve la construcción del pool en la ruta fría, y los eventos que contiene llevan el target rustango::tenancy::pools. Suscríbete para ver la latencia de construcción por tenant:

INFO rustango::tenancy::pools: tenant pool connected (database mode)
     slug=acme elapsed_ms=42 min_conn=1 max_conn=4

Actívalo con RUST_LOG=rustango::tenancy::pools=info. Un filtro sobre crate::tenancy::pools no casa nada — un target es una cadena, no una ruta; ver Logging.

Trampa de configuración — TLDs .local de macOS

Si accedes al admin del tenant vía http://acme.local:8080/admin/ en macOS y ves una pausa de 5 segundos en cada request: eso es Bonjour / mDNS, no Rustango. El resolver de macOS trata .local de forma especial y espera el timeout completo de mDNS antes de recurrir a /etc/hosts. Dos soluciones:

  1. Usa un TLD diferente: 127.0.0.1 acme.localhost funciona sin retraso. localhost está reservado (RFC 6761) y omite mDNS.
  2. Ejecuta dnsmasq con una zona .local que apunte a 127.0.0.1 para que el SO obtenga una respuesta inmediata.

Confírmalo con curl -w "%{time_connect}\n": si time_connect muestra ~5s pero cae a milisegundos con --resolve acme.local:8080:127.0.0.1, estás topándote con mDNS.


Véase también

Todos los verbos

Las secciones anteriores explican en detalle los verbos habituales. Esta tabla es la lista completa, tomada de los dos dispatchers (migrate/manage.rs y tenancy/manage/mod.rs) en vez de la prosa — así que un verbo que falte en la guía sigue siendo localizable aquí. Ejecuta <verb> --help para sus banderas; el texto de ayuda manda, esta página no.

Los verbos marcados con T requieren la característica tenancy y se alcanzan mediante Cli::tenancy().

Migraciones y esquema

VerboQué hace
makemigrations [name] / --empty <name>Genera una migración a partir del diff de modelos
migrate [target] / --dry-run / --squashAplica las migraciones pendientes
downgrade [N]Revierte las últimas N migraciones
showmigrations / statusLista las migraciones y su estado de aplicación
sqlmigrate <name>Imprime el SQL que ejecutaría una migración, sin ejecutarlo
forget-pending <name>Borra un JSON de migración no aplicada
add-data-op --sql <SQL> [--reverse-sql <SQL>]Añade una operación de datos escrita a mano
inspectdb [--schema <s>] [--table <t>]Lee un esquema existente y emite código #[derive(Model)]

Datos

VerboQué hace
dumpdataExporta filas como fixtures JSON
loaddata <fixture.json> [--fail-fast]Vuelve a cargar fixtures JSON
flush [--yes] [--app <label>] [--model <name>]Vacía todas las tablas de modelos; las banderas limitan el conjunto
prune [--model <name>] [--except <name>] [--pretend]Borrado masivo en streaming; --pretend informa sin borrar
db:dump / db:restore / db:infoDump / restauración / inspección nativos
dbshellEjecuta el cliente nativo (psql / mysql / sqlite3). Solo necesita DATABASE_URL, no un pool funcional — se gestiona antes de construir el pool, así que funciona cuando sqlx no puede conectar

Scaffolders y generadores

VerboQué hace
startapp <name>Crea un módulo de aplicación
make:viewset / make:serializer / make:formGenera un ViewSet, Serializer o Form
make:job / make:scheduled / make:worker / make:middleware / make:notification / make:testGenera un job de cola, una tarea de intervalo, un binario de worker, middleware, notificación o test
make:api_routes <app> [--tenant]Genera el módulo de rutas API de una app

Caché, sesiones y correo

VerboQué hace
createcachetable / create-cache-table [--table <name>]Crea la tabla de caché (y la de sesiones cuando las sesiones van a la BD)
clear-cache [--table <name>] / clearsessionsLa vacía; devuelve el número de filas borradas
sendtestemail --to <addr>Envía un correo de prueba fijo por el backend configurado

Introspección

VerboQué hace
showmodels [--format plain|json] [--app <label>]Cada modelo registrado, ordenado para una salida determinista
showurls [--format plain|json]Cada ruta con nombre, ordenada
check [--deploy]Comprobaciones de salud; --deploy añade las auditorías de producción
create-adminCrea una fila AdminUser para proyectos que usan admin::Builder::with_session_auth. No está condicionado a tenancy — recibe un &Pool simple y escribe rustango_admin_users, creando la tabla si falta. La única forma de conseguir un primer login de admin en un proyecto sin tenancy
about / version / --versionInformación de build y versión
docsAbre la documentación

Usuarios y acceso T

VerboQué hace
create-superuser / set-superuserCrea un superusuario, o promociona a un usuario existente
create-user / create-operatorCrea un usuario de tenant o un operador
reset-password / change-passwordRecuperación de contraseña de usuario de tenant
reset-operator-password / change-operator-passwordRecuperación de contraseña de operador
set-operator-activeActiva o desactiva un operador
create-role / assign-role / revoke-role / list-rolesRoles
grant-perm / revoke-permPermisos por codename
seed-permissions [--slug <s>]Siembra las filas de permisos por defecto
create-api-keyEmite una clave de API

Tenants T

VerboQué hace
create-tenant / edit-tenant / list-tenantsAprovisionar, editar, listar
drop-tenant / purge-tenantDesactivar (reversible) / destruir (no)
migrate-tenants / migrate-registryAplica migraciones a todos los tenants, o al registro
migrate-tenant-storage <slug> --to schema|databaseMueve un tenant entre modos de almacenamiento
add-host / remove-host / list-hosts / set-host-enabledEnrutado por host
test-tenant-connectionVerifica que la base de un tenant es alcanzable
prewarm-poolsAbre los pools de tenants antes de la primera petición
run-server / runserverEjecuta el servidor multi-tenant
init / init-tenancy / wizard / menu / actionsPuntos de entrada de configuración e interactivos

Audit T

VerboQué hace
audit-logLee el registro de auditoría
audit-cleanupLo recorta

MCP T

Documentado por completo en la guía MCP.

VerboQué hace
create-agent / list-agents / rotate-agent-secretAgentes
create-skill / list-skills / grant-skill / revoke-skillSkills
map-skill-permission / unmap-skill-permissionVincula un skill a un permiso
create-user-key / list-user-keys / revoke-user-keyCredenciales MCP por usuario
list-runs / show-runHistorial de ejecuciones