Standalone gateway botDiscordSH Bot
The standalone Discord gateway bot extracted from the axum-discordsh monolith. Built on the poise/serenity gateway with shard support, it handles every Discord interaction independently of the HTTP server — slash commands, an embed dungeon game, GitHub management, and a namespace-pinned Windmill job runner.
Allowlisted Windmill runs
Every /wm path is pinned to the f/discordsh/ namespace and matched against a globset allowlist before any call is made, with per-user rate limiting and strict validation that rejects traversal. The allowlist is mandatory — empty or absent disables the command.
- Gateway — poise/serenity with shard support.
- Health — minimal HTTP server for k8s probes.
What it gives you
Features
Slash commands
Serves /github, /gh, /dungeon, /ping, /status, /health, /restart, /cleanup, /skills, and /wm over the poise/serenity gateway.
Embed dungeon game
Runs an embed dungeon game with bevy_battle combat and bevy_inventory item management, persisting players via Supabase.
GitHub management
Manages GitHub issues and PRs with SVG card rendering from within Discord.
Windmill runner
Runs allowlisted Windmill scripts in-cluster, pinned to the f/discordsh/ namespace with per-user rate limiting.
Responsibilities
Overview & features
discordsh-bot is the standalone Discord gateway bot extracted from the axum-discordsh monolith. It handles all Discord interactions independently of the HTTP server.
Features
Section titled “Features”- Poise/serenity Discord gateway with shard support
- Slash commands:
/github,/gh,/dungeon,/ping,/status,/health,/restart,/cleanup,/skills,/wm - Embed Dungeon game with bevy_battle combat, bevy_inventory item management
- GitHub issue/PR management with SVG card rendering
- Player persistence via Supabase
- Allowlisted Windmill job runner (
/wm) - Minimal health HTTP server for k8s probes
Configuration
Environment variables
Required at runtime. In production the values come from discordsh-config (ConfigMap) and the discordsh-redis-secret / discordsh-supabase-shared / discordsh-windmill-token Secrets. For the dev container, drop them into apps/discordsh/discordsh-bot/.env (loaded via dotenvy at startup) — anything missing simply disables the related feature instead of failing the boot.
| Variable | Required | Default | Notes |
|---|---|---|---|
DISCORD_TOKEN | Yes* | — | Bot token. *Resolved from Supabase Vault in prod if unset locally. |
GUILD_ID | No | — | Dev guild for fast slash-command registration |
SHARD_ID / SHARD_COUNT | No | single-shard | Distributed sharding |
HEALTH_PORT | No | 4322 | Health server bind port |
FONT_PATH | No | alagard.ttf | Card render font |
SYMBOL_FONT_PATH | No | NotoSansSymbols-Regular.ttf | Unicode symbol font |
DB_PATH | No | — | Local KV (redb) path for L2 profile cache |
Supabase / GitHub
Section titled “Supabase / GitHub”| Variable | Required | Notes |
|---|---|---|
SUPABASE_URL | No | API URL; enables persistence + shard tracker |
SUPABASE_SERVICE_ROLE_KEY | No | Service-role JWT; pair with SUPABASE_URL |
GITHUB_TOKEN | No | PAT for /github + /gh commands |
GITHUB_DEFAULT_REPO | No | Default owner/name (default KBVE/kbve) |
GITHUB_ALLOWED_REPOS | No | Comma-separated repo allowlist |
Windmill runner (/wm)
Section titled “Windmill runner (/wm)”/wm <command> [args] runs an allowlisted Windmill script and returns its result inline. A blank invocation lists every available command. Scripts live under apps/windmill/f/discordsh/ and opt in by declaring args (array) plus an optional discord param; the bot POSTs {args, discord} to Windmill’s run_wait_result endpoint.
Bare paths collapse into the f/discordsh/ namespace, so /wm poem resolves to f/discordsh/poem. That namespace is a hard pin, not just a default — an explicit path outside it is rejected even if the allowlist would otherwise match, which also blocks prefix confusion like f/discordshEVIL/.... Paths are validated against traversal (..) and request injection before they reach the URL, and args are capped at 16 per call, 512 chars each.
| Variable | Required | Default | Notes |
|---|---|---|---|
WINDMILL_BASE_URL | Yes | — | http://windmill-app.windmill.svc.cluster.local:8000 in cluster |
WINDMILL_TOKEN | Yes | — | Bearer token; from the discordsh-windmill-token Secret |
WINDMILL_ALLOWED_PATHS | Yes | — | Globset allowlist, comma-separated. Globs carry the f/ prefix (e.g. f/discordsh/*) |
WINDMILL_WORKSPACE | No | kbve | Windmill workspace |
WM_RATE_LIMIT | No | 1 | Max calls per user per window |
WM_RATE_WINDOW | No | 5 | Sliding window length in seconds |
The allowlist is the security boundary — it is mandatory with no wildcard default. If WINDMILL_ALLOWED_PATHS is empty or absent (or any of the three required vars is missing), from_env returns None and /wm disables itself rather than falling open.
Dev-container .env example:
WINDMILL_BASE_URL=https://windmill.kbve.comWINDMILL_TOKEN=<token>WINDMILL_ALLOWED_PATHS=f/discordsh/*WM_RATE_LIMIT=1WM_RATE_WINDOW=5In production the token is the windmill-superadmin-token SealedSecret in the windmill namespace, mirrored into discordsh via ExternalSecret — see apps/kube/discordsh/manifest/discordsh-externalsecret.yaml and the cross-namespace RBAC grant in apps/kube/windmill/manifest/discordsh-windmill-token-rbac.yaml. It is a superadmin token, so the allowlist and namespace pin — not token scope — are what bound the blast radius.
Questions
Frequently asked
What is the DiscordSH Bot?
discordsh-bot is the standalone Discord gateway bot extracted from the axum-discordsh monolith. It handles all Discord interactions independently of the HTTP server, built on the poise/serenity gateway with shard support.
What can the DiscordSH Bot do?
It serves slash commands (/github, /gh, /dungeon, /ping, /status, /health, /restart, /cleanup, /skills, /wm), runs an embed dungeon game with bevy_battle combat and bevy_inventory items, manages GitHub issues and PRs with SVG card rendering, persists players via Supabase, and runs allowlisted Windmill scripts in-cluster.
How does the /wm command stay secure?
Paths are pinned to the f/discordsh/ namespace and matched against a globset allowlist before any call is made, with per-user rate limiting and strict validation that rejects path traversal. The allowlist is mandatory — if WINDMILL_ALLOWED_PATHS is empty or absent the command disables itself.