Reviewed-on: http://localhost:3000/kibi/5ducky/pulls/1
4ducky.com
Rust port of the original 4ducky.com image board (a Picturelicious fork, PHP 5 / MySQL, ~2012–2016).
Design, layout, URLs and features are kept 1:1. The original PHP source is preserved untouched in
legacy/.
Stack: Rust · Axum · SQLite (sqlx) · Askama templates · argon2 · vanilla JS
Running
cp config.example.toml config.toml # optional, defaults work for local use
cargo run --release # http://127.0.0.1:8080
Run it from the repository root: the server serves media/, images/ and favicon.ico from there.
The SQLite database and all uploads live in data_dir (default data/, git-ignored). Migrations run
automatically on start.
Behind a reverse proxy
Set these in config.toml, otherwise form posts are rejected as cross-site and every visitor
looks like 127.0.0.1 (which closes registration via the one-account-per-IP lock):
frontend_path = "https://your-domain.tld/" # the public URL, exactly as in the address bar
trust_proxy = true # take the client IP from X-Forwarded-For
secure_cookies = true # when the proxy terminates HTTPS
The proxy must append the client address to X-Forwarded-For (nginx:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;, Caddy does it by default).
Admin CLI
fourducky admin <name> [--revoke] # grant/revoke admin
fourducky settings --register on|off --upload on|off --message '<b>Dolan:</b> "..."'
fourducky invites <name> --count 5 # create invite codes (was runfav.php)
Register an account on the site first, then make it admin. User id 1 is the "superadmin" by default
(HoF / Ban links), see superadmins in the config.
Layout
| Path | What |
|---|---|
src/ |
server: routing (main.rs), sessions/CSRF (app.rs, auth.rs), pages (browse.rs, view.rs, account.rs, inbox.rs, misc.rs), uploads (uploader.rs, imaging.rs, fetch.rs), grid.rs, bbcode.rs, captcha.rs |
templates/ |
Askama templates, ported markup from legacy/templates/*.tpl.php |
media/ |
original CSS/images, plus 4ducky.js (replaces jQuery + picturelicious.js/functions.js/jqpr.js/lightbox.js) and 4ducky.css (small additions) |
migrations/ |
SQLite schema (from legacy/dumb.sql) |
legacy/ |
the original PHP site, unchanged |
Differences to the PHP version
Behaviour and look are the same; these are the deliberate changes:
Security
- All SQL is parameterized (the inbox had SQL injection; the DB layer used
preg_replace /e). - Output is escaped by default; BBCode runs on escaped text and only allows http(s) URLs. User
websites must be http(s) (no more
javascript:links). Abuse report links are only clickable when http(s). - Passwords: argon2id instead of unsalted md5. Sessions are random tokens stored hashed server-side.
- CSRF tokens on every form, AJAX call and state-changing link (logout, reset unread, manage actions); cross-site POSTs are rejected.
- Strict Content-Security-Policy (
script-src 'self'): inlineonclick/<script>becamedata-*hooks inmedia/4ducky.js. - Remote image fetching (URL upload, comment URLs, bookmarklet) blocks private/loopback/link-local addresses, limits redirects, size (8 MB) and time.
- Uploads are checked by magic bytes, decoded with size limits, and get server-generated file names.
- Admin endpoints check permissions server-side (HoF/Ban only for
superadmins); TimeBlock duration is computed on the server. - Login, register, abuse and PMs are rate limited per IP/user.
Replaced or removed
- reCAPTCHA v1 → self-hosted image captcha.
- Google Analytics, AddThis, Facebook like-box, coin widget → removed.
/chat,/forum,/shopkeep their layout and take an optional embed URL from the config. - No e-mail: accounts are active right after registering (as they were in practice).
- The bookmarklet opens a small first-party popup with a "Post" button instead of uploading from a cross-site iframe (modern browsers block third-party cookies; the old flow was also a CSRF hole).
- jQuery slimScroll → native thin scrollbars.
Bugs fixed on the way
- White-style users couldn't comment (form was inside the black-style branch).
- Blocking a user from the profile page wiped your website field.
- Comments could still be posted to closed threads via AJAX.
.b2 { width: 64x }CSS typo made guest teaser thumbnails overflow (fixed in4ducky.css).- Quicktagger referenced a missing
#homeelement and never worked.
The original content is kept verbatim, including rank badges and texts, for archival purposes.