---
title: Settings
section: Guides
order: 150
summary: Configuring a project in src/settings.rs - secrets from the environment, checks at start-up, and every setting with its default.
---
A project is configured in one file, `src/settings.rs`. It is plain Rust, so the compiler
catches a misspelt setting, and your editor completes the names and shows what each one
does. Anything left out keeps its default.
```rust
use cadmus::AdminError;
use cadmus::settings::*;
pub fn settings() -> Result<Settings, AdminError> {
let debug = env_or("DEBUG", false)?;
Ok(Settings {
secret_key: env("SECRET_KEY")?,
database: DatabaseSettings {
url: env("DATABASE_URL")?,
..Default::default()
},
auth: AuthSettings {
cookie_secure: !debug, // the login cookie needs HTTPS, except on your machine
..Default::default()
},
admin: AdminSettings {
site_title: "Acme".into(),
accent_color: "#0a7f5a".into(),
..Default::default()
},
..Default::default()
})
}
```
## Secrets stay out of the code
The database address, the secret key and passwords are read from the environment: from
`.env` on your machine (loaded automatically), from the server's environment variables in
production.
| `env("NAME")?` | a required value; start-up stops, naming it, if it is missing |
| `env_or("NAME", default)?` | an optional value of any type (`true/false`, `1/0`, `yes/no` for yes-or-no) |
| `env_opt("NAME")` | optional text, as `Option<String>` |
Values that differ per machine are ordinary code: `if debug { ... }`, or
`bind: env_or("APP_BIND", "127.0.0.1:3000".to_owned())?`.
## Checked at start-up
A mistake stops the server with the setting it is about, before it connects to the
database, for example:
```text
admin.accent_color: accent colour must be #rgb or #rrggbb, got `red`
```
`cargo run -- check` runs the same checks without starting the server.
## Every setting
| `secret_key` | signs logins; at least 32 bytes (`cadmus secret` prints one) |
| `database` | `url`, `max_connections`, `min_connections`, `acquire_timeout` |
| `server` | `bind` (`127.0.0.1:3000`), `request_timeout` (30 s; `None` for no limit), `cors_origins`, `client_ip_header`, `public_url` |
| `auth` | `methods` (password), `signup`, `signup_groups`, `token_lifetime` (1 h), `totp_required`, `cookie_secure` (`true`), `login_max_attempts` (5), `login_lockout` (5 min), `api_keys` (`true`) |
| `admin` | `path` (`/admin`), `site_title`, `accent_color`, `logo_url`, `theme`, `custom_css_url`, `footer_text`, `page_size` (25), `nav_links`, `web_fonts`, `template_dir` |
| `api` | `path` (`/api`) |
| `docs` | `enabled`, `username` + `password`, `title`, `version`, `description`, `swagger_js_url`, `swagger_css_url`, `redoc_js_url` |
| `home` | `enabled`, `title`, `tagline` |
| `uploads` | `storage_url` (off until set), `max_file_bytes` (1 GiB), `max_chunk_bytes` (8 MiB) |
| `mail` | `url` (SMTP; unset writes messages to the log), `from` |
| `log_level` | `LogLevel::INFO`; `DEBUG` also shows every SQL statement |
Durations are written `Duration::from_secs(600)`.
## What settings cannot hold
Things that are code rather than values are set on the `App` after it is built:
```rust
App::from_settings(settings::settings()?)
.await?
.docs(DocsConfig::new().extend(|doc| { /* your routes in the API documentation */ }))
.mailer(MyMailer::new()) // your own way of sending e-mail
.users(MyUserStore::new()) // accounts kept elsewhere
```
## Home page
`/` is a public status page: the service's name, whether it is up, whether the database
answers and how fast, and how long it has been running. It refreshes every 30 seconds and
links nowhere.
- With `Accept: application/json` it returns
`{"status", "database": {"status", "latency_ms"}, "uptime_secs", "checked_at"}`. Both forms
answer `503` when the database is down, so a load balancer can use it as a health check.
- A `/` route in `src/routes.rs` replaces it automatically. `home.enabled: false` turns it
off; `home.title` and `home.tagline` set what it says.
- API addresses typed into a browser's address bar are sent to `/`; programs reach the API
as usual.