Các bước riêng khi nâng cấp qua từng phase
Mỗi phase dưới đây chỉ cần làm một lần, khi bản deploy lần đầu chứa phase đó. Đi theo thứ tự K → L → M → N → O → P → Q → R → S nếu nhảy qua nhiều phase.
| Phase | Nội dung | Migration | Đụng tới VPS |
|---|---|---|---|
| K | Game Integration Kit (API v2, SDK, manifest) | 0029–0031 | Cấu hình Play Games trên Portal |
| L | Profile, bảng xếp hạng | 0032–0039 | Feature flag, cron tuần |
| M | Template game đầy đủ (SDK) | — | Không |
| N | Kho ảnh (upload, WebP) | 0040 | .env, volume ảnh, Nginx /media/, backup ảnh, chuyển icon cũ |
| O | GOLD/GEM, Vật phẩm, Catalog, kinh tế do server giữ, cửa hàng, IAP | 0041–0049 | Kiểm dữ liệu tiền tệ trước, cron mới, IAP, chuyển chế độ kinh tế |
| P | Cosmetic hồ sơ (huy hiệu, khung avatar), lịch sử thành tích, thưởng bảng xếp hạng | 0050 | Tạo cosmetic trên Portal + chạy script cấu hình tier |
| Q | Chiến dịch quà tân thủ/sự kiện, quà tặng kèm thưởng hồ sơ | 0051 | Bật feature flag player_inbox theo game |
| R | Shop là nơi duy nhất tạo món; Vật phẩm thưởng chọn từ Shop | 0052 | Không; báo đội vận hành đổi quy trình nhập món |
| S | Backup/restore mới, backup cấu hình, kiểm tra backup, kênh cảnh báo Telegram (bản v1.2.0) | 0053 | .env (khóa age, Telegram), cron mới, thư mục /opt/backups, kéo backup về máy |
8.1. Phase K — Game Integration Kit
Phần tiêu đề “8.1. Phase K — Game Integration Kit”Migration 0029 → 0031 tự chạy khi backend start. Làm thêm theo đúng
thứ tự, trước khi phát hành app Drone Strike build từ nhánh online
(app đó chỉ gọi /api/v2, Portal cũ sẽ trả 404):
- Backup database (§9) trước khi deploy.
- Deploy như §7, rồi chạy kiểm tra ở §5 “Kiểm tra API cho game”.
- Portal → Game
drone_strike→ Tiền tệ: phải thấyGOLDvà chỉ số chínhmax_level. - Game Registry → biểu tượng khiên trên dòng
drone_strike→ nhập OAuth Web client (id + secret) của Google Cloud project chứa Play Games Services. Thiếu bước này thì vớiIDENTITY_VERIFICATION_MODE=real, người chơi mới không link được (401). - (Tùy chọn) Remote Config: key
portal.client_policyđể bật bắt cập nhật / bảo trì; các keyclient.*được app đọc quabootstrap(trễ tối đa 30 giây). - Sau khi bản app mới đã rollout 100%: Game Registry → API Key → rotate key production của Drone Strike (key cũ từng nằm trong lịch sử git của repo game) và phát hành bản build với key mới.
Phase K không thêm biến bắt buộc nào vào .env và không cần sửa Nginx
(/api/* đã được proxy nguyên vẹn, gồm cả /api/v2).
8.2. Phase L — profile & bảng xếp hạng
Phần tiêu đề “8.2. Phase L — profile & bảng xếp hạng”Không bật PLAYER_PROFILE hoặc LEADERBOARD trước khi backend L.1–L.7,
SDK v0.2.0 trở lên và Drone Strike online đã được kiểm tra trên staging.
- Deploy migration/backend với hai flag tắt; kiểm tra
/health, bootstrap và job đóng kỳ tuần (close_leaderboard_periods.py, §10). - Bật profile trước, sau đó phát hành app staged 10% → 50% → 100%.
- Bật leaderboard và activate hai bảng vào thứ 2 theo UTC-offset của game; giữ kỳ đầu dưới rà soát thủ công.
- Mỗi thứ 2: PO rà top, loại/cấm anomaly nếu cần, rồi phát thưởng với MFA, lý do và audit log. Kiểm tra người chơi nhận quà trong Hộp thư. Bản nháp phát thưởng không bao giờ tự phát; cron chỉ nhắc khi quá 72 giờ.
- Trước khi phát hành thêm màn: tăng
max_valuecủatotal_starstrên Portal trước khi app mới lên store; ghi thay đổi vàodecisions_phase_l.md. - Chỉ chuyển
IDENTITY_RELINK_POLICY=strictkhi SDK mới chiếm ≥90% request link trong 7 ngày vàunverified <1%trong 3 ngày liên tiếp; force-update app cũ trước đó và theo dõi 401 trong 48 giờ.
Bằng chứng ghi tại tasks/phase_l/staging_week_report.md; test local không
thay được một kỳ staging thật. Xem thêm tasks/phase_l/guide_test_l.10.md.
8.3. Phase M — template game
Phần tiêu đề “8.3. Phase M — template game”Chỉ là SDK và template (portal_ui, portal_ads, tgk create), không
có gì trên VPS. Phát hành nằm trong SDK v0.3.0 (§13).
8.4. Phase N — kho ảnh
Phần tiêu đề “8.4. Phase N — kho ảnh”Phase N lưu ảnh trong volume Docker admin_portal_media_prod. Backend ghi
vào volume, Nginx đọc volume ở chế độ chỉ đọc tại /media/.
- Thêm 3 biến bắt buộc vào
.env(§3.1):PUBLIC_ASSET_BASE_URL,MEDIA_ROOT=/data/media,SERVE_MEDIA_LOCALLY=false. - Deploy như §7 (
deploy/mới chứanginx.conf.templatecó/media/và compose có volume ảnh). Kiểm tra volume ghi được (lệnh ở §5). Nếu báo không ghi được do volume cũ thuộcroot:Terminal window docker compose -f docker-compose.prod.yml exec -u root backend \chown -R appuser:appuser /data/media - Chuyển icon cũ
/game-icons/...vào kho ảnh. Luôn xem dry-run trước, kiểm tra đường dẫn và số lượng, rồi mới apply:Terminal window docker compose -f docker-compose.prod.yml exec -T backend \python scripts/migrate_game_icons_to_assets.py --dry-rundocker compose -f docker-compose.prod.yml exec -T backend \python scripts/migrate_game_icons_to_assets.py --apply - Cài lại cron (
./scripts/install_cron.sh): thêm backup ảnh 03:45 và dọn ảnh không dùng 04:40. Lần đầu nên chạy tay dry-run của dọn ảnh trước:Terminal window docker compose -f docker-compose.prod.yml exec -T backend \python scripts/prune_unused_assets.py --older-than-days 7 - Kiểm tra:
curl -I https://portal.tinysoft.io.vn/media/<game>/<kind>/<sha256>.webptrả 200 kèmCache-Control: public, max-age=31536000, immutable; bootstrap trảicon_urldạnghttps://.../media/...(lệnh ở §5).
Rollback Phase N: rollback image bằng rollback_to_version.sh, nhưng
giữ Nginx /media/ và volume admin_portal_media_prod. Image cũ không
hiểu URL /media/..., nên game client cũ có thể nhận đường dẫn tương đối
trong thời gian rollback; đây là lựa chọn an toàn cho dữ liệu ảnh. Cần
rollback cả dữ liệu thì restore DB backup ngay trước bước 3 cùng backup
ảnh của ngày đó. Không chạy docker compose down -v trên production.
Test staging bắt buộc: guide_test_n.6.md.
8.5. Phase O — GOLD/GEM, Vật phẩm, Catalog, kinh tế server, cửa hàng, IAP
Phần tiêu đề “8.5. Phase O — GOLD/GEM, Vật phẩm, Catalog, kinh tế server, cửa hàng, IAP”Migration 0041 → 0049. Ba trong số đó (0043–0045) không lùi được
(§7), nên backup trước khi deploy là bắt buộc.
Trước khi deploy — kiểm dữ liệu tiền tệ. Từ 0041, mọi game chỉ có
GOLD và GEM. Migration này cố ý dừng (backend crash-loop, log ghi
0041_gold_gem_currencies stopped: unsupported currency rows found kèm danh
sách) nếu còn ví, sổ cái, tiền tệ game hoặc luật thưởng quảng cáo dùng mã
khác. Kiểm trước trên production:
set -a; . ./.env; set +a # lấy POSTGRES_USER / POSTGRES_DBdocker compose -f docker-compose.prod.yml exec -T postgres \ psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" <<'SQL'SELECT 'wallets' AS bang, p.game_id, w.currency, count(*) FROM wallets w JOIN players p ON p.id = w.player_id WHERE w.currency NOT IN ('GOLD','GEM') GROUP BY 2, 3UNION ALLSELECT 'economy_ledger', game_id, currency, count(*) FROM economy_ledger WHERE currency NOT IN ('GOLD','GEM') GROUP BY 2, 3UNION ALLSELECT 'game_currencies', game_id, code, count(*) FROM game_currencies WHERE code NOT IN ('GOLD','GEM') GROUP BY 2, 3UNION ALLSELECT 'ad_reward_rules', game_id, reward_currency, count(*) FROM ad_reward_rules WHERE reward_currency NOT IN ('GOLD','GEM') GROUP BY 2, 3;SQLKết quả rỗng thì deploy bình thường. Có dòng thì không xóa tự động: xem
từng trường hợp (thường là dữ liệu thử tay, ví dụ ví USD hoặc tiền admin
cộng thử), quyết định xóa hoặc đổi sang GOLD/GEM, ghi lại lý do. Sổ cái
có trigger chặn xóa — muốn xóa phải tắt trigger trong cùng transaction rồi
bật lại. Riêng giá Catalog ghi GLD thì migration tự đổi sang GOLD.
Deploy và kiểm tra:
./scripts/backup_database.sh && ./scripts/backup_media.sh- Deploy như §7. Kiểm tra
alembic currentlà0049_o10_rollout_indexes (head). ./scripts/install_cron.sh— thêm các job Phase O ở §10 (đóng lượt chơi bỏ dở, dọn idempotency, retry IAP, đối soát hoàn tiền). Không bỏ qua.- Trên Portal, với từng game:
- Tiền tệ: có đủ
GOLDvàGEM; chỉnh trần GEM nếu cần. - Vật phẩm: đồ vĩnh viễn (Drone Strike:
drone_tgt,slot_1..3) có loại Vĩnh viễn và không tặng được. Mọi vật phẩm đang bán phải có icon — cửa hàng của game tự ẩn sản phẩm có vật phẩm chưa có icon. - Catalog: sản phẩm
itemtrỏ đúng vật phẩm, giáGOLD/GEM. - Cấu hình kinh tế game (Game Registry → sửa game): kho khởi đầu và trần chuyển save cũ.
- Trần thưởng mỗi lượt chơi (GOLD, GEM, mảnh ghép; có thể ghi đè theo
màn): chưa có màn hình trên web, cấu hình qua API
GET/POST/PATCH /api/v1/games/{game_id}/run-caps(quyền ghi Vật phẩm + MFA step-up, body cócurrency_or_fragments,max_per_run, tùy chọnlevel_key,min_duration_ms,reason). Chưa cấu hình thì dùng mặc định của Phase O (D17).
- Tiền tệ: có đủ
- Chạy
guide_test_o.10.mdtrên staging.
Bật IAP (gỡ quảng cáo, gói GOLD/GEM bằng tiền thật) — làm riêng, khi sẵn sàng:
- Google Play Console: tạo sản phẩm in-app với mã trùng
store_product_id_androidtrong Catalog; thêm license tester. - Service account có quyền Android Publisher API → đặt file vào
credentials/vàGOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH(§3.5). Khuyến nghị đặt thêmIAP_TOKEN_ENCRYPTION_KEY. (Tùy chọn) cấu hình Real-time Developer Notifications (GOOGLE_PUBSUB_*). - Portal → Feature flags của game → bật
iap. Khi tắt, API xác minh trả 403iap_disabledvà cửa hàng không hiện sản phẩm tiền thật. - Mua thử bằng license tester: gói GEM cộng đúng 1 lần, gỡ quảng cáo có trong state; hoàn tiền trên Play Console bị thu hồi (cron đối soát 02:25).
Theo dõi tỷ lệ chuyển save cũ (Drone Strike). Super Admin hoặc role đọc được Game Registry mở:
GET /api/v1/games/drone_strike/legacy-import-rate?window_days=7Trả số người chơi active theo last_seen_at, số đã import theo
legacy_imported_at, tỷ lệ tổng và theo ngày, cùng số anomaly
legacy_import_capped/legacy_import_invalid. Ngưỡng đề xuất: tối thiểu
90% người chơi active trong 7 ngày liên tiếp và không có spike capped/invalid
chưa giải thích.
Chuyển sang server_authoritative. Thao tác một chiều. UI Game
Registry chỉ hiện nút cho Super Admin, yêu cầu xác nhận rõ ràng và MFA
step-up; API kiểm cả ba điều kiện nên không bypass được bằng PATCH:
POST /api/v1/games/drone_strike/economy-mode/transitionX-Step-Up-Token: <token MFA dùng một lần>
{ "min_app_version": "1.2.0", "confirmation": "ENABLE_SERVER_AUTHORITATIVE", "reason": "O.10 rollout sau khi import đạt ngưỡng"}Trong cùng transaction, Portal phát hành phiên bản mới của
portal.client_policy với android.min_version và ios.min_version, đổi
games.economy_mode sang server_authoritative, ghi audit trước/sau và xóa
cache bootstrap. App cũ vì vậy gặp gate cập nhật trước khi tiếp tục ghi kinh
tế. API từ chối hạ min version và từ chối chạy lại hoặc quay về
client_reported. Game mới tạo từ template SDK v0.3.0 đã khai
economy_mode: server_authoritative trong manifest nên không cần bước này.
Rollback Phase O:
- Trước bước chuyển mode: nếu image hoặc migration lỗi, giữ game ở
client_reported, rollback image bằngrollback_to_version.sh(script sẽ chặn nếu DB đã qua0043) và restore DB từ backup chụp trước deploy. - Sau bước chuyển mode: không đổi ngược về
client_reportedvà không rollback DB để mở lại app cũ. Giữ server làm nguồn đúng, sửa lỗi bằng bản backend mới. Nếu cần khôi phục dữ liệu, lập incident và restore lên một bản sao riêng sau khi chặn traffic, không xóa volume production.
8.6. Phase P — cosmetic hồ sơ & thưởng bảng xếp hạng
Phần tiêu đề “8.6. Phase P — cosmetic hồ sơ & thưởng bảng xếp hạng”Migration 0050. Portal là nơi quyết định thưởng, thời hạn và quyền sở
hữu; game chỉ đọc và hiển thị. Thưởng mặc định cho mỗi kỳ bảng xếp hạng:
| Hạng | Huy hiệu (vĩnh viễn) | Khung avatar (7 ngày, chỉnh được theo tier) |
|---|---|---|
| #1 | hero_medal — Huân chương Anh Hùng | grand_marshal_frame — Khung Đại Nguyên Soái |
| #2 | victory_badge — Huy Hiệu Chiến Thắng | general_frame — Khung Tướng Quân |
| #3 | brave_warrior_badge — Huy Hiệu Chiến Sĩ Quả Cảm | gold_frame — Khung Vàng |
| #4–10 | liberation_badge — Huy Hiệu Giải Phóng | silver_frame — Khung Bạc |
- Backup (§9), deploy như §7, kiểm tra
alembic currentđã qua0050_profile_cosmetics. - Portal → Feature flags của game:
player_profilevàleaderboardphải đang bật. - Portal → Profile cosmetics: upload ảnh và tạo 8 cosmetic ở bảng trên
(đúng id, đúng loại, trạng thái
active). Upload ảnh cosmetic cần nhập lý do và MFA step-up. - Gán thưởng mặc định cho 4 tier (chỉ đặt
profile_reward, giữ nguyên thưởng GOLD/GEM đã có):Terminal window docker compose -f docker-compose.prod.yml exec -T backend \python scripts/configure_phase_p_defaults.py --game-id drone_strike --leaderboard-key stars_weeklyseed_phase_p_cosmetics.pygộp bước 3 và 4 nhưng lấy ảnh từbackend/seed_assets/, thư mục này không có trong image production, nên chỉ dùng script đó trên dev/staging. - Trước khi chốt/phát kỳ đầu tiên, xem lại snapshot thưởng của kỳ trên trang bảng xếp hạng. Đổi thời hạn khung chỉ áp dụng cho kỳ chưa chốt.
Rollback Phase P (chỉ đổi cấu hình, không mất dữ liệu): xóa
profile_reward khỏi các tier, tắt các cosmetic bị lỗi trên Profile
cosmetics. Cấp nhầm thì thu hồi ở trang người chơi (tự gỡ khỏi hồ sơ). Không
xóa lịch sử cấp/thành tích, không downgrade 0050 (§7).
8.7. Phase Q — chiến dịch quà tân thủ / sự kiện
Phần tiêu đề “8.7. Phase Q — chiến dịch quà tân thủ / sự kiện”Migration 0051 thêm snapshot thưởng hồ sơ (huy hiệu, khung) vào chiến dịch,
quà tặng trực tiếp và hộp thư người chơi. Dùng lại chiến dịch và hộp thư đã
có, không thêm cron mới (quà quá hạn vẫn do expire_player_rewards.py).
Audience mới new_players tính theo starts_at của chiến dịch. Quà chỉ được
tạo cho từng người khi họ mở hộp thư.
- Backup (§9), deploy như §7, kiểm tra
alembic currentđã qua0051_phase_q_gift_campaigns. - Tạo chiến dịch ở trạng thái chưa chạy, xem trước audience và phần thưởng.
- Bật
player_inboxcho đúng game (Feature flags, ghi lý do), cùngplayer_profilenếu chiến dịch có thưởng hồ sơ. Flag chỉ bật/tắt theo game, chưa hỗ trợ rollout theo phần trăm, nên thử với nhóm tài khoản test trước. - Theo dõi số quà được tạo, số lượt nhận và lỗi nhận trước khi mở rộng.
guide_test_q.6.md yêu cầu có bằng chứng
diễn tập rollback và restore trên staging trước khi mở production.
Rollback Phase Q: hủy/tắt chiến dịch để ngừng tạo quà mới, tắt
player_inbox của game. Không xóa sổ cái, lịch sử cấp cosmetic hay quà đã
nhận. 0051 từ chối downgrade khi đã có dữ liệu Phase Q (§7).
8.8. Phase R — Shop & Vật phẩm thưởng
Phần tiêu đề “8.8. Phase R — Shop & Vật phẩm thưởng”Migration 0052. Menu Catalog đổi thành Shop và là nơi duy nhất tạo
món (form hoặc import JSON). Vật phẩm thưởng chỉ chọn món từ Shop để bật/tắt
làm phần thưởng. Migration tự tạo cho mỗi vật phẩm cũ một sản phẩm Shop
Không bán (đã publish, không có giá) và ghi audit catalog_product.seed.
Game client và SDK không đổi: cửa hàng trong game chỉ nhận sản phẩm đang bán.
- Backup (§9), deploy như §7, kiểm tra
alembic currentlà0052_shop_owns_items (head). - Portal → Shop: mỗi vật phẩm cũ có một sản phẩm “Không bán”. Riêng Drone Strike là 16 món. Vật phẩm thưởng giữ nguyên danh sách cũ, cửa hàng trong game không hiện thêm món nào.
- Báo đội vận hành: món mới chỉ tạo ở Shop. Import nhận được file xuất từ
game (
dart run tool/export_shop_items.dartở repo Drone Strike). Ghi Vật phẩm thưởng cần MFA step-up.
Rollback Phase R: rollback_to_version.sh sẽ chặn vì image cũ không có
0052, và downgrade 0052 bị từ chối (§7). Ưu tiên fix-forward. Muốn quay
lại hẳn thì restore backup chụp trước deploy.
8.9. Phase S — backup, kiểm tra backup & cảnh báo Telegram (v1.2.0)
Phần tiêu đề “8.9. Phase S — backup, kiểm tra backup & cảnh báo Telegram (v1.2.0)”Migration 0053 chỉ thêm loại kênh telegram cho bảng kênh cảnh báo. Phần
lớn thay đổi nằm ở script trên VPS (§9) nên phải làm đủ các bước dưới đây,
trước lần deploy đầu tiên dùng deploy_local.sh mới (script này bắt
buộc backup trước khi đổi image).
- Thư mục backup thuộc user chạy cron (ở production là
huynqn, vì cron cài trong crontab của user đó):Terminal window sudo mkdir -p /opt/backups && sudo chown "$USER:$USER" /opt/backups && sudo chmod 750 /opt/backupssudo apt install -y age rsync # rsync có kèm rrsync - Bổ sung
.env(xem “Biến môi trường mới” ở §7):Compose project khác mặc định (vd thư mục cài đặt tênBACKUP_AGE_PUBLIC_KEY=age1... # một khóa là đủ (S-D13); private key KHÔNG để trên VPSTELEGRAM_BOT_TOKEN=123456789:AA... # bot từ @BotFather (§10 bước 2)BACKUP_ALERT_TELEGRAM_CHAT_ID=<chat id>web-portal) thìbackup_media.shtự lấy tên volume ảnh từ compose; chỉ đặtMEDIA_VOLUMEkhi dùng volume tự đặt tên. - Deploy như §7.
deploy_local.shtạo bộ backup/opt/backups/YYYYMMDD/predeploy/<timestamp>/(DB, ảnh, cấu hình) rồi mới khởi động image mới; backup lỗi thì dừng, bản cũ vẫn chạy. Kiểm traalembic currentlà0053_telegram_alert_channels (head). - Nạp biến mới vào backend nếu vừa thêm
TELEGRAM_BOT_TOKENsau lúc deploy:docker compose -f docker-compose.prod.yml up -d backend. - Cài lại cron (
./scripts/install_cron.sh): thêm backup cấu hình 04:00 và kiểm tra backup 09:00 (giờ VPS; VPS đặt UTC thì là 11:00 và 16:00 giờ Việt Nam). - Chạy thử một lượt:
Có cảnh báo thì tin nhắn tới Telegram ngay. Cảnh báo
Terminal window ./scripts/backup_database.sh && ./scripts/backup_media.sh && ./scripts/backup_config.sh./scripts/check_backups.shoperator pullhết sau khi làm bước 8. - Kênh cảnh báo của ứng dụng: Portal → Alert Channels → thêm kênh Telegram với chat id (§10 bước 2).
- Kéo backup về máy người vận hành theo §9.3 (bắt buộc: backup chỉ nằm trên VPS thì mất VPS là mất luôn backup).
Rollback Phase S: rollback_to_version.sh về bản trước v1.2.0 sẽ chặn
vì DB đã ở 0053. Downgrade 0053 chỉ bị từ chối khi đang có kênh Telegram;
xóa các kênh đó trong Portal rồi mới downgrade. Ưu tiên fix-forward.
Áp dụng cho Portal v1.2.2