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>
This commit is contained in:
2026-10-17 02:15:05 +02:00
co-authored by Claude Opus 5.5
parent 664d8c6117
commit d0f2d526df
56 changed files with 11133 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# 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/`](legacy/).
**Stack:** Rust · Axum · SQLite (sqlx) · Askama templates · argon2 · vanilla JS
## Running
```sh
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):
```toml
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
```sh
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.