| name | deploy |
| description | Use when deploying AI Poker Wizard bot to production, updating the running container, or troubleshooting deployment issues. Triggers on "deploy", "部署", "上線", "restart bot", "重啟". |
Deploy AI Poker Wizard
Prerequisites
Before deploying, ensure:
-
.env exists at project root with all required vars:
BOT_TOKEN — Telegram bot token
GEMINI_API_KEY — Gemini API key
SUPABASE_CONN — Supabase pooler connection string (transaction mode, port 6543)
SUPABASE_ACCESS_TOKEN — Supabase personal access token (for CLI migrations)
ADMIN_CHAT_ID — admin Telegram user ID for token expiry alerts
-
The owner row (OWNER_CHAT_ID) has a valid users.gto_refresh_token.
-
.game_modes_cache.json exists (can be empty {} — will populate on first use)
-
Docker and docker compose are installed
-
Supabase CLI installed (supabase --version). Project already linked (supabase/ dir).
Deploy Steps
Step 1: Run regression tests
python scripts/regression_test.py
regression_test.py loads .env itself (python-dotenv) — do not wrap it in
set -a && source .env (that shell form is blocked in this repo).
GTOW token for API-backed tests comes from the DB. On
startup the entry point reads the owner's users.gto_refresh_token
(OWNER_CHAT_ID env → that user's row) and injects it as GTOW_REFRESH_TOKEN,
so the suite mints access from the same GTOW session the owner's browser/
extension keeps fresh. This avoids opening a second GTOW session and triggering
FORCED_LOGOUT. You'll see a [gto-token] using owner … DB token line confirming
the shared-session path. Failure to resolve the DB token is fatal for API cases.
All non-API tests must pass. Transient GTOW API failures (429 rate-limit, or a
FORCED_LOGOUT when the owner's DB token itself is stale) are expected and OK to
ignore — refresh the owner's session (refresh-gto-token skill or just re-open
the GTOW browser tab so the extension re-syncs) if the API tests must pass.
Step 2: Commit and push changes
Only if there are uncommitted changes. Do NOT commit .env.
Step 3: Build and deploy
bash scripts/deploy.sh
This runs:
git pull
supabase db push — apply pending migrations to remote DB
docker compose build && docker compose up -d — rebuild and restart container
The old container receives SIGTERM, finishes any in-flight analysis (up to 5 min grace period), then the new container starts.
Step 4: Verify
docker compose ps
docker compose logs --tail=30
docker compose logs | grep -i database
Step 5: Mandatory Chrome extension release gate
bash scripts/deploy.sh deploys the bot, edge function, and DB — it does NOT
publish the Chrome extension. The extension ships as a separate GitHub release
(ext-vX.Y.Z) and has no update_url, so users stay on the old build until you publish.
After every deploy, compare the latest extension release tag with deployed main:
LATEST_EXT_TAG=$(gh release list --limit 100 --json tagName \
--jq '[.[].tagName | select(startswith("ext-v"))][0]')
git diff --quiet "$LATEST_EXT_TAG"..HEAD -- chrome-extension/ || EXTENSION_CHANGED=1
If any tracked file under chrome-extension/ changed, the deploy is not complete until
a new extension release is published and its zip asset is verified. Do not limit this gate
to changes guessed to be user-facing; any extension diff triggers it. The request to deploy
authorizes publishing the matching extension release, so do not ask for a second confirmation.
Publish after the backend deploy so any new edge-function routes already exist:
Use the release-extension skill — it covers version bump, scripts/package_extension.sh,
and gh release create ext-v${VERSION} with the notes template.
Backend-only deploys skip the release. Before reporting success, state either the published
extension tag + asset URL or that the comparison found no extension changes.
Database Migrations (Supabase CLI)
Schema is managed by Supabase CLI. Migration files live in supabase/migrations/.
The project is already linked to ivtfzwsytdkdqolzxhkh (ap-northeast-2).
Creating a new migration
supabase migration new <name>
This creates supabase/migrations/<timestamp>_<name>.sql. Write your DDL in that file.
Applying migrations to remote DB
set -a && source .env && set +a
supabase db push
This is also run automatically by scripts/deploy.sh. Uses the linked project — requires SUPABASE_ACCESS_TOKEN in env.
Checking migration status
set -a && source .env && set +a
supabase migration list
NEVER use psql directly
Do NOT run psql "$SUPABASE_CONN" -f migration.sql to apply migrations. This bypasses Supabase's migration tracking and supabase db push will fail or re-apply. Always use supabase db push — it tracks which migrations have been applied.
Important: adding tables
If you add a new table, also add its name to _REQUIRED_TABLES in src/database.py so the startup check catches missing migrations.
What Happens on Startup
- Bot connects to Supabase via
asyncpg pool (transaction pooler, statement_cache_size=0)
- Verifies required tables exist (fails fast with clear error if migrations haven't been applied)
- Bot starts polling Telegram for updates (open to all users with a GTO Wizard token)
Troubleshooting
Bot won't start — "Table not found"
Migrations haven't been applied. Run:
set -a && source .env && set +a
supabase db push
Bot won't start — other errors
docker compose logs --tail=50
GTO Wizard token expired
Re-login to GTO Wizard and use the extension sync or /settoken; the bot reads users.gto_refresh_token on the next request.
DB connection failed
Check SUPABASE_CONN in .env. Uses Supabase transaction pooler (port 6543, IPv4).
Format: postgresql://postgres.<ref>:<password>@aws-1-ap-northeast-2.pooler.supabase.com:6543/postgres
Rollback
git log --oneline -5
git checkout <commit>
docker compose build && docker compose up -d
Architecture
Host Container (/app)
─────────────────────────────────────────────
.env ──── env_file ────────→ environment vars
.game_modes_cache.json ───→ /app/.game_modes_cache.json
logs/ ──── volume ────────→ /app/logs/
stop_grace_period: 5m — in-flight HH analysis (up to 10 min timeout) finishes before container dies
restart: unless-stopped — auto-restart on crash
- DB schema managed by Supabase CLI migrations (
supabase/migrations/)
- DB connection uses Supabase transaction pooler (IPv4, port 6543,
statement_cache_size=0)