Station-Bot is a Discord operations platform for Star Citizen organizations, combining member verification, safety-first runtime controls, and a nomination pipeline that helps admins identify and process promising recruits with clear, auditable workflows.
Station-Bot currently provides two major capabilities:
- β Member verification workflow
/verifygenerates a verification code and verifies RSI profile ownership via the verify button flow.
- π§ Nomination and review workflow
- Community members can nominate potential recruits.
- Admins (and delegated roles) can review/process nominations.
/review-nominationsis a fast DB-backed listing view./refresh-nomination-org-statusperforms RSI org checks with retry, timeout, and rate-limit protections.
| Command | Who Can Run It | Purpose |
|---|---|---|
/verify |
Server members | Starts RSI verification flow and provides verification button. |
/healthcheck |
Server administrators | Returns bot tag, UTC time, read-only state, and active commands. |
/nominate-player |
Organization Member role (or higher) and admins |
Submits a candidate RSI handle with optional reason. |
/review-nominations |
Admins + delegated access roles | Reviews unprocessed nominations using persisted org-check status (DB-only). |
/refresh-nomination-org-status |
Admins + delegated access roles | Refreshes org membership checks for one or all unprocessed nominations. |
/process-nomination |
Admins + delegated access roles | Marks one nomination (by handle) or all unprocessed nominations as processed. |
/nomination-access |
Admins | Manages delegated roles for review/process commands (add/remove/list/reset). |
The bot defaults to read-only mode for safer deployment rollouts.
BOT_READ_ONLY_MODE=true(default)- In read-only mode:
- most commands/buttons return maintenance response with no mutations
- startup side effects are skipped (role creation + cleanup scheduler)
- slash commands remain registered
/healthcheckremains available for ops checks
- Set
BOT_READ_ONLY_MODE=false - Redeploy/restart bot
- Run
/healthcheck - Verify
/verifyand nomination commands behave as expected - Confirm logs show startup tasks and no schema errors
| Variable | Required | Description |
|---|---|---|
DISCORD_BOT_TOKEN |
Yes | Discord bot token. |
CLIENT_ID |
Yes (for command registration) | Discord application client ID. |
DATABASE_URL |
Yes for nomination features | PostgreSQL connection string. |
| Variable | Default | Description |
|---|---|---|
DEFAULT_LOCALE |
en |
Fallback locale for responses. |
BOT_READ_ONLY_MODE |
true |
Runtime safety mode. |
LOG_LEVEL |
info |
Logging verbosity. |
| Variable | Default | Description |
|---|---|---|
PG_POOL_MAX |
10 |
Max DB pool connections. |
PG_IDLE_TIMEOUT_MS |
30000 |
Idle connection timeout. |
PG_CONNECT_TIMEOUT_MS |
10000 |
Connection timeout. |
PG_STATEMENT_TIMEOUT_MS |
15000 |
Statement timeout. |
PG_SSL_ENABLED |
false |
Enable PostgreSQL TLS. |
PG_SSL_REJECT_UNAUTHORIZED |
true |
TLS cert verification behavior. |
PG_SSL_CA_PATH |
(unset) | Path to CA cert file for PG TLS. |
| Variable | Default | Description |
|---|---|---|
ORGANIZATION_MEMBER_ROLE_NAME |
Organization Member |
Minimum role name for /nominate-player. |
ORGANIZATION_MEMBER_ROLE_ID |
(unset) | Optional explicit role ID override. |
| Variable | Default | Description |
|---|---|---|
RSI_HTTP_TIMEOUT_MS |
12000 |
Per-request timeout. |
RSI_HTTP_MAX_RETRIES |
2 |
Retry attempts for transient failures. |
RSI_HTTP_RETRY_BASE_MS |
500 |
Exponential backoff base delay. |
RSI_HTTP_MAX_CONCURRENCY |
2 |
Max concurrent outbound RSI calls. |
RSI_HTTP_MIN_INTERVAL_MS |
400 |
Minimum spacing between outbound requests. |
RSI_CITIZEN_URL_PATTERN |
https://robertsspaceindustries.com/en/citizens/{handle} |
Citizen profile URL template. |
RSI_ORGANIZATIONS_URL_PATTERN |
https://robertsspaceindustries.com/en/citizens/{handle}/organizations |
Organizations URL template. |
- Node.js
>=20.11.0 - npm
- Docker (recommended for local Postgres)
npm ciCreate .env with at least (for running the bot on your host via npm run dev):
DISCORD_BOT_TOKEN=...
CLIENT_ID=...
DATABASE_URL=postgresql://station_bot:change_me@localhost:5432/station_bot
BOT_READ_ONLY_MODE=true
DEFAULT_LOCALE=enIf you run the bot on your host (npm run dev), your DB must be reachable at localhost:5432.
If you use docker compose up -d postgres, the default compose file does not publish port 5432 to the host.
Use one of these approaches:
- run Postgres locally on host (
localhost:5432) - or run the bot in Docker too (
docker compose up -d) and setDATABASE_URLhost topostgresin container env
For a full local Docker stack (bot + DB), use:
docker compose up -dnpm run migrate:upnpm run devdocker compose up -dThis starts:
postgres(persistent volume)discord-botcontainer
docker compose -f docker-compose.prod.yml up -dProduction compose uses:
- image:
ghcr.io/presstronic/station-bot:latest .env.production- bundled Postgres service by default
This project uses node-pg-migrate.
npm run migrate:create -- migration_name
npm run migrate:up
npm run migrate:downIf DATABASE_URL is configured and required schema objects are missing, startup will fail fast.
npm run lint
npm run typecheck
npm test -- --runInBand
npm run qualityCI (CI - Quality Gate) runs lint + typecheck + tests on PRs to main and pushes to main.
Container publish workflow runs on pushed v* tags and validates VERSION file alignment.
High-level release flow:
- Update
VERSION - Commit + push to
main - Tag:
v<VERSION> - Push tag
- Verify
Build & Publish Docker Imageworkflow success
- Run:
npm run migrate:up - Confirm
DATABASE_URLpoints to expected DB
- Wait for global command propagation
- Check startup logs for command registration warnings/errors
- Verify
CLIENT_IDis set
- Ensure Postgres reachable
- Confirm migrations applied
- Validate
DATABASE_URLand TLS env vars
BOT_READ_ONLY_MODEis likelytrue- Set to
falseand redeploy when ready
- Create issue
- Branch by convention (
feature/ISSUE-xx,bug/ISSUE-xx) - Open PR with tests
- Resolve review comments
- Merge to
mainand release/tag as needed
GPL-3.0 β see LICENSE.