Files
RustBox/README.md
T
Mateusz Gruszczyński 3b9110c8c8
RustBox CI / rustbox (push) Canceled after 0s
v0.2.22
2026-11-04 14:59:03 +01:00

286 lines
15 KiB
Markdown

# RustBox 0.2.22
RustBox is a webmail derived from `c0h1b4/oxi`. The consumed Oxi revision is recorded in `UPSTREAM_BASE`.
The repository is one Cargo application. The RustBox browser interface lives under `src/web/`, uses HTML/CSS/vanilla JavaScript, and is embedded into the Rust executable at build time. There is no TypeScript, React, Next.js, Node.js or Bun build step.
## Layout
```text
rustbox/
├── Cargo.toml
├── build.rs
├── src/
│ ├── main.rs
│ ├── ui.rs
│ └── web/
│ ├── index.html
│ ├── js/
│ │ ├── manifest.txt
│ │ └── *.js
│ ├── icons.svg
│ └── css/
├── migrations/
├── tests/
├── examples/
├── docs/
├── packaging/
├── scripts/
├── config.dev.toml
├── config.example.toml
├── UPSTREAM_BASE
└── dev.sh
```
`build.rs` combines the CSS modules from `src/web/css/manifest.txt` and JavaScript modules from `src/web/js/manifest.txt`. Cargo embeds the generated CSS/JS bundles, HTML and SVG sprite in the RustBox executable. There is no standalone source `src/web/app.js`; `src/web/` is the single source of truth and Cargo generates the embedded bundles in `OUT_DIR`.
## Storage and search in 0.2.16
RustBox separates three concerns: SQL metadata, blob storage and search. The SQL engine does **not** decide whether `data_dir` may be used. A single-node PostgreSQL/MySQL deployment may still use filesystem blobs and local Tantivy. In cluster mode, local node state is rejected unless the configured backend is explicitly shared.
Blob backends:
- `storage.backend="filesystem"` — local disk in single-node mode; set `storage.shared=true` only for a real shared POSIX mount such as NFS, CephFS or GlusterFS.
- `storage.backend="s3"` — AWS S3 or compatible APIs such as MinIO/Ceph RGW.
Physical blob deletion is durable across all blob backends. RustBox records deletion work in the per-account SQL `blob_delete_outbox` in the same transaction that removes attachment metadata, attempts the delete immediately, and retries temporary failures in the background with leases/backoff. This covers local disk, shared POSIX/NFS and S3-compatible storage without permanently dead-lettering failed cleanup.
Search backends:
- `search.backend="local_tantivy"` — single-node.
- `search.backend="opensearch"` — cluster-capable. Index mutations are written first to a durable per-tenant SQL outbox and processed with retry/backoff/dead-letter handling.
- `search.backend="disabled"` — no full-text index.
In a cluster using shared SQL + S3/shared POSIX + OpenSearch/disabled search + `pgp_enabled=false`, `data_dir` is not required as persistent node state. See `docker/README.md` for ready Compose variants.
Administrator-only deep runtime diagnostics can be enabled separately under `[diagnostics_endpoint]`. It is disabled and loopback-only by default, supports a network allowlist plus token or Basic Auth, redacts secrets, and probes SQL/Redis/storage/search/session/runtime/security state. See [`docs/SYSTEM_DIAGNOSTICS.md`](docs/SYSTEM_DIAGNOSTICS.md).
## Configure mail servers
Mail endpoints are configured by the administrator, not on the login screen. Users enter only email and password.
Login brute-force protection is on by default and configured in the `[login_rate_limit]` section (or `LOGIN_RATE_LIMIT__*` environment variables): a per-IP token bucket on the login endpoint plus failed-attempt lockouts per mailbox and per client IP with exponential back-off. Locked attempts are answered with `429` and `Retry-After` without contacting the IMAP server. Counters are in memory per node. See `config.example.toml` for every option.
Session cookies get the `Secure` attribute according to `cookie_secure` (default `auto`): only when the browser reached RustBox over HTTPS, detected from a trusted proxy's `X-Forwarded-Proto`/`Forwarded`/`X-Forwarded-Ssl` header or the browser's `Origin`/`Referer`. Plain-HTTP installations keep working; set `cookie_secure = "always"` behind an HTTPS proxy that does not forward the scheme.
Edit `config.dev.toml` before the first run:
```toml
default_theme = "system" # system | dark | light
default_interface_skin = "rustbox" # rustbox | gmail | proton | zoho | outlook | fastmail | apple-mail | yahoo | hey | nord | github | gitlab | discord | slack | reddit | notion | ...
log_level = "info" # RustBox app level; dependencies default to WARN. RUST_LOG can override the full filter.
# Recommended dev filter: warn,rustbox=debug,tower_http=info
# Avoid a global RUST_LOG=debug/info unless diagnosing a dependency: third-party
# crates may emit internal filesystem/source paths in verbose records.
# Mail hosts may be omitted: IMAP defaults to localhost:993 and SMTP to localhost:587.
# imap_host = "localhost"
# smtp_host = "localhost"
tls_enabled = true
# tls_verify = true # omitted: false only for localhost/loopback, true for remote hosts
# Upload limit for one raw attachment.
max_attachment_size_mb = 10
# Hard limit for the complete rendered RFC822/MIME message submitted to the MTA.
max_message_size_mb = 50
# Memory guard for messages with attachments. Sending a message or saving an IMAP
# draft snapshot loads every attachment into RAM and the MIME builder keeps several
# copies (about 3-4x the message size). Attachment sets that cannot fit
# max_message_size_mb are rejected with 413 before any file is read, and at most
# max_concurrent_message_builds requests may hold attachments in memory at once;
# the others wait up to message_build_wait_secs and then get 503.
# Rough peak RAM = max_concurrent_message_builds x 4 x max_message_size_mb.
max_concurrent_message_builds = 4
message_build_wait_secs = 30
# Optional features exposed by the server/UI.
sieve_enabled = true
diagnostics_enabled = true
about_enabled = true
# Adds content-type-aware browser security headers when enabled: full document policy on HTML, private no-store/CORP/anti-framing policy on /api, Referrer-Policy on HTML/CSS, and nosniff broadly.
security_headers_enabled = false
# Optional; defaults to imap_host. Ignored when sieve_enabled=false.
# sieve_host = "mail.example.com"
sieve_port = 4190
sieve_tls_mode = "starttls"
sieve_allow_insecure_auth = false
# Optional administrator-owned headers added to every outgoing message.
# Structural/MIME headers such as From, To, Subject and Content-Type are rejected.
outgoing_headers = [
"X-Company-Mailer: RustBox",
]
```
If the mail host settings are omitted, RustBox uses `localhost` with IMAP `993` and SMTP `587`. TLS is enabled by default. Certificate and hostname verification are skipped automatically only for localhost/loopback unless `tls_verify` is set explicitly.
Mail-size limits are intentionally separate. `max_attachment_size_mb` applies to each raw uploaded file, while `max_message_size_mb` is checked against the final RFC822/MIME byte stream immediately before SMTP submission. The latter therefore includes message bodies, headers, MIME boundaries, base64 expansion, inline CID parts and OpenPGP transformations. The composer displays only an estimate; the server-side final-MIME check is authoritative.
Attachments are streamed to blob storage on upload, but sending a message or saving an IMAP draft snapshot has to hold them in memory while the MIME message is built. RustBox therefore (1) rejects an attachment set whose base64-encoded size cannot fit `max_message_size_mb` with `413` before any file is read into memory, and also while an upload is still streaming, and (2) allows only `max_concurrent_message_builds` such operations at the same time; further requests wait up to `message_build_wait_secs` and then receive `503`. Peak RAM for these operations is roughly `max_concurrent_message_builds` x 4 x `max_message_size_mb`. Fully streamed MIME assembly is not implemented.
## Database, storage, search and cluster traffic
`database_backend = "sqlite" | "mysql" | "pgsql"` selects only the authoritative SQL database. It does not force or forbid local blob/search storage. In particular, PostgreSQL/MySQL are valid in a single-node installation together with `storage.backend="filesystem"` and `search.backend="local_tantivy"`.
`session_backend = "database"` stores sessions in the selected SQL database. `session_backend = "redis"` moves only session persistence to Redis/Redis Sentinel. Mailbox metadata and cached message bodies remain in SQL.
`data_dir` is persistent node state only when a selected local feature actually needs it: default SQLite, implicit filesystem storage, implicit local Tantivy, or local OpenPGP keys. In cluster mode it is not required when those features are replaced with cluster-safe backends. Cluster validation is capability-based:
- SQL: shared MySQL/PostgreSQL; SQLite is rejected;
- blobs: S3-compatible storage, or `filesystem` with `storage.shared=true` on a real NFS/CephFS/GlusterFS mount;
- search: OpenSearch or `disabled`; local Tantivy is rejected;
- OpenPGP: remains disabled until a cluster-safe key backend exists;
- all nodes use the same `session_encryption_key`.
Example stateless cluster:
```toml
database_backend = "pgsql"
database_url = "postgres://rustbox:secret@db/rustbox"
session_backend = "database"
session_encryption_key = "<shared-key>"
cluster_mode = true
pgp_enabled = false
[storage]
backend = "s3"
bucket = "rustbox"
region = "eu-central-1"
# endpoint = "https://s3.example.com" # MinIO/Ceph RGW/etc.
[sync]
interval_secs = 30
deep_index_batch_size = 10
[search]
backend = "opensearch"
urls = ["https://opensearch.example.com:9200"]
required = true
# Production-safe default: do not proactively download every full message body.
deep_index = false
```
Shared POSIX is also supported without S3:
```toml
[storage]
backend = "filesystem"
path = "/mnt/rustbox-shared"
shared = true
```
Existing attachment files can be migrated to the configured storage backend without deleting the source files:
```bash
rustbox storage migrate --from filesystem:/old/data/root --to configured --dry-run
rustbox storage migrate --from filesystem:/old/data/root --to configured --verify
```
The SQL row is switched to the logical object key only after upload and size/checksum verification. Search state is rebuildable from SQL:
```bash
rustbox search reindex
```
Ready Docker examples are in `docker/`: standalone SQLite, PostgreSQL + SILO (S3-compatible) + OpenSearch, and MySQL + NFS + OpenSearch.
All executable application SQL is centralized by dialect under `src/queries/{sqlite,mysql,pgsql}/queries.rs`. Migrations V047-V050 are consolidated into the V050 compatibility baseline; databases older than V050 must first be upgraded with a release that can reach that baseline. RustBox 0.2.16 adds V051 for adaptive HTML-mail colors and mailbox loading mode; fresh schemas already contain those columns, while existing V050 tenants migrate automatically to V051. V052 is the next migration number. See `migrations/README.md`.
## Development
Requirements: Rust toolchain with Cargo. OpenPGP key generation/sign/encrypt/decrypt is implemented with rPGP. Changing the passphrase of an existing private key additionally requires GnuPG (`gpg`) on the server; override the executable with `RUSTBOX_PGP_GPG_BINARY` if needed.
```bash
./dev.sh
```
Then open:
```text
http://127.0.0.1:3001
```
To use another configuration:
```bash
RUSTBOX_CONFIG=/path/to/rustbox.toml ./dev.sh
```
Normal Cargo commands work from the repository root:
```bash
cargo run
cargo test
cargo build --release
```
## UI
Multi-account browser sessions are enabled by default and can be disabled by the administrator with `multi_account_enabled = false`. When enabled, signed-in accounts can be switched from the top bar and selected together for new-mail notifications.
RustBox uses a compact three-pane mail layout: folders, message list and reader. The current internal baseline includes configurable special IMAP folders, collapsible folder hierarchy, folder CRUD/subscriptions, move/copy/bulk actions, drag-and-drop message moves, collapsible threads, raw headers/source download, labels, advanced/saved search, quota warnings, contacts/groups/vCard, calendar/ICS import, identities/signatures with automatic reply identity selection, HTML/plain/Markdown compose modes, CID inline images, and ManageSieve raw + visual rule editing with negation, dynamically addable conditions/actions and nested AND/OR chaining. OpenPGP includes per-user key management, identity binding, PGP/MIME + inline sign/encrypt/decrypt/verify, WKD/HKP discovery and private-key passphrase management.
IMAP IDLE events are delivered to the browser over the embedded WebSocket endpoint for realtime refresh and optional desktop notifications. Notification preferences include sound, folder selection and Do Not Disturb hours.
Appearance preferences are persisted per account and include light/dark/system theme, density, preview position, UI scale, multiple color presets, custom accent/background/panel/text colors, font, row height, pane widths, radius, contrast and visibility toggles. Administrators can set the global light/dark mode with `default_theme = "system" | "dark" | "light"` and the global interface style with `default_interface_skin = "rustbox" | "gmail" | "proton" | "zoho" | "outlook" | ...` in TOML. The global interface style is used on the login screen and by accounts set to **Global default**. Built-in interface styles include RustBox, Roundcube variants, Clean, Graphite, High contrast, Gmail/Proton/Zoho/Outlook/Fastmail/Apple Mail/Yahoo/HEY/GitHub/GitLab/Discord/Slack/Reddit/Notion-inspired profiles, Nord and Minimal. Predefined styles can optionally unlock the per-account UI scale control. The mailbox toolbar also exposes persisted date/sender/subject/size sorting, and remote images can be restricted to saved contacts.
The native RustBox interface intentionally uses flat surfaces, small radii and one SVG icon set. Compatibility skins may reproduce upstream visual treatments such as gradients, while toolbar icons remain SVG rather than Unicode glyphs.
## ManageSieve
Supported modes:
- `starttls` — recommended;
- `implicit` — TLS immediately after connect;
- `none` — plaintext transport; reusable credential authentication remains blocked unless `sieve_allow_insecure_auth = true` is explicitly enabled.
When `sieve_host` is not configured, RustBox uses the authenticated session's IMAP host. Sieve uses the same session credentials; passwords are not exposed to the browser after login.
## Native installation
```bash
./scripts/build-release.sh
sudo ./scripts/install-systemd.sh
sudo systemctl enable --now rustbox
sudo journalctl -u rustbox -f
```
For public access put an HTTPS reverse proxy in front of RustBox. The production example binds to `127.0.0.1:3001`.
## Following Oxi
Initialize Git after extracting the release archive:
```bash
git init
git add .
git commit -m "RustBox 0.2.16 release"
./scripts/setup-upstream.sh
```
Inspect newer Oxi changes:
```bash
./scripts/oxi-status.sh
```
Import the mapped Oxi backend delta:
```bash
./scripts/sync-upstream.sh
```
Oxi `backend/...` paths are mapped into RustBox's flat Cargo tree. Oxi frontend sources are never copied. Relevant upstream frontend behavior is reviewed and reimplemented in `src/web/`. `UPSTREAM_BASE` advances only after `./scripts/finish-upstream.sh` completes review and the build gate.
To port one backend commit without advancing the baseline:
```bash
./scripts/oxi-port-commit.sh <oxi-commit>
```
See `docs/UPSTREAM.md` for details.
## Attribution
See `NOTICE.md`. The supplied Oxi snapshot did not contain an authoritative `LICENSE` file; verify the exact upstream licensing terms before public redistribution.