c210197778
run535 部署后容器 crash loop(exit126),两个独立根因: 1. Permission denied: entrypoint.sh git mv 后 644 无执行位,rsync 推 NAS 也是 644, bind mount 覆盖镜像层 chmod +x 无效,tini exec 失败。→ entrypoint.sh git 100755。 2. Redis 连接超时:新版 entrypoint 检查 SKIP_REDIS_CHECK!=1 才查 Redis(旧版 if false 硬跳过),NAS 无 Redis 卡检查 crash。→ 默认 SKIP_REDIS_CHECK=1(复现旧版跳过)。 附:文档(nas-deploy-plan 方案①已实施 + ops/README 根治标记)。
344 lines
10 KiB
Bash
Executable File
344 lines
10 KiB
Bash
Executable File
#!/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 起强制单 worker(WEB_WORKERS 仅兼容保留)。
|
||
log_info "使用单 Worker 模式(sanguo_api 状态化 orchestrator)"
|
||
|
||
# 日志级别
|
||
UVICORN_ARGS="$UVICORN_ARGS --log-level ${VNPY_LOG_LEVEL:-info}"
|
||
|
||
# 访问日志
|
||
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 "$@"
|