mod-oud
A self-hostable Discord moderation bot with an accompanying web dashboard. 28 features, blazingly fast, and scalable.

What it is
A self-hostable Discord moderation bot written in Rust, with a companion web dashboard in Next.js. The bot serves its own HTTP API, and the dashboard talks to that API and to Postgres directly.
The point of the project was to replace a pile of small Discord bots with one binary and one dashboard that a single operator can actually run and reason about.
Architecture
One Rust binary runs the gateway bot, the web server, or both, selected by env flags:
RUN_BOT=true
RUN_WEB=true
RUN_MIGRATIONS=trueSharding
Gateway sharding is handled by index and count, so each shard is its own container sharing Postgres and Redis:
SHARD_INDEX=0
TOTAL_SHARDS=2Concurrency without double-execution
Background and cron jobs take distributed Redis locks before doing work, so N replicas never run the same job twice. This is the main reason Redis is a hard dependency rather than a cache.
No inbound ports
Production sits behind a Cloudflare Tunnel, so the host exposes nothing publicly. The compose file that ties it together is docker-compose.prod.yml; docker-compose.multi.yml covers spreading many guilds across several hosts.
Feature slices
Every feature is a vertical slice under src/features/ — 28 of them:
automod bad_words birthday custom_commands
economy gambling giveaways invite_tracking
join_leave leveling live_feed media_only
member_counter message_logging moderation music
raid_detection reaction_roles reminder reporting
search starboard temp_voice tickets
verification warning ...
Two categories:
- Moderation & safety — automod, bad-word rulesets, raid detection, warnings, moderation actions, media-only channels, human verification (Turnstile / hCaptcha), reporting, message logging, invite tracking.
- Community — leveling with rewards and multipliers, starboard, giveaways, birthdays, reaction roles, custom commands, reminders, member counters, join/leave messages, temp voice hubs, tickets, live feed, search integrations (Spotify, YouTube, Genius, GIPHY, TMDB, RAWG), and music playback via Songbird.
The convention is strict: code lives inside the feature that owns it, and nothing is shared until three features genuinely need it.
Compile-time SQL
Queries use SQLx with offline metadata. .sqlx/ is committed, so the project compiles with no database running:
cargo build --release # no live DB requiredMigrations run automatically on startup — 38 of them so far. Every query is compile-checked, so a typo against the schema is a build failure rather than a runtime surprise.
The dashboard
A separate Next.js app, 32 routes, talking to the bot's API. Its Server Actions reach Redis for cache invalidation and Postgres for config.
Markdown is rendered with react-markdown plus KaTeX for math, Prism for syntax highlighting, and rehype-slug for heading anchors:
import { MarkdownWithToc } from "@/components/ui/markdown/MarkdownWithToC";
<MarkdownWithToc content={post.body} />(The exact same architecture that my portfolio site uses)
Code fences get a language label resolved from a generated GitHub Linguist extension map, and a copy button.
Linting as design pressure
The Rust side runs pedantic and nursery Clippy lints, with missing_docs and rustdoc::all denied:
[lints.rust]
missing_docs = "deny"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }missing_docs = "deny" is the strict one. Every public item needs a doc comment or the build fails. It sounds fussy until you try reading unfamiliar code six months later.
By the numbers
| Rust source files | 449 |
| Rust lines | ~56,400 |
| Dashboard TS/TSX files | 478 |
| Dashboard lines | ~47,800 |
| Feature slices | 28 |
| Dashboard routes | 32 |
| Migrations | 38 |
| Commits | 297 |
| Edition | Rust 2024 |