Ignite rocketboost.rs

Installation

Ignite is a Rust crate that adds an admin panel to a Rocket app. The app needs a Rust toolchain, a MariaDB or MySQL database, and, for the panel's assets, Node.js.

Requirements

  • Rust, stable, with Cargo.
  • MariaDB or MySQL. Ignite supports no other database.
  • Node.js 22.12 or newer, or 20.19 or newer, for Vite and Tailwind.

Pinned versions

Ignite, its docs and its example use the same versions of these crates:

  • Rocket, from git, at revision 3a54d079.
  • Propel, propel_rocket, from git, at the full revision 71ecd1c0c9676db74ce0c90ea3578a12fc043f21. A short revision makes Cargo build two copies. Propel renders the panel's views.
  • Diesel, from a fork, at the full commit a07c4b2c20a466ef34f50ca283790f4df20b0699. The fork is Diesel 2.3.14 with one fix, for libmariadb 3.4, which returned an empty first row for some text queries.

Add the crates

Add Ignite to Cargo.toml, pinned to a commit you have tested. Use the same Rocket, Propel and Diesel pins as Ignite, so the app builds one copy of each:

# The Ignite commit this site documents.
[dependencies]
ignite-panel = { git = "https://github.com/lajennylove/Ignite", rev = "265cb1df1bccc7d8a343b5fa300027e28b8d011d" }
ignite-core = { git = "https://github.com/lajennylove/Ignite", rev = "265cb1df1bccc7d8a343b5fa300027e28b8d011d" }
rocket = { git = "https://github.com/rwf2/Rocket", rev = "3a54d079" }
propel_rocket = { git = "https://github.com/lajennylove/propel", rev = "71ecd1c0c9676db74ce0c90ea3578a12fc043f21" }
diesel = { git = "https://github.com/lajennylove/diesel", rev = "a07c4b2c20a466ef34f50ca283790f4df20b0699", features = ["mysql", "r2d2", "chrono"] }
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
dotenvy = "0.15"

[build-dependencies]
propel_compiler = { git = "https://github.com/lajennylove/propel", rev = "71ecd1c0c9676db74ce0c90ea3578a12fc043f21" }

The derive writes paths that start with ignite_core, so the app needs that crate too. A date field needs chrono with its serde feature. The app's CLI binary uses dotenvy to read .env.

Apps pin the same Diesel fork and commit, so no [patch] line is needed.

Database URL

Ignite reads the connection URL from the DATABASE_URL environment variable, in the form mysql://user:pass@host:port/db. It builds the connection pool when the panel is created, and panics at startup if the variable is missing or the database cannot be reached. In development, the variable can be set in a .env file.

Migrations

The panel owns a users table, which it creates with its own migration. Do not create a users table in your app. Run the panel's migrations at startup, with a connection from the pool:

use ignite_core::build_pool;

pub fn migrate(url: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let pool = build_pool(url)?;
    let mut conn = pool.get()?;
    ignite_panel::run_migrations(&mut conn)
}

Call run_migrations once at startup. It is the one way to run the panel's migrations, and running them again does nothing. Do not rely on diesel migration run for them: it cannot see the panel's migrations, which are embedded in the ignite-panel crate.

Attach the panel

Attach the panel to the Rocket build with the resources the app manages. The panel's routes mount when the app starts, under /admin. The app's own account route is guarded by the panel's AdminUser. See Panel:

mod article;
mod author;
mod reports;
mod views_dir;
mod migrate;
mod order;
mod mount;
mod schema;
mod tag;

use ignite_panel::Ignite;

#[rocket::launch]
fn rocket() -> rocket::Rocket<rocket::Build> {
    rocket::build().attach(
        Ignite::new()
            .resource::<article::Article>()
            .resource::<tag::Tag>()
            .resource::<author::Author>()
            .csrf_exempt(&["/hooks/*"])
            .mount("/admin")
            .fairing(),
    )
    .mount(
        "/",
        rocket::routes![
            reports::account,
            order::checkout,
            order::place_order,
            order::payment_hook
        ],
    )
}

Create the first user

Each app has a small binary at src/bin/ignite.rs. It loads .env first, as the workspace binary does:

fn main() {
    // The app reads DATABASE_URL from .env, so the CLI does too.
    let _ = dotenvy::dotenv();
    ignite_panel::cli::main();
}

Then create a user from the app:

cargo run --bin ignite -- user create --email you@example.com --name "You"

The command reads DATABASE_URL, connects, runs the panel's migrations and checks that the email is free. Only then does it ask for the password, twice, without showing it. A configuration error stops the command before the prompt. The password needs at least 8 characters.

A script can give the password instead. When standard input is not a terminal, or with --password-stdin, the password is the first line of standard input. The password has no environment variable.

printf '%s' "$PW" | cargo run --bin ignite -- user create --email you@example.com --password-stdin

Run it

npm install
npm run build   # once: writes public/build, which Rocket serves
npm run dev     # Vite, in one terminal
cargo run       # the Rocket app, in another

Rocket will not start until public/build exists, so run npm run build once first. In a debug build, views are read from resources/views/ at runtime, so an edit shows on the next reload. A release build compiles them, so rebuild after an edit.

Deploy

A release build renders compiled views, so the panel's views are part of the binary and a deployed app needs no view files. IGNITE_VIEWS_DIR works in debug builds only. An app with views of its own renders them compiled in release, with its own build.rs, as the panel does. See Views.

Secret key

Set IGNITE_SECRET_KEY before you start the app. It signs the sign-in and flash cookies. See Panel for how to create one, and for what happens without it.