#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" ENV_FILE="${ENV_FILE:-.env}" PROJECT_NAME="${PROJECT_NAME:-ai-trading}" if ! command -v docker >/dev/null 2>&1; then echo "[ERROR] docker 未安装,请先安装 Docker。" exit 1 fi if ! docker compose version >/dev/null 2>&1; then echo "[ERROR] docker compose 不可用,请升级 Docker Desktop 或安装 compose 插件。" exit 1 fi if [[ ! -f "$COMPOSE_FILE" ]]; then echo "[ERROR] 未找到 $COMPOSE_FILE" exit 1 fi MODE="${1:-local}" ACTION="${2:-up}" QUERY="${3:-}" compose_cmd=( docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" ) if [[ -n "$ENV_FILE" ]]; then if [[ -f "$ENV_FILE" ]]; then compose_cmd+=(--env-file "$ENV_FILE") else echo "[WARN] 未找到 env 文件: ${ENV_FILE}, 已跳过 --env-file" fi fi show_help() { cat <<'EOF' 用法: ./start.sh [mode] [action] mode: local 本地开发(默认) prod 生产部署 action: up 启动服务(默认) down 停止并删除容器/网络 restart 重启服务 quick 一键清理并启动(cleanup + up + web) logs 查看日志(Ctrl+C 退出) status 查看服务状态 pull 拉取基础镜像 build 构建镜像 quote 股票速查(代码/名称) web 启动前端页面(信息+曲线+买卖点) cleanup 清理历史遗留容器/项目(推荐迁移后执行一次) 示例: ./start.sh local up ./start.sh local logs ./start.sh local quote 600519 ./start.sh local quote 贵州茅台 ./start.sh local web ./start.sh prod up ./start.sh prod restart 可选环境变量: COMPOSE_FILE 指定 compose 文件(默认 docker-compose.yml) ENV_FILE 指定环境变量文件(默认 .env) PROJECT_NAME compose 项目名(默认 ai-trading) EOF } run_local() { case "$ACTION" in up) "${compose_cmd[@]}" up --build -d redis go-service ;; down) "${compose_cmd[@]}" down ;; restart) "${compose_cmd[@]}" down "${compose_cmd[@]}" up --build -d ;; quick) "${compose_cmd[@]}" down --remove-orphans || true docker compose -p aitrading -f "$COMPOSE_FILE" down --remove-orphans 2>/dev/null || true stale_run_ids="$(docker ps -aq --filter "name=^ai-trading-python-app-run-")" if [[ -n "$stale_run_ids" ]]; then docker rm -f $stale_run_ids >/dev/null 2>&1 || true fi docker rm -f ai-trading-redis >/dev/null 2>&1 || true "${compose_cmd[@]}" up --build -d redis go-service web echo "[INFO] Quick 启动完成: http://localhost:8000" ;; logs) "${compose_cmd[@]}" logs -f --tail=200 ;; status) "${compose_cmd[@]}" ps ;; pull) "${compose_cmd[@]}" pull || true ;; build) "${compose_cmd[@]}" build ;; quote) if [[ -z "$QUERY" ]]; then "${compose_cmd[@]}" run --rm --no-deps python-app python app/stock_lookup.py else "${compose_cmd[@]}" run --rm --no-deps python-app python app/stock_lookup.py "$QUERY" fi ;; web) "${compose_cmd[@]}" up --build -d web echo "[INFO] Web 已启动: http://localhost:8000" ;; cleanup) # 1) 优先清理当前 compose 项目里的孤儿资源 "${compose_cmd[@]}" down --remove-orphans || true # 2) 清理历史旧项目名(未显式 -p 时常见) docker compose -p aitrading -f "$COMPOSE_FILE" down --remove-orphans 2>/dev/null || true # 3) 清理遗留临时容器(例如 ai-trading-python-app-run-xxxxx) stale_run_ids="$(docker ps -aq --filter "name=^ai-trading-python-app-run-")" if [[ -n "$stale_run_ids" ]]; then docker rm -f $stale_run_ids >/dev/null 2>&1 || true fi # 4) 清理曾经手动起的固定 redis 容器 docker rm -f ai-trading-redis >/dev/null 2>&1 || true echo "[INFO] 清理完成,建议执行: ./start.sh local up && ./start.sh local web" ;; *) echo "[ERROR] 不支持的 action: $ACTION" show_help exit 1 ;; esac } run_prod() { case "$ACTION" in up) "${compose_cmd[@]}" pull || true "${compose_cmd[@]}" up -d --build --remove-orphans redis go-service ;; down) "${compose_cmd[@]}" down ;; restart) "${compose_cmd[@]}" up -d --build --force-recreate --remove-orphans ;; quick) "${compose_cmd[@]}" down --remove-orphans || true docker compose -p aitrading -f "$COMPOSE_FILE" down --remove-orphans 2>/dev/null || true stale_run_ids="$(docker ps -aq --filter "name=^ai-trading-python-app-run-")" if [[ -n "$stale_run_ids" ]]; then docker rm -f $stale_run_ids >/dev/null 2>&1 || true fi docker rm -f ai-trading-redis >/dev/null 2>&1 || true "${compose_cmd[@]}" pull || true "${compose_cmd[@]}" up -d --build --remove-orphans redis go-service web echo "[INFO] Quick 启动完成: http://localhost:8000" ;; logs) "${compose_cmd[@]}" logs -f --tail=300 ;; status) "${compose_cmd[@]}" ps ;; pull) "${compose_cmd[@]}" pull || true ;; build) "${compose_cmd[@]}" build --pull ;; quote) if [[ -z "$QUERY" ]]; then "${compose_cmd[@]}" run --rm --no-deps python-app python app/stock_lookup.py else "${compose_cmd[@]}" run --rm --no-deps python-app python app/stock_lookup.py "$QUERY" fi ;; web) "${compose_cmd[@]}" up -d --build --remove-orphans web echo "[INFO] Web 已启动: http://localhost:8000" ;; cleanup) "${compose_cmd[@]}" down --remove-orphans || true docker compose -p aitrading -f "$COMPOSE_FILE" down --remove-orphans 2>/dev/null || true stale_run_ids="$(docker ps -aq --filter "name=^ai-trading-python-app-run-")" if [[ -n "$stale_run_ids" ]]; then docker rm -f $stale_run_ids >/dev/null 2>&1 || true fi docker rm -f ai-trading-redis >/dev/null 2>&1 || true echo "[INFO] 清理完成,建议执行: ./start.sh prod up && ./start.sh prod web" ;; *) echo "[ERROR] 不支持的 action: $ACTION" show_help exit 1 ;; esac } if [[ "$MODE" == "-h" || "$MODE" == "--help" ]]; then show_help exit 0 fi echo "[INFO] mode=$MODE action=$ACTION compose=$COMPOSE_FILE env=$ENV_FILE project=$PROJECT_NAME" case "$MODE" in local) run_local ;; prod) run_prod ;; *) echo "[ERROR] 不支持的 mode: $MODE" show_help exit 1 ;; esac echo "[INFO] 完成: mode=$MODE action=$ACTION"