Skip to content
discord · bot

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.
discordsh-botApp
poise/serenityGateway
4322Health port
prodStatus

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.

  • 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.

VariableRequiredDefaultNotes
DISCORD_TOKENYes*Bot token. *Resolved from Supabase Vault in prod if unset locally.
GUILD_IDNoDev guild for fast slash-command registration
SHARD_ID / SHARD_COUNTNosingle-shardDistributed sharding
HEALTH_PORTNo4322Health server bind port
FONT_PATHNoalagard.ttfCard render font
SYMBOL_FONT_PATHNoNotoSansSymbols-Regular.ttfUnicode symbol font
DB_PATHNoLocal KV (redb) path for L2 profile cache
VariableRequiredNotes
SUPABASE_URLNoAPI URL; enables persistence + shard tracker
SUPABASE_SERVICE_ROLE_KEYNoService-role JWT; pair with SUPABASE_URL
GITHUB_TOKENNoPAT for /github + /gh commands
GITHUB_DEFAULT_REPONoDefault owner/name (default KBVE/kbve)
GITHUB_ALLOWED_REPOSNoComma-separated repo allowlist

/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.

VariableRequiredDefaultNotes
WINDMILL_BASE_URLYeshttp://windmill-app.windmill.svc.cluster.local:8000 in cluster
WINDMILL_TOKENYesBearer token; from the discordsh-windmill-token Secret
WINDMILL_ALLOWED_PATHSYesGlobset allowlist, comma-separated. Globs carry the f/ prefix (e.g. f/discordsh/*)
WINDMILL_WORKSPACENokbveWindmill workspace
WM_RATE_LIMITNo1Max calls per user per window
WM_RATE_WINDOWNo5Sliding 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.com
WINDMILL_TOKEN=<token>
WINDMILL_ALLOWED_PATHS=f/discordsh/*
WM_RATE_LIMIT=1
WM_RATE_WINDOW=5

In 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.