Standalone Sequoia homepage repo with a Rust frontend, API server, and Discord recruit monitor bot.
- Front page UI (WASM + Trunk)
- Server API:
/api/players/api/players/:username/api/guild/api/scrape-status/api/guild/raid-completions/api/health
- Background Wynncraft poller for homepage data
- App-side Postgres migrations (including recruit monitoring tables)
- Discord bot recruit monitor:
- 4-day warning alerts (based on Wynncraft guild join time)
- 7-day escalation alerts (based on Wynncraft guild join time)
- alert embeds include both current total raids and player raids completed since guild join
- promoted roles and non-recruit in-game ranks both take precedence over recruit role
This repo is fully self-managed for homepage data.
DATABASE_URL: homepage cache/snapshot Postgres DBplayer_dataplayer_data_historyplayer_poller_stateguild_snapshot
guild_metricsscrape_statusBOT_DATABASE_URLorAPP_DATABASE_URL: recruit monitor tables (can share the same Postgres DB asDATABASE_URL)BOT_ACTIVITY_DATABASE_URL(optional): recruit graid / war history source; point this atsequoia-backendto reuse canonical player snapshots
By default the repo is self-contained. If BOT_ACTIVITY_DATABASE_URL is unset, the bot falls back to the local homepage snapshot tables for graid / war history.
- Runs two background jobs on startup and on wall-clock schedules (
Europe/Stockholm):- Guild refresh every
900seconds (GUILD_POLL_INTERVAL_SECS) - Full player sweep every
3600seconds (PLAYER_FULL_SWEEP_INTERVAL_SECS)
- Guild refresh every
- Player sweeps are async and bounded (
PLAYER_MAX_CONCURRENCY=8,PLAYER_SUCCESSES_PER_MINUTE=30,PLAYER_ATTEMPTS_PER_MINUTE=45) and continue minute-to-minute until the target set is done. - Guild membership additions detected in guild refreshes enqueue incremental player sweeps for only newly-added members.
- Retention pruning runs daily and removes rows older than
RETENTION_DAYSfromguild_snapshot,guild_metrics, andplayer_data_history. - Cold start responses are stable (
200) even before first successful poll:/api/guildreturns default empty guild object/api/playersreturns{ "players": {}, "updated_at": null }/api/guild/raid-completionsreturns0/api/scrape-statusreturns the current scheduler/run status JSON
You can deploy in two ways:
- All-in-one stack:
docker-compose.coolify.yml(web + bot + dbin one resource). - Split stacks (recommended for faster bot-only redeploys):
docker-compose.coolify.web.yml(webonly)docker-compose.coolify.bot.yml(botonly)
Manual values for all-in-one stack (docker-compose.coolify.yml):
DISCORD_BOT_TOKENBOT_ACTIVITY_DATABASE_URL(optional; falls back toBOT_DATABASE_URL/the app DB when empty)BOT_ENABLE_LEGACY_COMMANDS(optional; only needed for the older guide helper commands)RUST_LOG(optional)
Auto-generated by Coolify (do not set manually unless overriding):
SERVICE_URL_WEB_3000/SERVICE_URL_WEBSERVICE_USER_DB/SERVICE_PASSWORD_DB
Manual values for split stacks:
- Web stack (
docker-compose.coolify.web.yml): use.env.coolify.web.example- Required:
DATABASE_URL
- Required:
- Bot stack (
docker-compose.coolify.bot.yml): use.env.coolify.bot.example- Required:
DISCORD_BOT_TOKEN,BOT_DATABASE_URL - Optional:
BOT_ACTIVITY_DATABASE_URLto reuse thesequoia-backendsnapshot database for graid / war history
- Required:
DATABASE_URL and BOT_DATABASE_URL can point to the same Postgres database. BOT_ACTIVITY_DATABASE_URL can also point at that same DB, or at the existing sequoia-backend Postgres service.
All-in-one stack:
- In Coolify, create a Docker Compose resource pointing at this repository.
- Set compose file path to
docker-compose.coolify.yml. - Add only the manual Discord variables from
.env.coolify.example. - Deploy the stack. Coolify should expose the
webservice (internal port3000).
Split stacks (recommended to avoid rebuilding frontend for bot-only changes):
- Create one Coolify Compose resource with
docker-compose.coolify.web.yml. - Create a second Coolify Compose resource with
docker-compose.coolify.bot.yml. - Set env vars from
.env.coolify.web.exampleand.env.coolify.bot.example. - Point both resources to the same DB URL if you want shared state.
Split stacks quick bootstrap (recommended):
- Export the values you already know:
export DATABASE_URL='postgres://user:password@host:5432/sequoia_web'
export DISCORD_BOT_TOKEN='...'
export BOT_ACTIVITY_DATABASE_URL='postgres://user:password@host:5432/sequoia'- Generate local ready-to-paste env files:
./scripts/coolify-split-setup.sh- Copy values from:
.env.coolify.web.localinto the web resource env panel.env.coolify.bot.localinto the bot resource env panel
Notes:
- All-in-one stack includes a Postgres container (
db) with persistent volume (db_data). - Split stacks expect external DB URLs (
DATABASE_URL,BOT_DATABASE_URL) from your DB resource. - DB resource network defaults:
- leave
Ports Mappingsempty (or use5432:5432only if Coolify forces a mapping) - do not use
3000:5432 - keep
Make it publicly availabledisabled unless you explicitly need external DB access
- leave
- Compose defaults are tuned for faster startup catch-up (
DATABASE_MAX_CONNECTIONS=20,PLAYER_MAX_CONCURRENCY=20,PLAYER_SUCCESSES_PER_MINUTE=90). - Docker images use
cargo-chefplanner/cook layers to cache Rust dependencies between redeploys. - Recruit monitor time thresholds use Wynncraft guild join timestamps matched against Discord names (nickname/global name/username token matching).
- Recruit alert raid metrics:
Guild Raidsshows the recruit's current total raids from latestplayer_data.Raids Since Guild Joinuses a persisted baseline (taken near Wynncraft join time fromplayer_data_history) and showsunknownwhen a baseline cannot be determined.
- Managed bootstrap flow:
- The bot starts in bootstrap mode until a profile is created from the private management guild.
- Run
/bot-admin profile init <source_guild_id> [wynn_guild_name]in the private management guild. - Add source roles with
/bot-admin roles add guild_scope:source role_kind:recruit|promoted role_id:<discord-role-id>. - Add ops roles with
/bot-admin roles add guild_scope:ops role_kind:operator|subscription role:@Role. - Add reminder rules with
/bot-admin rules add ..., then enable the profile with/bot-admin profile enable. - Source-guild roles must be entered as numeric role IDs because Discord slash role pickers only resolve roles from the current guild.
- Slash commands:
/bot-admin ...for profile, role, rule, command, and template management in the private management guild./notify-reminders [mode](toggle/on/off/status) for reminder-role signup./preview-reminder [rule_key]for a test alert preview; when omitted it previews the first enabled rule./check-unhandled [day](custom1-365, default4) for unhandled recruits./run-checkto run full sync + alert scan on demand./run-check-hereto run full sync + check immediately and post due alert previews in the current channel (no DB write, no role ping)./test-review-thread <target> [rule_key]to post a real test reminder in the current channel, create the review thread, and populate it.targetcan be a tracked recruit Wynn username or Discord user ID./player-stats <username> [page]for the paged player history charts.
- Text command equivalents are available only in the private management guild, and only when enabled for that command:
!run-check/!check-now!run-check-here/!check-here!test-review-thread <target> [rule_key]/!test-thread <target> [rule_key]!preview-reminder [rule_key|4|7|warning|escalation]
- Source guilds do not register management or operator slash commands; management and operator commands live in the private management guild.
webandbotboth auto-create required tables/schemas on startup.
Run these commands from a machine that can reach your deployed app and database.
# 1) API smoke test
./scripts/deploy-smoke-test.sh api https://your-domain.com
# 2) Recruit monitor DB checks (all guilds)
BOT_DATABASE_URL='postgres://user:pass@host:5432/sequoia_web' \
BOT_ACTIVITY_DATABASE_URL='postgres://user:pass@host:5432/sequoia' \
./scripts/deploy-smoke-test.sh db
# 3) Recruit monitor DB checks (single source guild)
BOT_DATABASE_URL='postgres://user:pass@host:5432/sequoia_web' \
BOT_ACTIVITY_DATABASE_URL='postgres://user:pass@host:5432/sequoia' \
SOURCE_GUILD_ID='123456789012345678' \
./scripts/deploy-smoke-test.sh dbTip for Coolify: if DB is not exposed publicly, run DB checks in a terminal on the same host/network as the stack.
- Start local databases:
docker compose -f docker-compose.dev.yml up -d app-db sequoia-web-dbapp-dbonlocalhost:5432(bot/recruit monitor state)sequoia-web-dbonlocalhost:5434(homepage cache/snapshots)
- Run backend API server:
DATABASE_URL=postgres://sequoia:sequoia@localhost:5434/sequoia_web \
GUILD_POLL_INTERVAL_SECS=900 \
PLAYER_FULL_SWEEP_INTERVAL_SECS=3600 \
SCRAPE_TIMEZONE=Europe/Stockholm \
PLAYER_SUCCESSES_PER_MINUTE=30 \
PLAYER_ATTEMPTS_PER_MINUTE=45 \
PLAYER_MAX_CONCURRENCY=8 \
PLAYER_MAX_RETRIES=2 \
RETENTION_DAYS=90 \
RETENTION_HOUR_LOCAL=0 \
RETENTION_MINUTE_LOCAL=5 \
PORT=3001 \
RUST_LOG=sequoia_home_server=debug \
cargo run --manifest-path server/Cargo.toml- Run frontend:
rustup target add wasm32-unknown-unknown
cargo install trunk --locked
trunk serveTrunk proxies /api/* to http://127.0.0.1:3001/api/*.
- Run recruit monitor bot:
DISCORD_BOT_TOKEN=... \
BOT_DATABASE_URL=postgres://sequoia:sequoia@localhost:5432/sequoia_app \
BOT_ACTIVITY_DATABASE_URL=postgres://sequoia:sequoia@localhost:5434/sequoia_web \
cargo run --manifest-path bot/Cargo.tomlThen, from the private management guild, run /bot-admin profile init <source_guild_id> [wynn_guild_name] and finish setup with the /bot-admin roles, /bot-admin rules, /bot-admin commands, and /bot-admin templates subcommands.