#!/bin/bash # IPAM 管理系统一键启动脚本 # # 特点: # - 目录自适应:不硬编码 /root/ipam,自动跟随本脚本所在目录。 # 无论部署在 /opt、/usr/local、/root 还是其它任意目录都能工作。 # - systemd 管理:后端、Celery Worker、Celery Beat 都生成 systemd 单元 # (ipam-backend / ipam-celery-worker / ipam-celery-beat), # 运行本脚本时自动生成并 enable,保证开机自启、崩溃自动重启。 # - 前端 vite dev server 仍用 nohup 启动(开发服务,不纳入 systemd)。 echo "==========================================" echo " IPAM 管理系统启动脚本" echo "==========================================" echo "" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # ============ 目录自适应(核心改动)============ # 无论脚本放哪里,都以其所在目录为项目根目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_DIR="$SCRIPT_DIR" BACKEND_DIR="$BASE_DIR/backend" FRONTEND_DIR="$BASE_DIR/frontend" # 端口配置 API_PORT=8008 FRONTEND_PORT=3000 # systemd 服务名 SYSTEMD_BACKEND="ipam-backend" SYSTEMD_CELERY_WORKER="ipam-celery-worker" SYSTEMD_CELERY_BEAT="ipam-celery-beat" # 日志目录 LOG_DIR="/var/log/ipam" mkdir -p "$LOG_DIR" 2>/dev/null echo -e "${GREEN}📁 项目目录: $BASE_DIR${NC}" # 检查 Docker 容器状态 check_docker_containers() { echo "🔍 检查 Docker 容器..." # 检查 MySQL if [ "$(docker inspect -f '{{.State.Running}}' ipam-mysql 2>/dev/null)" != "true" ]; then echo -e "${YELLOW}MySQL 容器未运行,正在启动...${NC}" docker start ipam-mysql 2>/dev/null || { echo -e "${YELLOW}创建 MySQL 容器...${NC}" docker run -d --name ipam-mysql -p 3308:3306 \ -e MYSQL_ROOT_PASSWORD=ipam2024 \ -e MYSQL_DATABASE=ipam \ -e MYSQL_USER=ipam \ -e MYSQL_PASSWORD=ipam2024 \ mysql:8.0 --default-authentication-plugin=mysql_native_password } fi echo -e "${GREEN}✅ MySQL 运行正常${NC}" # 检查 Redis if [ "$(docker inspect -f '{{.State.Running}}' ipam-redis 2>/dev/null)" != "true" ]; then echo -e "${YELLOW}Redis 容器未运行,正在启动...${NC}" docker start ipam-redis 2>/dev/null || { echo -e "${YELLOW}创建 Redis 容器...${NC}" docker run -d --name ipam-redis -p 6379:6379 redis:7-alpine } fi echo -e "${GREEN}✅ Redis 运行正常${NC}" echo "" } # 检查并杀死已占用的端口 kill_port() { local port=$1 local pid=$(lsof -t -i:$port 2>/dev/null || netstat -tlnp 2>/dev/null | grep ":$port" | awk '{print $7}' | cut -d'/' -f1) if [ -n "$pid" ]; then echo -e "${YELLOW}端口 $port 被占用 (PID $pid),正在关闭...${NC}" kill -9 $pid 2>/dev/null sleep 2 fi } # ============ systemd 单元动态生成 ============ # 生成 3 个服务单元(backend / celery-worker / celery-beat) # 路径全部跟随 BASE_DIR,保证部署目录变更后重新运行本脚本即可重写 setup_systemd_services() { echo "⚙️ 生成 systemd 服务单元..." # --- 后端 --- cat > /etc/systemd/system/$SYSTEMD_BACKEND.service < /etc/systemd/system/$SYSTEMD_CELERY_WORKER.service < /etc/systemd/system/$SYSTEMD_CELERY_BEAT.service </dev/null 2>&1 systemctl enable $SYSTEMD_CELERY_WORKER >/dev/null 2>&1 systemctl enable $SYSTEMD_CELERY_BEAT >/dev/null 2>&1 echo -e "${GREEN}✅ systemd 单元已生成并启用开机自启:${NC}" echo " - $SYSTEMD_BACKEND.service" echo " - $SYSTEMD_CELERY_WORKER.service" echo " - $SYSTEMD_CELERY_BEAT.service" echo "" } # 启动后端(systemd) start_backend() { echo "🚀 启动后端 API 服务..." # 检查虚拟环境 if [ ! -d "$BACKEND_DIR/venv" ]; then echo -e "${YELLOW}创建 Python 虚拟环境...${NC}" cd $BACKEND_DIR python3 -m venv venv fi # 检查并清理占用端口的旧进程 kill_port $API_PORT # 通过 systemd 启动 systemctl restart $SYSTEMD_BACKEND sleep 4 if curl -s http://localhost:$API_PORT/health > /dev/null; then echo -e "${GREEN}✅ 后端 API 启动成功 (端口 $API_PORT, systemd 管理)${NC}" return 0 else echo -e "${RED}❌ 后端 API 启动失败${NC}" echo " 查看日志: journalctl -u $SYSTEMD_BACKEND -n 50 或 tail -50 $LOG_DIR/ipam-backend.log" return 1 fi echo "" } # 启动 Celery(systemd:worker + beat) start_celery() { echo "🚀 启动 Celery Worker & Beat (systemd)..." systemctl restart $SYSTEMD_CELERY_WORKER systemctl restart $SYSTEMD_CELERY_BEAT sleep 4 systemctl is-active --quiet $SYSTEMD_CELERY_WORKER && WORKER_ACTIVE="yes" || WORKER_ACTIVE="no" systemctl is-active --quiet $SYSTEMD_CELERY_BEAT && BEAT_ACTIVE="yes" || BEAT_ACTIVE="no" if [ "$WORKER_ACTIVE" = "yes" ] && [ "$BEAT_ACTIVE" = "yes" ]; then echo -e "${GREEN}✅ Celery Worker & Beat 启动成功 (systemd 管理)${NC}" return 0 else echo -e "${RED}❌ Celery 启动异常 (worker=$WORKER_ACTIVE beat=$BEAT_ACTIVE)${NC}" echo " 查看日志: journalctl -u $SYSTEMD_CELERY_WORKER -n 30 -u $SYSTEMD_CELERY_BEAT -n 30" return 1 fi echo "" } # 启动前端(nohup,开发服务) start_frontend() { echo "🚀 启动前端服务..." cd $FRONTEND_DIR # 检查 node_modules 及关键依赖是否完整(含 vite,全局 npm 可能配置了 omit=dev) if [ ! -d "node_modules" ] || [ ! -f "node_modules/vite/bin/vite.js" ]; then echo -e "${YELLOW}安装前端依赖(含 devDependencies,保证 vite 可用)...${NC}" npm install --include=dev fi # 检查 OUI 数据库文件 if [ ! -f "$BACKEND_DIR/app/data/oui.txt" ]; then echo -e "${YELLOW}OUI 数据库不存在,尝试从 IEEE 下载...${NC}" cd $BACKEND_DIR source venv/bin/activate python3 -c " from mac_vendor_lookup import MacLookup import os os.makedirs('app/data', exist_ok=True) lookup = MacLookup() lookup.update_vendors() import shutil src = os.path.expanduser('~/.cache/mac-vendors.txt') if os.path.exists(src): shutil.copy(src, 'app/data/oui.txt') print('OUI 数据库下载完成') " 2>/dev/null || echo -e "${YELLOW}OUI 数据库下载失败(网络问题),使用内置硬编码回退${NC}" fi # 检查端口并清理 kill_port $FRONTEND_PORT # 清理 Vite 缓存,避免 element-plus exports 解析失败 if [ -d "node_modules/.vite" ]; then echo -e "${YELLOW}清理 Vite 缓存...${NC}" rm -rf node_modules/.vite fi # 后台启动前端 nohup npm run dev -- --host 0.0.0.0 --port $FRONTEND_PORT > /tmp/ipam-frontend.log 2>&1 & FRONTEND_PID=$! sleep 8 if curl -s http://localhost:$FRONTEND_PORT > /dev/null; then echo -e "${GREEN}✅ 前端服务启动成功 (端口 $FRONTEND_PORT)${NC}" echo " PID: $FRONTEND_PID" echo " 日志: /tmp/ipam-frontend.log" return 0 else echo -e "${YELLOW}首次启动失败,尝试清理 Vite 缓存重试...${NC}" kill -9 $FRONTEND_PID 2>/dev/null sleep 1 rm -rf node_modules/.vite nohup npm run dev -- --host 0.0.0.0 --port $FRONTEND_PORT > /tmp/ipam-frontend.log 2>&1 & FRONTEND_PID=$! sleep 12 if curl -s http://localhost:$FRONTEND_PORT > /dev/null; then echo -e "${GREEN}✅ 前端服务启动成功 (端口 $FRONTEND_PORT)${NC}" echo " PID: $FRONTEND_PID" echo " 日志: /tmp/ipam-frontend.log" return 0 else echo -e "${RED}❌ 前端服务启动失败${NC}" echo " 查看日志: tail -50 /tmp/ipam-frontend.log" return 1 fi fi echo "" } # 显示服务状态 show_status() { echo "" echo "==========================================" echo " 🎉 IPAM 系统启动完成!" echo "==========================================" echo "" echo "📊 服务状态:" echo " - MySQL: 127.0.0.1:3308 ✅" echo " - Redis: 127.0.0.1:6379 ✅" echo " - 后端 API: http://$(hostname -I | awk '{print $1}'):$API_PORT ✅ (systemd: $SYSTEMD_BACKEND)" echo " - Celery Worker: systemd: $SYSTEMD_CELERY_WORKER ✅" echo " - Celery Beat: systemd: $SYSTEMD_CELERY_BEAT ✅(每60秒触发SNMP设备自动轮询)" echo " - 前端界面: http://$(hostname -I | awk '{print $1}'):$FRONTEND_PORT ✅" echo "" echo "📖 访问地址:" echo " - 管理界面: http://$(hostname -I | awk '{print $1}'):$FRONTEND_PORT" echo " - API 文档: http://$(hostname -I | awk '{print $1}'):$API_PORT/docs" echo "" echo "🛑 停止服务命令:" echo " bash $SCRIPT_DIR/stop.sh" echo " 或 systemctl stop $SYSTEMD_CELERY_BEAT $SYSTEMD_CELERY_WORKER $SYSTEMD_BACKEND" echo "" echo "📈 查看状态:systemctl status $SYSTEMD_BACKEND $SYSTEMD_CELERY_WORKER $SYSTEMD_CELERY_BEAT" echo "📈 查看日志:journalctl -u $SYSTEMD_CELERY_BEAT -f" echo "" } # 主程序 main() { # 检查是否是 root 用户 if [ "$EUID" -ne 0 ]; then echo -e "${RED}请使用 root 权限运行此脚本(生成 systemd 单元需要)${NC}" exit 1 fi # 检查 Docker 是否安装 if ! command -v docker &> /dev/null; then echo -e "${RED}Docker 未安装,请先安装 Docker${NC}" exit 1 fi # 检查 Python if ! command -v python3 &> /dev/null; then echo -e "${RED}Python3 未安装${NC}" exit 1 fi # 检查 Node.js if ! command -v node &> /dev/null; then echo -e "${RED}Node.js 未安装${NC}" exit 1 fi echo "" check_docker_containers setup_systemd_services start_backend start_celery start_frontend show_status } # 运行主程序 main