298 lines
7.8 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Jingrow 统一开发脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 打印函数
info() { echo -e "${BLUE}${NC} $1"; }
success() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; }
error() { echo -e "${RED}${NC} $1"; }
# 检查命令是否存在
check_cmd() {
if ! command -v "$1" &> /dev/null; then
error "$1 未安装"
return 1
fi
}
# 安装 uv官方安装脚本
install_uv() {
info "尝试自动安装 uv..."
# 使用仓库内脚本进行安装
if [ -f ./install_uv.sh ]; then
bash ./install_uv.sh || {
warn "install_uv.sh 执行失败"
return 1
}
else
warn "install_uv.sh 不存在"
return 1
fi
# 尝试把本次会话的 PATH 补上
export PATH="$HOME/.local/bin:$PATH"
# 如果 uv 安装脚本生成了会话环境文件,则立刻激活
if [ -f "$HOME/.local/bin/env" ]; then
# shellcheck disable=SC1090
source "$HOME/.local/bin/env"
fi
if command -v uv &> /dev/null; then
success "uv 安装完成"
return 0
fi
return 1
}
# 清理进程
cleanup() {
info "清理现有进程..."
pkill -f "uvicorn.*jingrow.main:app" 2>/dev/null || true
pkill -f "concurrently" 2>/dev/null || true
pkill -f "dramatiq.*jingrow.services.queue" 2>/dev/null || true
pkill -f "vite" 2>/dev/null || true
sleep 1
# 强制释放端口
if lsof -ti:9001 &> /dev/null; then
warn "释放端口 9001..."
lsof -ti:9001 | xargs kill -9 2>/dev/null || true
fi
if lsof -ti:3002 &> /dev/null; then
warn "释放端口 3002..."
lsof -ti:3002 | xargs kill -9 2>/dev/null || true
fi
success "清理完成"
}
# 确保 uv 可用:最先执行
ensure_uv() {
if command -v uv &> /dev/null; then
return 0
fi
warn "uv 未安装,将尝试自动安装"
install_uv || {
error "自动安装 uv 失败,请手动安装: curl -fsSL https://astral.sh/uv/install.sh | sh"
exit 1
}
# 再次兜底激活环境,确保当前会话可用
if [ -f "$HOME/.local/bin/env" ]; then
# shellcheck disable=SC1090
source "$HOME/.local/bin/env"
fi
}
# 检查Redis
check_redis() {
if redis-cli ping &> /dev/null; then
success "Redis 已运行"
return 0
fi
warn "Redis 未运行,尝试启动..."
if command -v redis-server &> /dev/null; then
redis-server ./redis.conf &> /dev/null &
sleep 2
if redis-cli ping &> /dev/null; then
success "Redis 启动成功"
return 0
fi
fi
error "Redis 未运行,请手动启动: redis-server"
return 1
}
# 检查依赖
check_deps() {
info "检查依赖..."
check_cmd node || exit 1
# 这里假设 ensure_uv 已经在更早阶段被调用
check_cmd uv || exit 1
# 检查前端依赖
if [ ! -d "apps/jingrow/frontend/node_modules" ]; then
warn "前端依赖未安装"
info "安装前端依赖..."
cd apps/jingrow/frontend && npm install && cd ../..
fi
# 即使 node_modules 存在,也校验 vite 是否可用(可能之前安装不完整)
if [ ! -x "apps/jingrow/frontend/node_modules/.bin/vite" ]; then
warn "未检测到 vite可执行文件缺失重新安装前端依赖..."
(cd apps/jingrow/frontend && npm install)
fi
# 检查后端依赖(优先使用 uv
info "同步后端依赖 (uv)..."
if [ -f "apps/jingrow/pyproject.toml" ]; then
(cd apps/jingrow && uv sync) || {
error "uv 同步失败"
exit 1
}
elif [ -f "apps/jingrow/requirements.txt" ]; then
warn "未发现 pyproject.toml使用 requirements.txt 安装"
(cd apps/jingrow && uv pip install -r requirements.txt) || {
error "uv pip 安装失败"
exit 1
}
else
warn "未发现依赖清单pyproject.toml 或 requirements.txt"
fi
success "依赖检查完成"
}
# 构建热重载目录参数返回热重载参数日志输出到stderr
# 使用 --reload-include 模式,自动监听 apps 目录下所有 .py 文件,支持动态安装
build_reload_dirs() {
local RELOAD_DIRS=""
local APPS_DIR="apps"
if [ -d "$APPS_DIR" ]; then
# 监听整个 apps 目录,自动包含新安装的 app支持热重载
RELOAD_DIRS="--reload-dir $(cd "$APPS_DIR" && pwd)"
info "已启用 apps 目录热重载(自动监听所有 app" >&2
else
warn "apps 目录不存在" >&2
fi
echo "$RELOAD_DIRS"
}
# 检查并增加 inotify 限制
check_inotify_limit() {
local current_limit=$(cat /proc/sys/fs/inotify/max_user_watches 2>/dev/null || echo "0")
local min_limit=524288
if [ "$current_limit" -lt "$min_limit" ]; then
warn "inotify 限制较低 ($current_limit),尝试增加到 $min_limit..."
# 临时增加限制(需要 root 权限)
if sudo sysctl fs.inotify.max_user_watches=$min_limit 2>/dev/null; then
success "inotify 限制已临时增加到 $min_limit"
else
warn "无法自动增加 inotify 限制(需要 root 权限)"
warn "请手动运行: sudo sysctl -w fs.inotify.max_user_watches=524288"
warn "或永久设置: echo 'fs.inotify.max_user_watches=524288' | sudo tee -a /etc/sysctl.conf"
fi
fi
}
# 启动所有服务
start_all() {
info "启动开发环境..."
ensure_uv
cleanup
check_inotify_limit
check_redis
check_deps
# 加载环境变量
if [ -f .env ]; then
export $(grep -v '^#' .env | xargs)
fi
# 构建热重载目录参数
RELOAD_DIRS=$(build_reload_dirs)
info "启动所有服务..."
# 使用延迟启动让后端先启动3秒
npx --yes concurrently \
--names "BACKEND,WORKER,FRONTEND" \
--prefix-colors "blue,yellow,green" \
"cd apps/jingrow && uv run python -m jingrow.app" \
"cd apps/jingrow && uv run python -m jingrow.dramatiq" \
"sleep 3 && cd apps/jingrow/frontend && npm run dev"
}
# 只启动前端
start_frontend() {
info "启动前端..."
ensure_uv
cleanup
check_deps
cd apps/jingrow/frontend && npm run dev
}
# 只启动后端
start_backend() {
info "启动后端..."
ensure_uv
cleanup
check_redis
check_deps
if [ -f .env ]; then
export $(grep -v '^#' .env | xargs)
fi
# 构建热重载目录参数
RELOAD_DIRS=$(build_reload_dirs)
npx --yes concurrently \
--names "BACKEND,WORKER" \
--prefix-colors "blue,yellow" \
"cd apps/jingrow && uv run python -m jingrow.app" \
"cd apps/jingrow && uv run python -m jingrow.dramatiq"
}
# 停止服务
stop_all() {
info "停止所有服务..."
cleanup
success "已停止"
}
# 重启服务
restart() {
stop_all
sleep 2
start_all
}
# 主函数
main() {
case "${1:-start}" in
start)
start_all
;;
stop)
stop_all
;;
restart)
restart
;;
frontend)
start_frontend
;;
backend)
start_backend
;;
*)
echo "用法: ./dev.sh [start|stop|restart|frontend|backend]"
echo ""
echo "命令:"
echo " start 启动所有服务(默认)"
echo " stop 停止所有服务"
echo " restart 重启所有服务"
echo " frontend 只启动前端"
echo " backend 只启动后端"
exit 1
;;
esac
}
main "$@"