298 lines
7.8 KiB
Bash
Executable File
298 lines
7.8 KiB
Bash
Executable File
#!/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 "$@"
|
||
|