feat: align with upstream 1.5.9.9
This commit is contained in:
+362
@@ -0,0 +1,362 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ==================== 配置常量 ====================
|
||||
COMPOSE_DIR="/opt/voicehub"
|
||||
COMPOSE_FILE="${COMPOSE_DIR}/docker-compose.yml"
|
||||
|
||||
# 数据库固定账号库名(可自行修改)
|
||||
PG_USER="user"
|
||||
PG_DB_NAME="voicehub"
|
||||
GHCR_HOST="ghcr.nju.edu.cn"
|
||||
|
||||
# 镜像地址常量
|
||||
VOICEHUB_IMAGE="ghcr.nju.edu.cn/laoshuikaixue/voicehub:latest"
|
||||
POSTGRES_IMAGE="ghcr.nju.edu.cn/laoshuikaixue/voicehub-postgres:latest"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# ==================== root 提权 ====================
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e "权限:root 用户:${RED}非 root${NC}"
|
||||
exit 1
|
||||
# exec sudo "$0" "$@"
|
||||
else
|
||||
echo -e "权限:root 用户:${GREEN}正常${NC}"
|
||||
fi
|
||||
|
||||
# ==================== 系统信息展示 ====================
|
||||
OS_NAME=$(grep -oP 'PRETTY_NAME="\K[^"]+' /etc/os-release)
|
||||
echo -e "兼容性:操作系统:${GREEN}${OS_NAME}${NC}"
|
||||
|
||||
# 检查架构
|
||||
valid_archs=("x86_64" "armv6l" "armv7l" "aarch64" "ppc64le" "s390x")
|
||||
current_arch=$(uname -m)
|
||||
found=false
|
||||
|
||||
for arch in "${valid_archs[@]}"; do
|
||||
if [ "$arch" = "$current_arch" ]; then
|
||||
found=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if $found; then
|
||||
echo -e "兼容性:系统架构:${GREEN}${current_arch}(符合要求)${NC}"
|
||||
else
|
||||
echo -e "兼容性:系统架构:${RED}${current_arch}(不支持)${NC},${RED}支持的架构:${valid_archs[*]}${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ==================== 检查依赖 ====================
|
||||
# 检查 docker
|
||||
if command -v docker &> /dev/null; then
|
||||
echo -e "依赖:docker:${GREEN}已安装${NC}"
|
||||
else
|
||||
echo -e "依赖:docker:${RED}未安装,请先安装 Docker${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 docker-compose / docker compose
|
||||
DOCKER_COMPOSE_CMD=""
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
DOCKER_COMPOSE_CMD="docker-compose"
|
||||
echo -e "依赖:docker-compose:${GREEN}已安装${NC}"
|
||||
elif docker compose version &> /dev/null; then
|
||||
DOCKER_COMPOSE_CMD="docker compose"
|
||||
echo -e "依赖:docker compose:${GREEN}已安装${NC}"
|
||||
else
|
||||
echo -e "依赖:docker-compose:${RED}未安装${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ==================== 随机密钥生成逻辑:openssl优先,无则bash内置降级 ====================
|
||||
gen_bash_random() {
|
||||
local length="$1"
|
||||
local charset='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
local str=""
|
||||
for ((i=0; i<length; i++)); do
|
||||
str+=${charset:$(( RANDOM % ${#charset} )):1}
|
||||
done
|
||||
echo "$str"
|
||||
}
|
||||
|
||||
# 判定openssl可用性
|
||||
if command -v openssl &> /dev/null; then
|
||||
echo -e "依赖:openssl:${GREEN}已安装${NC}"
|
||||
gen_openssl() {
|
||||
local LEN="$1"
|
||||
openssl rand -base64 $((LEN*2)) | tr -dc 'A-Za-z0-9' | head -c "$LEN"
|
||||
echo
|
||||
}
|
||||
JWT_SECRET=$(gen_openssl 64)
|
||||
PG_PASSWORD=$(gen_openssl 64)
|
||||
else
|
||||
echo -e "${YELLOW}警告:未找到openssl,降级使用Bash内置RANDOM生成${NC}"
|
||||
JWT_SECRET=$(gen_bash_random 64)
|
||||
PG_PASSWORD=$(gen_bash_random 64)
|
||||
fi
|
||||
|
||||
# ==================== 检查网络 ====================
|
||||
echo -e "网络:${GHCR_HOST} ...${NC}"
|
||||
if ping -c 4 -W 5 "${GHCR_HOST}" >/dev/null 2>&1; then
|
||||
echo -e "网络:${GHCR_HOST} ${GREEN}连通正常${NC}"
|
||||
else
|
||||
echo -e "网络:${GHCR_HOST} ${RED}失败,网络无法连通该镜像仓库,退出执行${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ==================== 生成配置 ====================
|
||||
# 创建工作目录
|
||||
if [ ! -d "${COMPOSE_DIR}" ]; then
|
||||
echo -e "${YELLOW}未检测到工作目录,正在创建:${COMPOSE_DIR}${NC}"
|
||||
mkdir -p "${COMPOSE_DIR}"
|
||||
chmod 755 "${COMPOSE_DIR}"
|
||||
else
|
||||
echo -e "环境:工作目录:${GREEN}已存在${NC}"
|
||||
fi
|
||||
|
||||
# 生成 compose 文件模板
|
||||
generate_compose_template() {
|
||||
cat > "${COMPOSE_FILE}" <<'EOF'
|
||||
services:
|
||||
voicehub:
|
||||
image: ghcr.nju.edu.cn/laoshuikaixue/voicehub:latest
|
||||
ports:
|
||||
- '3000:3000'
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://user:PG_PWD_PLACEHOLDER@postgres:5432/voicehub?sslmode=disable
|
||||
- JWT_SECRET=JWT_SECRET_PLACEHOLDER
|
||||
- NODE_ENV=production
|
||||
- SKIP_BUILD=true
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: ghcr.nju.edu.cn/laoshuikaixue/voicehub-postgres:latest
|
||||
environment:
|
||||
- POSTGRES_USER=user
|
||||
- POSTGRES_PASSWORD=PG_PWD_PLACEHOLDER
|
||||
- POSTGRES_DB=voicehub
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U user -d voicehub']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
EOF
|
||||
}
|
||||
|
||||
# 生成配置文件
|
||||
generate_config() {
|
||||
echo -e "${YELLOW}正在生成全新配置文件...${NC}"
|
||||
generate_compose_template
|
||||
|
||||
# sed 批量替换占位符
|
||||
sed -i "s|PG_PWD_PLACEHOLDER|${PG_PASSWORD}|g" "${COMPOSE_FILE}"
|
||||
sed -i "s|JWT_SECRET_PLACEHOLDER|${JWT_SECRET}|g" "${COMPOSE_FILE}"
|
||||
|
||||
echo -e "环境:配置文件:${GREEN}已生成并填充密钥${NC}"
|
||||
echo -e " JWT_SECRET:${BLUE}${JWT_SECRET}${NC}"
|
||||
echo -e " 数据库密码:${BLUE}${PG_PASSWORD}${NC}"
|
||||
}
|
||||
|
||||
# ==================== 服务控制函数 ====================
|
||||
start_service() {
|
||||
echo -e "\n${GREEN}=== 正在启动 VoiceHub 服务 ===${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} up -d
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
echo -e "\n${RED}=== 正在停止 VoiceHub 服务 ===${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} down
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
echo -e "\n${YELLOW}=== 正在重启 VoiceHub 服务 ===${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} restart
|
||||
}
|
||||
|
||||
status_service() {
|
||||
echo -e "\n${BLUE}=== 当前服务状态 ===${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} ps
|
||||
}
|
||||
|
||||
# ==================== 检查/更新镜像函数(基于镜像ID对比) ====================
|
||||
check_and_update_image() {
|
||||
local IMAGE_FULL="$1"
|
||||
local IMAGE_NAME="$2"
|
||||
|
||||
# 1. 本地无镜像 → 直接拉取
|
||||
if [ -z "$(docker images -q "${IMAGE_FULL}")" ]; then
|
||||
echo -e "镜像版本:${YELLOW}${IMAGE_NAME}本地无镜像,开始拉取最新版${NC}"
|
||||
if docker pull "${IMAGE_FULL}"; then
|
||||
echo -e "镜像版本:${GREEN}${IMAGE_NAME}拉取成功${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "镜像版本:${RED}${IMAGE_NAME}拉取失败${NC}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. 执行拉取并判断状态
|
||||
echo -e "镜像版本:${YELLOW}检测${IMAGE_NAME}更新状态${NC}"
|
||||
local PULL_OUTPUT
|
||||
PULL_OUTPUT=$(docker pull "${IMAGE_FULL}" 2>&1)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "镜像版本:${RED}${IMAGE_NAME}拉取失败:${PULL_OUTPUT}${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if echo "${PULL_OUTPUT}" | grep -q "Image is up to date"; then
|
||||
echo -e "镜像版本:${GREEN}${IMAGE_NAME}已是最新${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "镜像版本:${YELLOW}${IMAGE_NAME}已更新至新版本${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# ==================== 交互式初始化函数(仅重新部署使用) ====================
|
||||
run_interactive_init() {
|
||||
echo -e "\n${BLUE}=== 进入交互式初始化环境 ===${NC}"
|
||||
echo -e "${YELLOW}提示:进入容器后执行可能需要按回车${NC}"
|
||||
|
||||
cd "${COMPOSE_DIR}"
|
||||
# 临时启动容器进入bash,退出自动删除
|
||||
${DOCKER_COMPOSE_CMD} run --rm voicehub pnpm run init-help
|
||||
}
|
||||
|
||||
# ==================== 1.重新部署(清空数据卷 + 执行初始化) ====================
|
||||
redeploy_service() {
|
||||
echo -e "\n${RED}=================================================="
|
||||
echo -e "警告:重新部署会清空所有数据库数据!"
|
||||
echo -e "==================================================${NC}"
|
||||
read -p "确定继续?(y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo -e "${YELLOW}已取消重新部署${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
# 停止服务
|
||||
echo -e "${YELLOW}停止旧的 voicehub (若有)${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} down 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# 检查并更新两个镜像
|
||||
echo -e "\n${BLUE}=== 开始检查并更新镜像 ===${NC}"
|
||||
if ! check_and_update_image "${VOICEHUB_IMAGE}" "VoiceHub主程序"; then
|
||||
echo -e "${RED}镜像更新失败,终止重新部署${NC}"
|
||||
return 1
|
||||
fi
|
||||
if ! check_and_update_image "${POSTGRES_IMAGE}" "PostgreSQL数据库"; then
|
||||
echo -e "${RED}镜像更新失败,终止重新部署${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 删除旧配置
|
||||
if [ -f "${COMPOSE_FILE}" ]; then
|
||||
echo -e "${YELLOW}删除旧 docker-compose.yml...${NC}"
|
||||
rm -f "${COMPOSE_FILE}"
|
||||
fi
|
||||
|
||||
# 删除数据库数据卷(清空数据)
|
||||
echo -e "${YELLOW}删除数据库数据卷 voicehub_postgres_data...${NC}"
|
||||
docker volume rm voicehub_postgres_data 2>/dev/null || true
|
||||
|
||||
# 生成新配置
|
||||
generate_config
|
||||
|
||||
# 启动整套服务
|
||||
echo -e "\n${YELLOW}启动数据库服务...${NC}"
|
||||
start_service
|
||||
sleep 10
|
||||
|
||||
# 执行交互式初始化
|
||||
run_interactive_init
|
||||
|
||||
# 重启正式业务容器
|
||||
echo -e "\n${YELLOW}重启正式业务容器...${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} restart
|
||||
|
||||
echo -e "\n${GREEN}✅ 重新部署完成(数据已清空,已执行初始化)${NC}"
|
||||
status_service
|
||||
}
|
||||
|
||||
# ==================== 2.版本更新(保留原有数据卷,不执行初始化) ====================
|
||||
update_version() {
|
||||
echo -e "\n${YELLOW}=================================================="
|
||||
echo -e "说明:版本更新【保留原有数据库数据,不执行初始化】"
|
||||
echo -e "==================================================${NC}"
|
||||
read -p "确定继续更新版本?(y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo -e "${YELLOW}已取消版本更新${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
# 停止服务(不删数据卷)
|
||||
echo -e "${YELLOW}停止当前服务${NC}"
|
||||
cd "${COMPOSE_DIR}" && ${DOCKER_COMPOSE_CMD} down 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# 检查并更新镜像
|
||||
echo -e "\n${BLUE}=== 检查并拉取最新镜像 ===${NC}"
|
||||
if ! check_and_update_image "${VOICEHUB_IMAGE}" "VoiceHub主程序"; then
|
||||
echo -e "${RED}镜像更新失败,终止版本升级${NC}"
|
||||
return 1
|
||||
fi
|
||||
if ! check_and_update_image "${POSTGRES_IMAGE}" "PostgreSQL数据库"; then
|
||||
echo -e "${RED}镜像更新失败,终止版本升级${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 直接启动服务,沿用旧数据、旧配置,跳过初始化
|
||||
echo -e "${YELLOW}使用现有配置与数据库,直接启动服务${NC}"
|
||||
start_service
|
||||
|
||||
echo -e "\n${YELLOW}等待容器启动完成(10秒)...${NC}"
|
||||
sleep 10
|
||||
|
||||
echo -e "\n${GREEN}✅ 版本更新完成(已保留原有数据库数据)${NC}"
|
||||
status_service
|
||||
}
|
||||
|
||||
# ==================== 主菜单 ====================
|
||||
show_menu() {
|
||||
echo -e "\n========================================"
|
||||
echo -e " VoiceHub 管理菜单"
|
||||
echo -e "========================================"
|
||||
echo -e " 1. 重新部署(清空数据,全新安装+初始化)"
|
||||
echo -e " 2. 版本更新(保留旧数据,仅升级镜像)"
|
||||
echo -e " 3. 查看服务状态"
|
||||
echo -e " 0. 退出脚本"
|
||||
echo -e "========================================"
|
||||
echo -n "请输入数字选择操作:"
|
||||
}
|
||||
|
||||
# 循环菜单
|
||||
while true; do
|
||||
show_menu
|
||||
read -r choice
|
||||
case $choice in
|
||||
1) redeploy_service ;;
|
||||
2) update_version ;;
|
||||
3) status_service ;;
|
||||
0) echo -e "\n${GREEN}退出脚本${NC}"; exit 0 ;;
|
||||
*) echo -e "\n${RED}输入错误,请输入有效数字!${NC}" ;;
|
||||
esac
|
||||
done
|
||||
@@ -0,0 +1,572 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 确保以 bash 运行
|
||||
if [ -z "$BASH_VERSION" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
|
||||
# VoiceHub 一键部署脚本
|
||||
# 支持 Ubuntu/Debian 系统
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 项目配置
|
||||
REPO_URL="https://github.com/laoshuikaixue/VoiceHub.git"
|
||||
PROJECT_DIR="/opt/voicehub"
|
||||
|
||||
ensure_pnpm() {
|
||||
export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
mkdir -p "$PNPM_HOME"
|
||||
export PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
if ! command -v corepack &> /dev/null; then
|
||||
sudo npm install -g corepack
|
||||
hash -r
|
||||
fi
|
||||
|
||||
corepack enable
|
||||
|
||||
local package_manager=""
|
||||
if [[ -f "$PROJECT_DIR/package.json" ]]; then
|
||||
package_manager=$(node -p "try { JSON.parse(require('fs').readFileSync('$PROJECT_DIR/package.json', 'utf8')).packageManager || '' } catch { '' }")
|
||||
fi
|
||||
|
||||
if [[ -n "$package_manager" ]]; then
|
||||
corepack prepare "$package_manager" --activate
|
||||
fi
|
||||
|
||||
hash -r
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
echo -e "${RED}错误: pnpm 不可用${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
persist_pm2_binary() {
|
||||
sudo tee /usr/local/bin/pm2 > /dev/null << 'EOF'
|
||||
#!/bin/bash
|
||||
# VoiceHub PM2 Wrapper - Managed by VoiceHub deployment
|
||||
set -e
|
||||
|
||||
export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
export PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
mkdir -p "$PNPM_HOME"
|
||||
|
||||
if ! command -v corepack &> /dev/null; then
|
||||
echo "错误: corepack 不可用,请重新运行部署脚本" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
corepack enable > /dev/null 2>&1 || true
|
||||
|
||||
if [[ -f "/opt/voicehub/package.json" ]]; then
|
||||
package_manager=$(node -p "try { JSON.parse(require('fs').readFileSync('/opt/voicehub/package.json', 'utf8')).packageManager || '' } catch { '' }" 2>/dev/null || true)
|
||||
if [[ -n "$package_manager" ]]; then
|
||||
corepack prepare "$package_manager" --activate > /dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
hash -r 2>/dev/null || true
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
echo "错误: pnpm 不可用,请检查 corepack 配置或重新运行部署脚本" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
PM2_ROOT="$(pnpm root -g 2>/dev/null || true)"
|
||||
PM2_BIN="$PM2_ROOT/pm2/bin/pm2"
|
||||
|
||||
if [[ ! -f "$PM2_BIN" ]]; then
|
||||
echo "错误: PM2 未正确安装,请重新运行部署脚本" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec node "$PM2_BIN" "$@"
|
||||
EOF
|
||||
sudo chmod +x /usr/local/bin/pm2
|
||||
}
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} VoiceHub 一键部署脚本${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 1: 检查系统是否为 Ubuntu 或 Debian
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[1/9] 检查系统类型...${NC}"
|
||||
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
. /etc/os-release
|
||||
OS=$ID
|
||||
VER=$VERSION_ID
|
||||
else
|
||||
echo -e "${RED}错误: 无法检测操作系统${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$OS" != "ubuntu" && "$OS" != "debian" ]]; then
|
||||
echo -e "${RED}错误: 此脚本仅支持 Ubuntu 或 Debian 系统${NC}"
|
||||
echo -e "${RED}当前系统: $OS $VER${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ 检测到系统: $OS $VER${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}安装目录: $PROJECT_DIR${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 2: 检查并安装 Node.js 22+
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[2/9] 检查 Node.js 版本...${NC}"
|
||||
|
||||
# 检查是否已安装 node
|
||||
if command -v node &> /dev/null; then
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
echo -e "当前 Node.js 版本: $(node -v)"
|
||||
|
||||
if [[ $NODE_VERSION -ge 22 ]]; then
|
||||
echo -e "${GREEN}✓ Node.js 版本满足要求 (>= 22)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}! Node.js 版本过低,需要升级到 22+${NC}"
|
||||
NODE_NEED_INSTALL=true
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}未检测到 Node.js,需要安装${NC}"
|
||||
NODE_NEED_INSTALL=true
|
||||
fi
|
||||
|
||||
if [[ "$NODE_NEED_INSTALL" == "true" ]]; then
|
||||
echo -e "${YELLOW}正在安装 Node.js 22...${NC}"
|
||||
|
||||
# 安装 NodeSource Node.js 22.x
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
|
||||
# 安装 Node.js
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
echo -e "${GREEN}✓ Node.js 安装完成: $(node -v)${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 3: 询问是否切换 pnpm 国内源
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[3/9] pnpm 镜像源配置${NC}"
|
||||
echo -e "是否需要将 pnpm 切换为国内淘宝源?"
|
||||
echo -e "${BLUE} y - 使用淘宝镜像 (https://registry.npmmirror.com)${NC}"
|
||||
echo -e "${BLUE} n - 使用官方源 (https://registry.npmjs.org)${NC}"
|
||||
read -p "请选择 (y/n): " USE_TAOBAO
|
||||
|
||||
if [[ "$USE_TAOBAO" == "y" || "$USE_TAOBAO" == "Y" ]]; then
|
||||
PNPM_REGISTRY="https://registry.npmmirror.com"
|
||||
else
|
||||
PNPM_REGISTRY="https://registry.npmjs.org"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 4: Git Clone 项目
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[4/9] 克隆项目...${NC}"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]; then
|
||||
echo -e "${YELLOW}项目目录已存在: $PROJECT_DIR${NC}"
|
||||
read -p "是否更新现有项目? (y/n): " UPDATE_PROJECT
|
||||
if [[ "$UPDATE_PROJECT" == "y" || "$UPDATE_PROJECT" == "Y" ]]; then
|
||||
cd "$PROJECT_DIR"
|
||||
git fetch origin main
|
||||
git stash
|
||||
git reset --hard origin/main
|
||||
echo -e "${GREEN}✓ 项目已更新${NC}"
|
||||
fi
|
||||
else
|
||||
sudo mkdir -p "$PROJECT_DIR"
|
||||
sudo chown -R $(whoami) "$PROJECT_DIR"
|
||||
|
||||
echo -e "正在克隆项目到: $PROJECT_DIR"
|
||||
git clone "$REPO_URL" "$PROJECT_DIR"
|
||||
echo -e "${GREEN}✓ 项目克隆完成${NC}"
|
||||
fi
|
||||
|
||||
# [开发调试] 如果远程仓库还没合并 sh 目录,尝试从本地拷贝
|
||||
if [[ ! -d "$PROJECT_DIR/sh" ]]; then
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
if [[ -f "$SCRIPT_DIR/main.sh" ]]; then
|
||||
echo -e "${YELLOW}提示: 正在从本地拷贝 sh 脚本到项目目录...${NC}"
|
||||
mkdir -p "$PROJECT_DIR/sh"
|
||||
cp "$SCRIPT_DIR/"*.sh "$PROJECT_DIR/sh/"
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
echo ""
|
||||
|
||||
ensure_pnpm
|
||||
|
||||
pnpm config set registry "$PNPM_REGISTRY"
|
||||
if [[ "$USE_TAOBAO" == "y" || "$USE_TAOBAO" == "Y" ]]; then
|
||||
echo -e "${GREEN}✓ 已切换为淘宝镜像源${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ 已使用官方 pnpm 源${NC}"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# 步骤 5: 配置 .env 文件
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[5/9] 配置环境变量...${NC}"
|
||||
|
||||
if [[ -f "$PROJECT_DIR/.env" ]]; then
|
||||
read -p ".env 文件已存在,是否重新配置? (y/n): " REBUILD_ENV
|
||||
if [[ "$REBUILD_ENV" != "y" && "$REBUILD_ENV" != "Y" ]]; then
|
||||
echo -e "${GREEN}✓ 使用现有的 .env 配置${NC}"
|
||||
echo ""
|
||||
else
|
||||
NEED_CONFIG=true
|
||||
fi
|
||||
else
|
||||
NEED_CONFIG=true
|
||||
fi
|
||||
|
||||
if [[ "$NEED_CONFIG" == "true" ]]; then
|
||||
echo -e "${YELLOW}请根据 .env.example 配置以下环境变量:${NC}"
|
||||
echo ""
|
||||
|
||||
# 读取 .env.example 获取必填字段
|
||||
if [[ -f "$PROJECT_DIR/.env.example" ]]; then
|
||||
echo -e "${BLUE}===== .env.example 参考 =====${NC}"
|
||||
cat "$PROJECT_DIR/.env.example"
|
||||
echo -e "${BLUE}==============================${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 创建 .env 文件
|
||||
echo "# VoiceHub 环境配置" > "$PROJECT_DIR/.env"
|
||||
echo "" >> "$PROJECT_DIR/.env"
|
||||
|
||||
# DATABASE_URL (必填)
|
||||
read -p "请输入 DATABASE_URL (数据库连接地址): " DATABASE_URL
|
||||
while [[ -z "$DATABASE_URL" ]]; do
|
||||
echo -e "${RED}DATABASE_URL 是必填项,不能为空${NC}"
|
||||
read -p "请输入 DATABASE_URL: " DATABASE_URL
|
||||
done
|
||||
echo "DATABASE_URL=\"$DATABASE_URL\"" >> "$PROJECT_DIR/.env"
|
||||
|
||||
# JWT_SECRET (必填)
|
||||
read -p "请输入 JWT_SECRET (JWT密钥,建议至少32位字符): " JWT_SECRET
|
||||
while [[ -z "$JWT_SECRET" ]]; do
|
||||
echo -e "${RED}JWT_SECRET 是必填项,不能为空${NC}"
|
||||
read -p "请输入 JWT_SECRET: " JWT_SECRET
|
||||
done
|
||||
echo "JWT_SECRET=\"$JWT_SECRET\"" >> "$PROJECT_DIR/.env"
|
||||
|
||||
# NODE_ENV
|
||||
read -p "请输入 NODE_ENV (development/production,默认 production): " NODE_ENV
|
||||
NODE_ENV=${NODE_ENV:-production}
|
||||
echo "NODE_ENV=\"$NODE_ENV\"" >> "$PROJECT_DIR/.env"
|
||||
|
||||
echo "" >> "$PROJECT_DIR/.env"
|
||||
echo "# WebAuthn 配置 (可选)" >> "$PROJECT_DIR/.env"
|
||||
|
||||
# WEBAUTHN_RP_ID (可选)
|
||||
read -p "请输入 WEBAUTHN_RP_ID (WebAuthn依赖方ID,可直接回车跳过): " WEBAUTHN_RP_ID
|
||||
if [[ -n "$WEBAUTHN_RP_ID" ]]; then
|
||||
echo "WEBAUTHN_RP_ID=\"$WEBAUTHN_RP_ID\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
# WEBAUTHN_ORIGIN (可选)
|
||||
read -p "请输入 WEBAUTHN_ORIGIN (允许的Origin列表,可直接回车跳过): " WEBAUTHN_ORIGIN
|
||||
if [[ -n "$WEBAUTHN_ORIGIN" ]]; then
|
||||
echo "WEBAUTHN_ORIGIN=\"$WEBAUTHN_ORIGIN\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
echo "" >> "$PROJECT_DIR/.env"
|
||||
echo "# OAuth 配置 (可选)" >> "$PROJECT_DIR/.env"
|
||||
|
||||
# OAUTH_REDIRECT_URI (可选)
|
||||
read -p "请输入 OAUTH_REDIRECT_URI (OAuth回调地址,可直接回车跳过): " OAUTH_REDIRECT_URI
|
||||
if [[ -n "$OAUTH_REDIRECT_URI" ]]; then
|
||||
echo "OAUTH_REDIRECT_URI=\"$OAUTH_REDIRECT_URI\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
# OAUTH_STATE_SECRET (可选)
|
||||
read -p "请输入 OAUTH_STATE_SECRET (OAuth state加密密钥,可直接回车跳过): " OAUTH_STATE_SECRET
|
||||
if [[ -n "$OAUTH_STATE_SECRET" ]]; then
|
||||
echo "OAUTH_STATE_SECRET=\"$OAUTH_STATE_SECRET\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
# GitHub OAuth (可选)
|
||||
read -p "请输入 GITHUB_CLIENT_ID (GitHub OAuth Client ID,可直接回车跳过): " GITHUB_CLIENT_ID
|
||||
if [[ -n "$GITHUB_CLIENT_ID" ]]; then
|
||||
echo "GITHUB_CLIENT_ID=\"$GITHUB_CLIENT_ID\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
read -p "请输入 GITHUB_CLIENT_SECRET (GitHub OAuth Client Secret,可直接回车跳过): " GITHUB_CLIENT_SECRET
|
||||
if [[ -n "$GITHUB_CLIENT_SECRET" ]]; then
|
||||
echo "GITHUB_CLIENT_SECRET=\"$GITHUB_CLIENT_SECRET\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
# Google OAuth (可选)
|
||||
read -p "请输入 GOOGLE_CLIENT_ID (Google OAuth Client ID,可直接回车跳过): " GOOGLE_CLIENT_ID
|
||||
if [[ -n "$GOOGLE_CLIENT_ID" ]]; then
|
||||
echo "GOOGLE_CLIENT_ID=\"$GOOGLE_CLIENT_ID\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
read -p "请输入 GOOGLE_CLIENT_SECRET (Google OAuth Client Secret,可直接回车跳过): " GOOGLE_CLIENT_SECRET
|
||||
if [[ -n "$GOOGLE_CLIENT_SECRET" ]]; then
|
||||
echo "GOOGLE_CLIENT_SECRET=\"$GOOGLE_CLIENT_SECRET\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
# Redis (可选)
|
||||
read -p "请输入 REDIS_URL (Redis连接地址,可直接回车跳过): " REDIS_URL
|
||||
if [[ -n "$REDIS_URL" ]]; then
|
||||
echo "REDIS_URL=\"$REDIS_URL\"" >> "$PROJECT_DIR/.env"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ .env 文件配置完成${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 6: pnpm install
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[6/9] 安装项目依赖...${NC}"
|
||||
echo -e "执行: pnpm install --frozen-lockfile"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
pnpm install --frozen-lockfile || pnpm install
|
||||
|
||||
echo -e "${GREEN}✓ 依赖安装完成${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 7: 部署 (数据库迁移 + 构建)
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[7/9] 执行部署脚本...${NC}"
|
||||
echo -e "执行: pnpm run deploy"
|
||||
echo ""
|
||||
|
||||
# 尝试执行 pnpm run deploy (包含数据库迁移、管理员创建、构建)
|
||||
if pnpm run deploy; then
|
||||
echo -e "${GREEN}✓ 部署脚本执行成功${NC}"
|
||||
else
|
||||
echo -e "${RED}部署脚本执行失败,尝试仅执行构建...${NC}"
|
||||
echo -e "${YELLOW}注意: 数据库迁移可能未完成,请检查日志${NC}"
|
||||
pnpm run build
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ 项目准备完成${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 8: 配置服务管理 (pm2 或 systemctl)
|
||||
# ============================================
|
||||
echo -e "${YELLOW}配置服务管理...${NC}"
|
||||
echo ""
|
||||
echo -e "请选择服务管理方式:"
|
||||
echo -e "${BLUE} 1 - 使用 pm2 管理 (推荐)${NC}"
|
||||
echo -e "${BLUE} 2 - 使用 systemctl 管理${NC}"
|
||||
echo -e "${BLUE} 3 - 不配置,自行管理${NC}"
|
||||
read -p "请选择 (1/2/3): " SERVICE_CHOICE
|
||||
|
||||
if [[ "$SERVICE_CHOICE" == "1" ]]; then
|
||||
# PM2 管理
|
||||
echo -e "${YELLOW}正在配置 PM2...${NC}"
|
||||
|
||||
# 检查并安装 pm2
|
||||
if ! command -v pm2 &> /dev/null; then
|
||||
echo -e "${YELLOW}正在安装 PM2...${NC}"
|
||||
if ! pnpm add -g pm2; then
|
||||
sudo env "PNPM_HOME=$PNPM_HOME" PATH="$PNPM_HOME:$PATH" pnpm add -g pm2
|
||||
fi
|
||||
persist_pm2_binary
|
||||
echo -e "${GREEN}✓ PM2 安装完成${NC}"
|
||||
else
|
||||
persist_pm2_binary
|
||||
echo -e "${GREEN}✓ PM2 已安装${NC}"
|
||||
fi
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "$PROJECT_DIR/logs"
|
||||
|
||||
# 生成 ecosystem.config.cjs
|
||||
echo -e "${YELLOW}正在生成 ecosystem.config.cjs...${NC}"
|
||||
cat > "$PROJECT_DIR/ecosystem.config.cjs" << EOF
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'voicehub',
|
||||
script: '.output/server/index.mjs',
|
||||
env: {
|
||||
NODE_ENV: 'production'
|
||||
},
|
||||
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
||||
error_file: 'logs/voicehub-error.log',
|
||||
out_file: 'logs/voicehub-out.log'
|
||||
}]
|
||||
}
|
||||
EOF
|
||||
|
||||
# 启动服务
|
||||
cd "$PROJECT_DIR"
|
||||
echo -e "${YELLOW}正在启动 VoiceHub 服务...${NC}"
|
||||
pm2 start ecosystem.config.cjs
|
||||
pm2 save
|
||||
echo -e "${YELLOW}正在设置 PM2 开机自起...${NC}"
|
||||
PM2_CMD=$(pm2 startup 2>&1 | grep -E '^sudo ' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'); [ -n "$PM2_CMD" ] && eval "$PM2_CMD" && pm2 save || echo "Failed to setup PM2 startup"
|
||||
echo -e "${GREEN}✓ PM2 已开机自起${NC}"
|
||||
|
||||
echo -e "${GREEN}✓ PM2 配置完成${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ VoiceHub 服务已启动${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}常用命令:${NC}"
|
||||
echo -e " pm2 status - 查看状态"
|
||||
echo -e " pm2 logs voicehub - 查看日志"
|
||||
echo -e " pm2 restart voicehub - 重启服务"
|
||||
echo -e " pm2 stop voicehub - 停止服务"
|
||||
|
||||
elif [[ "$SERVICE_CHOICE" == "2" ]]; then
|
||||
# Systemctl 管理
|
||||
echo -e "${YELLOW}正在配置 systemctl...${NC}"
|
||||
|
||||
# 动态获取 node 路径
|
||||
NODE_PATH=$(which node)
|
||||
if [[ -z "$NODE_PATH" ]]; then
|
||||
echo -e "${RED}错误: 无法找到 node 可执行文件${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${BLUE}Node 路径: $NODE_PATH${NC}"
|
||||
|
||||
# 创建 systemd 服务文件
|
||||
CURRENT_USER=$(whoami)
|
||||
sudo tee /etc/systemd/system/voicehub.service > /dev/null << EOF
|
||||
[Unit]
|
||||
Description=VoiceHub - 校园广播站点歌系统
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$CURRENT_USER
|
||||
Group=$CURRENT_USER
|
||||
WorkingDirectory=$PROJECT_DIR
|
||||
Environment=NODE_ENV=production
|
||||
ExecStart=$NODE_PATH $PROJECT_DIR/.output/server/index.mjs
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 修改项目目录权限
|
||||
echo -e "${BLUE}正在设置项目目录权限...${NC}"
|
||||
# 使用 find 分别设置目录和文件的权限
|
||||
# 目录设置为 755 (rwxr-xr-x) - 所有用户可读可进入
|
||||
sudo find "$PROJECT_DIR" -type d -exec chmod 755 {} +
|
||||
# 文件设置为 644 (rw-r--r--) - 所有用户可读
|
||||
sudo find "$PROJECT_DIR" -type f -exec chmod 644 {} +
|
||||
|
||||
# 重新加载 systemd
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# 启动服务
|
||||
sudo systemctl start voicehub
|
||||
sudo systemctl enable voicehub
|
||||
|
||||
echo -e "${GREEN}✓ systemctl 配置完成${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ VoiceHub 服务已启动${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}常用命令:${NC}"
|
||||
echo -e " sudo systemctl status voicehub - 查看状态"
|
||||
echo -e " sudo journalctl -u voicehub -f - 查看日志"
|
||||
echo -e " sudo systemctl restart voicehub - 重启服务"
|
||||
echo -e " sudo systemctl stop voicehub - 停止服务"
|
||||
else
|
||||
echo -e "${YELLOW}跳过服务配置${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 9: 安装 voicehub 命令快捷方式
|
||||
# ============================================
|
||||
echo -e "${YELLOW}配置 voicehub 命令快捷方式...${NC}"
|
||||
echo ""
|
||||
|
||||
chmod +x "$PROJECT_DIR/sh/main.sh"
|
||||
chmod +x "$PROJECT_DIR/sh/update.sh"
|
||||
|
||||
# 创建 /usr/local/bin/voicehub 软链接
|
||||
echo -e "${BLUE}是否安装 voicehub 命令到系统?(输入 y 安装)${NC}"
|
||||
read -p "请选择 (y/n): " INSTALL_CMD
|
||||
|
||||
if [[ "$INSTALL_CMD" == "y" || "$INSTALL_CMD" == "Y" ]]; then
|
||||
# 创建软链接
|
||||
sudo ln -sf "$PROJECT_DIR/sh/main.sh" /usr/local/bin/voicehub
|
||||
echo -e "${GREEN}✓ voicehub 命令已安装${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}现在可以使用以下命令管理 VoiceHub:${NC}"
|
||||
echo -e " voicehub status # 查看状态"
|
||||
echo -e " voicehub start # 启动服务"
|
||||
echo -e " voicehub stop # 停止服务"
|
||||
echo -e " voicehub restart # 重启服务"
|
||||
echo -e " voicehub update # 更新代码"
|
||||
echo -e " voicehub reinstall # 重新安装"
|
||||
echo -e " voicehub logs # 查看日志"
|
||||
echo -e " voicehub help # 查看帮助"
|
||||
else
|
||||
echo -e "${YELLOW}跳过命令安装${NC}"
|
||||
echo -e "${BLUE}如需手动使用,可运行:${NC}"
|
||||
echo -e " bash $PROJECT_DIR/voicehub.sh status"
|
||||
echo -e " bash $PROJECT_DIR/voicehub.sh restart"
|
||||
echo -e " ..."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 部署完成
|
||||
# ============================================
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} 部署完成!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}项目目录: $PROJECT_DIR${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ "$SERVICE_CHOICE" == "1" ]]; then
|
||||
echo -e "${BLUE}服务管理: PM2${NC}"
|
||||
echo -e "${YELLOW}常用命令:${NC}"
|
||||
echo -e " pm2 status - 查看状态"
|
||||
echo -e " pm2 logs voicehub - 查看日志"
|
||||
echo -e " pm2 restart voicehub - 重启服务"
|
||||
echo -e " pm2 stop voicehub - 停止服务"
|
||||
elif [[ "$SERVICE_CHOICE" == "2" ]]; then
|
||||
echo -e "${BLUE}服务管理: systemd${NC}"
|
||||
echo -e "${YELLOW}常用命令:${NC}"
|
||||
echo -e " sudo systemctl status voicehub - 查看状态"
|
||||
echo -e " sudo journalctl -u voicehub -f - 查看日志"
|
||||
echo -e " sudo systemctl restart voicehub - 重启服务"
|
||||
echo -e " sudo systemctl stop voicehub - 停止服务"
|
||||
else
|
||||
echo -e "${BLUE}启动命令: pnpm run dev (开发) 或 pnpm run start (生产)${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
+570
@@ -0,0 +1,570 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 确保以 bash 运行
|
||||
if [ -z "$BASH_VERSION" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
|
||||
# VoiceHub 日常维护管理脚本
|
||||
# 支持上下键选择菜单
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 默认安装目录
|
||||
PROJECT_DIR="/opt/voicehub"
|
||||
DIRECT_LOG_DIR="$PROJECT_DIR/logs"
|
||||
DIRECT_PID_FILE="$DIRECT_LOG_DIR/voicehub.pid"
|
||||
DIRECT_LOG_FILE="$DIRECT_LOG_DIR/voicehub-out.log"
|
||||
|
||||
ensure_pnpm() {
|
||||
export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
mkdir -p "$PNPM_HOME"
|
||||
export PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
if ! command -v corepack &> /dev/null; then
|
||||
sudo npm install -g corepack
|
||||
hash -r
|
||||
fi
|
||||
|
||||
corepack enable
|
||||
|
||||
local package_manager=""
|
||||
if [[ -f "$PROJECT_DIR/package.json" ]]; then
|
||||
package_manager=$(node -p "try { JSON.parse(require('fs').readFileSync('$PROJECT_DIR/package.json', 'utf8')).packageManager || '' } catch { '' }")
|
||||
fi
|
||||
|
||||
if [[ -n "$package_manager" ]]; then
|
||||
corepack prepare "$package_manager" --activate
|
||||
fi
|
||||
|
||||
hash -r
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
echo -e "${RED}错误: pnpm 不可用${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
has_working_pm2() {
|
||||
local pm2_root
|
||||
local pm2_bin
|
||||
|
||||
pm2_root=$(pnpm root -g 2>/dev/null || true)
|
||||
pm2_bin="$pm2_root/pm2/bin/pm2"
|
||||
|
||||
if [[ -f "$pm2_bin" ]]; then
|
||||
node "$pm2_bin" -v > /dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 -v > /dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_pm2() {
|
||||
local pm2_root
|
||||
local pm2_bin
|
||||
|
||||
pm2_root=$(pnpm root -g 2>/dev/null || true)
|
||||
pm2_bin="$pm2_root/pm2/bin/pm2"
|
||||
|
||||
if [[ -f "$pm2_bin" ]]; then
|
||||
node "$pm2_bin" "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
echo -e "${RED}错误: PM2 不可用,请重新运行部署脚本修复 PM2${NC}"
|
||||
return 1
|
||||
}
|
||||
|
||||
is_direct_service_running() {
|
||||
if [[ ! -f "$DIRECT_PID_FILE" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local pid
|
||||
pid=$(cat "$DIRECT_PID_FILE" 2>/dev/null)
|
||||
|
||||
if [[ -z "$pid" ]]; then
|
||||
rm -f "$DIRECT_PID_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$DIRECT_PID_FILE"
|
||||
return 1
|
||||
}
|
||||
|
||||
start_direct_service() {
|
||||
mkdir -p "$DIRECT_LOG_DIR"
|
||||
|
||||
if is_direct_service_running; then
|
||||
echo -e "${YELLOW}VoiceHub 已在直接运行模式下启动${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$PROJECT_DIR/.output/server/index.mjs" ]]; then
|
||||
echo -e "${RED}错误: 未找到构建产物,请先执行 voicehub build${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nohup node "$PROJECT_DIR/.output/server/index.mjs" >> "$DIRECT_LOG_FILE" 2>&1 &
|
||||
local pid=$!
|
||||
echo "$pid" > "$DIRECT_PID_FILE"
|
||||
sleep 2
|
||||
|
||||
if is_direct_service_running; then
|
||||
echo -e "${GREEN}✓ 直接运行模式已启动 (PID: $pid)${NC}"
|
||||
else
|
||||
echo -e "${RED}错误: 直接运行模式启动失败,请查看日志: $DIRECT_LOG_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop_direct_service() {
|
||||
if ! is_direct_service_running; then
|
||||
echo -e "${YELLOW}直接运行模式未启动${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local pid
|
||||
pid=$(cat "$DIRECT_PID_FILE")
|
||||
kill "$pid" 2>/dev/null || true
|
||||
|
||||
for _ in {1..10}; do
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
rm -f "$DIRECT_PID_FILE"
|
||||
echo -e "${GREEN}✓ 直接运行模式已停止${NC}"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
rm -f "$DIRECT_PID_FILE"
|
||||
echo -e "${GREEN}✓ 直接运行模式已强制停止${NC}"
|
||||
}
|
||||
|
||||
# 检查项目目录
|
||||
check_project() {
|
||||
if [[ ! -d "$PROJECT_DIR" ]]; then
|
||||
echo -e "${RED}错误: 项目目录不存在: $PROJECT_DIR${NC}"
|
||||
echo -e "${RED}请先运行部署脚本安装 VoiceHub${NC}"
|
||||
exit 1
|
||||
fi
|
||||
cd "$PROJECT_DIR"
|
||||
ensure_pnpm
|
||||
}
|
||||
|
||||
# 检测服务管理器类型
|
||||
detect_service_manager() {
|
||||
if [[ -f "$PROJECT_DIR/ecosystem.config.cjs" || -f "$PROJECT_DIR/ecosystem.config.js" ]]; then
|
||||
echo "pm2"
|
||||
elif [[ -f "/etc/systemd/system/voicehub.service" ]]; then
|
||||
echo "systemd"
|
||||
elif [[ -f "$PROJECT_DIR/.output/server/index.mjs" ]]; then
|
||||
echo "direct"
|
||||
else
|
||||
echo "none"
|
||||
fi
|
||||
}
|
||||
|
||||
# 查看状态
|
||||
cmd_status() {
|
||||
check_project
|
||||
echo -e "${BLUE}检查服务状态...${NC}"
|
||||
|
||||
local service_type=$(detect_service_manager)
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
if has_working_pm2 && run_pm2 list | grep -q "voicehub"; then
|
||||
echo -e "${GREEN}PM2 服务状态:${NC}"
|
||||
run_pm2 list | grep voicehub
|
||||
else
|
||||
echo -e "${YELLOW}PM2 服务未运行或 PM2 命令异常${NC}"
|
||||
fi
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
echo -e "${GREEN}systemctl 服务状态:${NC}"
|
||||
sudo systemctl status voicehub
|
||||
elif [[ "$service_type" == "direct" ]]; then
|
||||
if is_direct_service_running; then
|
||||
echo -e "${GREEN}直接运行模式状态: 运行中${NC}"
|
||||
echo -e "${BLUE}PID: $(cat "$DIRECT_PID_FILE")${NC}"
|
||||
echo -e "${BLUE}日志: $DIRECT_LOG_FILE${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}直接运行模式未启动${NC}"
|
||||
echo -e "${BLUE}可使用 voicehub start 启动${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}未检测到服务配置${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
cmd_start() {
|
||||
check_project
|
||||
echo -e "${BLUE}启动服务...${NC}"
|
||||
|
||||
local service_type=$(detect_service_manager)
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
if [[ -f "ecosystem.config.cjs" ]]; then
|
||||
run_pm2 start ecosystem.config.cjs
|
||||
else
|
||||
run_pm2 start ecosystem.config.js
|
||||
fi
|
||||
echo -e "${GREEN}✓ PM2 服务已启动${NC}"
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
sudo systemctl start voicehub
|
||||
echo -e "${GREEN}✓ systemctl 服务已启动${NC}"
|
||||
elif [[ "$service_type" == "direct" ]]; then
|
||||
start_direct_service
|
||||
else
|
||||
echo -e "${YELLOW}未找到服务配置,请先运行部署脚本配置服务${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止服务
|
||||
cmd_stop() {
|
||||
check_project
|
||||
echo -e "${BLUE}停止服务...${NC}"
|
||||
|
||||
local service_type=$(detect_service_manager)
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
run_pm2 stop voicehub
|
||||
echo -e "${GREEN}✓ PM2 服务已停止${NC}"
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
sudo systemctl stop voicehub
|
||||
echo -e "${GREEN}✓ systemctl 服务已停止${NC}"
|
||||
elif [[ "$service_type" == "direct" ]]; then
|
||||
stop_direct_service
|
||||
else
|
||||
echo -e "${YELLOW}未找到服务配置${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 重启服务
|
||||
cmd_restart() {
|
||||
check_project
|
||||
echo -e "${BLUE}重启服务...${NC}"
|
||||
|
||||
local service_type=$(detect_service_manager)
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
run_pm2 restart voicehub
|
||||
echo -e "${GREEN}✓ PM2 服务已重启${NC}"
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
sudo systemctl restart voicehub
|
||||
echo -e "${GREEN}✓ systemctl 服务已重启${NC}"
|
||||
elif [[ "$service_type" == "direct" ]]; then
|
||||
stop_direct_service
|
||||
start_direct_service
|
||||
else
|
||||
echo -e "${YELLOW}未找到服务配置,请先运行部署脚本配置服务${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 更新
|
||||
cmd_update() {
|
||||
check_project
|
||||
if [[ -f "$PROJECT_DIR/sh/update.sh" ]]; then
|
||||
bash "$PROJECT_DIR/sh/update.sh"
|
||||
else
|
||||
echo -e "${RED}错误: 找不到更新脚本 $PROJECT_DIR/sh/update.sh${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 重装依赖
|
||||
cmd_deps() {
|
||||
check_project
|
||||
echo -e "${BLUE}重新安装依赖...${NC}"
|
||||
|
||||
rm -rf node_modules
|
||||
pnpm install --frozen-lockfile || pnpm install
|
||||
|
||||
echo -e "${GREEN}✓ 依赖重装完成${NC}"
|
||||
}
|
||||
|
||||
# 重新编译
|
||||
cmd_build() {
|
||||
check_project
|
||||
echo -e "${BLUE}重新编译项目...${NC}"
|
||||
|
||||
pnpm run build
|
||||
|
||||
echo -e "${GREEN}✓ 编译完成${NC}"
|
||||
}
|
||||
|
||||
# 重新安装
|
||||
cmd_reinstall() {
|
||||
check_project
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} VoiceHub 重新安装${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
cmd_stop
|
||||
|
||||
echo -e "${YELLOW}[1/4] 更新代码...${NC}"
|
||||
git fetch origin main
|
||||
git stash
|
||||
git reset --hard origin/main
|
||||
echo -e "${GREEN}✓ 代码更新完成${NC}"
|
||||
|
||||
echo -e "${YELLOW}[2/4] 重装依赖...${NC}"
|
||||
rm -rf node_modules
|
||||
pnpm install --frozen-lockfile || pnpm install
|
||||
echo -e "${GREEN}✓ 依赖重装完成${NC}"
|
||||
|
||||
echo -e "${YELLOW}[3/4] 重新编译...${NC}"
|
||||
pnpm run build
|
||||
echo -e "${GREEN}✓ 编译完成${NC}"
|
||||
|
||||
echo -e "${YELLOW}[4/4] 启动服务...${NC}"
|
||||
cmd_start
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} 重新安装完成!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
}
|
||||
|
||||
# 查看日志
|
||||
cmd_logs() {
|
||||
check_project
|
||||
|
||||
local service_type=$(detect_service_manager)
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
run_pm2 logs voicehub
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
sudo journalctl -u voicehub -f
|
||||
elif [[ "$service_type" == "direct" ]]; then
|
||||
mkdir -p "$DIRECT_LOG_DIR"
|
||||
touch "$DIRECT_LOG_FILE"
|
||||
tail -f "$DIRECT_LOG_FILE"
|
||||
else
|
||||
echo -e "${YELLOW}未找到服务配置,无法查看日志${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 交互式菜单
|
||||
show_menu() {
|
||||
# 菜单选项
|
||||
local options=(
|
||||
"查看服务状态"
|
||||
"启动服务"
|
||||
"停止服务"
|
||||
"重启服务"
|
||||
"更新"
|
||||
"重新安装"
|
||||
"重装依赖"
|
||||
"重新编译"
|
||||
"查看日志"
|
||||
"退出"
|
||||
)
|
||||
|
||||
local selected=0
|
||||
local total=${#options[@]}
|
||||
|
||||
# 打印菜单
|
||||
print_menu() {
|
||||
clear
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} VoiceHub 管理面板${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
for i in "${!options[@]}"; do
|
||||
if [[ $i -eq $selected ]]; then
|
||||
echo -e "${GREEN}► ${options[$i]}${NC}"
|
||||
else
|
||||
echo -e " ${options[$i]}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}使用 ↑↓ 选择,Enter 确认${NC}"
|
||||
}
|
||||
|
||||
# 临时关闭 set -e,防止 read 超时或非零返回导致脚本退出
|
||||
set +e
|
||||
|
||||
while true; do
|
||||
print_menu
|
||||
|
||||
# 读取 1 个字符
|
||||
read -rsn1 key
|
||||
|
||||
# 检查是否是转义序列 (方向键)
|
||||
if [[ "$key" == $'\x1b' ]]; then
|
||||
# 使用 || true 防止超时导致 set -e 触发(虽然已关闭,但保持习惯)
|
||||
read -rsn2 -t 0.01 chars || true
|
||||
key="$key$chars"
|
||||
fi
|
||||
|
||||
case "$key" in
|
||||
$'\x1b[A'|$'\x1bOA'|k|K) # 上
|
||||
((selected--))
|
||||
if [[ $selected -lt 0 ]]; then
|
||||
selected=$((total - 1))
|
||||
fi
|
||||
;;
|
||||
$'\x1b[B'|$'\x1bOB'|j|J) # 下
|
||||
((selected++))
|
||||
if [[ $selected -ge $total ]]; then
|
||||
selected=0
|
||||
fi
|
||||
;;
|
||||
"") # Enter
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 恢复 set -e
|
||||
set -e
|
||||
|
||||
echo ""
|
||||
|
||||
# 执行选择的操作
|
||||
case $selected in
|
||||
0)
|
||||
cmd_status
|
||||
;;
|
||||
1)
|
||||
cmd_start
|
||||
;;
|
||||
2)
|
||||
cmd_stop
|
||||
;;
|
||||
3)
|
||||
cmd_restart
|
||||
;;
|
||||
4)
|
||||
cmd_update
|
||||
;;
|
||||
5)
|
||||
cmd_reinstall
|
||||
;;
|
||||
6)
|
||||
cmd_deps
|
||||
;;
|
||||
7)
|
||||
cmd_build
|
||||
;;
|
||||
8)
|
||||
cmd_logs
|
||||
;;
|
||||
9)
|
||||
echo -e "${CYAN}退出${NC}"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 检查是否有交互式终端
|
||||
if [[ ! -t 0 ]]; then
|
||||
# 非交互模式,显示帮助
|
||||
echo -e "${BLUE}VoiceHub 管理脚本${NC}"
|
||||
echo ""
|
||||
echo "用法: voicehub"
|
||||
echo ""
|
||||
echo "运行脚本后使用上下键选择操作"
|
||||
echo ""
|
||||
echo "或在非交互环境中使用以下命令:"
|
||||
echo " bash $0 status # 查看状态"
|
||||
echo " bash $0 start # 启动服务"
|
||||
echo " bash $0 stop # 停止服务"
|
||||
echo " bash $0 restart # 重启服务"
|
||||
echo " bash $0 update # 更新代码"
|
||||
echo " bash $0 reinstall # 重新安装"
|
||||
echo " bash $0 deps # 重装依赖"
|
||||
echo " bash $0 build # 重新编译"
|
||||
echo " bash $0 logs # 查看日志"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 如果有参数,直接执行对应命令
|
||||
if [[ -n "$1" ]]; then
|
||||
case "$1" in
|
||||
status)
|
||||
cmd_status
|
||||
;;
|
||||
start)
|
||||
cmd_start
|
||||
;;
|
||||
stop)
|
||||
cmd_stop
|
||||
;;
|
||||
restart)
|
||||
cmd_restart
|
||||
;;
|
||||
update)
|
||||
cmd_update
|
||||
;;
|
||||
reinstall)
|
||||
cmd_reinstall
|
||||
;;
|
||||
deps)
|
||||
cmd_deps
|
||||
;;
|
||||
build)
|
||||
cmd_build
|
||||
;;
|
||||
logs)
|
||||
cmd_logs
|
||||
;;
|
||||
help|--help|-h)
|
||||
echo -e "${BLUE}VoiceHub 管理脚本${NC}"
|
||||
echo ""
|
||||
echo "用法: voicehub"
|
||||
echo ""
|
||||
echo "直接运行脚本可使用交互式菜单(上下键选择)"
|
||||
echo ""
|
||||
echo "或使用以下命令:"
|
||||
echo " voicehub status # 查看状态"
|
||||
echo " voicehub start # 启动服务"
|
||||
echo " voicehub stop # 停止服务"
|
||||
echo " voicehub restart # 重启服务"
|
||||
echo " voicehub update # 更新代码"
|
||||
echo " voicehub reinstall # 重新安装"
|
||||
echo " voicehub deps # 重装依赖"
|
||||
echo " voicehub build # 重新编译"
|
||||
echo " voicehub logs # 查看日志"
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}未知命令: $1${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# 无参数,显示交互式菜单
|
||||
show_menu
|
||||
fi
|
||||
@@ -0,0 +1,308 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 确保以 bash 运行
|
||||
if [ -z "$BASH_VERSION" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
|
||||
# VoiceHub 更新脚本
|
||||
# 用于更新已部署的 VoiceHub 项目
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 默认安装目录
|
||||
PROJECT_DIR="/opt/voicehub"
|
||||
|
||||
ensure_pnpm() {
|
||||
export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
mkdir -p "$PNPM_HOME"
|
||||
export PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
if ! command -v corepack &> /dev/null; then
|
||||
npm install -g corepack
|
||||
hash -r
|
||||
fi
|
||||
|
||||
corepack enable
|
||||
|
||||
local package_manager=""
|
||||
if [[ -f "$PROJECT_DIR/package.json" ]]; then
|
||||
package_manager=$(node -p "try { JSON.parse(require('fs').readFileSync('$PROJECT_DIR/package.json', 'utf8')).packageManager || '' } catch { '' }")
|
||||
fi
|
||||
|
||||
if [[ -n "$package_manager" ]]; then
|
||||
corepack prepare "$package_manager" --activate
|
||||
fi
|
||||
|
||||
hash -r
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
echo -e "${RED}错误: pnpm 不可用${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
persist_pm2_binary() {
|
||||
if [[ ! -f "/usr/local/bin/pm2" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! grep -q "VoiceHub PM2 Wrapper" /usr/local/bin/pm2 2>/dev/null; then
|
||||
echo -e "${YELLOW}跳过 pm2 脚本更新:当前 pm2 非 VoiceHub 管理${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}正在更新系统 pm2 脚本...${NC}"
|
||||
|
||||
sudo tee /usr/local/bin/pm2 > /dev/null << 'EOF'
|
||||
#!/bin/bash
|
||||
# VoiceHub PM2 Wrapper - Managed by VoiceHub deployment
|
||||
set -e
|
||||
|
||||
export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
export PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
mkdir -p "$PNPM_HOME"
|
||||
|
||||
if ! command -v corepack &> /dev/null; then
|
||||
echo "错误: corepack 不可用,请重新运行部署脚本" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
corepack enable > /dev/null 2>&1 || true
|
||||
|
||||
if [[ -f "/opt/voicehub/package.json" ]]; then
|
||||
package_manager=$(node -p "try { JSON.parse(require('fs').readFileSync('/opt/voicehub/package.json', 'utf8')).packageManager || '' } catch { '' }" 2>/dev/null || true)
|
||||
if [[ -n "$package_manager" ]]; then
|
||||
corepack prepare "$package_manager" --activate > /dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
hash -r 2>/dev/null || true
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
echo "错误: pnpm 不可用,请检查 corepack 配置或重新运行部署脚本" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
PM2_ROOT="$(pnpm root -g 2>/dev/null || true)"
|
||||
PM2_BIN="$PM2_ROOT/pm2/bin/pm2"
|
||||
|
||||
if [[ ! -f "$PM2_BIN" ]]; then
|
||||
echo "错误: PM2 未正确安装,请重新运行部署脚本" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec node "$PM2_BIN" "$@"
|
||||
EOF
|
||||
sudo chmod +x /usr/local/bin/pm2
|
||||
echo -e "${GREEN}✓ 系统 pm2 脚本已更新${NC}"
|
||||
}
|
||||
|
||||
has_working_pm2() {
|
||||
local pm2_root
|
||||
local pm2_bin
|
||||
|
||||
pm2_root=$(pnpm root -g 2>/dev/null || true)
|
||||
pm2_bin="$pm2_root/pm2/bin/pm2"
|
||||
|
||||
if [[ -f "$pm2_bin" ]]; then
|
||||
node "$pm2_bin" -v > /dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 -v > /dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_pm2() {
|
||||
local pm2_root
|
||||
local pm2_bin
|
||||
|
||||
pm2_root=$(pnpm root -g 2>/dev/null || true)
|
||||
pm2_bin="$pm2_root/pm2/bin/pm2"
|
||||
|
||||
if [[ -f "$pm2_bin" ]]; then
|
||||
node "$pm2_bin" "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v pm2 &> /dev/null; then
|
||||
pm2 "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
echo -e "${RED}错误: PM2 不可用,请重新运行部署脚本修复 PM2${NC}"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 检测服务管理器类型
|
||||
detect_service_manager() {
|
||||
if [[ -f "$PROJECT_DIR/ecosystem.config.cjs" || -f "$PROJECT_DIR/ecosystem.config.js" ]]; then
|
||||
echo "pm2"
|
||||
elif [[ -f "/etc/systemd/system/voicehub.service" ]]; then
|
||||
echo "systemd"
|
||||
else
|
||||
echo "none"
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} VoiceHub 更新脚本${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 1: 检查项目目录
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[1/7] 检查项目目录...${NC}"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]; then
|
||||
echo -e "${GREEN}✓ 项目目录存在: $PROJECT_DIR${NC}"
|
||||
else
|
||||
echo -e "${RED}错误: 项目目录不存在: $PROJECT_DIR${NC}"
|
||||
echo -e "${RED}请先运行部署脚本安装 VoiceHub${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 2: Git Pull 更新代码
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[2/7] 更新代码...${NC}"
|
||||
echo -e "执行: git fetch && git reset --hard origin/main"
|
||||
echo ""
|
||||
|
||||
git stash
|
||||
git pull origin main
|
||||
|
||||
# 恢复脚本执行权限
|
||||
if [[ -f "$PROJECT_DIR/sh/main.sh" ]]; then
|
||||
chmod +x "$PROJECT_DIR/sh/main.sh"
|
||||
fi
|
||||
if [[ -f "$PROJECT_DIR/sh/update.sh" ]]; then
|
||||
chmod +x "$PROJECT_DIR/sh/update.sh"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ 代码更新完成${NC}"
|
||||
echo ""
|
||||
|
||||
ensure_pnpm
|
||||
|
||||
# ============================================
|
||||
# 步骤 3: 更新系统脚本
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[3/7] 更新系统脚本...${NC}"
|
||||
echo ""
|
||||
|
||||
persist_pm2_binary
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 4: pnpm install 更新依赖
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[4/7] 更新依赖...${NC}"
|
||||
echo -e "执行: pnpm install --frozen-lockfile"
|
||||
echo ""
|
||||
|
||||
pnpm install --frozen-lockfile || pnpm install
|
||||
|
||||
echo -e "${GREEN}✓ 依赖更新完成${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 5: 重新部署
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[5/7] 重新部署项目...${NC}"
|
||||
echo -e "执行: pnpm run deploy"
|
||||
echo ""
|
||||
|
||||
# 尝试执行 pnpm run deploy (包含数据库迁移、管理员创建、构建)
|
||||
if pnpm run deploy; then
|
||||
echo -e "${GREEN}✓ 部署更新脚本执行成功${NC}"
|
||||
else
|
||||
echo -e "${RED}部署更新脚本执行失败,尝试仅执行构建...${NC}"
|
||||
echo -e "${YELLOW}注意: 数据库迁移可能未完成,请检查日志${NC}"
|
||||
pnpm run build
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ 项目更新构建完成${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 6: 重启服务
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[6/7] 重启服务...${NC}"
|
||||
echo ""
|
||||
|
||||
service_type=$(detect_service_manager)
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
echo -e "${BLUE}检测到 PM2 服务,正在重启...${NC}"
|
||||
run_pm2 restart voicehub
|
||||
echo -e "${GREEN}✓ PM2 服务已重启${NC}"
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
echo -e "${BLUE}检测到 systemctl 服务,正在重启...${NC}"
|
||||
sudo systemctl restart voicehub
|
||||
echo -e "${GREEN}✓ systemctl 服务已重启${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ 未检测到服务配置${NC}"
|
||||
echo -e "${YELLOW}请先运行部署脚本配置服务${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 7: 显示状态
|
||||
# ============================================
|
||||
echo -e "${YELLOW}[7/7] 服务状态...${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ "$service_type" == "pm2" ]]; then
|
||||
if has_working_pm2; then
|
||||
run_pm2 list | grep voicehub
|
||||
else
|
||||
echo -e "${YELLOW}PM2 命令异常,请重新运行部署脚本修复 PM2${NC}"
|
||||
fi
|
||||
elif [[ "$service_type" == "systemd" ]]; then
|
||||
sudo systemctl status voicehub
|
||||
else
|
||||
echo -e "${YELLOW}未检测到服务配置${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 更新完成
|
||||
# ============================================
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} 更新完成!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}项目目录: $PROJECT_DIR${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}提示: 如需查看日志,请运行:${NC}"
|
||||
if has_working_pm2; then
|
||||
echo -e "${YELLOW} pm2 logs voicehub${NC}"
|
||||
else
|
||||
echo -e "${YELLOW} sudo journalctl -u voicehub -f${NC}"
|
||||
fi
|
||||
echo ""
|
||||
Reference in New Issue
Block a user