QuantDinger 宝塔 Linux 部署完整指南

QuantDinger 宝塔 Linux 部署完整指南

版本 : v4.0.x (PostgreSQL 18 + Redis 8)

适用 : 宝塔面板 / 纯 Linux 服务器

最后更新: 2026-08-16


目录

  • 一、概述
  • 二、系统要求
  • 三、环境准备
    • [3.1 安装 Docker](#3.1 安装 Docker)
    • [3.2 配置 Docker 镜像加速](#3.2 配置 Docker 镜像加速)
    • [3.3 创建 Swap 虚拟内存](#3.3 创建 Swap 虚拟内存)
  • 四、部署方式一:官方镜像模式(快速)
    • [4.1 拉取预构建镜像](#4.1 拉取预构建镜像)
    • [4.2 克隆代码获取 compose 文件](#4.2 克隆代码获取 compose 文件)
    • [4.3 修复 compose 配置](#4.3 修复 compose 配置)
    • [4.4 配置环境变量](#4.4 配置环境变量)
    • [4.5 启动服务](#4.5 启动服务)
    • [4.6 验证部署](#4.6 验证部署)
  • 五、部署方式二:源码构建模式(全本地)
    • [5.1 克隆代码](#5.1 克隆代码)
    • [5.2 修改前端 Dockerfile](#5.2 修改前端 Dockerfile)
    • [5.3 配置环境变量](#5.3 配置环境变量)
    • [5.4 构建并启动](#5.4 构建并启动)
    • [5.5 验证部署](#5.5 验证部署)
  • 六、一键部署脚本
    • [6.1 源码构建一键脚本](#6.1 源码构建一键脚本)
    • [6.2 官方镜像一键脚本](#6.2 官方镜像一键脚本)
  • [七、配置宝塔反向代理与 SSL](#七、配置宝塔反向代理与 SSL)
  • 八、日常管理命令
    • [8.1 容器管理](#8.1 容器管理)
    • [8.2 日志查看](#8.2 日志查看)
    • [8.3 更新升级](#8.3 更新升级)
    • [8.4 数据备份与恢复](#8.4 数据备份与恢复)
    • [8.5 系统维护](#8.5 系统维护)
  • 九、常见问题排查
  • 十、安全建议
  • 十一、两种部署模式对比

一、概述

QuantDinger 是一个量化交易平台,包含后端 API、前端 Web、移动端 H5、Celery 异步任务、PostgreSQL 数据库和 Redis 缓存等多个服务组件。

本指南提供两种部署方式:

模式 说明 适用场景
官方镜像模式 直接从 GHCR 拉取预构建镜像,无需编译 快速部署、低内存服务器(≥1GB)
源码构建模式 本地编译所有组件,不依赖 GHCR GHCR 不通、需要自定义代码

服务架构

服务 容器名 端口 说明
前端 Web quantdinger-frontend 8888 Vue 前端
移动端 H5 quantdinger-mobile 8889 移动端页面
后端 API quantdinger-backend 5000 Python Gunicorn
PostgreSQL quantdinger-db 5432 数据库
Redis quantdinger-redis 6379 缓存
Redis Jobs quantdinger-redis-jobs - 任务队列
Celery Worker quantdinger-celery-worker - 异步任务
Celery Beat quantdinger-celery-beat - 定时任务
Scheduler Worker quantdinger-scheduler-worker - 调度器
Trading Worker quantdinger-trading-worker - 交易执行

二、系统要求

项目 官方镜像模式 源码构建模式
CPU 1 核 1 核(推荐 2 核+)
内存 1GB 2GB(需加 8GB Swap)
磁盘 10GB 20GB
系统 CentOS 7+ / Ubuntu 20+ / alinux 3 同左
Docker 24.0+ 24.0+
Docker Compose v2.20+ v2.20+

三、环境准备

3.1 安装 Docker

bash 复制代码
# 方式一:宝塔面板安装
# 宝塔面板 -> 软件商店 -> 搜索 "Docker 管理器" -> 安装

# 方式二:命令行安装
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker

3.2 配置 Docker 镜像加速

国内服务器必须配置镜像加速,否则拉取基础镜像会超时。

bash 复制代码
cat > /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.nju.edu.cn",
    "https://mirror.ccs.tencentyun.com"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
EOF

systemctl restart docker

# 验证(注意:URL 不应该有反引号)
docker info | grep -A5 "Registry Mirrors"

常见坑 :daemon.json 中的 URL 如果被反引号包裹会导致配置失效。正确格式是 "https://docker.m.daocloud.io",错误格式是 "`https://docker.m.daocloud.io`"

3.3 创建 Swap 虚拟内存

仅源码构建模式需要。如果服务器内存 < 4GB,必须创建 Swap,否则前端编译时会 OOM 崩溃(JavaScript heap out of memory)。

bash 复制代码
# 关闭旧 Swap
swapoff /swapfile 2>/dev/null
swapoff /www/swap 2>/dev/null
rm -f /swapfile /www/swap 2>/dev/null

# 创建 8GB Swap
fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# 设置系统积极使用 Swap
sysctl vm.swappiness=100
sysctl vm.overcommit_memory=1

# 持久化(重启不丢失)
echo "/swapfile none swap sw 0 0" >> /etc/fstab
echo "vm.swappiness=100" >> /etc/sysctl.conf
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf

# 验证
free -h
# Swap 应显示 8.0Gi

四、部署方式一:官方镜像模式(快速)

适用场景 :快速部署、服务器内存 ≥ 1GB、无需编译代码

原理 :直接从 GHCR(GitHub Container Registry)拉取预构建镜像,启动即可使用

部署时间:约 1-2 分钟

4.1 拉取预构建镜像

bash 复制代码
# 后端镜像
docker pull ghcr.io/brokermr810/quantdinger-backend:latest

# 前端镜像
docker pull ghcr.io/brokermr810/quantdinger-frontend:latest

# 移动端镜像
docker pull ghcr.io/brokermr810/quantdinger-mobile:latest

# 验证
docker images | grep quantdinger

如果 GHCR 拉取失败,使用南京大学代理:

bash 复制代码
# 南大 GHCR 代理
docker pull ghcr.nju.edu.cn/brokermr810/quantdinger-backend:latest
docker pull ghcr.nju.edu.cn/brokermr810/quantdinger-frontend:latest
docker pull ghcr.nju.edu.cn/brokermr810/quantdinger-mobile:latest

# 给代理镜像打上正确的 tag
docker tag ghcr.nju.edu.cn/brokermr810/quantdinger-backend:latest ghcr.io/brokermr810/quantdinger-backend:latest
docker tag ghcr.nju.edu.cn/brokermr810/quantdinger-frontend:latest ghcr.io/brokermr810/quantdinger-frontend:latest
docker tag ghcr.nju.edu.cn/brokermr810/quantdinger-mobile:latest ghcr.io/brokermr810/quantdinger-mobile:latest

4.2 克隆代码获取 compose 文件

bash 复制代码
cd /www/wwwroot

# GitCode(国内推荐)
git clone https://gitcode.com/weixin_48616345/QuantDinger.git

# 备选:GitHub
# git clone https://github.com/brokermr810/QuantDinger.git

4.3 修复 compose 配置

compose 文件默认用 ghcr.io/openbyteinc/...,需改为 ghcr.io/brokermr810/...

bash 复制代码
cd /www/wwwroot/QuantDinger

# 修改镜像路径
sed -i 's|ghcr.io/openbyteinc/quantdinger-backend|ghcr.io/brokermr810/quantdinger-backend|g' docker-compose.ghcr.yml
sed -i 's|ghcr.io/openbyteinc/quantdinger-frontend|ghcr.io/brokermr810/quantdinger-frontend|g' docker-compose.ghcr.yml
sed -i 's|ghcr.io/openbyteinc/quantdinger-mobile|ghcr.io/brokermr810/quantdinger-mobile|g' docker-compose.ghcr.yml

# 修改 pull_policy(always -> missing,避免每次启动都去拉镜像)
sed -i 's/pull_policy: always/pull_policy: missing/g' docker-compose.ghcr.yml

4.4 配置环境变量

4.4.1 创建 backend.env
bash 复制代码
cd /www/wwwroot/QuantDinger

# 从模板创建
cp backend_api_python/env.example backend_api_python/.env

# 生成 SECRET_KEY
SECRET_KEY=$(openssl rand -hex 32)

# 设置管理员账号和密码
ADMIN_USER="admin"
ADMIN_PASSWORD="你的密码"  # 至少 8 位

# 获取服务器 IP
SERVER_IP=$(curl -s ifconfig.me)

# 写入配置
sed -i "s|^SECRET_KEY=.*|SECRET_KEY=${SECRET_KEY}|" backend_api_python/.env
sed -i "s|^ADMIN_USER=.*|ADMIN_USER=${ADMIN_USER}|" backend_api_python/.env
sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PASSWORD}|" backend_api_python/.env
sed -i "s|^FRONTEND_URL=.*|FRONTEND_URL=http://${SERVER_IP}|" backend_api_python/.env

# 复制为 backend.env(compose 挂载的是 ./backend.env)
cp backend_api_python/.env backend.env
4.4.2 创建 Compose 编排配置
bash 复制代码
cat > /www/wwwroot/QuantDinger/.env << EOF
POSTGRES_DB=quantdinger
POSTGRES_USER=quantdinger
POSTGRES_PASSWORD=quantdinger123

# 端口配置(纯端口号,不要带 127.0.0.1: 前缀)
DB_PORT=5432
REDIS_PORT=6379
BACKEND_PORT=5000
FRONTEND_PORT=8888
MOBILE_PORT=8889

FRONTEND_URL=http://${SERVER_IP}

# 镜像路径覆盖
BACKEND_IMAGE=ghcr.io/brokermr810/quantdinger-backend
FRONTEND_IMAGE=ghcr.io/brokermr810/quantdinger-frontend
MOBILE_IMAGE=ghcr.io/brokermr810/quantdinger-mobile
EOF

重要 :端口变量必须是纯端口号(如 5000),不能带 IP 前缀(如 127.0.0.1:5000),否则会报 invalid IP address: 127.0.0.1:127.0.0.1 错误。

4.5 启动服务

bash 复制代码
cd /www/wwwroot/QuantDinger

# 启动(无需编译,几秒钟完成)
docker compose -f docker-compose.ghcr.yml up -d

# 查看容器状态
docker compose -f docker-compose.ghcr.yml ps

# 查看日志
docker compose -f docker-compose.ghcr.yml logs -f

4.6 验证部署

bash 复制代码
# 后端健康检查
curl http://127.0.0.1:5000/api/health

# 前端页面
curl -I http://127.0.0.1:8888

# 移动端页面
curl -I http://127.0.0.1:8889

五、部署方式二:源码构建模式(全本地)

适用场景 :无法访问 GHCR 镜像仓库、需要自定义代码、服务器内存 ≥ 2GB(需加 Swap)

部署时间:约 10-20 分钟(取决于网络和磁盘速度)

5.1 克隆代码

5.1.1 克隆主仓库
bash 复制代码
cd /www/wwwroot

# 方式一:GitCode(国内推荐)
git clone https://gitcode.com/weixin_48616345/QuantDinger.git

# 方式二:GitHub 加速
git clone https://ghproxy.com/https://github.com/brokermr810/QuantDinger.git

# 方式三:GitHub 直连
git clone https://github.com/brokermr810/QuantDinger.git
5.1.2 克隆前端仓库(全源码构建必须)
bash 复制代码
cd /www/wwwroot/QuantDinger

# Vue 前端
git clone https://gitcode.com/weixin_48616345/QuantDinger-Vue.git
# 备选:git clone https://ghproxy.com/https://github.com/brokermr810/QuantDinger-Vue.git

# 移动端 H5
git clone https://gitee.com/ycwhmm/QuantDinger-Mobile.git
# 备选:git clone https://ghproxy.com/https://github.com/brokermr810/QuantDinger-Mobile.git

5.2 修改前端 Dockerfile

pnpm build 前加 NODE_OPTIONS,防止 V8 堆内存溢出:

bash 复制代码
cd /www/wwwroot/QuantDinger/QuantDinger-Vue

sed -i 's/pnpm build/NODE_OPTIONS=--max-old-space-size=4096 pnpm build/g' Dockerfile

# 验证修改
grep NODE_OPTIONS Dockerfile
# 应输出包含 NODE_OPTIONS=--max-old-space-size=4096 的内容

5.3 配置环境变量

5.3.1 创建 backend.env
bash 复制代码
cd /www/wwwroot/QuantDinger

# 从模板创建
cp backend_api_python/env.example backend_api_python/.env

# 生成 SECRET_KEY
SECRET_KEY=$(openssl rand -hex 32)

# 设置管理员账号和密码
ADMIN_USER="admin"
ADMIN_PASSWORD="你的密码"  # 至少 8 位

# 获取服务器 IP
SERVER_IP=$(curl -s ifconfig.me)

# 写入配置
sed -i "s|^SECRET_KEY=.*|SECRET_KEY=${SECRET_KEY}|" backend_api_python/.env
sed -i "s|^ADMIN_USER=.*|ADMIN_USER=${ADMIN_USER}|" backend_api_python/.env
sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PASSWORD}|" backend_api_python/.env
sed -i "s|^FRONTEND_URL=.*|FRONTEND_URL=http://${SERVER_IP}|" backend_api_python/.env

# 复制为 backend.env(compose 挂载的是这个文件)
cp backend_api_python/.env backend.env
5.3.2 创建 Compose 编排配置
bash 复制代码
cat > /www/wwwroot/QuantDinger/.env << EOF
POSTGRES_DB=quantdinger
POSTGRES_USER=quantdinger
POSTGRES_PASSWORD=quantdinger123

# 端口配置(纯端口号,不要带 127.0.0.1: 前缀)
DB_PORT=5432
REDIS_PORT=6379
BACKEND_PORT=5000
FRONTEND_PORT=8888
MOBILE_PORT=8889

FRONTEND_URL=http://${SERVER_IP}
EOF

5.4 构建并启动

方式一:后台构建(推荐,终端断开不影响)
bash 复制代码
cd /www/wwwroot/QuantDinger

nohup docker compose -f docker-compose.build.yml up -d --build > /tmp/build.log 2>&1 &

# 查看构建进度
tail -f /tmp/build.log
方式二:前台构建(可实时看到输出)
bash 复制代码
cd /www/wwwroot/QuantDinger
docker compose -f docker-compose.build.yml up -d --build

5.5 验证部署

bash 复制代码
cd /www/wwwroot/QuantDinger

# 查看容器状态
docker compose -f docker-compose.build.yml ps

# 查看日志
docker compose -f docker-compose.build.yml logs -f

# 健康检查
curl http://127.0.0.1:5000/api/health
curl -I http://127.0.0.1:8888

六、一键部署脚本

6.1 源码构建一键脚本

文件名:quantdinger-build-deploy.sh

功能:自动检测系统环境、配置 Swap、克隆代码、修改 Dockerfile、配置环境变量、构建并启动

bash 复制代码
#!/bin/bash
# =============================================================
#   QuantDinger 宝塔 Linux 一键部署脚本 - 源码构建版
#   适用:低内存服务器(2GB+,需加 swap)
#   架构:PostgreSQL 18 + Redis 8 + 全源码本地构建
# =============================================================

set -euo pipefail

# ==================== 配置区 ====================
DEPLOY_DIR="/www/wwwroot/QuantDinger"
LOG_FILE="/tmp/quantdinger_build_$(date +%Y%m%d_%H%M%S).log"
GIT_TERMINAL_PROMPT=0
export GIT_TERMINAL_PROMPT

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

log()     { echo -e "${GREEN}[$(date '+%H:%M:%S')]${NC} $1"; }
info()    { echo -e "${CYAN}[INFO]${NC} $1"; }
warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; }
error()   { echo -e "${RED}[ERROR]${NC} $1"; }
step()    { echo -e "\n${CYAN}[STEP]${NC} $1"; }

# ==================== 函数定义 ====================

check_system() {
    step "检测系统环境..."
    info "操作系统: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2 || echo unknown)"
    info "CPU 架构: $(uname -m)"
    
    local total_mem=$(free -m | awk '/^Mem:/{print $2}')
    info "总内存: ${total_mem} MB"
    
    if [ "$total_mem" -lt 4096 ]; then
        warn "内存不足 4GB,源码构建可能 OOM"
        check_swap
    fi
}

check_swap() {
    local swap_total=$(free -m | awk '/^Swap:/{print $2}')
    
    if [ "$swap_total" -lt 2048 ]; then
        warn "当前 Swap 仅 ${swap_total}MB,源码构建需要更多虚拟内存"
        echo ""
        echo "  是否创建 Swap 虚拟内存(用磁盘当内存)?"
        echo "  - 2GB 内存服务器:建议创建 8GB Swap"
        echo "  - 4GB 内存服务器:建议创建 4GB Swap"
        echo ""
        read -p "  创建 Swap?(y/n, 默认 y): " create_swap
        create_swap=${create_swap:-y}
        
        if [[ "$create_swap" == "y" || "$create_swap" == "Y" ]]; then
            local swap_size=8
            if [ "$total_mem" -ge 4096 ]; then
                swap_size=4
            fi
            
            local swap_path="/swapfile"
            
            # 如果已有 swap,先关掉
            swapoff /swapfile 2>/dev/null || true
            swapoff /www/swap 2>/dev/null || true
            rm -f /swapfile /www/swap 2>/dev/null || true
            
            info "创建 ${swap_size}GB Swap..."
            fallocate -l ${swap_size}G /swapfile
            chmod 600 /swapfile
            mkswap /swapfile
            swapon /swapfile
            
            # 设置系统积极使用 swap
            sysctl vm.swappiness=100
            sysctl vm.overcommit_memory=1
            
            # 持久化
            if ! grep -q "/swapfile" /etc/fstab 2>/dev/null; then
                echo "/swapfile none swap sw 0 0" >> /etc/fstab
            fi
            if ! grep -q "vm.swappiness=100" /etc/sysctl.conf 2>/dev/null; then
                echo "vm.swappiness=100" >> /etc/sysctl.conf
                echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
            fi
            
            info "Swap 创建完成: $(free -h | awk '/^Swap:/{print $2}')"
        else
            warn "跳过 Swap 创建,构建可能因内存不足失败"
        fi
    else
        info "Swap 已有 ${swap_total}MB,满足要求"
    fi
}

check_docker() {
    step "检查 Docker 环境..."
    if ! command -v docker &>/dev/null; then
        error "Docker 未安装!"
        echo ""
        echo "  安装方式:"
        echo "  1. 宝塔面板 -> 软件商店 -> 搜索 Docker 管理器"
        echo "  2. 命令行: curl -fsSL https://get.docker.com | sh"
        echo ""
        exit 1
    fi
    info "Docker 已安装: $(docker --version)"
    
    if ! docker compose version &>/dev/null; then
        error "Docker Compose 未安装!"
        exit 1
    fi
    info "Docker Compose 已安装: $(docker compose version)"
}

configure_mirror() {
    step "配置 Docker 镜像加速..."
    
    if [ -f /etc/docker/daemon.json ]; then
        info "Docker 配置已存在,检查是否需要修复..."
        if grep -q '`' /etc/docker/daemon.json 2>/dev/null; then
            warn "检测到 daemon.json 中有非法反引号,修复中..."
            sed -i 's/`//g' /etc/docker/daemon.json
            systemctl restart docker
            info "已修复并重启 Docker"
        else
            info "镜像加速配置正常,跳过"
        fi
        return
    fi
    
    info "创建 Docker 镜像加速配置..."
    cat > /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.nju.edu.cn",
    "https://mirror.ccs.tencentyun.com"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
EOF
    systemctl restart docker
    info "镜像加速配置完成"
}

clone_repo() {
    step "获取 QuantDinger 代码..."
    
    if [ -d "$DEPLOY_DIR/.git" ]; then
        info "代码目录已存在: $DEPLOY_DIR"
        read -p "是否执行 git pull 更新?(y/n, 默认 n): " do_pull
        if [[ "$do_pull" == "y" || "$do_pull" == "Y" ]]; then
            cd "$DEPLOY_DIR" && git pull || warn "git pull 失败,使用现有代码"
        else
            info "使用现有代码"
        fi
    else
        info "克隆代码到: $DEPLOY_DIR"
        
        local repos=(
            "https://gitcode.com/weixin_48616345/QuantDinger.git"
            "https://ghproxy.com/https://github.com/brokermr810/QuantDinger.git"
            "https://github.com/brokermr810/QuantDinger.git"
        )
        
        local cloned=false
        for repo_url in "${repos[@]}"; do
            info "尝试: $repo_url"
            if git clone --depth 1 "$repo_url" "$DEPLOY_DIR" >> "$LOG_FILE" 2>&1; then
                info "克隆成功: $repo_url"
                cloned=true
                break
            else
                warn "克隆失败: $repo_url"
                rm -rf "$DEPLOY_DIR" 2>/dev/null
            fi
        done
        
        if [ "$cloned" = false ]; then
            error "所有源均克隆失败,请手动克隆代码"
            exit 1
        fi
    fi
    
    if [ -f "$DEPLOY_DIR/docker-compose.build.yml" ]; then
        info "代码验证通过(支持全源码构建)"
    else
        error "代码验证失败:未找到 docker-compose.build.yml"
        exit 1
    fi
}

clone_frontend_repos() {
    step "获取前端仓库(全源码构建需要)..."
    
    local vue_dir="$DEPLOY_DIR/QuantDinger-Vue"
    local mobile_dir="$DEPLOY_DIR/QuantDinger-Mobile"
    
    # QuantDinger-Vue
    if [ -d "$vue_dir/.git" ]; then
        info "QuantDinger-Vue 已存在,跳过"
    else
        local vue_repos=(
            "https://gitcode.com/weixin_48616345/QuantDinger-Vue.git"
            "https://ghproxy.com/https://github.com/brokermr810/QuantDinger-Vue.git"
            "https://github.com/brokermr810/QuantDinger-Vue.git"
        )
        local cloned=false
        for repo_url in "${vue_repos[@]}"; do
            info "尝试克隆 QuantDinger-Vue: $repo_url"
            if git clone --depth 1 "$repo_url" "$vue_dir" >> "$LOG_FILE" 2>&1; then
                info "QuantDinger-Vue 克隆成功"
                cloned=true
                break
            else
                warn "克隆失败: $repo_url"
                rm -rf "$vue_dir" 2>/dev/null
            fi
        done
        if [ "$cloned" = false ]; then
            error "QuantDinger-Vue 克隆失败"
            exit 1
        fi
    fi
    
    # QuantDinger-Mobile
    if [ -d "$mobile_dir/.git" ]; then
        info "QuantDinger-Mobile 已存在,跳过"
    else
        local mobile_repos=(
            "https://gitee.com/ycwhmm/QuantDinger-Mobile.git"
            "https://ghproxy.com/https://github.com/brokermr810/QuantDinger-Mobile.git"
            "https://github.com/brokermr810/QuantDinger-Mobile.git"
        )
        local cloned=false
        for repo_url in "${mobile_repos[@]}"; do
            info "尝试克隆 QuantDinger-Mobile: $repo_url"
            if git clone --depth 1 "$repo_url" "$mobile_dir" >> "$LOG_FILE" 2>&1; then
                info "QuantDinger-Mobile 克隆成功"
                cloned=true
                break
            else
                warn "克隆失败: $repo_url"
                rm -rf "$mobile_dir" 2>/dev/null
            fi
        done
        if [ "$cloned" = false ]; then
            error "QuantDinger-Mobile 克隆失败"
            exit 1
        fi
    fi
    
    info "前端仓库就绪"
}

patch_dockerfile_memory() {
    step "修改前端 Dockerfile,增加内存限制..."
    
    local vue_dockerfile="$DEPLOY_DIR/QuantDinger-Vue/Dockerfile"
    
    if [ -f "$vue_dockerfile" ]; then
        if ! grep -q "max-old-space-size" "$vue_dockerfile"; then
            sed -i 's/pnpm build/NODE_OPTIONS=--max-old-space-size=4096 pnpm build/g' "$vue_dockerfile"
            info "已在前端 Dockerfile 中添加 4GB V8 堆内存限制"
        else
            info "Dockerfile 已有内存限制配置,跳过"
        fi
    fi
}

interactive_config() {
    step "配置 QuantDinger 参数..."
    
    echo ""
    echo "================================================="
    echo "            请填写以下配置信息                  "
    echo "================================================="
    echo ""
    
    read -p "管理员用户名 (默认 admin): " ADMIN_USER
    ADMIN_USER=${ADMIN_USER:-admin}
    
    while true; do
        read -s -p "管理员密码 (至少 8 位): " ADMIN_PASSWORD
        echo ""
        if [ ${#ADMIN_PASSWORD} -ge 8 ]; then
            read -s -p "确认密码: " ADMIN_PASSWORD_CONFIRM
            echo ""
            if [ "$ADMIN_PASSWORD" == "$ADMIN_PASSWORD_CONFIRM" ]; then
                break
            else
                warn "两次密码不一致,请重新输入"
            fi
        else
            warn "密码太短,至少 8 位"
        fi
    done
    
    info "管理员账号已设置: $ADMIN_USER"
    
    local server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
    
    read -p "访问地址(域名或IP,默认 $server_ip): " frontend_url
    frontend_url=${frontend_url:-$server_ip}
    
    FRONTEND_URL="http://$frontend_url"
    info "FRONTEND_URL 将设为: $FRONTEND_URL"
    
    echo ""
    echo "  如果服务器在国内,访问交易所和 AI 服务可能需要代理。"
    read -p "是否配置网络代理?(y/n, 默认 n): " config_proxy
    if [[ "$config_proxy" == "y" || "$config_proxy" == "Y" ]]; then
        read -p "代理地址(如 socks5h://host.docker.internal:10808): " proxy_url
        if [ -z "$proxy_url" ]; then
            warn "代理地址为空,跳过"
        fi
    fi
}

generate_secret_key() {
    step "生成 SECRET_KEY..."
    SECRET_KEY=$(openssl rand -hex 32)
    info "SECRET_KEY 已生成(64 字符)"
}

configure_env() {
    step "配置环境变量文件..."
    
    local env_file="$DEPLOY_DIR/backend_api_python/.env"
    local env_template="$DEPLOY_DIR/backend_api_python/env.example"
    
    if [ ! -f "$env_file" ]; then
        if [ -f "$env_template" ]; then
            cp "$env_template" "$env_file"
            info "已从模板创建 .env 文件"
        else
            error "未找到 env.example 模板文件"
            exit 1
        fi
    else
        info ".env 文件已存在,将更新关键配置"
    fi
    
    sed -i "s|^SECRET_KEY=.*|SECRET_KEY=${SECRET_KEY}|" "$env_file" 2>/dev/null || echo "SECRET_KEY=${SECRET_KEY}" >> "$env_file"
    sed -i "s|^ADMIN_USER=.*|ADMIN_USER=${ADMIN_USER}|" "$env_file" 2>/dev/null || echo "ADMIN_USER=${ADMIN_USER}" >> "$env_file"
    sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PASSWORD}|" "$env_file" 2>/dev/null || echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> "$env_file"
    sed -i "s|^FRONTEND_URL=.*|FRONTEND_URL=${FRONTEND_URL}|" "$env_file" 2>/dev/null || echo "FRONTEND_URL=${FRONTEND_URL}" >> "$env_file"
    
    if [ -n "${proxy_url:-}" ]; then
        sed -i "s|^PROXY_URL=.*|PROXY_URL=${proxy_url}|" "$env_file" 2>/dev/null || echo "PROXY_URL=${proxy_url}" >> "$env_file"
    fi
    
    info "环境变量配置完成"
    info "配置文件: $env_file"
    
    local compose_env="$DEPLOY_DIR/.env"
    cat > "$compose_env" << EOF
# Compose 编排配置
POSTGRES_DB=quantdinger
POSTGRES_USER=quantdinger
POSTGRES_PASSWORD=quantdinger123
FRONTEND_URL=${FRONTEND_URL}
EOF
    info "Compose 编排配置已创建: $compose_env"
    
    cp "$env_file" "$DEPLOY_DIR/backend.env"
    info "已创建 backend.env"
}

start_docker_services() {
    step "启动 Docker 服务..."
    
    cd "$DEPLOY_DIR"
    
    info "使用全源码构建模式(docker-compose.build.yml)"
    info "构建并启动(首次编译约需 10-20 分钟,请耐心等待)..."
    info "此模式完全不依赖 GHCR,基础镜像走国内加速"
    info "如果内存不足,脚本已自动配置 Swap 虚拟内存"
    info ""
    info "建议使用 nohup 后台运行,避免终端断开中断构建"
    info "日志文件: $LOG_FILE"
    info ""
    
    read -p "是否后台运行构建?(y/n, 默认 y): " bg_run
    bg_run=${bg_run:-y}
    
    if [[ "$bg_run" == "y" || "$bg_run" == "Y" ]]; then
        nohup docker compose -f docker-compose.build.yml up -d --build > /tmp/quantdinger_build.log 2>&1 &
        info "构建已在后台启动"
        info "查看进度: tail -f /tmp/quantdinger_build.log"
        info "等待 10-20 分钟后检查状态: cd $DEPLOY_DIR && docker compose -f docker-compose.build.yml ps"
    else
        info "前台运行构建..."
        docker compose -f docker-compose.build.yml up -d --build 2>&1 | tee "$LOG_FILE"
        
        if [ ${PIPESTATUS[0]} -ne 0 ]; then
            error "构建失败"
            warn "常见原因: 内存不足、网络问题、前端仓库未克隆"
            warn "如内存不足,请确保已创建足够 Swap"
            exit 1
        fi
    fi
}

show_result() {
    step "验证部署状态..."
    
    sleep 10
    
    echo ""
    echo "================================================="
    echo "              容器运行状态                       "
    echo "================================================="
    echo ""
    
    cd "$DEPLOY_DIR"
    docker compose -f docker-compose.build.yml ps 2>/dev/null || echo "  容器可能仍在启动中..."
    
    echo ""
    echo "================================================="
    echo "              部署完成!后续步骤                  "
    echo "================================================="
    echo ""
    echo "1. 配置宝塔反向代理(必须)"
    echo "   宝塔面板 -> 网站 -> 添加站点"
    echo "   - 域名: $(echo $FRONTEND_URL | sed 's|https\?://||')"
    echo "   - PHP版本: 纯静态"
    echo "   - 创建后 -> 设置 -> 反向代理"
    echo "   - 目标URL: http://127.0.0.1:8888"
    echo ""
    echo "2. 配置 SSL 证书(推荐)"
    echo "   网站设置 -> SSL -> Let's Encrypt -> 申请"
    echo "   开启「强制 HTTPS」"
    echo ""
    echo "3. 更新 FRONTEND_URL(如已配置 HTTPS)"
    echo "   编辑: $DEPLOY_DIR/backend_api_python/.env"
    echo "   修改: FRONTEND_URL=https://你的域名"
    echo "   重启: docker compose -f docker-compose.build.yml restart backend"
    echo ""
    echo "4. 访问 QuantDinger"
    echo "   地址: $FRONTEND_URL(配置反代后)"
    echo "   账号: $ADMIN_USER"
    echo "   密码: (你设置的密码)"
    echo ""
    echo "日志文件: $LOG_FILE"
    echo ""
}

# ==================== 主流程 ====================
main() {
    echo ""
    echo "============================================================="
    echo "   QuantDinger 宝塔 Linux 一键部署脚本(源码构建版)        "
    echo "   PostgreSQL 18 + Redis 8 + 全源码本地构建                "
    echo "============================================================="
    echo ""
    echo "  部署目录: $DEPLOY_DIR"
    echo "  日志文件: $LOG_FILE"
    echo ""
    
    check_system
    check_docker
    configure_mirror
    clone_repo
    clone_frontend_repos
    patch_dockerfile_memory
    interactive_config
    generate_secret_key
    configure_env
    start_docker_services
    show_result
    
    log "QuantDinger 部署脚本执行完成!"
}

main "$@"

使用方法

bash 复制代码
# 赋予执行权限
chmod +x quantdinger-build-deploy.sh

# 执行
sudo bash quantdinger-build-deploy.sh

6.2 官方镜像一键脚本

文件名:quantdinger-ghcr-deploy.sh

功能:自动检测系统环境、配置镜像加速、拉取 GHCR 镜像(含代理回退)、克隆代码、修复 compose 配置、配置环境变量、启动服务

bash 复制代码
#!/bin/bash
# =============================================================
#   QuantDinger 宝塔 Linux 一键部署脚本 - 官方镜像版
#   适用:所有服务器(无需编译,启动快,内存需求低)
#   架构:拉取 GHCR 预构建镜像 + PostgreSQL 18 + Redis 8
# =============================================================

set -euo pipefail

# ==================== 配置区 ====================
DEPLOY_DIR="/www/wwwroot/QuantDinger"
LOG_FILE="/tmp/quantdinger_ghcr_$(date +%Y%m%d_%H%M%S).log"
GIT_TERMINAL_PROMPT=0
export GIT_TERMINAL_PROMPT

# 镜像源
BACKEND_IMAGE="ghcr.io/brokermr810/quantdinger-backend"
FRONTEND_IMAGE="ghcr.io/brokermr810/quantdinger-frontend"
MOBILE_IMAGE="ghcr.io/brokermr810/quantdinger-mobile"

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

log()     { echo -e "${GREEN}[$(date '+%H:%M:%S')]${NC} $1"; }
info()    { echo -e "${CYAN}[INFO]${NC} $1"; }
warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; }
error()   { echo -e "${RED}[ERROR]${NC} $1"; }
step()    { echo -e "\n${CYAN}[STEP]${NC} $1"; }

# ==================== 函数定义 ====================

check_system() {
    step "检测系统环境..."
    info "操作系统: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2 || echo unknown)"
    info "CPU 架构: $(uname -m)"
    
    local total_mem=$(free -m | awk '/^Mem:/{print $2}')
    info "总内存: ${total_mem} MB"
    
    if [ "$total_mem" -lt 1024 ]; then
        warn "内存不足 1GB,可能影响运行"
    else
        info "内存充足,官方镜像模式不需要编译,内存消耗低"
    fi
    
    local server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
    info "检测到服务器 IP: $server_ip"
    echo "$server_ip" > /tmp/quantdinger_server_ip
}

check_docker() {
    step "检查 Docker 环境..."
    if ! command -v docker &>/dev/null; then
        error "Docker 未安装!"
        echo ""
        echo "  安装方式:"
        echo "  1. 宝塔面板 -> 软件商店 -> 搜索 Docker 管理器"
        echo "  2. 命令行: curl -fsSL https://get.docker.com | sh"
        echo ""
        exit 1
    fi
    info "Docker 已安装: $(docker --version)"
    
    if ! docker compose version &>/dev/null; then
        error "Docker Compose 未安装!"
        exit 1
    fi
    info "Docker Compose 已安装: $(docker compose version)"
}

configure_mirror() {
    step "配置 Docker 镜像加速..."
    
    local need_fix=false
    local need_create=true
    
    if [ -f /etc/docker/daemon.json ]; then
        need_create=false
        if grep -q '`' /etc/docker/daemon.json 2>/dev/null; then
            warn "检测到 daemon.json 中有非法反引号字符"
            need_fix=true
        fi
        
        if ! grep -q "registry-mirrors" /etc/docker/daemon.json 2>/dev/null; then
            need_fix=true
        fi
    fi
    
    if [ "$need_create" = true ] || [ "$need_fix" = true ]; then
        info "写入正确的镜像加速配置..."
        cat > /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.nju.edu.cn",
    "https://mirror.ccs.tencentyun.com"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
EOF
        systemctl restart docker
        info "镜像加速配置完成并已重启 Docker"
    else
        info "镜像加速配置正常"
    fi
    
    info "当前加速源:"
    docker info 2>/dev/null | grep -A5 "Registry Mirrors" | head -6
}

pull_images() {
    step "拉取官方预构建镜像..."
    
    info "从 GHCR 拉取预构建镜像(无需编译,直接启动)"
    info "镜像列表:"
    info "  后端: $BACKEND_IMAGE"
    info "  前端: $FRONTEND_IMAGE"
    info "  移动端: $MOBILE_IMAGE"
    info ""
    
    local images=("$BACKEND_IMAGE" "$FRONTEND_IMAGE" "$MOBILE_IMAGE")
    for img in "${images[@]}"; do
        info "拉取: $img"
        if docker pull "$img:latest" >> "$LOG_FILE" 2>&1; then
            info "拉取成功: $img"
        else
            # 尝试南大代理
            local proxy_img=$(echo "$img" | sed "s|ghcr.io|ghcr.nju.edu.cn|")
            warn "直接拉取失败,尝试南大代理: $proxy_img"
            if docker pull "$proxy_img:latest" >> "$LOG_FILE" 2>&1; then
                docker tag "$proxy_img:latest" "$img:latest"
                info "通过代理拉取成功: $img"
            else
                error "拉取失败: $img"
                error "请检查网络或手动配置代理后重试"
                exit 1
            fi
        fi
    done
    
    info "所有镜像拉取完成"
    docker images | grep quantdinger
}

clone_repo() {
    step "获取 QuantDinger 代码(用于 compose 文件和配置)..."
    
    if [ -d "$DEPLOY_DIR/.git" ]; then
        info "代码目录已存在: $DEPLOY_DIR"
        read -p "是否执行 git pull 更新?(y/n, 默认 n): " do_pull
        if [[ "$do_pull" == "y" || "$do_pull" == "Y" ]]; then
            cd "$DEPLOY_DIR" && git pull || warn "git pull 失败,使用现有代码"
        else
            info "使用现有代码"
        fi
    else
        info "克隆代码到: $DEPLOY_DIR"
        
        local repos=(
            "https://gitcode.com/weixin_48616345/QuantDinger.git"
            "https://ghproxy.com/https://github.com/brokermr810/QuantDinger.git"
            "https://github.com/brokermr810/QuantDinger.git"
        )
        
        local cloned=false
        for repo_url in "${repos[@]}"; do
            info "尝试: $repo_url"
            if git clone --depth 1 "$repo_url" "$DEPLOY_DIR" >> "$LOG_FILE" 2>&1; then
                info "克隆成功: $repo_url"
                cloned=true
                break
            else
                warn "克隆失败: $repo_url"
                rm -rf "$DEPLOY_DIR" 2>/dev/null
            fi
        done
        
        if [ "$cloned" = false ]; then
            error "所有源均克隆失败,请手动克隆代码"
            exit 1
        fi
    fi
    
    if [ -f "$DEPLOY_DIR/docker-compose.ghcr.yml" ]; then
        info "代码验证通过(支持 GHCR 模式)"
    else
        error "代码验证失败:未找到 docker-compose.ghcr.yml"
        exit 1
    fi
}

patch_compose_file() {
    step "修复 compose 文件配置..."
    
    local compose_file="$DEPLOY_DIR/docker-compose.ghcr.yml"
    
    sed -i "s|ghcr.io/openbyteinc/quantdinger-backend|$BACKEND_IMAGE|g" "$compose_file"
    sed -i "s|ghcr.io/openbyteinc/quantdinger-frontend|$FRONTEND_IMAGE|g" "$compose_file"
    sed -i "s|ghcr.io/openbyteinc/quantdinger-mobile|$MOBILE_IMAGE|g" "$compose_file"
    info "镜像路径已修改为 brokermr810"
    
    sed -i 's/pull_policy: always/pull_policy: missing/g' "$compose_file"
    info "pull_policy 已改为 missing"
    
    info "compose 文件配置完成"
}

interactive_config() {
    step "配置 QuantDinger 参数..."
    
    echo ""
    echo "================================================="
    echo "            请填写以下配置信息                  "
    echo "================================================="
    echo ""
    
    read -p "管理员用户名 (默认 admin): " ADMIN_USER
    ADMIN_USER=${ADMIN_USER:-admin}
    
    while true; do
        read -s -p "管理员密码 (至少 8 位): " ADMIN_PASSWORD
        echo ""
        if [ ${#ADMIN_PASSWORD} -ge 8 ]; then
            read -s -p "确认密码: " ADMIN_PASSWORD_CONFIRM
            echo ""
            if [ "$ADMIN_PASSWORD" == "$ADMIN_PASSWORD_CONFIRM" ]; then
                break
            else
                warn "两次密码不一致,请重新输入"
            fi
        else
            warn "密码太短,至少 8 位"
        fi
    done
    
    info "管理员账号已设置: $ADMIN_USER"
    
    local server_ip=$(cat /tmp/quantdinger_server_ip 2>/dev/null || echo "localhost")
    read -p "访问地址(域名或IP,默认 $server_ip): " frontend_url
    frontend_url=${frontend_url:-$server_ip}
    FRONTEND_URL="http://$frontend_url"
    info "FRONTEND_URL 将设为: $FRONTEND_URL"
    
    echo ""
    echo "  如果服务器在国内,访问交易所和 AI 服务可能需要代理。"
    read -p "是否配置网络代理?(y/n, 默认 n): " config_proxy
    if [[ "$config_proxy" == "y" || "$config_proxy" == "Y" ]]; then
        read -p "代理地址(如 socks5h://host.docker.internal:10808): " proxy_url
        if [ -z "$proxy_url" ]; then
            warn "代理地址为空,跳过"
        fi
    fi
}

generate_secret_key() {
    step "生成 SECRET_KEY..."
    SECRET_KEY=$(openssl rand -hex 32)
    info "SECRET_KEY 已生成(64 字符)"
}

configure_env() {
    step "配置环境变量文件..."
    
    local env_file="$DEPLOY_DIR/backend_api_python/.env"
    local env_template="$DEPLOY_DIR/backend_api_python/env.example"
    
    if [ ! -f "$env_file" ]; then
        if [ -f "$env_template" ]; then
            cp "$env_template" "$env_file"
            info "已从模板创建 .env 文件"
        else
            error "未找到 env.example 模板文件"
            exit 1
        fi
    else
        info ".env 文件已存在,将更新关键配置"
    fi
    
    sed -i "s|^SECRET_KEY=.*|SECRET_KEY=${SECRET_KEY}|" "$env_file" 2>/dev/null || echo "SECRET_KEY=${SECRET_KEY}" >> "$env_file"
    sed -i "s|^ADMIN_USER=.*|ADMIN_USER=${ADMIN_USER}|" "$env_file" 2>/dev/null || echo "ADMIN_USER=${ADMIN_USER}" >> "$env_file"
    sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PASSWORD}|" "$env_file" 2>/dev/null || echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> "$env_file"
    sed -i "s|^FRONTEND_URL=.*|FRONTEND_URL=${FRONTEND_URL}|" "$env_file" 2>/dev/null || echo "FRONTEND_URL=${FRONTEND_URL}" >> "$env_file"
    
    if [ -n "${proxy_url:-}" ]; then
        sed -i "s|^PROXY_URL=.*|PROXY_URL=${proxy_url}|" "$env_file" 2>/dev/null || echo "PROXY_URL=${proxy_url}" >> "$env_file"
    fi
    
    info "环境变量配置完成"
    info "配置文件: $env_file"
    
    local compose_env="$DEPLOY_DIR/.env"
    cat > "$compose_env" << EOF
# Compose 编排配置
POSTGRES_DB=quantdinger
POSTGRES_USER=quantdinger
POSTGRES_PASSWORD=quantdinger123

# 端口配置(纯端口号,不要带 IP 前缀)
DB_PORT=5432
REDIS_PORT=6379
BACKEND_PORT=5000
FRONTEND_PORT=8888
MOBILE_PORT=8889

# 前端访问地址
FRONTEND_URL=${FRONTEND_URL}

# 镜像路径覆盖
BACKEND_IMAGE=$BACKEND_IMAGE
FRONTEND_IMAGE=$FRONTEND_IMAGE
MOBILE_IMAGE=$MOBILE_IMAGE
EOF
    info "Compose 编排配置已创建: $compose_env"
    
    cp "$env_file" "$DEPLOY_DIR/backend.env"
    info "已创建 backend.env"
}

start_docker_services() {
    step "启动 Docker 服务..."
    
    cd "$DEPLOY_DIR"
    
    info "使用 GHCR 预构建镜像模式"
    info "无需编译,直接启动,几秒钟内完成"
    
    docker compose -f docker-compose.ghcr.yml up -d 2>&1 | tee "$LOG_FILE"
    
    if [ ${PIPESTATUS[0]} -ne 0 ]; then
        error "启动失败"
        warn "常见原因:镜像未拉取成功、配置文件错误"
        warn "请查看日志: $LOG_FILE"
        exit 1
    fi
}

show_result() {
    step "验证部署状态..."
    
    echo ""
    echo "================================================="
    echo "              容器运行状态                       "
    echo "================================================="
    echo ""
    
    cd "$DEPLOY_DIR"
    docker compose -f docker-compose.ghcr.yml ps 2>/dev/null || echo "  容器可能仍在启动中..."
    
    echo ""
    echo "================================================="
    echo "              服务健康检查                      "
    echo "================================================="
    echo ""
    
    sleep 15
    
    local backend_ok=false
    local frontend_ok=false
    
    if curl -s --max-time 5 http://127.0.0.1:5000/api/health &>/dev/null; then
        echo "  后端 API (5000):  正常"
        backend_ok=true
    else
        echo "  后端 API (5000):  启动中(可能需要 30 秒初始化)"
    fi
    
    if curl -s --max-time 5 http://127.0.0.1:8888 &>/dev/null; then
        echo "  前端 Web (8888):  正常"
        frontend_ok=true
    else
        echo "  前端 Web (8888):  启动中"
    fi
    
    echo ""
    echo "============================================================="
    echo "              部署完成!后续步骤                            "
    echo "============================================================="
    echo ""
    echo "1. 配置宝塔反向代理(必须)"
    echo "   宝塔面板 -> 网站 -> 添加站点"
    echo "   - 域名: $(echo $FRONTEND_URL | sed 's|https\?://||')"
    echo "   - PHP版本: 纯静态"
    echo "   - 创建后 -> 设置 -> 反向代理"
    echo "   - 目标URL: http://127.0.0.1:8888"
    echo ""
    echo "2. 配置 SSL 证书(推荐)"
    echo "   网站设置 -> SSL -> Let's Encrypt -> 申请"
    echo "   开启「强制 HTTPS」"
    echo ""
    echo "3. 更新 FRONTEND_URL(如已配置 HTTPS)"
    echo "   编辑: $DEPLOY_DIR/backend.env"
    echo "   修改: FRONTEND_URL=https://你的域名"
    echo "   重启: docker compose -f docker-compose.ghcr.yml restart backend"
    echo ""
    echo "4. 访问 QuantDinger"
    echo "   地址: $FRONTEND_URL(配置反代后)"
    echo "   账号: $ADMIN_USER"
    echo "   密码: (你设置的密码)"
    echo ""
    echo "5. 常用运维命令"
    echo "   查看状态: cd $DEPLOY_DIR && docker compose -f docker-compose.ghcr.yml ps"
    echo "   查看日志: cd $DEPLOY_DIR && docker compose -f docker-compose.ghcr.yml logs -f"
    echo "   重启服务: cd $DEPLOY_DIR && docker compose -f docker-compose.ghcr.yml restart"
    echo "   停止服务: cd $DEPLOY_DIR && docker compose -f docker-compose.ghcr.yml down"
    echo "   更新版本: docker pull $BACKEND_IMAGE && docker pull $FRONTEND_IMAGE && docker pull $MOBILE_IMAGE && cd $DEPLOY_DIR && docker compose -f docker-compose.ghcr.yml up -d"
    echo ""
    echo "6. 安全提醒"
    echo "   不要在宝塔安全中放行 5000/5432/6379 端口"
    echo "   只放行 80/443/22 端口"
    echo ""
    echo "日志文件: $LOG_FILE"
    echo ""
}

# ==================== 主流程 ====================
main() {
    echo ""
    echo "============================================================="
    echo "   QuantDinger 宝塔 Linux 一键部署脚本(官方镜像版)        "
    echo "   PostgreSQL 18 + Redis 8 + GHCR 预构建镜像              "
    echo "============================================================="
    echo ""
    echo "  部署目录: $DEPLOY_DIR"
    echo "  日志文件: $LOG_FILE"
    echo ""
    
    check_system
    check_docker
    configure_mirror
    pull_images
    clone_repo
    patch_compose_file
    interactive_config
    generate_secret_key
    configure_env
    start_docker_services
    show_result
    
    log "QuantDinger 部署脚本执行完成!"
}

main "$@"

使用方法

bash 复制代码
# 赋予执行权限
chmod +x quantdinger-ghcr-deploy.sh

# 执行
sudo bash quantdinger-ghcr-deploy.sh

七、配置宝塔反向代理与 SSL

部署完成后,通过宝塔面板配置域名访问和 HTTPS。

7.1 添加站点

  1. 宝塔面板网站添加站点
  2. 域名:输入你的域名(如 quant.example.com
  3. PHP 版本:纯静态
  4. 数据库:不创建
  5. 点击 提交

7.2 配置反向代理

配置项
代理名称 QuantDinger
目标 URL http://127.0.0.1:8888
发送域名 $host

步骤:创建站点后 → 设置反向代理添加反向代理

7.3 配置 SSL 证书

  1. 网站设置 → SSLLet's Encrypt
  2. 点击 申请
  3. 申请成功后开启 强制 HTTPS

7.4 更新 FRONTEND_URL(配置 HTTPS 后必须)

bash 复制代码
# 官方镜像模式
sed -i 's|FRONTEND_URL=.*|FRONTEND_URL=https://你的域名|' /www/wwwroot/QuantDinger/backend.env
cd /www/wwwroot/QuantDinger && docker compose -f docker-compose.ghcr.yml restart backend

# 源码构建模式
sed -i 's|FRONTEND_URL=.*|FRONTEND_URL=https://你的域名|' /www/wwwroot/QuantDinger/backend_api_python/.env
cp /www/wwwroot/QuantDinger/backend_api_python/.env /www/wwwroot/QuantDinger/backend.env
cd /www/wwwroot/QuantDinger && docker compose -f docker-compose.build.yml restart backend

八、日常管理命令

以下命令中,根据你的部署模式选择对应的 compose 文件:

  • 官方镜像模式docker-compose.ghcr.yml
  • 源码构建模式docker-compose.build.yml

8.1 容器管理

bash 复制代码
cd /www/wwwroot/QuantDinger

# 查看所有容器状态
docker compose -f docker-compose.ghcr.yml ps

# 查看指定服务状态
docker compose -f docker-compose.ghcr.yml ps backend
docker compose -f docker-compose.ghcr.yml ps frontend
docker compose -f docker-compose.ghcr.yml ps postgres

# 重启所有服务
docker compose -f docker-compose.ghcr.yml restart

# 重启单个服务
docker compose -f docker-compose.ghcr.yml restart backend
docker compose -f docker-compose.ghcr.yml restart frontend

# 停止所有服务
docker compose -f docker-compose.ghcr.yml down

# 启动所有服务
docker compose -f docker-compose.ghcr.yml up -d

# 启动单个服务
docker compose -f docker-compose.ghcr.yml up -d backend

8.2 日志查看

bash 复制代码
cd /www/wwwroot/QuantDinger

# 查看所有服务日志(实时跟踪)
docker compose -f docker-compose.ghcr.yml logs -f

# 查看指定服务日志
docker compose -f docker-compose.ghcr.yml logs -f backend
docker compose -f docker-compose.ghcr.yml logs -f frontend
docker compose -f docker-compose.ghcr.yml logs -f postgres

# 查看最近 50 行日志
docker compose -f docker-compose.ghcr.yml logs --tail=50 backend

# 查看最近 1 小时的日志
docker compose -f docker-compose.ghcr.yml logs --since=1h backend

# 查看指定时间段的日志
docker compose -f docker-compose.ghcr.yml logs --since="2026-08-16T00:00:00" --until="2026-08-16T06:00:00" backend

8.3 更新升级

官方镜像模式
bash 复制代码
cd /www/wwwroot/QuantDinger

# 拉取最新镜像
docker pull ghcr.io/brokermr810/quantdinger-backend:latest
docker pull ghcr.io/brokermr810/quantdinger-frontend:latest
docker pull ghcr.io/brokermr810/quantdinger-mobile:latest

# 重启服务(自动使用新镜像)
docker compose -f docker-compose.ghcr.yml up -d

# 如果南大代理拉取
docker pull ghcr.nju.edu.cn/brokermr810/quantdinger-backend:latest
docker tag ghcr.nju.edu.cn/brokermr810/quantdinger-backend:latest ghcr.io/brokermr810/quantdinger-backend:latest
docker compose -f docker-compose.ghcr.yml up -d
源码构建模式
bash 复制代码
cd /www/wwwroot/QuantDinger

# 更新代码
git pull
cd QuantDinger-Vue && git pull
cd ../QuantDinger-Mobile && git pull
cd ..

# 重新构建并启动
docker compose -f docker-compose.build.yml up -d --build

8.4 数据备份与恢复

数据库备份
bash 复制代码
# 手动备份
docker exec quantdinger-db pg_dump -U quantdinger quantdinger > /www/backup/quantdinger_$(date +%Y%m%d).sql

# 压缩备份
docker exec quantdinger-db pg_dump -U quantdinger -Fc quantdinger > /www/backup/quantdinger_$(date +%Y%m%d).dump

# 定时备份(添加到 crontab)
# 每天凌晨 3 点自动备份
echo "0 3 * * * docker exec quantdinger-db pg_dump -U quantdinger quantdinger > /www/backup/quantdinger_\$(date +\%Y\%m\%d).sql 2>/dev/null" | crontab -
数据库恢复
bash 复制代码
# 从 SQL 文件恢复
docker exec -i quantdinger-db psql -U quantdinger quantdinger < /www/backup/quantdinger_20260816.sql

# 从压缩文件恢复
docker exec -i quantdinger-db pg_restore -U quantdinger -d quantdinger < /www/backup/quantdinger_20260816.dump
备份配置文件
bash 复制代码
# 备份所有配置文件
tar -czf /www/backup/quantdinger_config_$(date +%Y%m%d).tar.gz \
    /www/wwwroot/QuantDinger/.env \
    /www/wwwroot/QuantDinger/backend.env \
    /www/wwwroot/QuantDinger/backend_api_python/.env \
    /www/wwwroot/QuantDinger/docker-compose.ghcr.yml

8.5 系统维护

清理 Docker 资源
bash 复制代码
# 清理已停止的容器
docker container prune -f

# 清理未使用的镜像
docker image prune -a -f

# 清理未使用的卷
docker volume prune -f

# 清理构建缓存
docker builder prune -f

# 一键清理所有未使用的资源
docker system prune -a -f
检查系统资源
bash 复制代码
# 查看磁盘使用情况
df -h

# 查看内存使用情况
free -h

# 查看 Docker 磁盘占用
docker system df

# 查看容器资源使用
docker stats --no-stream
端口冲突排查
bash 复制代码
# 查看端口占用
lsof -i :6379
ss -tlnp | grep 6379
netstat -tlnp | grep 6379

# 停掉系统 Redis(宝塔自带的)
systemctl stop redis
systemctl disable redis

# 或者修改 QuantDinger 的 Redis 端口
sed -i 's/REDIS_PORT=6379/REDIS_PORT=6380/' /www/wwwroot/QuantDinger/.env
重启服务
bash 复制代码
# 重启单个服务
cd /www/wwwroot/QuantDinger
docker compose -f docker-compose.ghcr.yml restart backend

# 重启所有服务
docker compose -f docker-compose.ghcr.yml restart

# 完全重建(保留数据)
docker compose -f docker-compose.ghcr.yml down
docker compose -f docker-compose.ghcr.yml up -d

九、常见问题排查

Q1: 前端构建 OOM 崩溃(JavaScript heap out of memory)

原因:Vite 编译时 V8 堆内存默认限制约 1.4GB,不够用。

解决

  1. 确认已创建 8GB Swap
  2. 确认已修改 Dockerfile 加 NODE_OPTIONS=--max-old-space-size=4096
  3. 确认 vm.swappiness=100
bash 复制代码
# 检查 Swap
free -h

# 检查 Dockerfile
grep NODE_OPTIONS /www/wwwroot/QuantDinger/QuantDinger-Vue/Dockerfile

# 检查 swappiness
sysctl vm.swappiness

Q2: Docker 镜像加速无效

原因:daemon.json 中 URL 被反引号包围。

解决

bash 复制代码
cat /etc/docker/daemon.json
# 正确格式: "https://docker.m.daocloud.io"
# 错误格式: "`https://docker.m.daocloud.io`"

# 修复
sed -i 's/`//g' /etc/docker/daemon.json
systemctl restart docker

Q3: invalid IP address: 127.0.0.1:127.0.0.1

原因.env 文件中端口变量带了 127.0.0.1: 前缀,compose 拼接后变成 127.0.0.1:127.0.0.1:端口

解决

bash 复制代码
# 错误格式
FRONTEND_PORT=127.0.0.1:8888

# 正确格式
FRONTEND_PORT=8888

# 批量修复
sed -i 's/DB_PORT=127.0.0.1:5432/DB_PORT=5432/' /www/wwwroot/QuantDinger/.env
sed -i 's/REDIS_PORT=127.0.0.1:6379/REDIS_PORT=6379/' /www/wwwroot/QuantDinger/.env
sed -i 's/BACKEND_PORT=127.0.0.1:5000/BACKEND_PORT=5000/' /www/wwwroot/QuantDinger/.env
sed -i 's/FRONTEND_PORT=127.0.0.1:8888/FRONTEND_PORT=8888/' /www/wwwroot/QuantDinger/.env
sed -i 's/MOBILE_PORT=127.0.0.1:8889/MOBILE_PORT=8889/' /www/wwwroot/QuantDinger/.env

Q4: GHCR 镜像拉取超时

解决

  1. 使用南大代理:ghcr.nju.edu.cn/brokermr810/...
  2. 检查 daemon.json 是否有加速源
  3. 多次重试 docker pull
bash 复制代码
# 南大代理
docker pull ghcr.nju.edu.cn/brokermr810/quantdinger-backend:latest
docker tag ghcr.nju.edu.cn/brokermr810/quantdinger-backend:latest ghcr.io/brokermr810/quantdinger-backend:latest

Q5: 镜像名不匹配(openbyteinc vs brokermr810)

原因 :compose 文件默认用 openbyteinc,实际镜像在 brokermr810 下。

解决

bash 复制代码
sed -i 's|ghcr.io/openbyteinc/quantdinger-backend|ghcr.io/brokermr810/quantdinger-backend|g' docker-compose.ghcr.yml
sed -i 's|ghcr.io/openbyteinc/quantdinger-frontend|ghcr.io/brokermr810/quantdinger-frontend|g' docker-compose.ghcr.yml
sed -i 's|ghcr.io/openbyteinc/quantdinger-mobile|ghcr.io/brokermr810/quantdinger-mobile|g' docker-compose.ghcr.yml

Q6: backend.env 文件不存在

原因 :compose 挂载的是 ./backend.env,但默认不存在。

解决

bash 复制代码
cp backend_api_python/.env backend.env

Q7: 端口 6379 被占用(Redis 冲突)

原因:宿主机自带 Redis 服务占用了 6379 端口。

解决

bash 复制代码
# 查看占用进程
lsof -i :6379

# 停掉系统 Redis
systemctl stop redis
systemctl disable redis

# 或通过宝塔停止
/etc/init.d/redis stop

# 确认端口已释放
ss -tlnp | grep 6379

# 重启 QuantDinger 容器
cd /www/wwwroot/QuantDinger
docker compose -f docker-compose.ghcr.yml down
docker compose -f docker-compose.ghcr.yml up -d

Q8: 构建被终端中断

解决 :使用 nohup 后台运行,不要在前台跑构建。

bash 复制代码
nohup docker compose -f docker-compose.build.yml up -d --build > /tmp/build.log 2>&1 &
tail -f /tmp/build.log

Q9: 前端仓库克隆失败

解决:使用多个源尝试,GitCode > Gitee > ghproxy > GitHub。

Q10: 日志中出现 duplicate key value 报错

原因:migration 容器首次启动已创建 admin 用户,后端启动再次尝试创建时触发唯一约束冲突。

解决 :这是正常的无害报错,admin 用户已存在且可用,不影响任何功能,可以忽略。


十、安全建议

  • 只在宝塔安全中放行 80/443/22 端口
  • 不要放行 5000/5432/6379 端口(这些端口只供内部容器间通信)
  • 定期备份数据库(建议设置 crontab 定时备份)
  • 及时更新系统补丁
  • 使用强密码(至少 12 位,含大小写 + 数字 + 符号)
  • 启用宝塔面板的登录保护和安全入口
  • 定期检查容器日志,关注异常访问

十一、两种部署模式对比

项目 源码构建模式 官方镜像模式
内存要求 2GB+(需 Swap) 1GB+
部署时间 10-20 分钟 1-2 分钟
是否需编译
是否依赖 GHCR
代码可定制
适合场景 定制需求 / GHCR 不通 快速部署 / 低内存
Compose 文件 docker-compose.build.yml docker-compose.ghcr.yml
前端仓库 需要额外克隆 不需要
Swap 配置 必须 不需要

文档结束

如有问题请参考 QuantDinger 官方文档或提交 Issue。

相关推荐
RisunJan2 小时前
Linux命令-tcpdump(网络数据包捕获)
linux·网络·tcpdump
头茬韭菜2 小时前
第 9 篇:「Fluss 运维实战」—— 部署、监控与故障排查
运维·fluss
Titan20242 小时前
Linux网络学习:套接字、UDP的封装与应用
linux·服务器·开发语言·网络·c++
.冰块.2 小时前
两套完整 Linux 建站实战:Wordpress 博客(LAMP)+ECShop 电商(LNMP)全套手册
linux·运维·项目实战·lnmp·lamp·电商平台·博客平台
杨云龙UP2 小时前
生产环境MySQL多实例XtraBackup全量备份与rsync异地自动传输实践
linux·运维·数据库·mysql·xtrabackup·主从复制·备份恢复
XR1234567882 小时前
医院有线无线一体化运维选型指南
运维
拂拉氏3 小时前
【知识讲解】 Linux中vim编辑器的学习与使用
linux·编辑器·vim
苏灿烤鱼3 小时前
一套进程代替八件套,桌面更稳还是单点更大
linux·github·shell
星恒随风3 小时前
C++ 红黑树封装(二):迭代器、const_iterator 与 operator[] 完整实现 mymap 和 myset
开发语言·c++·状态模式