Rustango docs
← Anleitungen

manage-CLI-Referenz

Dies ist Rustangos Kommandozeilenwerkzeug, vergleichbar mit Laravels artisan oder Rails' rails-Befehl. In einem via cargo rustango new generierten Projekt führt ein einziges Binary jeden Befehl aus („Verb"):

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

Quelle: rustango::manage (Cli, der Verb-Dispatcher) — hinter dem manage-Feature (standardmäßig aktiviert).

Ausführbare Version: jedes Verb hier läuft in einem generierten Projekt; das Beispiel getting_started_blog wird von cargo run -- migrate und Konsorten angetrieben.

Neu bei einem Begriff hier? scaffold, migration, tenant — siehe das Glossar.

Der Befehls-Router liegt in rustango::manage::Cli; Ihre src/main.rs verdrahtet ihn so:

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

Multi-Tenant-Projekte fügen der Kette .tenancy() hinzu. Das schaltet den Router auf rustango::tenancy::manage um und schaltet die Multi-Tenant-Befehle frei.

Ältere Form — Projekte, die von manage startapp --with-manage-bin (oder vor v0.16) generiert wurden, liefern weiterhin src/bin/manage.rs. Diese verwenden cargo run --bin manage -- <verb>. Beide Formen akzeptieren dieselben Verben.

Jeder Befehl gibt auf stdout aus und beendet sich bei Validierungs- oder E/A-Fehlern mit einem Exit-Code ungleich null. Führen Sie cargo run -- --help (oder <verb> --help) für die eingebaute Nutzungshilfe aus.


Inhaltsverzeichnis


Migrationen

makemigrations [name]

Generiert eine Migrationsdatei aus Änderungen an Ihren Modellen. Es vergleicht Ihre registrierten Modelle mit dem letzten gespeicherten Schema-Snapshot in migrations/ und schreibt eine neue JSON-Datei mit allen Änderungen.

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

Automatisch erkannte Änderungen:

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

NICHT automatisch erkannt (Umbenennen vs. Löschen+Hinzufügen ist mehrdeutig):

  • RenameTable, RenameColumn — verwenden Sie --empty und bearbeiten Sie das JSON.

makemigrations --app <app>

Beschränkt die Migration auf eine einzelne App. Sie schreibt in das eigene Verzeichnis <project_root>/<app>/migrations/ dieser App und betrachtet nur Modelle, die zu dieser App gehören.

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

makemigrations --scope <registry|tenant>

Nur Multi-Tenant. Schreibt eine einzige Migration für nur die Modelle in einem Scope — jene, deren #[rustango(scope = "...")]-Attribut übereinstimmt. („Registry"-Tabellen werden von allen Tenants gemeinsam genutzt; „Tenant"-Tabellen leben pro Tenant.) Ohne dieses Flag teilt ein einfaches makemigrations in einem Tenancy-Projekt die Änderungen automatisch in ZWEI Dateien auf — eine für Registry-Modelle, eine für Tenant-Modelle — damit gemeinsame Framework-Tabellen (Org, Operator) nicht in die Per-Tenant-Migrationen durchsickern, die migrate-tenants ausführt.

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

Warum die Aufteilung wichtig ist: vor v0.24.2 bündelte ein einfaches makemigrations in einem Tenancy-Projekt Operationen auf rustango_operators (einer Registry-Tabelle) in eine Tenant-Migration. Als migrate-tenants diese Datei ausführte, wurde rustango_operators über den search_path auf die Registry-Kopie aufgelöst und kollidierte mit dem dort bereits vorhandenen Constraint.

makemigrations --empty <name>

Erstellt eine leere Migration (keine forward-Operationen), die Sie von Hand ausfüllen. Verwenden Sie sie, wenn Sie Datenoperationen oder Umbenennungsoperationen schreiben müssen, die der Auto-Detektor nicht generieren kann. Bearbeiten Sie das resultierende JSON selbst.

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

Repariert eine Migrationshistorie, die sich in zwei Zweige aufgeteilt hat (Issue #346). Das passiert, wenn zwei Personen jeweils makemigrations auf ihrem eigenen Feature-Branch ausführen, sodass beide neuen Dateien auf denselben Elternknoten zeigen. Nachdem beide Branches gemergt sind, hat die Historie zwei „Blätter" (Endpunkte), und das nächste makemigrations würde willkürlich eines davon als Elternknoten wählen.

--merge erkennt dies und schreibt eine leere NNNN_merge.json, deren Elternknoten auf das alphabetisch letzte Blatt zeigt und die Historie wieder zu einer einzigen Kette vereint. Ihr Schema-Snapshot spiegelt den kombinierten Zustand wider, gelesen aus der lebendigen Modell-Registry — die Modelle beider Branches sind zu diesem Zeitpunkt einkompiliert, sodass der Snapshot genau ist.

cargo run -- makemigrations --merge
# wrote migrations/0004_merge.json
#     merge node — empty `forward`, anchors the chain after divergent leaves
  • Bereits eine einzelne Kette → gibt no merge needed aus und beendet sich sauber. Sicher auf einer gesunden Historie auszuführen.
  • Wirklich getrennte Historien (keine Branch-Kollision) → bricht mit einem Fehler ab, statt einen Elternknoten zu erfinden.
  • Nicht kombinierbar mit --empty, --app, --scope oder einem positionalen Namen.

migrate

Wendet alle ausstehenden Migrationen der Reihe nach auf die Datenbank an — das Gegenstück zu Laravels php artisan migrate. Dies ist der Befehl, den Sie nach makemigrations ausführen, um Ihr Schema tatsächlich zu ändern.

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

Jede Datei läuft standardmäßig innerhalb einer Transaktion, sodass ein Fehler die gesamte Datei zurückrollt. Setzen Sie "atomic": false im JSON, um sich abzumelden — das brauchen Sie für Anweisungen wie CREATE INDEX CONCURRENTLY, die nicht in einer Transaktion laufen können.

Im Tenancy-Modus (Cli::tenancy()) ist migrate scope-bewusst: es wendet zuerst Registry-Migrationen auf die gemeinsame Registry-Datenbank an, dann Tenant-Migrationen über jeden aktiven Tenant. Für feinere Kontrolle verwenden Sie migrate-registry / migrate-tenants.

migrate <target>

Migriert zu einem bestimmten Punkt in der Historie, vorwärts oder rückwärts. Nennen Sie eine Migration, um zu ihr zu wechseln; das spezielle Ziel zero macht alles rückgängig.

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

Fasst jede ausstehende (nicht angewendete) Migration in einem frisch generierten Diff zusammen — der Dev-Iterations-Notausgang, wenn ein Stapel halbfertiger Migrationen leichter neu zu generieren als zu reparieren ist. Es weigert sich, an bereits Angewendetem etwas zu ändern.

cargo run -- migrate --squash

Die neu generierte Datei zeichnet die Namen, die sie zusammengefasst hat, in ihrer replaces-Liste auf. Das ist in dem Moment wichtig, in dem eine andere Datenbank beteiligt ist: der Checkout Ihres Kollegen, Staging oder CI hat möglicherweise bereits einige der Dateien angewendet, die Sie gerade gelöscht haben. Ohne replaces würde das CREATE TABLE der neuen Datei dort kollidieren; mit ihr versöhnt der Runner stattdessen (siehe unten).

Squash-Versöhnung

Ein Squash stellt den Endzustand der Migrationen wieder her, die er ersetzt, sodass das, was der Runner tun sollte, vollständig davon abhängt, was die Zieldatenbank bereits enthält. Er entscheidet automatisch:

Datenbankzustandwas passiert
frisch — keine Historie, keine Tabellender Squash läuft echt
jede ersetzte Migration steht im Ledgerverzeichnet, Vorgänger als erledigt markiert, kein DDL
Tabellen existieren, aber das Ledger hat keine Historieverzeichnet, kein DDL — das Ledger wird nachträglich an die vorhandenen Tabellen angeglichen
nur einige ersetzte Zeilen / Tabellen vorhandenverweigert, benennt, was fehlt

Der partielle Fall ist bewusst ein harter Fehler: keine automatische Wahl ist dort sicher, also stoppt der Runner und sagt Ihnen, was er gefunden hat, statt zu raten. Lösen Sie ihn mit migrate --fake (unten).

Migrationen, die von einem angewendeten Squash abgelöst wurden, werden als angewendet behandelt, sodass Sie die alten Dateien für ein oder zwei Releases auf der Platte lassen können — Deployments, die sie nie ausgeführt haben, migrieren trotzdem korrekt vorwärts.

Gewöhnliche (Nicht-Squash-)Migrationen bleiben unberührt: eine einfache Migration, deren Tabelle bereits existiert, scheitert weiterhin lautstark, denn das ist ein echter Konflikt und keine bekannt-äquivalente Historie.

migrate --fake <name>

Stempelt eine Migration als angewendet ohne ihr SQL auszuführen — der Betreiber-Notausgang, wenn die Datenbank bereits im Zielzustand ist, das Ledger es aber nicht weiß (eine außerhalb des Kanals eingerichtete DB, eine gelöschte Ledger-Tabelle, eine teilweise gelungene Migration, ein verweigerter partieller Squash). Wiederholen Sie das Flag, um mehrere Zeilen auf einmal zu reparieren.

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

Der Name wird zuerst gegen das Migrationsverzeichnis validiert, sodass ein Tippfehler keine unechte Zeile einschleusen kann. Das Stempeln ist idempotent.

--system zielt auf die eigene Migrationskette des Frameworks (system/migrations/, verzeichnet in __rustango_system_migrations__) statt auf die Ihres Projekts. --all-tenants fächert den Stempel über jeden aktiven Tenant auf, berichtet über jeden einzelnen und fährt über Fehler hinweg fort — die Tabellen des Frameworks leben pro Tenant, ihre Reparatur ist also eine Per-Tenant-Aufgabe.

downgrade [N]

Rollt die letzten N angewendeten Migrationen zurück (Standard 1) — Laravels migrate:rollback. Jede Migration muss reversibel sein: Schemaänderungen kehren sich automatisch um, aber Datenoperationen brauchen ein definiertes reverse_sql, sonst scheitert der Rollback.

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

showmigrations / status

Listet jede Migration und ob sie angewendet wurde. [X] bedeutet angewendet, [ ] bedeutet noch ausstehend.

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

Ausgabe:

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

Datenmigrationen

add-data-op

Fügt einer Migration einen Roh-SQL-Datenschritt hinzu, ohne das JSON von Hand zu bearbeiten. Greifen Sie dazu, wenn Sie vorhandene Zeilen transformieren müssen — eine Spalte nachfüllen, Daten bereinigen — als Teil einer Migration. Das Ergebnis ist eine Datenmigration, die rohes SQL ausführt — für Sie von der Kommandozeile aus generiert.

# 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
FlagErforderlichBeschreibung
--sql <SQL>jaVorwärts-SQL, das bei migrate läuft
--reverse-sql <SQL>neinRollback-SQL bei unapply; weglassen für irreversibel
--name <name>neinNamenssuffix der neuen Migration; Standard ist data_op
--to <migration>neinAn eine bestehende Migration anhängen, statt eine neue zu erstellen

Lassen Sie --reverse-sql weg, und der Schritt wird als reversible: false markiert — jeder Versuch, ihn zurückzurollen, scheitert sofort.


Projekt-/App-Scaffolder

cargo rustango new <name> (separates Binary)

Erstellt ein brandneues Rustango-Projekt — wie laravel new oder rails new. Dies ist ein separates Werkzeug, installieren Sie es also zuerst mit cargo install cargo-rustango. Wählen Sie aus drei Vorlagen:

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

Schreibt:

<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

Die config/-Ebene ist die Settings-Quelle: default.toml enthält, was alle Umgebungen teilen, und je eine <env>_settings.toml überschreibt sie, zur Laufzeit ausgewählt über RUSTANGO_ENV (Standard dev) — ein frisches cargo run läuft also ohne jede TOML-Änderung. Zusammen tragen sie das [database]-Pool-Tuning, [admin], [mcp] und die secure_cookies-Policy — und sie sind es, was das Settings-Audit von check --deploy liest. Die Inhalte pro Ebene stehen unter Scaffolding.

Die Tenant-Vorlage liefert einen leeren system/migrations/-Ordner. Die eigenen Tabellen des Frameworks (rustango_orgs, rustango_users, Rollen/Berechtigungen, …) werden beim ersten cargo run -- migrate aus den kompilierten Modellen dorthin generiert — es gibt kein handgeliefertes Bootstrap-JSON. Siehe migrate / migrate-registry.

startapp <name> [flags]

Erstellt eine neue App (ein Feature-Modul) unter src/<name>/. Verwenden Sie es, um Modelle, Views und URLs für einen Teil Ihres Projekts gruppiert zu halten.

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

Erstellt:

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

Sicher erneut ausführbar — bestehende Dateien bleiben unberührt. Ein manueller Schritt: fügen Sie pub mod <name>; zu src/lib.rs hinzu, damit Rust das neue Modul kompiliert.


Dateigeneratoren (make:*)

Diese erstellen Startdateien für gängige Bausteine — sehr ähnlich zu Laravels make:*-Befehlen (make:controller, make:model, …). Jeder Generator schreibt nach src/<snake_name>.rs (oder tests/<snake_name>.rs für make:test) und:

  • Prüft, ob der Name gültig ist (PascalCase, Buchstaben/Ziffern/Unterstrich).
  • Wandelt ihn für den Dateinamen in snake_case um (PostViewSet → post_view_set.rs).
  • Überschreibt keine bestehende Datei.
  • Erinnert Sie daran, pub mod X; zu Ihrer lib.rs hinzuzufügen.

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

Generiert eine vollständige CRUD-REST-Ressource für ein Modell.

Zwei Vorlagen, für Sie ausgewählt. Das #[derive(ViewSet)] mit einem Pool fängt einen einzigen Pool beim Einbinden ein, was für ein Tenancy-Projekt falsch ist — der Generator wählt daher zwischen zwei Formen. Auflösungsreihenfolge: --no-tenant gewinnt, dann --tenant, dann die Auto-Erkennung — tenancy in der Feature-Liste der rustango-Abhängigkeit in Cargo.toml — und sonst die Pool-Vorlage. Wenn die Auto-Erkennung den Tenant-Modus wählt, sagt sie das auf stdout und nennt --no-tenant als Override.

cargo run -- make:viewset PostViewSet --model Post
cargo run -- make:viewset PostViewSet --model Post --no-tenant   # Pool-Form erzwingen

Pool-Vorlage — generierte src/post_view_set.rs:

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

Einbinden mit: .merge(PostViewSet::router("/api/posts", pool.clone())).

Tenant-Vorlage — kein Derive. Sie erzeugt ein pub fn router() -> Router<()>, gebaut aus ViewSet::for_model(Post::SCHEMA).tenant_router("/api/posts"), mit der ganzen Builder-Kette (fields / filter_fields / search_fields / ordering / page_size / permissions_for_model / read_only) als auskommentierte Zeilen vorgestubbt. Die Verbindung wird pro Anfrage über den Tenant-Extractor aufgelöst statt einmalig beim Einbinden eingefangen — ein router() bedient also jeden Tenant.

Einbinden mit: .merge(crate::viewsets::post::router()) — nicht mit der PostViewSet::router(path, pool)-Zeile oben.

--crate <path> benennt die Framework-Crate in den generierten use-Zeilen um, für Projekte, die rustango unter einem anderen Namen importieren.

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

Generiert eine #[derive(Serializer)]-Struktur — steuert, wie ein Modell nach und von JSON konvertiert wird.

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

make:form <Name>

Generiert eine #[derive(Form)]-Struktur zum Validieren und Verarbeiten von Formular-Eingaben.

cargo run -- make:form ContactForm

make:job <Name>

Generiert einen jobs::Job — eine Payload-Struktur plus die Trait-Implementierung mit NAME, MAX_ATTEMPTS und async fn run(&self). Das ist Arbeit, die Sie aus einem Handler in die Queue stellen und ein Worker später ausführt.

run erhält nur die Payload: keinen Pool, keinen Tenant, keinen Request-Kontext. Tragen Sie alles Nötige in den Feldern.

cargo run -- make:job EmailDigestJob

make:scheduled <Name>

Generiert eine Aufgabe mit festem Intervall für scheduler::Scheduler — die Form, die make:job emittierte, bevor dieser einen echten Job erzeugte.

cargo run -- make:scheduled NightlySweep

make:worker <Name>

Generiert ein eigenständiges Worker-Binary für src/bin/ — ein Prozess, der die Job-Queue abarbeitet und kein HTTP bedient. Betreiben Sie ihn neben dem Web-Prozess oder als eigenen Container.

Die Form ist kurz und lässt sich so falsch schreiben, dass es erst in Produktion auffällt: Ein Worker, der auf tokio::signal::ctrl_c() wartet, behandelt SIGINT, aber nicht SIGTERM — und genau das senden docker stop, Kubernetes und systemd. Das Abarbeiten läuft dann nie, der Container wird nach seiner Schonfrist getötet, und laufende Jobs gehen verloren, ohne dass etwas protokolliert wird. Der generierte Worker wartet auf shutdown::shutdown_signal(), das beide Signale annimmt.

cargo run -- make:worker JobsWorker

make:notification <Name>

Generiert eine Notification-Struktur, die eine E-Mail aufbaut — wie Laravels make:notification.

cargo run -- make:notification WelcomeEmail

make:middleware <Name>

Generiert eine Middleware-Funktion — Code, der vor und nach jedem Request läuft (Auth-Prüfungen, Logging und so weiter). „axum" ist das Web-Framework, auf dem Rustango aufbaut, sodass der Stub der Middleware-Form von axum entspricht.

cargo run -- make:middleware AuditLog

make:test <Name>

Generiert einen Integrationstest in tests/, der TestClient verwendet, um Requests gegen Ihre App zu stellen.

cargo run -- make:test post_smoke

Datenbank-Werkzeuge

db:info

Zeigt, mit welcher Datenbank dieser Build zu sprechen konfiguriert ist, ohne sich zu verbinden. Es gibt die Framework-Version aus, welche Datenbanktreiber (postgres/mysql Cargo-Features) einkompiliert sind, die Verbindungs-URL mit verstecktem Passwort und das erkannte Backend. Da es nie eine Verbindung öffnet, ist es praktisch in CI oder Containern, in denen die Datenbank noch nicht läuft, Sie aber bestätigen wollen, dass die Einstellungen stimmen.

cargo run -- db:info

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

Sichert Ihre Datenbank, indem pg_dump gegen DATABASE_URL ausgeführt wird — wie php artisan db:dump. Standardmäßig geht das SQL nach stdout (damit Sie es pipen können); übergeben Sie --out <path> (-o), um stattdessen eine Datei zu schreiben. --data-only und --schema-only bilden direkt auf die Flags von pg_dump ab, und --no-owner lässt die OWNER-Zeilen weg. Sie brauchen pg_dump installiert und in Ihrem PATH.

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

Die Statuszeile running: pg_dump … geht auf stderr und bleibt damit aus der Umleitung und aus einer Pipe heraus. Bis #1404 ging sie auf stdout und landete so in der ersten Zeile der .sql-Datei.

db:restore <path> [--clean]

Lädt eine Dump-Datei zurück in Ihre Datenbank — das Gegenstück zu db:dump. Es lässt die Datei durch psql gegen DATABASE_URL mit ON_ERROR_STOP=1 laufen, sodass es beim ersten Fehler stoppt. Fügen Sie --clean hinzu, um zuerst das bestehende Schema zu löschen (es stellt DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public; voran), damit die Wiederherstellung auf einer leeren Datenbank landet. Sie brauchen psql in Ihrem PATH.

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

Systembefehle

version / --version

Gibt die Version des Rustango-Frameworks aus.

$ cargo run -- version
rustango 0.57.11

about

Gibt eine Momentaufnahme Ihrer Umgebung aus: Framework-Version, registrierte Modelle und Apps, ob die Datenbank erreichbar ist, und wichtige Umgebungsvariablen. Legen Sie dies in Support-Tickets, wenn etwas nicht stimmt.

$ 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]

Führt Gesundheitsprüfungen an Ihrem Projekt durch. Fügen Sie --deploy für die strengeren Produktionsreife-Prüfungen hinzu.

Immer aktive Prüfungen:

  • ≥ 1 Modell via inventory registriert
  • DB erreichbar (SELECT 1)
  • Modelle registriert, aber keine Migrationen auf der Platte (es vergleicht keine Zahlen — ein vorhandenes migrations/-Verzeichnis wird unabhängig von der Anzahl als Info gemeldet)

Mit --deploy:

  • RUSTANGO_ENV ist prod oder production
  • RUSTANGO_SESSION_SECRET gesetzt und ≥ 32 Byte (der HMAC-Schlüssel für Cookies + JWTs; SECRET_KEY wird vom Framework nie gelesen)
  • DATABASE_URL gesetzt
  • RUSTANGO_APEX_DOMAIN gesetzt — die Warnung erscheint bei jedem Projekt, wenn unset oder localhost, und sagt das auch; Single-Tenant-Projekte können sie ignorieren
  • DATABASE_URL zeigt auf localhost / 127.0.0.1 (Warnung — in Produktion meist ein Managed-Hostname)
  • RUSTANGO_BIND beginnt mit 127.0.0.1 (Warnung — nur Loopback nimmt keinen externen Traffic an)
  • Ein Settings-Tier-Audit über dein TOML, das Dev-Defaults in einer Prod-Stufe meldet (braucht das Feature config)
  • Meta.required_db_vendor / required_db_features jedes registrierten Models, geprüft gegen den tatsächlich verbundenen Dialekt
$ 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

Beendet sich mit Exit-Code ungleich null, wenn eine Prüfung auf Fehler-Ebene scheitert. Warnungen allein verursachen kein Scheitern.

docs

Öffnet die Rustango-Dokumentation (https://docs.rs/rustango) in Ihrem Browser. Es gibt immer auch die URL aus, sodass es auch auf einem Headless-Server funktioniert.

cargo run -- docs

--help / help

Listet jeden Befehl mit einer einzeiligen Beschreibung. Im Tenancy-Modus werden auch die unten aufgeführten Multi-Tenant-Befehle hinzugefügt.


Tenancy-Befehle

Diese Befehle existieren nur in Multi-Tenant-Projekten (eine App, die viele isolierte Kunden/Orgs bedient). Sie erscheinen nur, wenn das Projekt mit features = ["tenancy"] gebaut wird UND Cli::new() mit .tenancy() verkettet ist.

Alles, was die Operator-Konsole kann, können diese Befehle auch — damit eine Aktion in einem Deploy-Hook, in einem Cron-Job oder auf einer Maschine laufen kann, auf der niemand einen Browser öffnen kann.

menu / actions

Es gibt über vierzig Tenancy-Verben. --help sagt Ihnen, dass sie existieren; das Menü hilft Ihnen, eines auszuführen, das Sie noch nie ausgeführt haben.

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

Wählen Sie eine Nummer (oder tippen Sie das Verb) und es fragt nur nach dem, wonach das Verb nicht selbst fragt, gibt die Kommandozeile aus, die es gleich ausführt, führt sie aus und kommt für die nächste Aktion zurück. Es läuft durch denselben Dispatcher wie die Flags und kann daher nicht von ihnen abweichen.

Verwandt, aber anders: wizard ist eine einmalige Einrichtung; menu ist die ständige Liste dessen, was Sie danach tun können.

wizard / init

Führt ein frisches Projekt von „gerade generiert" zu einem funktionierenden Tenant, einem Operator und einem Tenant-Superuser. Jeder Schritt ist optional — drücken Sie n, um einen zu überspringen.

cargo run -- wizard

init-tenancy

No-op — aus Kompatibilitätsgründen beibehalten. Das Framework liefert keine handgebauten Bootstrap-Migrationen mehr. Seine eigenen Tabellen (rustango_orgs, rustango_operators, rustango_users, Rollen/Berechtigungen, …) werden aus den kompilierten Modellen in system/migrations/ generiert — der normale Ablauf (Modelle → makemigrations → migrate) — und von migrate / migrate-registry angewendet, die sie bei Bedarf generieren, falls die Dateien fehlen.

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

Ältere Versionen schrieben hier 0001_rustango_*_initial.json; dieser hartkodierte Fluss ist verschwunden. Zum Bereitstellen führen Sie einfach cargo run -- migrate aus. Ein eigenes Benutzermodell (.user_model::<AppUser>()) fließt durch dieselben generierten system/migrations/ — siehe Eigenes Benutzermodell.

migrate-registry

Wendet nur die Registry-Migrationen an — die gemeinsamen, tenant-übergreifenden Tabellen. Die Registry hält rustango_orgs und rustango_operators sowie alle registry-scoped Tabellen, die Sie definieren. Tenant-Tabellen bleiben unberührt.

cargo run -- migrate-registry

migrate-tenants

Wendet Tenant-Migrationen auf jeden aktiven Tenant nacheinander an. Jeder Tenant verwendet seine eigene Verbindung (sein eigenes Schema oder seine eigene Datenbank), und wenn ein Tenant scheitert, laufen die übrigen trotzdem weiter — der Befehl berichtet am Ende das Ergebnis pro Tenant.

cargo run -- migrate-tenants

Für den häufigen Fall macht ein einfaches migrate bereits zuerst die Registry, dann die Tenants — greifen Sie zu migrate-tenants nur, wenn Sie diesen Schritt für sich allein brauchen.

runserver / run-server

Startet den Multi-Tenant-Webserver. In einem Tenancy-Projekt ist dies dasselbe wie ein nacktes cargo run; die benannte Form existiert, damit eigene Binaries, die ihre eigenen Argumente parsen, es trotzdem auslösen können.

cargo run                        # implicit
cargo run -- runserver           # explicit

create-tenant <slug> [options]

Richtet einen neuen Tenant (Kunde/Org) ein und wendet die Tenant-Migrationen darauf an. Der <slug> ist sein kurzer Bezeichner. Sicher erneut ausführbar — ein erneuter Aufruf auf einem bestehenden Slug wird vorab mit tenant slug `<slug>` already exists abgelehnt (tenancy/provision.rs:599), bevor sonst etwas passiert.

cargo run -- create-tenant acme --display-name "ACME Corp"
cargo run -- create-tenant beta --mode database --database-url postgres://...
FlagBeschreibung
--display-name <name>Menschenlesbares Label, das in Admin-Seitenleisten angezeigt wird
--mode schema | databaseSpeichermodus (Standard: schema)
--database-url <url>Tenant-spezifische DB-URL (erforderlich für den database-Modus)
--host-pattern <pattern>Überschreibt das vom SubdomainResolver verwendete Host-Muster
--no-migrateÜberspringt das Anwenden tenant-scoped Migrationen nach dem Provisioning
--backend postgres | mysql | sqliteTreiber für einen Database-Mode-Tenant (Default: postgres). Wird gegen --mode validiert
--schema-name <s>Überschreibt den generierten Schemanamen im Schema-Modus
--port <n>Port, unter dem der Tenant erreichbar ist, fürs Routing
--path-prefix <s>Pfad-Präfix, unter dem der Tenant erreichbar ist, fürs Routing

edit-tenant <slug> [options]

Ändert Routing- und Anzeige-Konfiguration eines Tenants — dieselben Felder, die die Bearbeitungsseite der Operator-Konsole anbietet.

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
FlagBeschreibung
--display-name <name>Menschenlesbares Label
--host-pattern <host>Nackter Hostname, unter dem der Tenant antwortet
--path-prefix <path>Ein Segment mit führendem Slash, z. B. /acme
--port <n>Port, auf dem der Tenant gematcht wird
--database-url <url>Rotiert die Verbindungs-URL des Tenants
--activate / --deactivateTenant in oder außer Betrieb nehmen
--clear <field>Leert host-pattern, path-prefix oder port

Nur die Felder, die Sie nennen, werden angefasst. „In Ruhe lassen" und „leeren" sind unterschiedliche Anweisungen — dafür ist --clear da. Es vermeidet die Abhängigkeit von --host-pattern "", das manche Shells und CI-Runner verschlucken.

Werte werden so validiert, wie create-tenant sie validiert: ein Host-Muster mit Port oder ein Pfad-Präfix, das der Resolver nie erzeugen könnte, wird abgelehnt statt gespeichert, um dann stillschweigend nie zu matchen.

Ein Rotieren von --database-url verwirft den zwischengespeicherten Pool des Tenants, sodass die nächste Anfrage mit der neuen Zugangsinformation verbindet; andere Änderungen lassen warme Verbindungen unangetastet.

test-tenant-connection <url> [flags]

Prüft eine Datenbank-URL, bevor Sie sich darauf festlegen — verbindet und schreibt standardmäßig testweise mit Rollback, sodass eine Nur-Lese-Zugangsinformation hier auffällt statt bei der ersten Tenant-Anfrage.

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>]

Deaktiviert einen Tenant, indem active = false gesetzt wird. Dies ist die weiche, umkehrbare Option — die Daten des Tenants bleiben auf der Platte, und reaktiviere ihn mit edit-tenant <slug> --activate. Ein erneutes create-tenant funktioniert nicht: die Org-Zeile existiert weiterhin, der Slug gilt also als Duplikat. Wenn Sie nicht interaktiv laufen (kein Terminal angehängt), müssen Sie --confirm <slug> mit dem erneut getippten Slug zur Bestätigung übergeben.

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

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

Löscht einen Tenant dauerhaft. Es löscht das Schema des Tenants und entfernt seine Zeile aus rustango_orgs, ohne Rückgängig-Machen. Wenn Sie nicht interaktiv laufen (kein Terminal angehängt), müssen Sie --confirm <slug> mit dem erneut getippten Slug übergeben. Bei Tenants im database-Modus bleibt die zugrunde liegende Datenbank an Ort und Stelle, es sei denn, Sie übergeben zusätzlich --purge-database. Ohne dieses Flag verweigert der Befehl bei Database-Mode-Tenants komplett — er entfernt nicht die Org-Zeile und lässt die Datenbank stehen, er tut gar nichts (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

Listet jeden Tenant mit seinem Speichermodus und aktiv/inaktiv-Status.

cargo run -- list-tenants

Hostnamen

Ein Tenant ist unter seiner Subdomain erreichbar und optional unter weiteren Hostnamen, die Sie an ihn binden. Ein Hostname führt zu genau einem Tenant.

cargo run -- list-hosts acme
cargo run -- add-host acme shop.example.com
cargo run -- set-host-enabled acme shop.example.com --off   # stilllegen
cargo run -- remove-host acme shop.example.com
VerbWas es tut
list-hosts <slug>Alle Hostnamen des Tenants, Basis-Host zuerst
add-host <slug> <hostname>Bindet einen. Abgelehnt, wenn ein anderer Tenant ihn bereits beansprucht
remove-host <slug> <hostname>Löst die Bindung
set-host-enabled <slug> <hostname> --on|--offAusliefern oder stilllegen

Stilllegen (--off) behält die Zeile und nimmt den Host aus dem Betrieb — nützlich, während DNS propagiert, oder beim Ausmustern einer Domain, die Sie vielleicht zurückhaben wollen.

Hostnamen werden beim Eingang normalisiert (kleingeschrieben, ohne Schema, ohne Port, ohne Pfad), denn der gespeicherte Wert wird Byte für Byte gegen den Host-Header verglichen. Die Verben geben aus, was gespeichert wurde, nicht, was Sie getippt haben.

Der Basis-Host stammt aus dem host_pattern des Tenants und hat keine eigene Zeile, kann hier also nicht entfernt werden — ändern Sie ihn mit edit-tenant --host-pattern. Ein rename-host gibt es bewusst nicht: entfernen und neu hinzufügen, damit die Änderung auch andere laufende Pods erreicht.

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

Erstellt einen Operator — einen globalen Admin, der jeden Tenant von einer tenant-übergreifenden Konsole aus verwalten kann. Operatoren leben in der gemeinsamen Registry, nicht innerhalb eines einzelnen Tenants.

cargo run -- create-operator admin --password letmein
cargo run -- create-operator admin --generate     # stattdessen ein zufälliges ausgeben

Lassen Sie --password auf einem Terminal weg, wird ohne Echo gefragt.

list-operators

Jeder Operator, mit aktiv-Status und Erstellungsdatum, plus eine Zählung, wie viele noch aktiv sind.

cargo run -- list-operators

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

Schaltet den Zugang eines Operators ab oder wieder an. Die Zeile bleibt in beiden Fällen erhalten, sodass sich „wer war das?" später noch auflösen lässt — und die Konsole liest sie bei jeder Anfrage neu, sodass eine Deaktivierung beim nächsten Klick greift und nicht erst, wenn das Cookie abläuft.

cargo run -- set-operator-active grace --off   # Offboarding
cargo run -- set-operator-active grace --on

Zwei Dinge lehnt es ab, aus demselben Grund wie die Konsole:

  • Den letzten aktiven Operator deaktivieren. Das sperrt alle aus der Konsole aus, und nur eine Shell auf der Registry könnte es rückgängig machen.
  • Sich selbst deaktivieren, wenn die Anfrage aus der Konsole kommt. Die CLI hat keine Session, aus der sie sich aussperren könnte, dort gilt also nur die erste Regel.

Die Richtung ist erforderlich — Raten würde entweder Zugang entziehen oder gewähren, und beides ist falsch, wenn es stillschweigend geschieht. Den bereits vorhandenen Zustand zu setzen wird gemeldet und ist erfolgreich, damit ein erneut laufendes Provisioning-Skript nicht rot wird.

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

Erstellt einen Benutzer innerhalb eines Tenants — mit --superuser auch einen Administrator, aber immer auf einen einzelnen Tenant beschränkt.

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

--superuser setzt is_superuser = true für diesen Benutzer innerhalb des Tenants. Das macht ihn zu einem Admin des Tenants (voller Schreibzugriff im Tenant-Admin), gewährt aber nie Zugang zur tenant-übergreifenden Operator-Konsole.

create-role <tenant> <name>

Erstellt eine Rolle (ein benanntes Bündel von Berechtigungen) innerhalb eines Tenants.

cargo run -- create-role acme editor

list-roles <tenant>

Listet die in einem bestimmten Tenant definierten Rollen.

cargo run -- list-roles acme

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

Gibt einem Benutzer eine der Rollen des Tenants.

cargo run -- assign-role acme alice editor

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

Entfernt eine Rolle von einem Benutzer — die Umkehrung von assign-role.

cargo run -- revoke-role acme alice editor

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

Gewährt eine einzelne Berechtigung. Standardmäßig ist das zweite Argument ein Benutzername, sodass die Berechtigung direkt an diesen Benutzer geht; fügen Sie --role hinzu, um sie stattdessen einer Rolle zu gewähren. Berechtigungs-Codenames verwenden das Format <app>.<action>_<model> (blog.add_post, blog.change_post, …). Das Feature auto_create_permissions erstellt die vier standardmäßigen CRUD-Codenames automatisch für jedes Modell, das mit #[rustango(permissions)] markiert ist.

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]

Entfernt eine Berechtigung — die Umkehrung von grant-perm. Zielt standardmäßig auf einen Benutzer; fügen Sie --role hinzu, um sie stattdessen von einer Rolle zu entziehen.

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>]

Stellt einen API-Schlüssel für einen Tenant-Benutzer aus. Das vollständige Token wird einmal ausgegeben und nie wieder — kopieren Sie es jetzt, denn nur sein Präfix und ein Hash werden gespeichert.

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

audit-cleanup

Beschneidet alte Einträge aus dem Audit-Log (rustango_audit_log), damit es nicht ewig wächst. Kürzen Sie nach Alter (--days) oder nach Anzahl (--keep-last), und optional auf einen Tenant beschränkt.

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            # nur das Log der Registry

Standardmäßig räumt es das eigene Log der Registry und das jedes aktiven Tenants auf. Im Registry-Log hält die Operator-Konsole fest, was Operatoren getan haben — es wächst also mit der Nutzung der Konsole. Nennen Sie einen Tenant (--tenant), fragen Sie nach diesem Tenant und lassen die Registry in Ruhe. Ein defekter Tenant wird gemeldet und gezählt, statt den Durchlauf zu beenden.

Nachsehen, was passiert ist

Drei Nur-Lese-Verben, die ausgeben, was die Konsole rendert — die Antworten, die Sie während eines Vorfalls brauchen, ohne Browser und ohne SQL-Client.

cargo run -- list-runs                        # letzte Provisioning- und Migrationsläufe
cargo run -- list-runs --kind migrate --limit 50
cargo run -- show-run 42                      # die Schritte eines Laufs
cargo run -- audit-log                        # wer was wann geändert hat
cargo run -- audit-log --pk acme --limit 100
VerbFlags
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 zeigt Neuestes zuerst, und seine Filter laufen in der Abfrage — die Suche nach einem migrate-Lauf findet also einen, selbst wenn die jüngsten Läufe alle Provisionings sind. show-run gibt den Kopf des Laufs und jeden aufgezeichneten Schritt aus, und das sagt Ihnen, wo ein Fehler auftrat.

Über die CLI angelegte Tenants werden ebenfalls aufgezeichnet, markiert mit requested_by = cli, sodass die Historie beide Oberflächen abdeckt.

prewarm-pools

Öffnet im Voraus eine Verbindung für jeden aktiven Tenant im database-Modus. Lohnt sich nach einem Deploy, einem Registry-Neustart oder einer Rotation von Zugangsdaten: es verwandelt „die erste Anfrage an jeden Tenant zahlt den Verbindungsaufbau" in eine einzige bewusste Wartezeit und bringt einen nicht erreichbaren Tenant ans Licht, bevor ein Benutzer ihn findet.

cargo run -- prewarm-pools

Die Operator-Konsole hat dafür ebenfalls einen Button, auf der Tenant-Liste.


Eigenes Benutzermodell (zusätzliche Spalten auf rustango_users)

So fügen Sie der Benutzertabelle Ihre eigenen Felder hinzu. Der eingebaute Tenant-User hat sieben feste Spalten: id, username, password_hash, is_superuser, active, created_at, plus eine data-JSONB-Spalte (ein flexibler JSON-Blob) für beliebige zusätzliche Metadaten pro Benutzer. Für die meisten Apps ist diese JSONB-Spalte alles, was Sie brauchen — keine Migration, kein Override, keine Überraschungen.

Wenn Sie stattdessen typisierte, indexierbare Spalten auf rustango_users wollen, gibt es zwei Ansätze. Sie sind nicht austauschbar; wählen Sie den, der dazu passt, wo Ihr Projekt in seinem Leben steht.

Option 1 — Geschwister-Profilmodell mit FK (funktioniert bei jedem Projekt)

Am besten, wenn das Projekt bereits existiert, oder wenn Sie die User-Tabelle des Frameworks lieber als einzige Quelle der Wahrheit belassen möchten.

#[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,
}

Führen Sie cargo run -- makemigrations dann cargo run -- migrate aus, und Sie haben eine typisierte Extra-Tabelle, die per Fremdschlüssel mit dem Benutzer verknüpft ist. Lesen Sie sie mit dem ORM:

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

Kompromiss: eine zusätzliche Zeile und ein JOIN bei jedem Zugriff. Vorteil: null Risiko, die Framework-Auth zu brechen.

Option 2 — Cli::user_model::<AppUser>() (nur auf der grünen Wiese)

Verwenden Sie dies nur auf einem frischen Projekt, bei dem Sie die zusätzlichen Felder direkt auf der rustango_users-Tabelle selbst wollen. Da AppUser das rustango_users-Modell ist, fließen seine Spalten durch die gewöhnliche makemigrations → migrate-Engine: die Tabellen des Frameworks werden in system/migrations/ generiert, sodass die Spalten von AppUser im generierten CREATE TABLE rustango_users landen.

Schritt 1. Definieren Sie Ihr Modell. Es muss jede vom Framework geforderte Spalte exakt deklarieren (id, username, password_hash, is_superuser, active, created_at, data), plus Ihre Extras. Jede zusätzliche Spalte muss entweder NULL erlauben oder einen default = "…" haben.

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 {}

Schritt 2. Verdrahten Sie den Override in 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
}

Schritt 3. Registrieren Sie AppUser anstelle des Framework-User — nur ein Modell darf table = "rustango_users" beanspruchen. Der Scaffolder liefert kein statisches Bootstrap-JSON (nur ein leeres system/migrations/), es gibt also nichts zu löschen; registrieren Sie einfach nicht auch den Framework-User.

Schritt 4. Generieren + anwenden:

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

Vorbehalte:

  • AppUser später zu ändern ist eine normale Schemaänderung: führen Sie makemigrations erneut aus, um die AddColumn-Migration zu emittieren, dann migrate.
  • Nur ein Modell darf auf rustango_users abbilden. Das Registrieren beider, des Framework-User und Ihres AppUser, macht makemigrations mehrdeutig — registrieren Sie AppUser allein. Das ist der Hauptgrund, warum Option 2 nur für frische Projekte ist; bei einem bestehenden Projekt vermeidet Option 1 das Problem.
  • Framework-Auth- und Admin-Code liest die sieben Kernspalten namentlich; Ihre zusätzlichen Spalten sind nur über AppUser::objects().fetch(...) erreichbar.

Builder::user_model::<AppUser>() macht dasselbe für Code, der den Server-Builder direkt baut, ohne über Cli zu gehen.


Eigene Unterbefehle

Sie können Ihre eigenen Befehle hinzufügen und sie neben den eingebauten Verben ausführen. Der Trick besteht darin, die Argumente selbst zu inspizieren und Ihren Befehl zu behandeln, bevor Sie den Rest an Cli::run weitergeben. Zwei Wege, es zu tun:

Inline in src/main.rs (kein zusätzliches Binary):

#[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
}

Via --with-manage-bin (separates src/bin/manage.rs):

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

Dann in 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),
    }
}

Führen Sie Ihre eigenen Befehle genau wie die eingebauten aus: cargo run -- import-csv path/to/file.csv (oder cargo run --bin manage -- import-csv … bei Verwendung von --with-manage-bin).


Häufige Arbeitsabläufe

Erstmalige Projekteinrichtung (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

Erstmalige Projekteinrichtung (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

Tenants hinzufügen, nachdem die App bereits läuft

Eine echte Tenancy-App baut in der Regel lange vor der Anmeldung ihres ersten Tenants Modelle und Migrationen auf. Dieser Ablauf funktioniert zu jedem Zeitpunkt im Leben des Projekts:

# 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

Warum das sicher ist:

  • #[rustango(scope = "registry")] auf Org/Operator hält Änderungen an gemeinsamen Tabellen aus den Per-Tenant-Migrationen heraus.
  • migrate-tenants besucht jeden aktiven Tenant und wendet nur die Tenant-Migrationen an — Registry-Dateien werden übersprungen.
  • create-tenant führt denselben migrate-tenants-Durchlauf gegen das Schema des neuen Tenants aus, sodass er vollständig auf dem neuesten Stand startet, ohne manuelle Nachbesserung.

Ein Modell hinzufügen

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

Eine JSON-API für dieses Modell hinzufügen

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

Ein Daten-Backfill hinzufügen

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

Pre-Deploy-Audit

cargo run --release -- check --deploy

Die letzte Migration zurückrollen

cargo run -- downgrade 1

Eine Tenancy-Migration auf einen bestimmten Scope anwenden

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

Einen Tenant außer Betrieb nehmen

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

Tenant-Pool-Feinabstimmung (v0.27.7+)

Tenants im database-Modus erhalten ihren eigenen Verbindungspool (einen PgPool — eine Menge wiederverwendeter Datenbankverbindungen), zwischengespeichert nach Slug in TenantPools. Standardmäßig wird ein Pool faul, beim ersten Request des Tenants gebaut, es sei denn, Sie schalten das Pre-Warming ein. Die Einstellungen leben auf TenantPoolsConfig:

FeldStandardZweck
max_cached_database_pools64Obergrenze für den Pool-Cache. Ist er voll, scheitert der nächste nicht-gecachte Tenant (keine stille Verdrängung).
database_pool_max_connections16max_connections pro Pool. Halten Sie es klein, damit ein Tenant-Fan-out PGs max_connections nicht erschöpft.
database_pool_min_connections0Hält jederzeit N Verbindungen warm. ≥1 senkt die Latenz des ersten Requests, indem der TCP/TLS/Auth-Roundtrip beim Boot bezahlt wird.
database_pool_acquire_timeout30sWie lange pool.acquire() wartet, bevor es mit PoolTimedOut scheitert.
database_pool_idle_timeout10 minSchließt untätige Verbindungen nach dieser Dauer. Wehrt Kappungen durch Load-Balancer / idle_in_transaction_session_timeout ab.
database_pool_max_lifetime30 minErzwingt das Rotieren von Verbindungen, damit vault-geleaste Anmeldedaten aufgefrischt werden.
prewarm_active_tenantsfalseWenn true, ruft Server::Builder::serve beim Boot prewarm_database_tenants() auf.

Beim Boot vorwärmen

Zwei Wege zum Auslösen:

  1. Automatisch — setzen Sie prewarm_active_tenants = true auf der TenantPoolsConfig, die Sie TenantPools::new(...).config(...) übergeben. Server::Builder::serve führt das Pre-Warming vor dem Binden aus.

  2. CLI-Verb — cargo run -- prewarm-pools baut Pools für jeden aktiven Tenant im database-Modus und beendet sich. Nützlich als Post-Deploy-Hook (z. B. nach einer Anmeldedaten-Rotation) oder um zu validieren, dass jeder Tenant erreichbar ist, bevor ein Load-Balancer umgeschaltet wird.

Das Pre-Warming durchläuft Org::objects().where(active = true, storage_mode = "database") und bricht kurz, wenn die Cache-Obergrenze erreicht ist (gemeldet als skipped_cap im [PrewarmReport]). Per-Tenant-Build-Fehler loggen ein tracing::warn!, brechen die Schleife aber nicht ab.

Tracing

tenant_pool_init ist ein tracing::info_span!, der den Cold-Path-Pool-Build umschließt; die Events darin tragen das Target rustango::tenancy::pools. Abonnieren Sie es, um die Per-Tenant-Build-Latenz zu sehen:

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

Einschalten mit RUST_LOG=rustango::tenancy::pools=info. Ein Filter auf crate::tenancy::pools passt auf nichts — ein Target ist ein String und kein Pfad, siehe Logging.

Einrichtungs-Falle — macOS .local-TLDs

Wenn Sie den Tenant-Admin über http://acme.local:8080/admin/ auf macOS erreichen und bei jedem Request eine 5-Sekunden-Pause sehen: das ist Bonjour / mDNS, nicht Rustango. Der Resolver von macOS behandelt .local speziell und wartet den vollen mDNS-Timeout ab, bevor er auf /etc/hosts zurückfällt. Zwei Lösungen:

  1. Verwenden Sie eine andere TLD: 127.0.0.1 acme.localhost funktioniert ohne Verzögerung. localhost ist reserviert (RFC 6761) und überspringt mDNS.
  2. Betreiben Sie dnsmasq mit einer .local-Zone, die auf 127.0.0.1 zeigt, damit das OS eine sofortige Antwort erhält.

Bestätigen Sie mit curl -w "%{time_connect}\n": wenn time_connect ~5s zeigt, aber mit --resolve acme.local:8080:127.0.0.1 auf Millisekunden fällt, treffen Sie auf mDNS.


Siehe auch

Alle Verben

Die Abschnitte oben erklären die gängigen Verben im Detail. Diese Tabelle ist die vollständige Liste, aus den beiden Dispatchern (migrate/manage.rs und tenancy/manage/mod.rs) gezogen statt aus der Prosa — ein Verb, das im Leitfaden fehlt, ist hier trotzdem auffindbar. Für die Flags <verb> --help ausführen; der Hilfetext ist maßgeblich, diese Seite nicht.

Mit T markierte Verben brauchen das Feature tenancy und werden über Cli::tenancy() erreicht.

Migrationen und Schema

VerbWas es tut
makemigrations [name] / --empty <name>Erzeugt eine Migration aus dem Model-Diff
migrate [target] / --dry-run / --squashWendet ausstehende Migrationen an
downgrade [N]Rollt die letzten N Migrationen zurück
showmigrations / statusListet Migrationen und ihren Anwendungsstand
sqlmigrate <name>Gibt das SQL einer Migration aus, ohne es auszuführen
forget-pending <name>Löscht eine noch nicht angewendete Migrations-JSON
add-data-op --sql <SQL> [--reverse-sql <SQL>]Hängt eine handgeschriebene Datenoperation an
inspectdb [--schema <s>] [--table <t>]Liest ein bestehendes Schema und erzeugt #[derive(Model)]-Quellcode

Daten

VerbWas es tut
dumpdataExportiert Zeilen als JSON-Fixtures
loaddata <fixture.json> [--fail-fast]Lädt JSON-Fixtures wieder ein
flush [--yes] [--app <label>] [--model <name>]Leert jede Model-Tabelle; die Flags grenzen die Menge ein
prune [--model <name>] [--except <name>] [--pretend]Streamendes Massenlöschen; --pretend meldet nur, ohne zu löschen
db:dump / db:restore / db:infoNatives Dump / Restore / Inspect
dbshellFührt den nativen Client aus (psql / mysql / sqlite3). Braucht nur DATABASE_URL, keinen funktionierenden Pool — es wird vor dem Pool-Aufbau behandelt und funktioniert daher auch, wenn sqlx nicht verbinden kann

Scaffolder und Generatoren

VerbWas es tut
startapp <name>Legt ein App-Modul an
make:viewset / make:serializer / make:formErzeugt ein ViewSet, einen Serializer oder ein Form
make:job / make:scheduled / make:worker / make:middleware / make:notification / make:testErzeugt einen Queue-Job, eine Intervall-Aufgabe, ein Worker-Binary, eine Middleware, eine Notification oder einen Test
make:api_routes <app> [--tenant]Erzeugt das API-Routen-Modul einer App

Cache, Sessions und Mail

VerbWas es tut
createcachetable / create-cache-table [--table <name>]Legt die Cache-Tabelle an (und die Session-Tabelle, wenn Sessions in die DB gehen)
clear-cache [--table <name>] / clearsessionsLeert ihn; gibt die Anzahl gelöschter Zeilen zurück
sendtestemail --to <addr>Sendet eine feste Test-Mail über das konfigurierte Backend

Introspektion

VerbWas es tut
showmodels [--format plain|json] [--app <label>]Jedes registrierte Model, sortiert für deterministische Ausgabe
showurls [--format plain|json]Jede benannte Route, sortiert
check [--deploy]Health-Checks; --deploy ergänzt die Produktions-Audits
create-adminLegt eine AdminUser-Zeile für Projekte mit admin::Builder::with_session_auth an. Nicht tenancy-gated — nimmt einen einfachen &Pool und schreibt rustango_admin_users, die Tabelle wird bei Bedarf angelegt. Der einzige Weg zu einem ersten Admin-Login in einem Nicht-Tenancy-Projekt
about / version / --versionBuild- und Versionsinformationen
docsÖffnet die Dokumentation

Benutzer und Zugriff T

VerbWas es tut
create-superuser / set-superuserLegt einen Superuser an oder befördert einen bestehenden Benutzer
create-user / create-operatorLegt einen Tenant-Benutzer oder einen Operator an
reset-password / change-passwordPasswort-Wiederherstellung für Tenant-Benutzer
reset-operator-password / change-operator-passwordPasswort-Wiederherstellung für Operatoren
set-operator-activeAktiviert oder deaktiviert einen Operator
create-role / assign-role / revoke-role / list-rolesRollen
grant-perm / revoke-permBerechtigungen per Codename
seed-permissions [--slug <s>]Legt die Standard-Berechtigungszeilen an
create-api-keyStellt einen API-Key aus

Tenants T

VerbWas es tut
create-tenant / edit-tenant / list-tenantsAnlegen, bearbeiten, auflisten
drop-tenant / purge-tenantDeaktivieren (umkehrbar) / zerstören (nicht)
migrate-tenants / migrate-registryWendet Migrationen über alle Tenants oder auf die Registry an
migrate-tenant-storage <slug> --to schema|databaseVerschiebt einen Tenant zwischen Speichermodi
add-host / remove-host / list-hosts / set-host-enabledHost-Routing
test-tenant-connectionPrüft, ob die Datenbank eines Tenants erreichbar ist
prewarm-poolsÖffnet Tenant-Pools vor dem ersten Request
run-server / runserverStartet den Multi-Tenant-Server
init / init-tenancy / wizard / menu / actionsSetup und interaktive Einstiegspunkte

Audit T

VerbWas es tut
audit-logLiest den Audit-Trail
audit-cleanupKürzt ihn

MCP T

Vollständig im MCP-Leitfaden dokumentiert.

VerbWas es tut
create-agent / list-agents / rotate-agent-secretAgents
create-skill / list-skills / grant-skill / revoke-skillSkills
map-skill-permission / unmap-skill-permissionBindet einen Skill an eine Berechtigung
create-user-key / list-user-keys / revoke-user-keyMCP-Zugangsdaten pro Benutzer
list-runs / show-runLauf-Historie