Files
claude_dev 0f6cdbedc9
CI/CD / test (push) Successful in 9s
CI/CD / nas-deploy (push) Successful in 22s
CI/CD / nas-verify (push) Successful in 2s
fix(entrypoint): uvicorn --log-level 转小写(治 exit2 crash loop,第3根因)
新版 entrypoint(uvicorn 工厂)拼 --log-level ${VNPY_LOG_LEVEL:-info},但 VNPY_LOG_LEVEL
已 export INFO 大写,默认值不生效→传 INFO→uvicorn case-sensitive 拒绝→exit2 crash。
改 ${VNPY_LOG_LEVEL,,} 转小写(bash4+,shebang /bin/bash 支持)。

新版 entrypoint uvicorn 工厂首次在 NAS 跑,连暴露 3 个 crash 根因(已全修):
  6d786fb: PYTHONPATH(spawn import 治 BrokenProcessPool)
  c210197: +x 执行位(tini exit126) + SKIP_REDIS_CHECK(Redis 超时)
  本commit: --log-level 小写(uvicorn exit2)
旧版 python run_web.py 硬编码 log_level='info' 无后两个问题。
2026-08-01 21:59:39 +08:00

344 lines
10 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Sanguo VeighNa Docker 容器启动脚本
set -e
# ============================================
# 颜色定义
# ============================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ============================================
# 日志函数
# ============================================
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# ============================================
# 打印启动信息
# ============================================
print_banner() {
cat << "EOF"
╔════════════════════════════════════════════════════════════╗
║ ║
║ _____ ____ ____ _ _____ _ ___ _ ║
| __ \ / __ \ / __ \ | ||_ _|__ _| | |_ _|___| |
| |__) | | | | | | |_| | |/ _` | |_____|| | _ \ |
| ___/| | | | | | |_ | | (_| | |_____| | || |||
|_| | |__| | \___/\__| |\_____|_| |___||_||_|
\____/ ║
║ ║
║ Docker Web 版本 v1.0.0 ║
║ 基于 VeighNa 4.4.0 构建 ║
║ ║
╚════════════════════════════════════════════════════════════╝
EOF
}
# ============================================
# 检查环境变量
# ============================================
check_env() {
log_step "检查环境变量..."
# 设置默认值
export TZ="${TZ:-Asia/Shanghai}"
export PYTHONUNBUFFERED="${PYTHONUNBUFFERED:-1}"
export PYTHONDONTWRITEBYTECODE="${PYTHONDONTWRITEBYTECODE:-1}"
# Python 路径(关键): ProcessPoolExecutor(spawn)子进程不继承主进程 sys.path(内存修改),
# 但继承环境变量。设 PYTHONPATH 让 spawn 子进程 import sanguo_*/vnpy 不 ModuleNotFoundError→BrokenProcessPool。
# 镜像 Dockerfile ENV 同设此值(优先),此行为旧镜像/无 ENV 时兜底。
export PYTHONPATH="${PYTHONPATH:-/app:/app/vnpy_v4.4.0}"
# VeighNa 配置
export VNPY_LOG_LEVEL="${VNPY_LOG_LEVEL:-INFO}"
export VNPY_LOG_FILE="${VNPY_LOG_FILE:-/app/logs/vnpy.log}"
# 数据库配置
export DB_TYPE="${DB_TYPE:-sqlite}"
export DB_PATH="${DB_PATH:-/app/data/vnpy.db}"
# Web 服务配置
export WEB_HOST="${WEB_HOST:-0.0.0.0}"
export WEB_PORT="${WEB_PORT:-8000}"
export WEB_WORKERS="${WEB_WORKERS:-2}"
# 安全配置
export JWT_SECRET_KEY="${JWT_SECRET_KEY:-change-this-to-a-random-secret-key}"
export SESSION_TIMEOUT="${SESSION_TIMEOUT:-3600}"
# Redis 配置
export REDIS_HOST="${REDIS_HOST:-redis}"
export REDIS_PORT="${REDIS_PORT:-6379}"
# 开发选项
export SKIP_REDIS_CHECK="${SKIP_REDIS_CHECK:-1}" # 默认跳过(NAS/dev 无 Redis;旧版 entrypoint 即 if false 跳过)。生产需 Redis 设 SKIP_REDIS_CHECK=0
export USE_POSTGRES="${USE_POSTGRES:-0}"
export DEBUG="${DEBUG:-0}"
# 打印关键配置
log_info "时区: $TZ"
log_info "日志级别: $VNPY_LOG_LEVEL"
log_info "数据库类型: $DB_TYPE"
log_info "Web 端口: $WEB_PORT"
log_info "Workers: $WEB_WORKERS"
log_info "环境变量检查完成"
}
# ============================================
# 创建数据目录
# ============================================
create_directories() {
log_step "创建数据目录..."
# 创建主要目录
mkdir -p /app/data
mkdir -p /app/logs
mkdir -p /app/temp
mkdir -p /app/config
# 创建日志子目录
mkdir -p /app/logs/web
mkdir -p /app/logs/vnpy
# 创建数据子目录
mkdir -p /app/data/database
mkdir -p /app/data/storage
# 设置权限
chmod -R 755 /app/data
chmod -R 755 /app/logs
chmod -R 755 /app/temp
log_info "数据目录创建完成"
}
# ============================================
# 初始化数据库
# ============================================
init_database() {
log_step "初始化数据库..."
if [ "$DB_TYPE" = "sqlite" ]; then
if [ ! -f /app/data/vnpy.db ]; then
log_info "创建 SQLite 数据库目录..."
mkdir -p /app/data/database
# 数据库初始化将在 Web 服务启动时完成
log_info "SQLite 数据库准备完成"
else
log_info "SQLite 数据库已存在"
fi
elif [ "$DB_TYPE" = "postgres" ] && [ "$USE_POSTGRES" = "1" ]; then
log_info "使用 PostgreSQL 数据库"
# PostgreSQL 配置由环境变量提供
else
log_warn "未知的数据库类型: $DB_TYPE,使用默认 SQLite"
export DB_TYPE=sqlite
fi
}
# ============================================
# 生成 SSL 证书(自签名,用于测试)
# ============================================
generate_ssl_cert() {
if [ ! -f /etc/nginx/ssl/cert.pem ]; then
log_warn "SSL 证书不存在,生成自签名证书..."
mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/key.pem \
-out /etc/nginx/ssl/cert.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=Sanguo/CN=localhost" \
2>/dev/null
log_warn "自签名证书已生成,请勿用于生产环境"
fi
}
# ============================================
# 等待依赖服务
# ============================================
wait_for_services() {
log_step "等待依赖服务启动..."
# 等待 Redis
if [ "$SKIP_REDIS_CHECK" != "1" ]; then
log_info "等待 Redis 服务..."
REDIS_MAX_WAIT=30
REDIS_WAIT_COUNT=0
while ! nc -z ${REDIS_HOST:-redis} ${REDIS_PORT:-6379} 2>/dev/null; do
REDIS_WAIT_COUNT=$((REDIS_WAIT_COUNT + 1))
if [ $REDIS_WAIT_COUNT -ge $REDIS_MAX_WAIT ]; then
log_error "Redis 连接超时"
log_info "设置 SKIP_REDIS_CHECK=1 可跳过此检查(开发环境)"
exit 1
fi
sleep 1
done
log_info "Redis 服务已就绪 (${REDIS_HOST}:${REDIS_PORT})"
else
log_warn "跳过 Redis 检查"
fi
# 等待 PostgreSQL(如果启用)
if [ "$USE_POSTGRES" = "1" ]; then
log_info "等待 PostgreSQL 服务..."
POSTGRES_MAX_WAIT=30
POSTGRES_WAIT_COUNT=0
while ! nc -z ${POSTGRES_HOST:-postgres} ${POSTGRES_PORT:-5432} 2>/dev/null; do
POSTGRES_WAIT_COUNT=$((POSTGRES_WAIT_COUNT + 1))
if [ $POSTGRES_WAIT_COUNT -ge $POSTGRES_MAX_WAIT ]; then
log_error "PostgreSQL 连接超时"
exit 1
fi
sleep 1
done
log_info "PostgreSQL 服务已就绪"
fi
}
# ============================================
# 健康检查
# ============================================
health_check() {
log_step "执行健康检查..."
# 检查 Python 环境
if ! python --version &>/dev/null; then
log_error "Python 环境检查失败"
exit 1
fi
log_info "Python 版本: $(python --version)"
# 检查关键模块
python -c "import sys; print(f'Python 路径: {sys.executable}')" || {
log_error "Python 系统检查失败"
exit 1
}
# 检查数据目录
if [ ! -d "/app/data" ]; then
log_error "数据目录不存在: /app/data"
exit 1
fi
log_info "健康检查完成"
}
# ============================================
# 启动 Web 服务
# ============================================
start_web_service() {
log_step "启动 Web 服务..."
# 切换到工作目录
cd /app
# 确定 uvicorn 参数
UVICORN_ARGS="--host ${WEB_HOST} --port ${WEB_PORT}"
# Worker 配置
# sanguo_api 的 orchestrator 任务状态在内存(_pending/pool._tasks),
# 必须单 worker:多 worker 下"提交"与"查询状态/结果"可能落到不同 worker
# 而查不到。Phase 3b 起强制单 workerWEB_WORKERS 仅兼容保留)。
log_info "使用单 Worker 模式(sanguo_api 状态化 orchestrator"
# 日志级别
UVICORN_ARGS="$UVICORN_ARGS --log-level ${VNPY_LOG_LEVEL,,}"
# 访问日志
UVICORN_ARGS="$UVICORN_ARGS --access-log"
# 调试模式(仅开发环境)
if [ "$DEBUG" = "1" ]; then
log_warn "启用调试模式(不应在生产环境使用)"
UVICORN_ARGS="$UVICORN_ARGS --reload"
fi
# 启动服务(Phase 3b 起:研究/回测 API sanguo_api,工厂模式启动)
log_info "启动命令: uvicorn sanguo_api.main:create_app --factory $UVICORN_ARGS"
exec uvicorn sanguo_api.main:create_app --factory $UVICORN_ARGS
}
# ============================================
# 显示访问信息
# ============================================
show_access_info() {
echo ""
log_info "=========================================="
log_info "服务启动中..."
log_info "=========================================="
echo ""
log_info "访问地址:"
echo ""
echo " HTTP: http://localhost:${WEB_PORT:-8000}"
echo " WebSocket: ws://localhost:${WS_PORT:-8080}"
echo ""
log_info "默认登录:"
echo ""
echo " 用户名: admin"
echo " 密码: admin123"
echo ""
log_warn "请在首次登录后修改默认密码!"
echo ""
log_info "=========================================="
echo ""
}
# ============================================
# 主函数
# ============================================
main() {
print_banner
echo ""
check_env
create_directories
init_database
generate_ssl_cert
wait_for_services
health_check
show_access_info
log_info "所有检查完成,启动服务..."
echo ""
start_web_service
}
# ============================================
# 信号处理
# ============================================
trap 'log_info "收到退出信号,优雅关闭..."; exit 0' SIGTERM SIGINT
# ============================================
# 执行主函数
# ============================================
main "$@"