Bỏ qua để đến nội dung

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.

PhaseNội dungMigrationĐụng tới VPS
KGame Integration Kit (API v2, SDK, manifest)0029–0031Cấu hình Play Games trên Portal
LProfile, bảng xếp hạng0032–0039Feature flag, cron tuần
MTemplate game đầy đủ (SDK)—Không
NKho ảnh (upload, WebP)0040.env, volume ảnh, Nginx /media/, backup ảnh, chuyển icon cũ
OGOLD/GEM, Vật phẩm, Catalog, kinh tế do server giữ, cửa hàng, IAP0041–0049Kiểm dữ liệu tiền tệ trước, cron mới, IAP, chuyển chế độ kinh tế
PCosmetic hồ sơ (huy hiệu, khung avatar), lịch sử thành tích, thưởng bảng xếp hạng0050Tạo cosmetic trên Portal + chạy script cấu hình tier
QChiến dịch quà tân thủ/sự kiện, quà tặng kèm thưởng hồ sơ0051Bật feature flag player_inbox theo game
RShop là nơi duy nhất tạo món; Vật phẩm thưởng chọn từ Shop0052Không; báo đội vận hành đổi quy trình nhập món
SBackup/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

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):

  1. Backup database (§9) trước khi deploy.
  2. Deploy như §7, rồi chạy kiểm tra ở §5 “Kiểm tra API cho game”.
  3. Portal → Game drone_strike → Tiền tệ: phải thấy GOLD và chỉ số chính max_level.
  4. 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ới IDENTITY_VERIFICATION_MODE=real, người chơi mới không link được (401).
  5. (Tùy chọn) Remote Config: key portal.client_policy để bật bắt cập nhật / bảo trì; các key client.* được app đọc qua bootstrap (trễ tối đa 30 giây).
  6. 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).

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.

  1. 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).
  2. Bật profile trước, sau đó phát hành app staged 10% → 50% → 100%.
  3. 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.
  4. 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ờ.
  5. Trước khi phát hành thêm màn: tăng max_value của total_stars trên Portal trước khi app mới lên store; ghi thay đổi vào decisions_phase_l.md.
  6. Chỉ chuyển IDENTITY_RELINK_POLICY=strict khi 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.

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).

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/.

  1. 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.
  2. Deploy như §7 (deploy/ mới chứa nginx.conf.template có /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ộc root:
    Terminal window
    docker compose -f docker-compose.prod.yml exec -u root backend \
    chown -R appuser:appuser /data/media
  3. 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-run
    docker compose -f docker-compose.prod.yml exec -T backend \
    python scripts/migrate_game_icons_to_assets.py --apply
  4. 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
  5. Kiểm tra: curl -I https://portal.tinysoft.io.vn/media/<game>/<kind>/<sha256>.webp trả 200 kèm Cache-Control: public, max-age=31536000, immutable; bootstrap trả icon_url dạng https://.../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:

Terminal window
set -a; . ./.env; set +a # lấy POSTGRES_USER / POSTGRES_DB
docker 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, 3
UNION ALL
SELECT 'economy_ledger', game_id, currency, count(*) FROM economy_ledger
WHERE currency NOT IN ('GOLD','GEM') GROUP BY 2, 3
UNION ALL
SELECT 'game_currencies', game_id, code, count(*) FROM game_currencies
WHERE code NOT IN ('GOLD','GEM') GROUP BY 2, 3
UNION ALL
SELECT 'ad_reward_rules', game_id, reward_currency, count(*) FROM ad_reward_rules
WHERE reward_currency NOT IN ('GOLD','GEM') GROUP BY 2, 3;
SQL

Kế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:

  1. ./scripts/backup_database.sh && ./scripts/backup_media.sh
  2. Deploy như §7. Kiểm tra alembic current là 0049_o10_rollout_indexes (head).
  3. ./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.
  4. Trên Portal, với từng game:
    • Tiền tệ: có đủ GOLD và 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 item trỏ đú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ọn level_key, min_duration_ms, reason). Chưa cấu hình thì dùng mặc định của Phase O (D17).
  5. Chạy guide_test_o.10.md trê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:

  1. Google Play Console: tạo sản phẩm in-app với mã trùng store_product_id_android trong Catalog; thêm license tester.
  2. 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êm IAP_TOKEN_ENCRYPTION_KEY. (Tùy chọn) cấu hình Real-time Developer Notifications (GOOGLE_PUBSUB_*).
  3. Portal → Feature flags của game → bật iap. Khi tắt, API xác minh trả 403 iap_disabled và cửa hàng không hiện sản phẩm tiền thật.
  4. 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=7

Trả 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/transition
X-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ằng rollback_to_version.sh (script sẽ chặn nếu DB đã qua 0043) và restore DB từ backup chụp trước deploy.
  • Sau bước chuyển mode: không đổi ngược về client_reported và 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ạngHuy hiệu (vĩnh viễn)Khung avatar (7 ngày, chỉnh được theo tier)
#1hero_medal — Huân chương Anh Hùnggrand_marshal_frame — Khung Đại Nguyên Soái
#2victory_badge — Huy Hiệu Chiến Thắnggeneral_frame — Khung Tướng Quân
#3brave_warrior_badge — Huy Hiệu Chiến Sĩ Quả Cảmgold_frame — Khung Vàng
#4–10liberation_badge — Huy Hiệu Giải Phóngsilver_frame — Khung Bạc
  1. Backup (§9), deploy như §7, kiểm tra alembic current đã qua 0050_profile_cosmetics.
  2. Portal → Feature flags của game: player_profile và leaderboard phải đang bật.
  3. 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.
  4. 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_weekly
    seed_phase_p_cosmetics.py gộ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.
  5. 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ư.

  1. Backup (§9), deploy như §7, kiểm tra alembic current đã qua 0051_phase_q_gift_campaigns.
  2. Tạo chiến dịch ở trạng thái chưa chạy, xem trước audience và phần thưởng.
  3. Bật player_inbox cho đúng game (Feature flags, ghi lý do), cùng player_profile nế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.
  4. 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).

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.

  1. Backup (§9), deploy như §7, kiểm tra alembic current là 0052_shop_owns_items (head).
  2. 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.
  3. 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).

  1. 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/backups
    sudo apt install -y age rsync # rsync có kèm rrsync
  2. Bổ sung .env (xem “Biến môi trường mới” ở §7):
    BACKUP_AGE_PUBLIC_KEY=age1... # một khóa là đủ (S-D13); private key KHÔNG để trên VPS
    TELEGRAM_BOT_TOKEN=123456789:AA... # bot từ @BotFather (§10 bước 2)
    BACKUP_ALERT_TELEGRAM_CHAT_ID=<chat id>
    Compose project khác mặc định (vd thư mục cài đặt tên web-portal) thì backup_media.sh tự lấy tên volume ảnh từ compose; chỉ đặt MEDIA_VOLUME khi dùng volume tự đặt tên.
  3. Deploy như §7. deploy_local.sh tạ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 tra alembic current là 0053_telegram_alert_channels (head).
  4. Nạp biến mới vào backend nếu vừa thêm TELEGRAM_BOT_TOKEN sau lúc deploy: docker compose -f docker-compose.prod.yml up -d backend.
  5. 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).
  6. Chạy thử một lượt:
    Terminal window
    ./scripts/backup_database.sh && ./scripts/backup_media.sh && ./scripts/backup_config.sh
    ./scripts/check_backups.sh
    Có cảnh báo thì tin nhắn tới Telegram ngay. Cảnh báo operator pull hết sau khi làm bước 8.
  7. 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).
  8. 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