cosmo/scripts/run.sh

245 lines
7.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/bin/bash
#
# Cosmo 开发环境一键启动脚本
#
# 先检查运行环境Python / Node / 依赖 / 配置 / 数据库 / Redis / 端口),
# 检查通过后启动:
# - 后端 FastAPI http://localhost:8000 (API 前缀 /api)
# - 前端 Vite http://localhost:5173
#
# 用法:
# ./scripts/run.sh # 检查环境并启动前后端Ctrl+C 停止)
# ./scripts/run.sh --check # 只检查环境,不启动服务
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BACKEND_DIR="$PROJECT_ROOT/backend"
FRONTEND_DIR="$PROJECT_ROOT/frontend"
BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}"
BACKEND_PORT="${BACKEND_PORT:-8000}"
FRONTEND_PORT="${PORT:-5173}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
ok() { echo -e "${GREEN}[ OK ]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; exit 1; }
CHECK_ONLY=0
if [ "${1:-}" = "--check" ] || [ "${1:-}" = "-c" ]; then
CHECK_ONLY=1
fi
print_header() {
echo "================================================================="
echo " Cosmo 开发环境启动脚本"
echo " 项目目录: $PROJECT_ROOT"
echo "================================================================="
}
# ---------------------------------------------------------------- 后端环境
PYTHON=""
check_backend_env() {
info "检查后端环境..."
if [ -x "$BACKEND_DIR/venv/bin/python" ]; then
PYTHON="$BACKEND_DIR/venv/bin/python"
else
PYTHON="$(command -v python3 || true)"
[ -n "$PYTHON" ] || fail "未找到 python3请先安装 Python 3.11+"
info "未找到 backend/venv正在创建虚拟环境..."
"$PYTHON" -m venv "$BACKEND_DIR/venv" || fail "创建虚拟环境失败"
PYTHON="$BACKEND_DIR/venv/bin/python"
fi
"$PYTHON" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' \
|| fail "Python 版本过低(需 3.11+: $("$PYTHON" --version 2>&1)"
ok "Python: $("$PYTHON" --version 2>&1) ($PYTHON)"
if ! "$PYTHON" -c 'import fastapi, uvicorn, sqlalchemy, asyncpg' &> /dev/null; then
info "后端依赖不完整,正在安装 requirements.txt..."
"$PYTHON" -m pip install -r "$BACKEND_DIR/requirements.txt" || fail "依赖安装失败"
fi
ok "后端依赖已就绪"
if [ ! -f "$BACKEND_DIR/.env" ]; then
if [ -f "$BACKEND_DIR/.env.example" ]; then
cp "$BACKEND_DIR/.env.example" "$BACKEND_DIR/.env"
warn "已从 .env.example 生成 backend/.env请核对数据库等配置"
else
fail "缺少 backend/.env且未找到 backend/.env.example"
fi
fi
ok "配置文件: backend/.env"
}
# ---------------------------------------------------------------- 前端环境
check_frontend_env() {
info "检查前端环境..."
command -v node &> /dev/null || fail "未找到 node请先安装 Node.js 20+"
local node_major
node_major="$(node -p 'process.versions.node.split(".")[0]')"
if [ "$node_major" -lt 20 ]; then
fail "Node.js 版本过低(需 20+: $(node --version)"
fi
ok "Node.js: $(node --version)"
command -v yarn &> /dev/null || fail "未找到 yarn请先安装 Yarn"
ok "Yarn: $(yarn --version)"
if [ ! -d "$FRONTEND_DIR/node_modules" ]; then
info "未找到 frontend/node_modules正在安装前端依赖..."
(cd "$FRONTEND_DIR" && yarn install --ignore-engines) || fail "前端依赖安装失败"
fi
ok "前端依赖已就绪"
}
# ------------------------------------------------------- 数据库 / Redis / 端口
check_services() {
info "检查数据库与 Redis 连接..."
local output status
set +e
output="$(cd "$BACKEND_DIR" && "$PYTHON" - <<'PY' 2>&1
import socket
import sys
from app.config import settings
def reachable(host: str, port: int, timeout: float = 3.0) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
database_ok = reachable(settings.database_host, settings.database_port)
redis_ok = reachable(settings.redis_host, settings.redis_port)
print(f"database|{settings.database_host}:{settings.database_port}|{'ok' if database_ok else 'fail'}")
print(f"redis|{settings.redis_host}:{settings.redis_port}|{'ok' if redis_ok else 'fail'}")
sys.exit(0 if database_ok else 1)
PY
)"
status=$?
set -e
echo "$output" | sed -u 's/^/ /'
[ "$status" -eq 0 ] || fail "无法连接 PostgreSQL请先启动数据库如 docker compose up -d postgres redis"
ok "PostgreSQL 连接正常"
if echo "$output" | grep -q '^redis|.*|fail$'; then
warn "Redis 未连接,后端将降级为内存缓存"
else
ok "Redis 连接正常"
fi
}
port_in_use() {
"$PYTHON" - "$1" <<'PY'
import socket
import sys
with socket.socket() as sock:
sock.settimeout(1.0)
sys.exit(0 if sock.connect_ex(("127.0.0.1", int(sys.argv[1]))) == 0 else 1)
PY
}
# ---------------------------------------------------------------- 服务启动
PIDS=()
start_backend() {
info "启动后端: http://localhost:$BACKEND_PORT (API 文档 /api/docs)"
(cd "$BACKEND_DIR" && exec "$PYTHON" -m uvicorn app.main:app \
--host "$BACKEND_HOST" --port "$BACKEND_PORT" --reload) \
> >(sed -u 's/^/[后端] /') 2>&1 &
PIDS+=("$!")
}
start_frontend() {
info "启动前端: http://localhost:$FRONTEND_PORT"
(cd "$FRONTEND_DIR" && PORT="$FRONTEND_PORT" exec yarn dev) \
> >(sed -u 's/^/[前端] /') 2>&1 &
PIDS+=("$!")
}
cleanup() {
trap - INT TERM
echo
info "正在停止服务..."
if [ "${#PIDS[@]}" -gt 0 ]; then
for pid in "${PIDS[@]}"; do
kill "$pid" 2> /dev/null || true
done
fi
wait 2> /dev/null || true
ok "服务已停止"
}
main() {
print_header
check_backend_env
check_frontend_env
check_services
info "检查端口占用..."
local backend_free=1 frontend_free=1
if port_in_use "$BACKEND_PORT"; then
warn "端口 $BACKEND_PORT 已被占用,跳过启动后端"
backend_free=0
fi
if port_in_use "$FRONTEND_PORT"; then
warn "端口 $FRONTEND_PORT 已被占用,跳过启动前端"
frontend_free=0
fi
if [ "$backend_free" = 1 ] && [ "$frontend_free" = 1 ]; then
ok "端口 $BACKEND_PORT / $FRONTEND_PORT 均可用"
fi
if [ "$CHECK_ONLY" = 1 ]; then
echo
ok "环境检查完成"
exit 0
fi
echo
if [ "$backend_free" = 1 ]; then
start_backend
fi
if [ "$frontend_free" = 1 ]; then
start_frontend
fi
if [ "${#PIDS[@]}" -eq 0 ]; then
warn "前后端均已在运行,未启动新服务"
exit 0
fi
echo
ok "启动完成,按 Ctrl+C 停止服务"
trap cleanup INT TERM
wait
}
main "$@"