βββββββ βββ ββββββββββββββββββββ ββββββββββ βββ ββββββ ββββ βββ
βββββββββββ βββββββββββββββββββββββββββββββ ββββββββββββββββ βββ
βββββββββββ βββββββββββ βββ βββ ββββββββββββββββββββββ βββ
βββββββββββ βββββββββββ βββ βββ ββββββββββββββββββββββββββ
βββ ββββββββββββββββββββ βββ βββββββββββ ββββββ ββββββ ββββββ
βββ βββ βββββββ ββββββββ βββ ββββββββββ ββββββ ββββββ βββββ
Quick Start Β· Features Β· Optional Integrations Β· Configuration Β· Backup System Β· Deployment Β· Themes Β· Changelog
RustChan is a fully-featured imageboard server compiled into a single Rust binary. Drop it on a VPS, a Raspberry Pi, or a local machine β it runs immediately with no containers, no runtime, and no package manager required. All persistent data lives in one directory next to the binary, making migrations a cp -r.
Two external tools plug in as optional enhancements: ffmpeg for video transcoding and audio waveforms, and Tor for anonymous .onion access. Neither is required β RustChan degrades gracefully without either.
|
|
|
|
|
|
RustChan is fully functional without either of these tools. Install them and additional capabilities activate automatically at startup.
When ffmpeg is detected on PATH, RustChan will:
- Transcode MP4 uploads to WebM (VP9 + Opus) automatically for maximum browser compatibility β the original MP4 is never stored
- Re-encode AV1 WebM uploads to VP9+Opus, ensuring playback on browsers without AV1 support
- Generate audio waveform thumbnails β standalone audio uploads (MP3, FLAC, OGG, etc.) display a colour-matched waveform PNG instead of a generic music-note placeholder
- Generate video thumbnails from the first frame of WebM files for catalog and index previews
Without ffmpeg, uploaded videos are stored and served in their original format, and audio posts show a generic icon. RustChan logs a warning at startup if ffmpeg is absent but continues normally. Set require_ffmpeg = true in settings.toml to make its absence a hard startup error instead.
See SETUP.md β Installing ffmpeg for step-by-step instructions on Linux, macOS, and Windows.
When enable_tor_support = true is set in settings.toml and a Tor daemon is running, RustChan will:
- Detect Tor at startup by checking common install paths (system PATH, Homebrew on macOS, etc.)
- Read the
.onionaddress from the hidden-servicehostnamefile and display it on the home page and in the admin panel so users can copy it easily - Print setup hints to the console at startup if Tor is installed but a hidden service has not yet been configured
Tor handles all onion routing independently β RustChan simply binds to its normal port and reads the address file. Your torrc tells Tor to forward .onion traffic to that port.
See SETUP.md β Installing Tor for installation and hidden-service configuration on Linux, macOS, and Windows.
# 1. Build
cargo build --release
# 2. Create your first admin account
./rustchan-cli admin create-admin admin "YourStrongPassword!"
# 3. Create some boards
./rustchan-cli admin create-board b "Random" "General discussion"
./rustchan-cli admin create-board tech "Technology" "Programming and hardware"
# 4. Start the server
./rustchan-cliOpen http://localhost:8080 β the admin panel is at /admin.
On first launch, rustchan-data/settings.toml is generated automatically with a freshly-generated cookie_secret and every setting documented inline. Edit it and restart to apply changes.
Everything lives in rustchan-data/ next to the binary. Nothing is written elsewhere unless you override paths via environment variables.
rustchan-cli β single self-contained binary
rustchan-data/
βββ settings.toml β instance config (auto-generated on first run)
βββ chan.db β SQLite database (WAL mode)
βββ full-backups/ β full site backups (saved from admin panel)
β βββ rustchan-backup-20260304_120000.zip
βββ board-backups/ β per-board backups (saved from admin panel)
β βββ rustchan-board-tech-20260304_120000.zip
βββ boards/
βββ b/
β βββ <uuid>.<ext> β uploaded files
β βββ thumbs/
β βββ <uuid>_thumb.jpg β auto-generated thumbnails
βββ tech/
βββ <uuid>.<ext>
βββ thumbs/
Auto-generated on first run. Edit and restart to apply.
# Site display name β shown in the browser title, header, and home page.
forum_name = "RustChan"
# TCP port (binds to 0.0.0.0:<port>).
port = 8080
# Upload size limits in megabytes.
max_image_size_mb = 8
max_video_size_mb = 50
max_audio_size_mb = 150
# Auto-generated on first run. DO NOT change after your first post β
# all existing IP hashes and bans will become invalid.
cookie_secret = "<auto-generated 32-byte hex>"
# Set true to detect a running Tor daemon and display the .onion
# address on the home page and admin panel.
enable_tor_support = true
# Set true to hard-exit if ffmpeg is not found (default: warn only).
require_ffmpeg = false
# How often (seconds) to run PRAGMA wal_checkpoint(TRUNCATE) to prevent
# the SQLite WAL from growing unbounded. Set to 0 to disable.
wal_checkpoint_interval_secs = 3600All settings can be overridden with environment variables, which take precedence over settings.toml. Recommended for secrets in production (e.g. via systemd's Environment= directive).
| Variable | Default | Description |
|---|---|---|
CHAN_FORUM_NAME |
RustChan |
Site display name |
CHAN_PORT |
8080 |
TCP port |
CHAN_BIND |
0.0.0.0:8080 |
Full bind address (overrides CHAN_PORT) |
CHAN_DB |
<exe-dir>/rustchan-data/chan.db |
SQLite database path |
CHAN_UPLOADS |
<exe-dir>/rustchan-data/boards |
Uploads directory |
CHAN_COOKIE_SECRET |
(from settings.toml) | Required in production. CSRF tokens & IP hashing. |
CHAN_MAX_IMAGE_MB |
8 |
Max image upload size (MiB) |
CHAN_MAX_VIDEO_MB |
50 |
Max video upload size (MiB) |
CHAN_MAX_AUDIO_MB |
150 |
Max audio upload size (MiB) |
CHAN_THUMB_SIZE |
250 |
Thumbnail max dimension in pixels |
CHAN_BUMP_LIMIT |
500 |
Reply count after which a thread stops bumping |
CHAN_MAX_THREADS |
150 |
Max live threads per board before oldest is pruned/archived |
CHAN_RATE_POSTS |
10 |
Max POSTs per rate window per IP |
CHAN_RATE_WINDOW |
60 |
Rate-limit window duration in seconds |
CHAN_SESSION_SECS |
28800 |
Admin session duration in seconds (default: 8 h) |
CHAN_BEHIND_PROXY |
false |
Trust X-Forwarded-For when behind nginx / Caddy |
CHAN_HTTPS_COOKIES |
(same as CHAN_BEHIND_PROXY) |
Add Secure flag to session cookies |
CHAN_WAL_CHECKPOINT_SECS |
3600 |
WAL checkpoint interval in seconds; 0 to disable |
RUST_LOG |
rustchan-cli=info |
Log verbosity (=debug for verbose output) |
RustChan's backup system is entirely web-based β no shell access or file explorer needed. Every backup action is available directly from the admin panel.
A full backup is a .zip containing a consistent SQLite snapshot (via VACUUM INTO, safe under live writes) plus all uploaded files and thumbnails.
| Action | Description |
|---|---|
| πΎ Save to server | Creates the backup and writes it to rustchan-data/full-backups/ |
| β¬ Download to computer | Streams a saved server-side backup as a .zip to your browser |
| βΊ Restore from server | Restores the live DB from a saved file β no re-upload, no restart needed |
| βΊ Restore from local file | Upload a .zip from your computer to restore directly |
| β Delete | Permanently removes the .zip from the server filesystem |
Board backups are self-contained: a board.json manifest (all posts, threads, polls, votes, file hash records) plus that board's upload directory. Other boards are never touched.
Each board card in the admin panel has both a πΎ Save to server and a β¬ Download to computer button for quick one-click access.
Restore behaviour:
- Board exists β content is wiped and replaced; settings updated from the manifest
- Board doesn't exist β created from scratch with the manifest's configuration
- All row IDs are remapped on import β zero collision risk with existing data
How restore works internally: RustChan uses SQLite's
sqlite3_backup_init()API rather than file swapping. This copies pages directly into the live connection's open file descriptors, so every pooled connection immediately reads the restored data. No file renaming, no WAL deletion, no restart required.
Board and account management is also available from the command line β useful for scripting and provisioning.
# Admin accounts
./rustchan-cli admin create-admin <username> <password>
./rustchan-cli admin reset-password <username> <new-password>
./rustchan-cli admin list-admins
# Boards
./rustchan-cli admin create-board <short> <name> [description] [--nsfw]
./rustchan-cli admin delete-board <short>
./rustchan-cli admin list-boards
# Bans
./rustchan-cli admin ban <ip_hash> "<reason>" [duration_hours] # omit hours = permanent
./rustchan-cli admin unban <ban_id>
./rustchan-cli admin list-bans<short> is the board slug used in URLs (e.g. tech β /tech/). Lowercase alphanumeric, 1β8 characters.
See SETUP.md for a complete, step-by-step production guide covering:
- System user creation and hardened directory layout
- systemd service with security directives (
NoNewPrivileges,PrivateTmp,ProtectSystem=strict) - nginx reverse proxy with TLS via Let's Encrypt
- Installing ffmpeg on Linux, macOS, and Windows
- Installing Tor and configuring a hidden service on Linux, macOS, and Windows
- First-run configuration and board creation walkthrough
- Raspberry Pi SD card wear reduction
- Security hardening checklist
- Troubleshooting reference
# ARM64 β Raspberry Pi 4/5
rustup target add aarch64-unknown-linux-gnu
cargo install cross # uses Docker for the cross-linker
cross build --release --target aarch64-unknown-linux-gnu
# Windows x86-64
rustup target add x86_64-pc-windows-gnu
cargo build --release --target x86_64-pc-windows-gnuThe release profile sets strip = true, lto = "thin", and panic = "abort". Typical stripped binary: 12β18 MiB.
RustChan is intentionally minimal. No template engine, no ORM, no JavaScript framework. HTML is rendered with plain Rust format! strings. The result is a single binary that starts in under a second.
| Layer | Technology |
|---|---|
| Web framework | Axum 0.8 |
| Async runtime | Tokio 1.x |
| Database | SQLite via rusqlite β bundled, no system library needed |
| Connection pool | r2d2 + r2d2_sqlite |
| Image processing | image crate (JPEG, PNG, GIF, WebP) |
| Video transcoding | ffmpeg (optional β degrades gracefully) |
| Audio waveforms | ffmpeg showwavespic filter (optional) |
| Onion address display | Tor hidden-service hostname file (optional) |
| Password hashing | argon2 crate β Argon2id |
| HTML rendering | Plain Rust format! β zero template engine overhead |
| Config | settings.toml + env var overrides via once_cell::Lazy |
| Logging | tracing + tracing-subscriber (stdout / journald) |
src/
βββ main.rs β entry point, router, keyboard console, background tasks
βββ config.rs β settings.toml + env var resolution, first-run generation
βββ db.rs β all SQL queries (no ORM)
βββ error.rs β AppError β HTTP response conversion; ban page rendering
βββ models.rs β DB row structs + BackupInfo + BanAppeal
βββ middleware/mod.rs β rate limiting, CSRF, IP hashing, proxy trust
βββ handlers/
β βββ admin.rs β admin panel, board/ban/filter/backup/appeal management
β βββ board.rs β board index, catalog, archive, search, thread creation, ban appeals
β βββ thread.rs β thread view, reply posting, poll voting, post editing
βββ templates/mod.rs β pure-Rust HTML generation (all five themes, live site name/subtitle)
βββ utils/
βββ crypto.rs β Argon2id, CSRF, session IDs, IP hashing, PoW verification
βββ files.rs β upload validation, thumbnail generation, EXIF stripping, waveforms
βββ sanitize.rs β HTML escaping, markup renderer (greentext, spoilers, dice, embeds)
βββ tripcode.rs β SHA-256 tripcode system
| Concern | Implementation |
|---|---|
| Passwords | Argon2id (t=2, m=65536, p=2) β memory-hard, GPU-resistant. ~200 ms on a Raspberry Pi 4 |
| Sessions | HttpOnly, SameSite=Strict, path-scoped to /admin. Configurable duration (default 8 h) |
| CSRF | Double-submit cookie pattern β every POST validates _csrf against the session cookie |
| IP privacy | Raw IPs never stored. A salted SHA-256 keyed to cookie_secret is stored instead |
| Rate limiting | In-memory sliding window per hashed IP. Default: 10 POSTs / 60 seconds |
| File safety | Two-layer check: Content-Type header + magic byte inspection. Extension never trusted |
| EXIF stripping | All JPEG uploads re-encoded via image crate β GPS, device ID, all metadata discarded |
| XSS | All user input passes through escape_html() before insertion. Markup applied after escaping |
| Path traversal | Backup filenames validated to [a-zA-Z0-9._-] only before any filesystem operation |
| Backup restore | Uses sqlite3_backup_init() β no file swapping, no WAL corruption, no restart required |
| PoW CAPTCHA | SHA-256 hashcash at 20-bit difficulty, verified server-side with a 5-minute grace window |
>quoted text greentext line
>>123 reply link β jumps to post #123 on the same board
>>>/board/ cross-board index link (amber, hover preview)
>>>/board/123 cross-board thread link (amber, hover preview)
**text** bold
__text__ italic
[spoiler]text[/spoiler] hidden until clicked/hovered
[dice NdM] server-side dice roll e.g. [dice 2d6] β π² 2d6 βΈ β β
= 11
:fire: :think: :based: :kek: β¦ (25 emoji shortcodes)
Five built-in themes, user-selectable via the floating picker in the bottom-right corner of every page. Choice persists in localStorage with no load flash.
| Theme | Aesthetic |
|---|---|
| Terminal (default) | Dark matrix-green. Monospace font, glowing green accents, scanline body texture |
| Frutiger Aero | Frosted glass panels, pearl-blue gradients, rounded corners β Vista-era glassmorphism |
| DORFic Aero | Dark hewn-stone walls, torchlit amber/copper glass panels β Dwarf Fortress meets Vista |
| FluoroGrid | Pale sage background, muted teal grid lines, dusty lavender panels, plum accents |
| NeonCubicle | Cool off-white, horizontal scanlines, steel-teal borders, soft orchid accents |
See CHANGELOG.md for the full version history.
Latest β v1.0.10:
- Per-post inline ban+delete (β button on every post in admin view)
- Ban appeal system β appeal form on ban page, admin queue with dismiss/accept+unban, 24h cooldown
- PoW CAPTCHA for new threads (per-board opt-in; replies exempt; 5-minute server-side grace window)
- Video embed unfurling β YouTube/Invidious/Streamable URLs become webm-style thumbnail+iframe widgets; thumbnails appear in catalog and board index
- Cross-board quotelink hover previews with client-side result caching
- Floating "+N new replies" pill; delta-compressed live thread state
- "(You)" post tracking persisted across page refreshes
v1.0.9: Per-board post editing toggle, configurable edit window, per-board archive toggle
v1.0.8: Thread archiving with /{board}/archive page Β· Mobile reply drawer Β· Server-side dice rolling Β· Post sage Β· Post editing with deletion-token auth Β· Draft autosave Β· WAL checkpoint background task Β· SQLite VACUUM from admin panel Β· IP history view
v1.0.7: JPEG EXIF stripping on upload Β· Image+audio combo posts Β· Audio waveform thumbnails via ffmpeg
v1.0.6: Full web-based backup system β full and per-board backups, in-panel management, download/restore/delete Β· GitHub Actions CI across 5 targets
v1.0.5: Automatic MP4βWebM transcoding via ffmpeg Β· Home page live stats panel Β· Tor detection on Homebrew (Apple Silicon + Intel)
Built with π¦ Rust Β Β·Β Powered by SQLite Β Β·Β Optional integrations: ffmpeg Β· Tor
Drop it anywhere. It just runs.