Discord bot for the Research Community.
TypeScript · Sapphire (discord.js) · DocumentDB (MongoDB wire protocol on Postgres) · Hono · Pino · zod
- Node.js 26 (
nvm usepicks it up from.nvmrc) - Yarn 4 via Corepack (
npm i -g corepack && corepack enable, since Node ≥ 25 doesn't bundle Corepack) - Docker (for the database, and for production)
yarn install
cp .env.example .env # add your bot token
cp config.example.yaml config.yaml # add your guild ID
yarn db:up # starts DocumentDB on 127.0.0.1:10260
yarn dev # compiles in watch mode and restarts the bot on changes| Script | What it does |
|---|---|
yarn dev |
tsc watch mode and restarts the bot on every successful build |
yarn build |
Compile src/ to dist/ |
yarn start |
Run the compiled bot |
yarn check |
Typecheck, lint, format check and tests |
yarn lint |
Oxlint with type-aware rules |
yarn format |
Format everything with Oxfmt |
yarn test |
Vitest |
yarn config:schema |
Regenerate config.schema.json after changing the config schema |
yarn db:up/down |
Start/stop the dev database |
.env: secrets and infrastructure (token, database URL, ports). Validated bysrc/lib/env.ts.config.yaml: guild-specific settings (IDs, presence, …). Validated bysrc/lib/config.ts. Editors with the YAML extension get autocomplete fromconfig.schema.json.
The bot refuses to start, with a readable error, if either one is invalid.
The stack is built and run on the server with Docker Compose. Caddy on the host terminates TLS
and proxies to 127.0.0.1:3050.
cp .env.example .env && cp config.example.yaml config.yaml # fill in; set DB_PASSWORD
git pull && docker compose up -d --build
docker compose logs -f alfredThe database runs on an internal-only network with no published ports. Back it up with
docker compose exec documentdb pg_dumpall ... (the documents are stored in Postgres).
The rules panel's Verify link points to onboarding.publicUrl in config.yaml. Caddy
serves that domain and proxies it to Alfred:
verify.research-community.org {
reverse_proxy 127.0.0.1:3050
}- Set
ALTCHA_HMAC_KEYin.env(generate one withopenssl rand -base64 48). Alfred refuses to start without it whileonboardingis configured. - Mark the server age-restricted in Discord (Server Settings → Access → Age-Restricted Server). That's the real age barrier: Alfred doesn't check ages and warns at startup if the setting is off.
- Health check:
GET /healthzreturns 200 once the bot is connected to Discord.
- Stop the old bot. Close any open tripsit tickets it created (they're named after members).
- Fill in
config.yaml(onboarding,tickets,nationality) and.env, then start Alfred. - Point the verification domain at Alfred in Caddy. Old
/captchalinks show an "outdated" page. - The old rules and ticket panels keep working (their buttons are still recognised).
Re-post them with
/panelwhenever convenient for the new look.