chessecon / docker-entrypoint.sh
suvasis's picture
code add
e4d7d50
#!/bin/bash
# ─────────────────────────────────────────────────────────────────────────────
# ChessEcon Docker Entrypoint
#
# Modes (CMD argument):
# backend — Start the FastAPI server (default)
# train — Run the RL training loop
# selfplay — Run self-play data collection only (no training)
# download — Download the HuggingFace model and exit
# demo — Run a quick 3-game demo and exit
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
MODE="${1:-backend}"
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ ChessEcon — Multi-Agent Chess RL ║"
echo "║ TextArena + Meta OpenEnv + GRPO | Hackathon 2026 ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
echo "Mode: $MODE"
echo "Model: ${PLAYER_MODEL:-Qwen/Qwen2.5-0.5B-Instruct}"
echo "RL Method: ${RL_METHOD:-grpo}"
echo ""
# ── Validate required environment variables ───────────────────────────────
check_env() {
local var_name="$1"
local required="${2:-false}"
if [ -z "${!var_name:-}" ]; then
if [ "$required" = "true" ]; then
echo "ERROR: Required environment variable $var_name is not set."
echo " Please set it in your .env file or Docker environment."
exit 1
else
echo "WARNING: Optional variable $var_name is not set."
fi
fi
}
# Always required
check_env "HF_TOKEN" "true"
# Required for Claude coaching
if [ "${ENABLE_CLAUDE_COACHING:-true}" = "true" ]; then
check_env "ANTHROPIC_API_KEY" "true"
fi
# ── Download model from HuggingFace if not cached ────────────────────────
MODEL_NAME="${PLAYER_MODEL:-Qwen/Qwen2.5-0.5B-Instruct}"
MODEL_CACHE_DIR="/app/models/$(echo $MODEL_NAME | tr '/' '_')"
if [ ! -d "$MODEL_CACHE_DIR" ] || [ "${FORCE_DOWNLOAD:-false}" = "true" ]; then
echo "Downloading model: $MODEL_NAME"
echo "Cache directory: $MODEL_CACHE_DIR"
python3 -c "
from huggingface_hub import snapshot_download
import os
snapshot_download(
repo_id='${MODEL_NAME}',
local_dir='${MODEL_CACHE_DIR}',
token=os.environ.get('HF_TOKEN'),
ignore_patterns=['*.bin', '*.pt'] if os.environ.get('USE_SAFETENSORS', 'true') == 'true' else []
)
print('Model downloaded successfully.')
"
echo "Model ready at: $MODEL_CACHE_DIR"
else
echo "Model already cached at: $MODEL_CACHE_DIR"
fi
export MODEL_LOCAL_PATH="$MODEL_CACHE_DIR"
# ── Execute the requested mode ────────────────────────────────────────────
case "$MODE" in
backend)
echo ""
echo "Starting ChessEcon API server on port ${PORT:-8000}..."
echo "Dashboard: http://localhost:${PORT:-8000}"
echo "API docs: http://localhost:${PORT:-8000}/docs"
echo "WebSocket: ws://localhost:${PORT:-8000}/ws"
echo ""
exec python3 -m uvicorn backend.main:app \
--host 0.0.0.0 \
--port "${PORT:-8000}" \
--workers "${WORKERS:-1}" \
--log-level "${LOG_LEVEL:-info}"
;;
train)
echo ""
echo "Starting RL training..."
echo "Method: ${RL_METHOD:-grpo}"
echo "Games per batch: ${GAMES_PER_BATCH:-8}"
echo "Training steps: ${MAX_TRAINING_STEPS:-1000}"
echo ""
exec python3 -m training.run \
--method "${RL_METHOD:-grpo}" \
--model-path "$MODEL_LOCAL_PATH" \
--games-per-batch "${GAMES_PER_BATCH:-8}" \
--max-steps "${MAX_TRAINING_STEPS:-1000}" \
--output-dir "/app/data/training" \
--log-dir "/app/logs"
;;
selfplay)
echo ""
echo "Starting self-play data collection..."
echo "Games: ${SELFPLAY_GAMES:-100}"
echo ""
exec python3 -m training.run \
--method selfplay \
--model-path "$MODEL_LOCAL_PATH" \
--games "${SELFPLAY_GAMES:-100}" \
--output-dir "/app/data/games"
;;
download)
echo "Model download complete. Exiting."
exit 0
;;
demo)
echo ""
echo "Running 3-game demo..."
exec python3 -c "
import asyncio
import sys
sys.path.insert(0, '/app')
from backend.chess.engine import ChessEngine
from backend.economy.ledger import EconomicConfig, WalletManager, TournamentOrganizer
async def run_demo():
config = EconomicConfig()
wallets = WalletManager(config)
wallets.create_wallet('white', 100.0)
wallets.create_wallet('black', 100.0)
organizer = TournamentOrganizer(config, wallets)
for game_num in range(1, 4):
print(f'\n--- Game {game_num} ---')
engine = ChessEngine()
game_id = organizer.open_game('white', 'black')
print(f'Game ID: {game_id}')
print(f'Prize pool: {organizer.games[game_id].prize_pool}')
move_count = 0
while not engine.is_game_over() and move_count < 20:
legal = engine.get_legal_moves()
if not legal:
break
import random
move = random.choice(legal)
engine.make_move(move)
move_count += 1
result = engine.get_result() or '1/2-1/2'
winner = 'white' if result == '1-0' else ('black' if result == '0-1' else None)
payout = organizer.close_game(game_id, winner)
print(f'Result: {result} | White: {payout[\"white\"]:.1f} | Black: {payout[\"black\"]:.1f}')
print(f'Wallets — White: {wallets.get_balance(\"white\"):.1f} | Black: {wallets.get_balance(\"black\"):.1f}')
print('\nDemo complete.')
asyncio.run(run_demo())
"
;;
*)
echo "Unknown mode: $MODE"
echo "Valid modes: backend | train | selfplay | download | demo"
exit 1
;;
esac