Fix deploy.sh CRLF line endings for Linux execution.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-12 13:37:24 +08:00
parent b38b821c35
commit 136fc51f62
3 changed files with 960 additions and 945 deletions
+13
View File
@@ -0,0 +1,13 @@
# 强制 Shell 脚本使用 LF,避免 Linux 上 $'\r': command not found
*.sh text eol=lf
deploy.sh text eol=lf
# 其他应在 Linux 服务器运行的文本文件
*.py text eol=lf
*.js text eol=lf
*.md text eol=lf
*.txt text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
.gitignore text eol=lf
.gitattributes text eol=lf
+607 -605
View File
File diff suppressed because it is too large Load Diff
+340 -340
View File
@@ -1,340 +1,340 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================= # =============================================================================
# Trading Studio 一键部署脚本 # Trading Studio 一键部署脚本
# 安装路径: /opt/Trading_Studio # 安装路径: /opt/Trading_Studio
# 运行用户: root # 运行用户: root
# 功能: 系统依赖 → 代码拉取 → Python 虚拟环境 → PyTorch CUDA → PM2 常驻 # 功能: 系统依赖 → 代码拉取 → Python 虚拟环境 → PyTorch CUDA → PM2 常驻
# #
# 用法: # 用法:
# sudo bash deploy.sh # 首次完整部署并 PM2 启动 # sudo bash deploy.sh # 首次完整部署并 PM2 启动
# sudo bash deploy.sh update # 拉取最新代码、更新依赖、重启 PM2 # sudo bash deploy.sh update # 拉取最新代码、更新依赖、重启 PM2
# sudo bash deploy.sh restart # 仅重启 PM2 进程 # sudo bash deploy.sh restart # 仅重启 PM2 进程
# sudo bash deploy.sh stop # 停止 PM2 进程 # sudo bash deploy.sh stop # 停止 PM2 进程
# sudo bash deploy.sh status # 查看 PM2 与 GPU 状态 # sudo bash deploy.sh status # 查看 PM2 与 GPU 状态
# sudo bash deploy.sh logs # 查看 PM2 最近日志 # sudo bash deploy.sh logs # 查看 PM2 最近日志
# ============================================================================= # =============================================================================
set -euo pipefail set -euo pipefail
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 可配置常量 # 可配置常量
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
INSTALL_DIR="/opt/Trading_Studio" INSTALL_DIR="/opt/Trading_Studio"
GIT_REPO="https://git.bz121.com/dekun/Trading_Studio.git" GIT_REPO="https://git.bz121.com/dekun/Trading_Studio.git"
PM2_APP_NAME="trading_studio" PM2_APP_NAME="trading_studio"
GRADIO_PORT=5683 GRADIO_PORT=5683
GPU_POWER_LIMIT=120 GPU_POWER_LIMIT=120
PYTORCH_INDEX="https://download.pytorch.org/whl/cu121" PYTORCH_INDEX="https://download.pytorch.org/whl/cu121"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 颜色输出 # 颜色输出
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
NC='\033[0m' NC='\033[0m'
log_info() { echo -e "${CYAN}[INFO]${NC} $*"; } log_info() { echo -e "${CYAN}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; } log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 前置检查 # 前置检查
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
require_root() { require_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
log_error "请使用 root 用户运行: sudo bash deploy.sh" log_error "请使用 root 用户运行: sudo bash deploy.sh"
exit 1 exit 1
fi fi
} }
check_gpu() { check_gpu() {
if command -v nvidia-smi &>/dev/null; then if command -v nvidia-smi &>/dev/null; then
log_ok "检测到 NVIDIA GPU:" log_ok "检测到 NVIDIA GPU:"
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader
else else
log_warn "未检测到 nvidia-smiWhisper/ChatTTS CUDA 加速可能不可用。" log_warn "未检测到 nvidia-smiWhisper/ChatTTS CUDA 加速可能不可用。"
fi fi
} }
set_gpu_power_limit() { set_gpu_power_limit() {
if command -v nvidia-smi &>/dev/null; then if command -v nvidia-smi &>/dev/null; then
log_info "设置 GPU 功耗上限为 ${GPU_POWER_LIMIT}W ..." log_info "设置 GPU 功耗上限为 ${GPU_POWER_LIMIT}W ..."
if nvidia-smi -pl "${GPU_POWER_LIMIT}" &>/dev/null; then if nvidia-smi -pl "${GPU_POWER_LIMIT}" &>/dev/null; then
log_ok "GPU 功耗墙已设为 ${GPU_POWER_LIMIT}W" log_ok "GPU 功耗墙已设为 ${GPU_POWER_LIMIT}W"
else else
log_warn "无法设置功耗墙,请手动执行: nvidia-smi -pl ${GPU_POWER_LIMIT}" log_warn "无法设置功耗墙,请手动执行: nvidia-smi -pl ${GPU_POWER_LIMIT}"
fi fi
fi fi
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 系统依赖 # 系统依赖
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
install_system_deps() { install_system_deps() {
log_info "安装系统依赖 ..." log_info "安装系统依赖 ..."
apt-get update -qq apt-get update -qq
apt-get install -y \ apt-get install -y \
git curl wget build-essential \ git curl wget build-essential \
python3 python3-venv python3-dev python3-pip \ python3 python3-venv python3-dev python3-pip \
ffmpeg libsndfile1 portaudio19-dev \ ffmpeg libsndfile1 portaudio19-dev \
ca-certificates gnupg ca-certificates gnupg
log_ok "系统依赖安装完成" log_ok "系统依赖安装完成"
} }
install_node_pm2() { install_node_pm2() {
if command -v pm2 &>/dev/null; then if command -v pm2 &>/dev/null; then
log_ok "PM2 已安装: $(pm2 -v)" log_ok "PM2 已安装: $(pm2 -v)"
return return
fi fi
log_info "安装 Node.js 20 LTS 与 PM2 ..." log_info "安装 Node.js 20 LTS 与 PM2 ..."
if ! command -v node &>/dev/null; then if ! command -v node &>/dev/null; then
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs apt-get install -y nodejs
fi fi
npm install -g pm2 npm install -g pm2
log_ok "PM2 安装完成: $(pm2 -v)" log_ok "PM2 安装完成: $(pm2 -v)"
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 代码部署 # 代码部署
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
deploy_code() { deploy_code() {
if [[ -d "${INSTALL_DIR}/.git" ]]; then if [[ -d "${INSTALL_DIR}/.git" ]]; then
log_info "更新已有代码: ${INSTALL_DIR}" log_info "更新已有代码: ${INSTALL_DIR}"
git -C "${INSTALL_DIR}" pull --ff-only || { git -C "${INSTALL_DIR}" pull --ff-only || {
log_warn "git pull 失败,尝试保留本地更改继续部署 ..." log_warn "git pull 失败,尝试保留本地更改继续部署 ..."
} }
elif [[ -d "${INSTALL_DIR}" ]]; then elif [[ -d "${INSTALL_DIR}" ]]; then
log_error "${INSTALL_DIR} 已存在但不是 git 仓库,请手动处理后重试。" log_error "${INSTALL_DIR} 已存在但不是 git 仓库,请手动处理后重试。"
exit 1 exit 1
else else
log_info "克隆仓库到 ${INSTALL_DIR} ..." log_info "克隆仓库到 ${INSTALL_DIR} ..."
git clone "${GIT_REPO}" "${INSTALL_DIR}" git clone "${GIT_REPO}" "${INSTALL_DIR}"
fi fi
log_ok "代码就绪: ${INSTALL_DIR}" log_ok "代码就绪: ${INSTALL_DIR}"
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Python 环境 # Python 环境
# ------------------------------------------------------------------- # ---------------------------------------------------------------------------
setup_python_venv() { setup_python_venv() {
local venv_path="${INSTALL_DIR}/venv" local venv_path="${INSTALL_DIR}/venv"
if [[ ! -d "${venv_path}" ]]; then if [[ ! -d "${venv_path}" ]]; then
log_info "创建 Python 虚拟环境 ..." log_info "创建 Python 虚拟环境 ..."
python3 -m venv "${venv_path}" python3 -m venv "${venv_path}"
fi fi
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source "${venv_path}/bin/activate" source "${venv_path}/bin/activate"
log_info "升级 pip ..." log_info "升级 pip ..."
pip install --upgrade pip setuptools wheel -q pip install --upgrade pip setuptools wheel -q
log_info "安装 PyTorch (CUDA 12.1) ..." log_info "安装 PyTorch (CUDA 12.1) ..."
pip install torch torchvision torchaudio --index-url "${PYTORCH_INDEX}" -q pip install torch torchvision torchaudio --index-url "${PYTORCH_INDEX}" -q
log_info "安装项目依赖 ..." log_info "安装项目依赖 ..."
pip install -r "${INSTALL_DIR}/requirements.txt" -q pip install -r "${INSTALL_DIR}/requirements.txt" -q
# 验证 CUDA # 验证 CUDA
if python -c "import torch; assert torch.cuda.is_available()" 2>/dev/null; then if python -c "import torch; assert torch.cuda.is_available()" 2>/dev/null; then
log_ok "PyTorch CUDA 可用: $(python -c 'import torch; print(torch.cuda.get_device_name(0))')" log_ok "PyTorch CUDA 可用: $(python -c 'import torch; print(torch.cuda.get_device_name(0))')"
else else
log_warn "PyTorch CUDA 不可用,请检查 NVIDIA 驱动与 CUDA 运行时。" log_warn "PyTorch CUDA 不可用,请检查 NVIDIA 驱动与 CUDA 运行时。"
fi fi
deactivate deactivate
log_ok "Python 虚拟环境配置完成" log_ok "Python 虚拟环境配置完成"
} }
create_runtime_dirs() { create_runtime_dirs() {
mkdir -p "${INSTALL_DIR}/logs" mkdir -p "${INSTALL_DIR}/logs"
mkdir -p "${INSTALL_DIR}/uploads" mkdir -p "${INSTALL_DIR}/uploads"
mkdir -p "${INSTALL_DIR}/outputs" mkdir -p "${INSTALL_DIR}/outputs"
log_ok "运行时目录已创建 (logs, uploads, outputs)" log_ok "运行时目录已创建 (logs, uploads, outputs)"
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 防火墙 # 防火墙
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
configure_firewall() { configure_firewall() {
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
log_info "放行 Gradio 端口 ${GRADIO_PORT} ..." log_info "放行 Gradio 端口 ${GRADIO_PORT} ..."
ufw allow "${GRADIO_PORT}/tcp" || true ufw allow "${GRADIO_PORT}/tcp" || true
log_ok "防火墙规则已更新" log_ok "防火墙规则已更新"
else else
log_info "ufw 未启用,跳过防火墙配置" log_info "ufw 未启用,跳过防火墙配置"
fi fi
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# PM2 管理 # PM2 管理
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
pm2_start() { pm2_start() {
log_info "通过 PM2 启动 Trading Studio ..." log_info "通过 PM2 启动 Trading Studio ..."
cd "${INSTALL_DIR}" cd "${INSTALL_DIR}"
# 若已有同名进程则先删除再启动,避免重复 # 若已有同名进程则先删除再启动,避免重复
if pm2 describe "${PM2_APP_NAME}" &>/dev/null; then if pm2 describe "${PM2_APP_NAME}" &>/dev/null; then
pm2 delete "${PM2_APP_NAME}" || true pm2 delete "${PM2_APP_NAME}" || true
fi fi
pm2 start ecosystem.config.js pm2 start ecosystem.config.js
pm2 save pm2 save
# 配置 root 用户开机自启 # 配置 root 用户开机自启
local startup_cmd local startup_cmd
startup_cmd=$(pm2 startup systemd -u root --hp /root 2>&1 | grep "sudo env" || true) startup_cmd=$(pm2 startup systemd -u root --hp /root 2>&1 | grep "sudo env" || true)
if [[ -n "${startup_cmd}" ]]; then if [[ -n "${startup_cmd}" ]]; then
eval "${startup_cmd}" || log_warn "PM2 startup 可能已配置过" eval "${startup_cmd}" || log_warn "PM2 startup 可能已配置过"
fi fi
pm2 save pm2 save
log_ok "PM2 启动完成" log_ok "PM2 启动完成"
pm2 status pm2 status
} }
pm2_restart() { pm2_restart() {
cd "${INSTALL_DIR}" cd "${INSTALL_DIR}"
if pm2 describe "${PM2_APP_NAME}" &>/dev/null; then if pm2 describe "${PM2_APP_NAME}" &>/dev/null; then
pm2 restart "${PM2_APP_NAME}" pm2 restart "${PM2_APP_NAME}"
log_ok "PM2 已重启: ${PM2_APP_NAME}" log_ok "PM2 已重启: ${PM2_APP_NAME}"
else else
log_warn "进程不存在,执行完整启动 ..." log_warn "进程不存在,执行完整启动 ..."
pm2_start pm2_start
fi fi
pm2 status pm2 status
} }
pm2_stop() { pm2_stop() {
if pm2 describe "${PM2_APP_NAME}" &>/dev/null; then if pm2 describe "${PM2_APP_NAME}" &>/dev/null; then
pm2 stop "${PM2_APP_NAME}" pm2 stop "${PM2_APP_NAME}"
log_ok "PM2 已停止: ${PM2_APP_NAME}" log_ok "PM2 已停止: ${PM2_APP_NAME}"
else else
log_warn "PM2 进程 ${PM2_APP_NAME} 不存在" log_warn "PM2 进程 ${PM2_APP_NAME} 不存在"
fi fi
pm2 status pm2 status
} }
pm2_status() { pm2_status() {
echo "" echo ""
log_info "=== PM2 状态 ===" log_info "=== PM2 状态 ==="
pm2 status || true pm2 status || true
echo "" echo ""
log_info "=== GPU 状态 ===" log_info "=== GPU 状态 ==="
nvidia-smi 2>/dev/null || log_warn "nvidia-smi 不可用" nvidia-smi 2>/dev/null || log_warn "nvidia-smi 不可用"
echo "" echo ""
log_info "=== 端口 ${GRADIO_PORT} 监听 ===" log_info "=== 端口 ${GRADIO_PORT} 监听 ==="
ss -tlnp | grep ":${GRADIO_PORT}" || log_warn "端口 ${GRADIO_PORT} 未监听,服务可能未启动" ss -tlnp | grep ":${GRADIO_PORT}" || log_warn "端口 ${GRADIO_PORT} 未监听,服务可能未启动"
echo "" echo ""
log_info "访问地址: http://$(hostname -I | awk '{print $1}'):${GRADIO_PORT}" log_info "访问地址: http://$(hostname -I | awk '{print $1}'):${GRADIO_PORT}"
} }
pm2_logs() { pm2_logs() {
pm2 logs "${PM2_APP_NAME}" --lines 80 --nostream || true pm2 logs "${PM2_APP_NAME}" --lines 80 --nostream || true
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 主流程 # 主流程
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
cmd_install() { cmd_install() {
log_info "========== Trading Studio 一键部署开始 ==========" log_info "========== Trading Studio 一键部署开始 =========="
log_info "安装目录: ${INSTALL_DIR}" log_info "安装目录: ${INSTALL_DIR}"
log_info "运行用户: root" log_info "运行用户: root"
install_system_deps install_system_deps
install_node_pm2 install_node_pm2
deploy_code deploy_code
setup_python_venv setup_python_venv
create_runtime_dirs create_runtime_dirs
set_gpu_power_limit set_gpu_power_limit
configure_firewall configure_firewall
pm2_start pm2_start
echo "" echo ""
log_ok "========== 部署完成 ==========" log_ok "========== 部署完成 =========="
echo "" echo ""
echo -e " Web 中控: ${GREEN}http://$(hostname -I | awk '{print $1}'):${GRADIO_PORT}${NC}" echo -e " Web 中控: ${GREEN}http://$(hostname -I | awk '{print $1}'):${GRADIO_PORT}${NC}"
echo -e " 项目目录: ${INSTALL_DIR}" echo -e " 项目目录: ${INSTALL_DIR}"
echo -e " 查看日志: ${CYAN}pm2 logs ${PM2_APP_NAME}${NC}" echo -e " 查看日志: ${CYAN}pm2 logs ${PM2_APP_NAME}${NC}"
echo -e " 重启服务: ${CYAN}bash ${INSTALL_DIR}/deploy.sh restart${NC}" echo -e " 重启服务: ${CYAN}bash ${INSTALL_DIR}/deploy.sh restart${NC}"
echo "" echo ""
log_warn "首次使用请打开 Web UI「音色锁定」上传参考人声,生成 speaker_emb.pt" log_warn "首次使用请打开 Web UI「音色锁定」上传参考人声,生成 speaker_emb.pt"
} }
cmd_update() { cmd_update() {
log_info "========== 更新部署 ==========" log_info "========== 更新部署 =========="
deploy_code deploy_code
setup_python_venv setup_python_venv
create_runtime_dirs create_runtime_dirs
pm2_restart pm2_restart
log_ok "更新完成" log_ok "更新完成"
} }
print_usage() { print_usage() {
cat <<EOF cat <<EOF
Trading Studio 一键部署脚本 Trading Studio 一键部署脚本
用法: 用法:
sudo bash deploy.sh [命令] sudo bash deploy.sh [命令]
命令: 命令:
(无参数) 首次完整部署到 /opt/Trading_Studio 并由 PM2 启动 (无参数) 首次完整部署到 /opt/Trading_Studio 并由 PM2 启动
install 同上 install 同上
update 拉取最新代码、更新 Python 依赖、重启 PM2 update 拉取最新代码、更新 Python 依赖、重启 PM2
restart 重启 PM2 进程 restart 重启 PM2 进程
stop 停止 PM2 进程 stop 停止 PM2 进程
status 查看 PM2 / GPU / 端口状态 status 查看 PM2 / GPU / 端口状态
logs 查看 PM2 最近日志 logs 查看 PM2 最近日志
help 显示本帮助 help 显示本帮助
示例: 示例:
cd /opt/Trading_Studio && sudo bash deploy.sh update cd /opt/Trading_Studio && sudo bash deploy.sh update
EOF EOF
} }
main() { main() {
require_root require_root
local cmd="${1:-install}" local cmd="${1:-install}"
case "${cmd}" in case "${cmd}" in
install|"") install|"")
check_gpu check_gpu
cmd_install cmd_install
;; ;;
update) update)
check_gpu check_gpu
cmd_update cmd_update
;; ;;
restart) restart)
pm2_restart pm2_restart
;; ;;
stop) stop)
pm2_stop pm2_stop
;; ;;
status) status)
pm2_status pm2_status
;; ;;
logs) logs)
pm2_logs pm2_logs
;; ;;
help|-h|--help) help|-h|--help)
print_usage print_usage
;; ;;
*) *)
log_error "未知命令: ${cmd}" log_error "未知命令: ${cmd}"
print_usage print_usage
exit 1 exit 1
;; ;;
esac esac
} }
main "$@" main "$@"