Skip to content

csd113/RustChan

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

135 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—  β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β•šβ•β•β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘
β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘
β•šβ•β•  β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•   β•šβ•β•    β•šβ•β•β•β•β•β•β•šβ•β•  β•šβ•β•β•šβ•β•  β•šβ•β•β•šβ•β•  β•šβ•β•β•β•

A self-hosted imageboard engine. One binary. Zero runtime dependencies.


Rust SQLite Axum License: MIT Version


Quick Start Β· Features Β· Optional Integrations Β· Configuration Β· Backup System Β· Deployment Β· Themes Β· Changelog



RustChan is a fully-featured imageboard engine compiled into a single Rust binary. Deploy it on a VPS, a Raspberry Pi, or a local machine β€” no containers, no runtime, no package manager required. All persistent data lives in a single directory alongside the binary, making migrations as simple as cp -r.

Two external tools are supported as optional enhancements: ffmpeg for video transcoding and audio waveforms, and Tor for anonymous .onion access. Neither is required β€” RustChan degrades gracefully without them.


✦ Features

πŸ“‹ Boards & Posting

  • Multiple boards with independent per-board configuration
  • Threaded replies with globally unique post numbers
  • Thread polls β€” OP-only, 2–10 options, live percentage bar results, one vote per IP; expired vote rows cleaned up automatically
  • Spoiler tags β€” [spoiler]text[/spoiler] with click-to-reveal
  • Dice rolling β€” [dice NdM] resolved server-side at post time (e.g. [dice 2d6] β†’ 🎲 2d6 β–Έ βš„ βš… = 11)
  • Emoji shortcodes β€” 25 built-in (:fire: β†’ πŸ”₯, :think: β†’ πŸ€”, :based: β†’ πŸ—Ώ)
  • Cross-board links β€” >>>/board/123 with floating hover previews
  • **bold**, __italic__, greentext, inline quote-links
  • Sage β€” reply without bumping the thread
  • Post editing β€” edit within a configurable window using your deletion token
  • Draft autosave β€” reply text persisted to localStorage every 3 seconds; survives refreshes and crashes
  • Tripcodes and user-deletable posts via deletion tokens
  • Per-board NSFW tagging, bump limits, and thread caps
  • Board index, catalog grid, full-text search, and pagination
  • Trailing slash normalization β€” all URL variants resolve correctly

πŸ–ΌοΈ Media

  • Images: JPEG (EXIF-stripped and orientation-corrected on upload), PNG, GIF, WebP
  • Video: MP4, WebM β€” auto-transcoded to VP9+Opus WebM via ffmpeg; AV1 streams re-encoded to VP9
  • Audio: MP3, OGG, FLAC, WAV, M4A, AAC (up to 150 MB default)
  • Image + audio combo posts β€” attach both an image and an audio file simultaneously
  • Audio waveform thumbnails β€” generated via ffmpeg's showwavespic filter for standalone audio uploads
  • Waveform cache eviction β€” background task prunes oldest thumbnails when the cache exceeds waveform_cache_max_mb (default 200 MiB); originals never touched
  • Video embed unfurling β€” per-board opt-in; YouTube, Invidious, and Streamable URLs render as thumbnail + click-to-play widgets
  • Auto-generated thumbnails with configurable dimensions
  • Resizable inline image expansion via drag-to-resize
  • Client-side auto-compression β€” oversized files are compressed in-browser before upload with a live progress bar
  • Streaming multipart β€” uploads are validated against size limits in flight; never fully buffered in RAM
  • Two-layer file validation: Content-Type header + magic byte inspection (extensions are never trusted)

πŸ›‘οΈ Moderation & Administration

  • Board creation, configuration, and deletion from the web panel
  • Thread sticky and lock toggles
  • Per-post ban + delete β€” single-click to ban an IP hash and remove the post simultaneously
  • Ban appeal system β€” banned users can submit appeals; admins review from a dedicated queue with dismiss and accept+unban actions
  • IP history view β€” paginated list of all posts from any IP hash across all boards
  • PoW CAPTCHA β€” per-board opt-in SHA-256 proof-of-work for all posts (threads and replies); nonce replay blocked within the 5-minute validity window
  • Report system β€” users can report posts; admins see an inbox with resolve and resolve+ban actions
  • Moderation log β€” append-only audit trail of all admin actions, viewable from the panel
  • Word filters (pattern β†’ replacement, site-wide)
  • Full Site Backup & Restore β€” entirely web-based, no shell access required; all operations stream from disk, never buffering the full backup in RAM
  • Scheduled VACUUM β€” automatic database compaction on a configurable interval; reclaimed bytes logged
  • DB size warning β€” admin panel shows a red banner when the database exceeds db_warn_threshold_mb
  • Expired poll cleanup β€” background task purges stale vote rows on a configurable schedule
  • Per-board controls: editing, edit window, archiving, video embeds, PoW CAPTCHA

πŸ”’ Security

  • Argon2id password hashing (t=2, m=65536, p=2)
  • Security headers β€” CSP (script-src 'self', no unsafe-inline), HSTS (1 year + subdomains), and Permissions-Policy on all responses
  • Inline JS eliminated β€” all JavaScript extracted to external .js files; CSP fully enforced
  • CSRF β€” double-submit cookie with constant-time token comparison (subtle::ct_eq)
  • HttpOnly + SameSite=Strict session cookies with configurable Secure flag and Max-Age
  • Admin brute-force protection β€” progressive lockout after 5 consecutive failed login attempts
  • PoW nonce replay prevention β€” used nonces tracked in memory for the 5-minute validity window; stale entries auto-pruned
  • Raw IPs never stored or logged β€” HMAC-keyed SHA-256 hash used everywhere
  • Per-IP sliding-window rate limiting on POST endpoints (10/min) and page-load GET endpoints (60/min); /api/ routes excluded from GET limiting
  • JPEG EXIF stripping + orientation correction β€” GPS, device IDs, and all metadata removed; rotation normalized on upload
  • All user input HTML-escaped before rendering; markup applied post-escape
  • Zip-bomb protection β€” backup restore capped at 1 GiB per entry, 50,000 entries max
  • Backup upload size cap β€” full and board restore endpoints reject uploads over 512 MiB
  • Redirect hardening β€” backslash and encoded variants blocked on redirect parameters
  • Path traversal prevention on all filesystem operations
  • Job queue back-pressure β€” queue capped at job_queue_capacity entries; excess jobs dropped with a log warning, never causing OOM

πŸ—‚οΈ Thread Lifecycle

  • Thread archiving β€” overflow threads are archived (readable, locked, hidden from index) rather than deleted; configurable per board
  • Global archive_before_prune flag β€” ensures no thread is silently hard-deleted on any archiving-enabled instance, even if the individual board didn't opt in
  • Archive page β€” /{board}/archive with thumbnails, reply counts, and pagination
  • Thread auto-update β€” delta-compressed polling keeps reply counts, lock/sticky badges, and new posts in sync without full reloads
  • ETag / Conditional GET β€” board index and thread pages return 304 Not Modified on cache hits; ETags included on all 200 responses
  • Response compression β€” gzip, Brotli, or zstd negotiated automatically via Accept-Encoding
  • Floating new-reply pill β€” "+N new replies ↓" notification; click to scroll, auto-dismisses after 30 seconds
  • "(You)" tracking β€” posts you authored are marked with a (You) badge, persisted across refreshes
  • Per-board toggle between archive-on-overflow and permanent deletion

πŸ“± Mobile & UX

  • Mobile reply drawer β€” floating action button slides up a full-width reply panel on small screens
  • Cross-board hover previews β€” >>>/board/123 links show a floating popup with client-side caching
  • Six built-in themes β€” user-selectable via a floating picker; persisted in localStorage with no flash
  • Default theme β€” default_theme in settings.toml sets the server-side default for new visitors; also configurable from the admin panel
  • Site subtitle β€” site_subtitle in settings.toml customises the home page tagline at install time
  • Live stats β€” total posts, uploads, and content size displayed on the home page
  • Background worker system β€” video transcoding, waveform generation, and thread cleanup run asynchronously; duplicate media jobs coalesced; configurable ffmpeg timeout; exponential backoff on retries
  • Interactive keyboard console β€” [s] stats Β· [l] boards Β· [c] create Β· [d] delete Β· [q] quit

RustChan board view RustChan thread view RustChan mobile view

πŸ”Œ Optional Integrations: ffmpeg & Tor

RustChan is fully functional without either tool. When detected at startup, additional capabilities activate automatically.

ffmpeg β€” Video & Audio Processing

When ffmpeg is available on PATH:

  • MP4 β†’ WebM transcoding (VP9 + Opus) for maximum browser compatibility
  • AV1 WebM β†’ VP9 re-encoding for browsers without AV1 support
  • Audio waveform thumbnails via the showwavespic filter
  • Video thumbnail extraction from the first frame for catalog previews

Without ffmpeg, videos are served in their original format and audio posts use a generic icon. Set require_ffmpeg = true in settings.toml to enforce its presence at startup. The ffmpeg execution timeout is configurable via ffmpeg_timeout_secs (default: 120).

See SETUP.md β€” Installing ffmpeg for platform-specific instructions.

Tor β€” Onion Service

When enable_tor_support = true and a Tor daemon is running:

  • The .onion address is read from the hidden-service hostname file and displayed on the home page and admin panel
  • Setup hints are printed to the console if Tor is detected but not yet configured

Tor handles all onion routing independently β€” RustChan binds to its normal port while your torrc forwards .onion traffic to it.

See SETUP.md β€” Installing Tor for configuration details.


⚑ Quick Start

# 1. Build
cargo build --release

# 2. Create an admin account
./rustchan-cli admin create-admin admin "YourStrongPassword!"

# 3. Create 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-cli

Open http://localhost:8080 β€” the admin panel is at /admin.

On first launch, rustchan-data/settings.toml is generated with a fresh cookie_secret and all settings documented inline. Edit and restart to apply changes.


πŸ“ Data Layout

All data lives in rustchan-data/ alongside the binary. Nothing is written elsewhere unless explicitly overridden via environment variables.

rustchan-cli                              ← single self-contained binary
rustchan-data/
β”œβ”€β”€ settings.toml                         ← instance configuration (auto-generated)
β”œβ”€β”€ chan.db                               ← SQLite database (WAL mode)
β”œβ”€β”€ full-backups/                         ← full site backups
β”‚   └── rustchan-backup-20260304_120000.zip
β”œβ”€β”€ board-backups/                        ← per-board backups
β”‚   └── rustchan-board-tech-20260304_120000.zip
└── boards/
    β”œβ”€β”€ b/
    β”‚   β”œβ”€β”€ <uuid>.<ext>                  ← uploaded files
    β”‚   └── thumbs/
    β”‚       └── <uuid>_thumb.jpg          ← auto-generated thumbnails & waveforms
    └── tech/
        β”œβ”€β”€ <uuid>.<ext>
        └── thumbs/

βš™οΈ Configuration

settings.toml

Auto-generated on first run. Edit and restart to apply.

# Site identity
forum_name = "RustChan"
site_subtitle = "A self-hosted imageboard"

# Default theme served to new visitors. Options: terminal, frutiger-aero,
# dorific-aero, fluorogrid, neoncubicle, chan-classic
default_theme = "terminal"

# TCP port (binds to 0.0.0.0:<port>).
port = 8080

# Upload size limits (MiB).
max_image_size_mb = 8
max_video_size_mb = 50
max_audio_size_mb = 150

# Auto-generated on first run. Do not change after first use β€”
# existing IP hashes and bans will become invalid.
cookie_secret = "<auto-generated 32-byte hex>"

# Display .onion address if a Tor daemon is running.
enable_tor_support = false

# Hard-exit if ffmpeg is not found (default: warn only).
require_ffmpeg = false

# Maximum time (seconds) to allow a single ffmpeg job to run.
ffmpeg_timeout_secs = 120

# WAL checkpoint interval in seconds (0 = disabled).
wal_checkpoint_interval_secs = 3600

# Automatic VACUUM: compact the database this many hours after startup,
# then repeat on the same interval. Set to 0 to disable.
auto_vacuum_interval_hours = 24

# Expired poll vote cleanup interval (hours). Vote rows for expired polls
# are deleted; poll questions and options are preserved.
poll_cleanup_interval_hours = 72

# Show a red warning banner in the admin panel when the DB exceeds this size.
db_warn_threshold_mb = 2048

# Maximum number of pending background jobs. Excess jobs are dropped with
# a warning log rather than causing OOM under a post flood.
job_queue_capacity = 1000

# Maximum waveform/thumbnail cache size per board's thumbs/ directory (MiB).
# A background task evicts oldest files when the limit is exceeded.
waveform_cache_max_mb = 200

# Tokio blocking thread pool size. Defaults to logical_cpus Γ— 4.
# Tune downward on memory-constrained hardware (e.g. Raspberry Pi).
# blocking_threads = 16

# Archive overflow threads globally before any hard-delete, even on boards
# where per-board archiving is disabled.
archive_before_prune = true

Environment Variables

All settings can be overridden via environment variables, which take precedence over settings.toml.

Variable Default Description
CHAN_FORUM_NAME RustChan Site display name
CHAN_SITE_SUBTITLE (from settings.toml) Home page subtitle
CHAN_DEFAULT_THEME terminal Default theme for new visitors
CHAN_PORT 8080 TCP port
CHAN_BIND 0.0.0.0:8080 Full bind address (overrides CHAN_PORT)
CHAN_DB rustchan-data/chan.db SQLite database path
CHAN_UPLOADS rustchan-data/boards Uploads directory
CHAN_COOKIE_SECRET (from settings.toml) CSRF tokens and IP hashing key
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 (px)
CHAN_BUMP_LIMIT 500 Replies before a thread stops bumping
CHAN_MAX_THREADS 150 Max threads per board before pruning/archiving
CHAN_RATE_POSTS 10 Max POSTs per rate window per IP
CHAN_RATE_WINDOW 60 Rate-limit window (seconds)
CHAN_SESSION_SECS 28800 Admin session duration (default: 8 hours)
CHAN_BEHIND_PROXY false Trust X-Forwarded-For behind a reverse proxy
CHAN_HTTPS_COOKIES (same as CHAN_BEHIND_PROXY) Set Secure flag on session cookies
CHAN_WAL_CHECKPOINT_SECS 3600 WAL checkpoint interval; 0 to disable
CHAN_AUTO_VACUUM_HOURS 24 Scheduled VACUUM interval (hours); 0 to disable
CHAN_POLL_CLEANUP_HOURS 72 Expired poll vote cleanup interval (hours)
CHAN_DB_WARN_MB 2048 DB size warning threshold (MiB)
CHAN_JOB_QUEUE_CAPACITY 1000 Max pending background jobs
CHAN_FFMPEG_TIMEOUT_SECS 120 Max duration for a single ffmpeg job
CHAN_WAVEFORM_CACHE_MB 200 Max waveform thumbnail cache per board (MiB)
CHAN_BLOCKING_THREADS cpus Γ— 4 Tokio blocking thread pool size
CHAN_ARCHIVE_BEFORE_PRUNE true Archive globally before any hard-delete
CHAN_TOR_HOSTNAME_FILE (auto-detected) Override path to the Tor hostname file
RUST_LOG rustchan-cli=info Log verbosity

πŸ’Ύ Backup & Restore

The entire backup system is accessible from the admin panel β€” no shell access required. All backup operations stream from disk in 64 KiB chunks; peak RAM overhead is roughly 64 KiB regardless of instance size. Backups are written to disk as temp files with an atomic rename on success, so partial backups never appear in the saved list.

Full Site Backups

A full backup is a .zip containing a consistent SQLite snapshot (via VACUUM INTO) and all uploaded files.

Action Description
πŸ’Ύ Save Creates a backup and writes it to rustchan-data/full-backups/
⬇ Download Streams a saved backup to your browser
β†Ί Restore (server) Restores from a file already on the server
β†Ί Restore (upload) Restores from a .zip uploaded from your computer (max 512 MiB)
βœ• Delete Permanently removes the backup file

Per-Board Backups

Board backups are self-contained: a board.json manifest plus the board's upload directory. Other boards are never affected.

Restore behaviour:

  • Board exists β†’ content is wiped and replaced from the manifest
  • Board doesn't exist β†’ created from scratch
  • All row IDs are remapped on import to prevent collisions

Restore uses SQLite's sqlite3_backup_init() API internally β€” pages are copied directly into the live connection, so no file swapping, WAL deletion, or restart is needed.


🧰 Admin CLI

# 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]
./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.


πŸš€ Production Deployment

See SETUP.md for a complete production guide covering:

  • System user creation and hardened directory layout
  • systemd service with security directives
  • nginx reverse proxy with TLS via Let's Encrypt
  • ffmpeg and Tor installation on Linux, macOS, and Windows
  • First-run configuration walkthrough
  • Raspberry Pi SD card wear reduction and blocking thread tuning
  • Security hardening checklist

Cross-Compilation

# ARM64 (Raspberry Pi 4/5)
rustup target add aarch64-unknown-linux-gnu
cargo install cross
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-gnu

The release profile enables strip = true, lto = "thin", and panic = "abort". Typical binary size: 12–18 MiB.


πŸ—οΈ Architecture

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 (manually sized blocking pool)
Database SQLite via rusqlite (bundled, 32 MiB page cache)
Connection pool r2d2 + r2d2_sqlite (5-second acquisition timeout)
Image processing image crate + kamadak-exif for JPEG orientation
Video transcoding ffmpeg (optional, configurable timeout)
Audio waveforms ffmpeg showwavespic filter (optional)
Password hashing argon2 crate (Argon2id)
Timing-safe comparison subtle crate
Response compression tower-http CompressionLayer (gzip, Brotli, zstd)
HTML rendering Plain Rust format! strings
Configuration settings.toml + env var overrides via once_cell::Lazy
Logging tracing + tracing-subscriber

Source Layout

src/
β”œβ”€β”€ main.rs             β€” entry point, router, background tasks, keyboard console
β”œβ”€β”€ config.rs           β€” settings.toml + env var resolution
β”œβ”€β”€ error.rs            β€” error handling and ban page rendering
β”œβ”€β”€ models.rs           β€” database row structs
β”œβ”€β”€ middleware/mod.rs    β€” rate limiting, CSRF, IP hashing, proxy trust
β”œβ”€β”€ workers/mod.rs       β€” background job queue, media transcoding, cache eviction
β”œβ”€β”€ handlers/
β”‚   β”œβ”€β”€ admin.rs        β€” admin panel, moderation, backup/restore, appeals
β”‚   β”œβ”€β”€ board.rs        β€” board index, catalog, archive, search, thread creation
β”‚   β”œβ”€β”€ mod.rs          β€” streaming multipart, shared upload helpers
β”‚   └── thread.rs       β€” thread view, replies, polls, editing
β”œβ”€β”€ db/
β”‚   β”œβ”€β”€ mod.rs          β€” connection pool, schema init, shared helpers
β”‚   β”œβ”€β”€ boards.rs       β€” site settings, board CRUD, stats
β”‚   β”œβ”€β”€ threads.rs      β€” thread listing, creation, mutation, archiving, pruning
β”‚   β”œβ”€β”€ posts.rs        β€” post CRUD, file deduplication, polls, job queue
β”‚   └── admin.rs        β€” sessions, bans, word filters, reports, mod log, appeals
β”œβ”€β”€ templates/
β”‚   β”œβ”€β”€ mod.rs          β€” base layout, pagination, timestamp formatting, utilities
β”‚   β”œβ”€β”€ board.rs        β€” home page, board index, catalog, search, archive
β”‚   β”œβ”€β”€ thread.rs       β€” thread view, post rendering, polls, edit form
β”‚   β”œβ”€β”€ admin.rs        β€” login page, admin panel, mod log, VACUUM results, IP history
β”‚   └── forms.rs        β€” new thread and reply forms
└── utils/
    β”œβ”€β”€ crypto.rs       β€” Argon2id, CSRF, sessions, IP hashing, PoW verification
    β”œβ”€β”€ files.rs        β€” upload validation, thumbnails, EXIF stripping + orientation
    β”œβ”€β”€ sanitize.rs     β€” HTML escaping, markup (greentext, spoilers, dice, embeds)
    └── tripcode.rs     β€” SHA-256 tripcode generation

πŸ” Security Model

Concern Implementation
Passwords Argon2id (t=2, m=65536, p=2) β€” memory-hard, GPU-resistant
Brute-force Progressive lockout after 5 failed admin login attempts per IP
Sessions HttpOnly, SameSite=Strict, Max-Age aligned to server config
CSRF Double-submit cookie with constant-time token comparison (subtle::ct_eq)
Security headers CSP (script-src 'self', no unsafe-inline), HSTS (1 year + subdomains), Permissions-Policy
Inline JavaScript Fully eliminated β€” all JS in external files; CSP enforced with no unsafe-inline
IP privacy Raw IPs never stored or logged β€” HMAC-keyed SHA-256 hash used everywhere
Rate limiting Sliding-window per hashed IP: POST endpoints (10/min), page-load GETs (60/min); /api/ routes excluded
Proxy support All handlers use proxy-aware IP extraction when CHAN_BEHIND_PROXY=true
File safety Content-Type + magic byte validation; file extensions never trusted
EXIF stripping All JPEG uploads re-encoded β€” GPS, device IDs, and all metadata discarded; EXIF orientation applied before strip
XSS All user input HTML-escaped before rendering; markup applied post-escape
Zip-bomb protection Backup restore capped at 1 GiB per entry, 50,000 entries max
Backup upload cap Full and board restore endpoints reject uploads over 512 MiB
Redirect hardening Backslash and percent-encoded variants blocked on return_to parameters
Path traversal Backup filenames validated against [a-zA-Z0-9._-] before filesystem access
Body limits Per-route limits on small endpoints (64 KiB) to prevent memory exhaustion
Connection pool 5-second acquisition timeout prevents thread-pool exhaustion under load
PoW CAPTCHA SHA-256 hashcash (20-bit difficulty), verified server-side with 5-minute grace window; covers threads and replies
PoW nonce replay Used nonces tracked in memory; stale entries auto-pruned after the validity window expires
Job queue Capped at job_queue_capacity; excess jobs logged and dropped, never causing OOM
Streaming uploads Multipart fields validated against size limits in flight; memory use bounded regardless of payload

πŸ“ Post Markup Reference

>quoted text              greentext line
>>123                     reply link to post #123
>>>/board/                cross-board index link
>>>/board/123             cross-board thread link (with hover preview)
**text**                  bold
__text__                  italic
[spoiler]text[/spoiler]   hidden until clicked or hovered
[dice NdM]                server-side dice roll (e.g. [dice 2d6] β†’ 🎲 2d6 β–Έ βš„ βš… = 11)
:fire:  :think:  :based:  :kek:  …  (25 emoji shortcodes)

🎨 Themes

Six built-in themes, selectable via the floating picker on every page. Persisted in localStorage with no flash on load. The site-wide default for new visitors is set via default_theme in settings.toml or from the admin panel.

Theme Description
Terminal (default) Dark background, matrix-green monospace, glowing accents
Frutiger Aero Frosted glass panels, pearl-blue gradients, rounded corners
DORFic Aero Dark stone walls, torchlit amber/copper glass panels
FluoroGrid Pale sage, muted teal grid lines, dusty lavender panels
NeonCubicle Cool off-white, horizontal scanlines, steel-teal borders
ChanClassic Light tan/beige background, maroon accents, blue post-number links β€” classic imageboard styling

πŸ“‹ Changelog

See CHANGELOG.md for the full version history.

Latest β€” v1.0.13: Scheduled VACUUM Β· expired poll vote cleanup Β· DB size warning banner Β· job queue back-pressure Β· duplicate media job coalescing Β· configurable ffmpeg timeout Β· global archive_before_prune flag Β· waveform cache eviction Β· streaming multipart Β· ETag / Conditional GET (304) Β· gzip/Brotli/zstd response compression Β· manual Tokio blocking pool sizing Β· EXIF orientation correction Β· streaming backup I/O (peak RAM ~64 KiB) Β· ChanClassic theme Β· default_theme + site_subtitle in settings.toml Β· default theme selector in admin panel Β· admin panel reorganised Β· prepared statement caching audit Β· RETURNING clause for inserts Β· 32 MiB SQLite page cache Β· two new DB indexes (idx_posts_thread_id, idx_posts_ip_hash)

v1.0.12: Database module split into 5 focused files Β· template module split into 5 focused files Β· PoW bypass on replies fixed (critical) Β· PoW nonce replay protection Β· inline JS fully eliminated (script-src 'self' CSP) Β· backup upload size cap (512 MiB) Β· post rate limiting simplified Β· /api/ routes excluded from GET rate limit Β· trailing slash 404s fixed

v1.0.11: Security headers (CSP, HSTS, Permissions-Policy) Β· proxy-aware IP extraction on all handlers Β· GET rate limiting (60 req/min) Β· zip-bomb protection on restore Β· IP hashing everywhere Β· admin brute-force lockout Β· constant-time CSRF comparison Β· poll input caps Β· session cookie Max-Age Β· connection pool timeout Β· per-route body limits Β· open redirect hardening Β· worker exponential backoff Β· file dedup race fix Β· per-post ban+delete Β· ban appeal system Β· PoW CAPTCHA Β· video embeds Β· cross-board hover previews Β· new-reply pill Β· live thread metadata Β· "(You)" tracking Β· spoiler text

v1.0.9: Per-board editing toggle Β· configurable edit window Β· per-board archive toggle Β· AV1β†’VP9 transcoding fix

v1.0.8: Thread archiving Β· mobile reply drawer Β· dice rolling Β· sage Β· post editing Β· draft autosave Β· WAL checkpointing Β· VACUUM button Β· IP history

v1.0.7: EXIF stripping Β· image+audio combo posts Β· audio waveform thumbnails

v1.0.6: Web-based backup management Β· board-level backup/restore Β· GitHub Actions CI

v1.0.5: MP4β†’WebM auto-transcoding Β· home page stats Β· macOS Tor detection fix



Built with πŸ¦€ Rust Β Β·Β  Powered by SQLite Β Β·Β  Optional integrations: ffmpeg Β· Tor

Drop it anywhere. It just runs.

About

Self-hosted imageboard engine built in Rust. Single binary, SQLite database, optional ffmpeg transcoding and Tor onion support. No containers, no runtime dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors