Files
5ducky/README.md
kibiandClaude Opus 5.5 d0f2d526df Port 4ducky to Rust (Axum + SQLite)
Same design, layout, URLs and features as the PHP version: browse grid,
image view with AJAX comments, BBCode, likes, uploads (file, URL and
bookmarklet), quicktagger, profile, block list, inbox, invites, Hall of
Ducks, Truhe, abuse reports and the admin tools.

- Parameterized SQL, escaped templates (askama), argon2 passwords,
  server-side sessions, CSRF tokens on every write, strict script CSP
- SSRF-safe remote image fetching, magic-byte upload checks
- Self-hosted captcha replaces reCAPTCHA v1; analytics/social widgets removed
- Vanilla JS replaces jQuery and its plugins with the same DOM behaviour
- Reverse proxy support (frontend_path, trust_proxy, secure_cookies)
- Admin CLI: admin, settings, invites

See README.md for running it and the full list of changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:15:05 +02:00

4.6 KiB
Raw Permalink Blame History

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'): inline onclick/<script> became data-* hooks in media/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, /shop keep 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 in 4ducky.css).
  • Quicktagger referenced a missing #home element and never worked.

The original content is kept verbatim, including rank badges and texts, for archival purposes.