-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Developer PC
└─ git push to GitHub main
GitHub Actions (7 GB RAM runner)
├─ docker build → TypeScript compiles INSIDE the multi-stage image (tsc, builder stage)
├─ push image → ghcr.io/jason-tucker/squishybot:{sha, latest}
├─ run the built image once → node dist/bot/registerCommands.js (deploy slash commands)
└─ SSH to VPS → git reset --hard origin/main && docker compose pull && up -d
VPS
├─ watchtower → polls :latest (~30s), restarts the container when the digest changes
├─ entrypoint → drizzle-kit push --force (syncs schema to Postgres on every start)
└─ node dist/index.js ← bot runs from compiled JS
Two things deploy a new image to the VPS: the CI SSH step (fast path) and watchtower (polls :latest). The container carries com.centurylinklabs.watchtower.enable="true".
The VPS has ~900 MB free RAM. tsc OOMs it. Never run pnpm build / tsc on the VPS. TypeScript compiles only in the Docker builder stage (with --max-old-space-size=4096) on the CI runner; the production stage copies dist/ + node_modules and runs node dist/index.js.
src/bot/events/ready.ts:
-
loadSettings()first (so presence can read persisted state), theninitPresence. - In parallel:
loadGames,loadSocialFeeds, resolve bot owners. - Start the schedulers: birthday scheduler, social poller, game auto-archiver, reaction-role cache + cleanup.
- Redis fan-out:
publishReady+ a 60spublishHeartbeatonbot.squishy.bot.*. -
runReconciler()— repair voice state (see below). -
startRpcServer()(botpanel command bus) andstartCacheInvalidator()(cache-reload subscriber). - DM the bot owner a Components V2 boot card (version, build SHA, reconciler stats, disabled feature flags).
| Service | File | What it does |
|---|---|---|
| Hub Manager | services/voice/hubManager.ts |
Detects hub joins, renames the hub in place, creates the replacement hub, seeds hubs from env |
| Auto Channel | services/voice/autoChannel.ts |
Creates/deletes the voice+text pair, sets permission overwrites, applies per-hub defaults |
| Auto Naming | services/voice/autoNaming.ts |
Presence-driven rename (every member counts, not just the owner); shared by presenceUpdate, the Auto/Counter templates, and reconciler retry |
| Control Panel | services/voice/controlPanel.ts |
Posts/updates the Components V2 panel |
| Sticky | services/voice/sticky.ts |
Keeps the 📋 Open Panel button pinned at the bottom of the text channel |
| Cleanup Scheduler | services/voice/cleanupScheduler.ts |
DB-backed timers; deletes empty channels after the configured delay |
| Owner Grace | services/voice/ownerGrace.ts |
Grace window + acting-owner promotion when the owner leaves |
| Hub Lockdown | services/voice/hubLockdown.ts |
Per-hub + server-wide Connect kill switch with restore-on-boot |
| Hosts Service | services/voice/hostsService.ts |
Add/remove hosts (shared by slash + RPC) |
| Permissions | services/voice/permissions.ts |
isSudo, isOwner, isHost, syncTextChannelPermissions
|
| Reconciler | services/voice/reconciler.ts |
Startup repair (below) |
| Service | File | What it does |
|---|---|---|
| Settings | services/settings.ts |
bot_settings cache with env fallback; getSetting / getBoolSetting / setSetting
|
| Games | services/games.ts |
Game catalog cache; pref apply/sync |
| Birthday scheduler | services/birthdayScheduler.ts |
Per-minute wall-clock check; daily idempotent ping |
| Social poller |
services/social/poller.ts + rssParser.ts
|
Fetch/dedupe/repost RSS feeds (~30 min) |
| Reaction roles | services/reactionRoles.ts |
Watched-message cache + expiry cleanup |
| Game auto-archive | services/gameAutoArchive.ts |
Archives stale game channels |
| Bot owner | services/botOwner.ts |
Dynamic owner resolution from the dev-portal Team (see Bot Owner) |
| Presence | services/presence.ts |
Bot status driven by activity/errors |
| Logger | services/logger.ts |
Console + DM to the bot owner on errors |
| Event bus | services/eventBus.ts |
Redis publish on bot.squishy.* (ready/heartbeat/voice/member events) |
| RPC server |
services/rpcServer.ts + rpc/
|
botpanel command bus subscriber (below) |
| Cache invalidator | services/cacheInvalidator.ts |
Reloads caches on HMAC-signed bot.squishy.settings.invalidate events |
runReconciler() is the self-repair pass run on every boot:
- Seed hubs from
HUB_CHANNEL_IDSenv. - For each
auto_channelsrow (bounded concurrency): verify the VC exists; clean up orphans (delete the text channel + row + member rows); otherwise re-sync text-channel permissions, rebuild the panel + sticky, and sweep stale panel messages. - Backfill
auto_channel_membersfor currently-occupying members, and re-run auto-rename where the owner is present and playing. - Restore in-flight cleanup timers, owner-grace windows, and hub lockdowns.
Untracked channels inside the auto-voice category are logged only — never auto-adopted (a previous version over-reached and swept up manually-created channels).
The bot exposes most flows as RPC verbs so the botpanel web dashboard can drive them:
-
Command bus —
startRpcServerpsubscribes tocmd.squishy.*. Each envelope is HMAC-signed withBOTPANEL_RPC_SECRET; bad signatures drop with a warn. Handlers live underservices/rpc/handlers/(voice rename/lock/hide/transfer/delete, hosts, hubs lockdown, games provision/set_prefs, discord create_role/channel, play.post, report.submit, color.assign, reaction-role create/delete/expire, staff grant/revoke, users.resolve, meta pickers, admin reconciler/orphan-scan/reload-caches). Each verb delegates to the same service helper the slash flow uses, so the two surfaces stay byte-identical. -
Cache invalidate —
startCacheInvalidatorsubscribes tobot.squishy.settings.invalidate; on a verified event it reloads the matching cache (settings, reaction roles, …) so panel edits take effect without a restart. -
Event bus —
publish*onbot.squishy.*emits ready/heartbeat plus voice/member lifecycle events the panel consumes.
All three are optional: with BOTPANEL_RPC_SECRET unset or Redis down, the bot logs a warning and runs normally.
A push to main that touches src/db/schema/** fires a repository_dispatch (bot-schema-changed) at botpanel, whose companion workflow re-vendors the Drizzle schemas — closing the race where the panel's main could go red after a schema merge here.
| Layer | Tool |
|---|---|
| Language | TypeScript (strict) |
| Runtime | Node 24 — node dist/index.js in Docker |
| Discord | discord.js v14 (Components V2) |
| Database | PostgreSQL 16 + Drizzle ORM |
| Schema |
drizzle-kit push (no SQL files in git) |
| Cache/bus | Redis (ioredis) — optional |
| Env | Zod-validated .env
|
| CI/CD | GitHub Actions → GHCR → watchtower |