jingrow/dev.sh

380 lines
10 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
# 获取脚本所在目录(项目根目录)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR" || exit 1
# 项目路径常量
JINGROW_APP_DIR="apps/jingrow"
FRONTEND_DIR="$JINGROW_APP_DIR/frontend"
# 颜色定义
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
}
# 安装 Redis
install_redis() {
info "尝试自动安装 Redis..."
local pkg_manager=""
local pkg_name=""
local install_cmd=""
# 检测包管理器
if command -v apt-get &> /dev/null; then
pkg_manager="apt-get"
pkg_name="redis-server"
install_cmd="apt-get update && apt-get install -y $pkg_name"
elif command -v yum &> /dev/null; then
pkg_manager="yum"
pkg_name="redis"
install_cmd="yum install -y $pkg_name"
elif command -v dnf &> /dev/null; then
pkg_manager="dnf"
pkg_name="redis"
install_cmd="dnf install -y $pkg_name"
else
error "未检测到支持的包管理器apt-get/yum/dnf请手动安装 Redis"
return 1
fi
# 执行安装(自动处理 root 权限)
if [ "$EUID" -eq 0 ]; then
eval "$install_cmd" || {
error "$pkg_manager 安装 Redis 失败"
return 1
}
else
warn "需要 root 权限安装 Redis尝试使用 sudo..."
if ! sudo bash -c "$install_cmd"; then
error "sudo 安装 Redis 失败,请手动安装: sudo $install_cmd"
return 1
fi
fi
# 验证安装
if command -v redis-server &> /dev/null; then
success "Redis 安装完成"
return 0
else
error "Redis 安装后未找到 redis-server 命令"
return 1
fi
}
# 安装前端依赖
install_frontend_deps() {
local reason="${1:-前端依赖}"
info "安装$reason..."
(cd "$FRONTEND_DIR" && npm install) || {
error "$reason安装失败"
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
# 检查 redis-server 是否安装
if ! command -v redis-server &> /dev/null; then
warn "redis-server 未安装,将尝试自动安装"
install_redis || {
error "自动安装 Redis 失败,请根据系统手动安装: sudo apt-get install -y redis-server (Ubuntu/Debian) 或 sudo yum install -y redis (CentOS/RHEL)"
return 1
}
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 "$FRONTEND_DIR/node_modules" ]; then
warn "前端依赖未安装"
install_frontend_deps "前端依赖" || exit 1
fi
# 即使 node_modules 存在,也校验 vite 是否可用(可能之前安装不完整)
if [ ! -x "$FRONTEND_DIR/node_modules/.bin/vite" ]; then
warn "未检测到 vite可执行文件缺失重新安装前端依赖..."
install_frontend_deps "前端依赖" || exit 1
fi
# 检查后端依赖(优先使用 uv
info "同步后端依赖 (uv)..."
if [ -f "$JINGROW_APP_DIR/pyproject.toml" ]; then
# 同步主项目依赖
(cd "$JINGROW_APP_DIR" && uv sync) || {
error "uv 同步失败"
exit 1
}
# 安装所有节点的依赖(使用异步并行安装,提升性能)
info "安装节点依赖..."
(cd "$JINGROW_APP_DIR" && uv run python -c "from jingrow.utils.node_dependencies import install_all_nodes_dependencies; install_all_nodes_dependencies(max_concurrent=5)" 2>&1) || {
warn "安装节点依赖失败,但继续执行"
}
elif [ -f "$JINGROW_APP_DIR/requirements.txt" ]; then
warn "未发现 pyproject.toml使用 requirements.txt 安装"
(cd "$JINGROW_APP_DIR" && 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 $JINGROW_APP_DIR && uv run python -m jingrow.app" \
"cd $JINGROW_APP_DIR && uv run python -m jingrow.dramatiq" \
"sleep 3 && cd $FRONTEND_DIR && npm run dev"
}
# 只启动前端
start_frontend() {
info "启动前端..."
ensure_uv
cleanup
check_deps
(cd "$FRONTEND_DIR" && 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 $JINGROW_APP_DIR && uv run python -m jingrow.app" \
"cd $JINGROW_APP_DIR && 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 "$@"