CivUp is a Civ VI draft bot with a Discord Activity. The bot and Activity are separate Cloudflare Workers.
This page is the setup guide. See the Manual for features and the player guide for everyday use.
- Bun
- a Cloudflare account
cloudflaredfor local tunnels- two Discord apps: one for development and one for production
No privileged Discord Gateway intents are needed.
Do this once for each Discord app.
- Under General Information, copy the Application ID and Public Key. Copy the token from Bot and the client secret from OAuth2.
- Under Installation, enable Guild Install under Installation Contexts. User Install is optional. Select Discord Provided Link, then add the
applications.commandsandbotscopes under Default Install Settings > Guild Install. - In the Guild Install bot permissions, select View Channels, Send Messages, Embed Links, Attach Files, and Manage Roles.
- After installing it, keep the CivUp bot role above every role it manages.
- Under Activities > Settings, turn on Enable Activities and select Supported Platforms > Web. Keep Discord's global Launch command.
- Under OAuth2 > Redirects, add the Activity origin itself and
<activity origin>/api/auth/discord/callback. - Under Activities > URL Mappings, add prefix
/with the Activity hostname as its target. The target must omithttps://.
Use the development app only with local tunnels. Use the production app only with deployed Workers.
-
Install dependencies and copy the examples.
bun install cp cloudflared.dev.example.yml cloudflared.dev.yml cp apps/bot/.dev.vars.example apps/bot/.dev.vars cp apps/activity/.dev.vars.example apps/activity/.dev.vars
-
Generate one shared secret and put it in both
.dev.varsfiles asCIVUP_SECRET.bunx @rttnd/gau secret
Put the development app's public values and bot token in the bot file. Put its client ID and client secret in the Activity file. Both files need the same guild ID and Activity tunnel origin.
-
Create the tunnel, route your two development hostnames to it, and fill
cloudflared.dev.yml.cloudflared tunnel create civup-dev cloudflared tunnel route dns civup-dev bot-dev.example.com cloudflared tunnel route dns civup-dev activity-dev.example.com
The bot hostname goes to port 8787 and the Activity hostname to 5173. Add your Activity hostname to
server.allowedHostsinapps/activity/vite.config.ts. -
Create the local database schema.
bun run bot:l:migrate
Local D1, KV, R2, and Durable Objects are created by Wrangler. They do not need remote resources.
-
Start the bot, Activity, and tunnel before saving the Discord endpoint.
bun run dev:new
-
In the development Discord app, set the Interactions Endpoint URL to the bot tunnel URL. Add the Activity tunnel origin and callback under OAuth2 > Redirects, then map
/under Activities > URL Mappings to the Activity tunnel hostname withouthttps://. -
Install the development app in the configured server, then register its guild commands.
bun run bot:register
-
Try
/ping,/admin health, and the Launch command.
bun run dev reuses the last Activity build. bun run dev:live uses Vite live mode.
Pick the Cloudflare account first. Confirm it with bunx wrangler whoami, then fill both standard Wrangler files:
apps/bot/wrangler.jsoncapps/activity/wrangler.json
The account ID, guild ID, and Activity origin must match. The Activity DISCORD_CLIENT_ID and bot DISCORD_APPLICATION_ID must be the production app ID, and the bot DISCORD_PUBLIC_KEY must be that app's 64-character public key.
Find or change the account's workers.dev subdomain on the Cloudflare Workers & Pages page under Your subdomain. With the checked-in Worker names, the Activity origin is https://civup-activity.<account subdomain>.workers.dev.
Keep the Activity BOT service binding pointed at the bot Worker's name. Activity-to-bot traffic uses this binding; Discord interactions still use the bot Worker's public URL.
Create D1 and KV once, after selecting the right account.
bun run bot:d1:create
bun run bot:kv:createAutosave uploads are optional. To enable this niche feature, keep the AUTOSAVE_UPLOADS R2 binding and create its bucket once:
bun run bot:r2:createOtherwise, remove the r2_buckets block. The rest of the bot works normally and /admin health reports only a warning. Durable Objects are created automatically when the bot Worker deploys.
Copy the examples without committing the copies.
cp apps/bot/.prod.secrets.example apps/bot/.prod.secrets
cp apps/activity/.prod.secrets.example apps/activity/.prod.secretsThe bot Worker secrets are DISCORD_TOKEN and CIVUP_SECRET. Its file also contains the public DISCORD_APPLICATION_ID and ALLOWED_DISCORD_GUILD_ID values used by command registration; keep them in sync with apps/bot/wrangler.jsonc. Activity secrets are DISCORD_CLIENT_SECRET and CIVUP_SECRET. The sync commands filter these files to the actual Worker secrets, so public registration values are not uploaded over same-named Wrangler vars. Vite reads the browser client ID from apps/activity/wrangler.json. Generate CIVUP_SECRET with bunx @rttnd/gau secret and use the same value for both Workers.
bun run bot:secrets:prod
bun run a:secrets:prodFor the first deployment, run:
bun run deploy:prodThe command keeps the existing order: remote D1 migration, bot deploy, Activity build, then Activity deploy. Check the public Wrangler values before running it.
After both URLs exist:
- under General Information, set Interactions Endpoint URL to the bot Worker URL;
- under OAuth2 > Redirects, add the Activity origin and browser callback;
- under Activities > URL Mappings, map
/to the Activity hostname withouthttps://; - install the app in the configured server;
- register guild commands with
bun run bot:register:prod.
For later releases, this does deploy plus registration:
bun run deploy:prod:fullThe registration script always requires a guild. It does not replace Discord's global Launch command.
Browser access is optional. After its callback redirect exists, enable it with /admin setup target:Browser Access value:on. Keep the bot role above the zero-permission preference role it creates.
Smoke test /ping, /admin health, a Discord Activity launch, browser launch if enabled, and a saved-game upload if R2 is enabled.
bun run dev:new
bun run dev
bun run dev:live
bun run bot:l:migrate
bun run bot:register
bun run bot:kv:localHourly cleanup:
curl.exe "http://127.0.0.1:8787/cdn-cgi/handler/scheduled?cron=0+%2A+%2A+%2A+%2A"Leaderboard refresh every 15 minutes:
curl.exe "http://127.0.0.1:8787/cdn-cgi/handler/scheduled?cron=%2A%2F15+%2A+%2A+%2A+%2A"Daily ranked-role sync at 00:00 UTC:
curl.exe "http://127.0.0.1:8787/cdn-cgi/handler/scheduled?cron=0+0+%2A+%2A+%2A"