Files
ipam/start.sh
T
Your Name 18cefa6ded feat(deploy): 目录自适应 + Celery 自动轮询接入 systemd
1. start.sh BASE_DIR 不再硬编码 /root/ipam,改为跟随脚本所在目录
   (BASH_SOURCE 自动检测),无论部署在 /opt /usr/local /root 等
   任意目录都能工作。
2. 运行 start.sh 时自动生成 3 个 systemd 单元并 enable 开机自启:
   - ipam-backend (uvicorn :8008)
   - ipam-celery-worker
   - ipam-celery-beat (每60秒触发SNMP设备自动轮询)
   均含 Restart=always 崩溃自动重启,日志写入 /var/log/ipam/。
3. 前端 vite dev server 保持 nohup 启动(不纳入 systemd)。
4. stop.sh 改为 systemctl stop 停止 systemd 服务,pkill 兜底。
5. .gitignore 忽略 celerybeat-schedule 运行期文件。
2026-08-12 11:05:18 +08:00

358 lines
12 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
# 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 <<EOF
[Unit]
Description=IPAM Backend API (uvicorn)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=$BACKEND_DIR
Environment=PYTHONPATH=$BACKEND_DIR
ExecStart=$BACKEND_DIR/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port $API_PORT
Restart=always
RestartSec=3
StandardOutput=append:$LOG_DIR/ipam-backend.log
StandardError=append:$LOG_DIR/ipam-backend.log
[Install]
WantedBy=multi-user.target
EOF
# --- Celery Worker ---
cat > /etc/systemd/system/$SYSTEMD_CELERY_WORKER.service <<EOF
[Unit]
Description=IPAM Celery Worker
After=$SYSTEMD_BACKEND.service network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=$BACKEND_DIR
Environment=PYTHONPATH=$BACKEND_DIR
ExecStart=$BACKEND_DIR/venv/bin/celery -A app.tasks.celery_app worker --loglevel=info --concurrency=4
Restart=always
RestartSec=5
StandardOutput=append:$LOG_DIR/ipam-celery-worker.log
StandardError=append:$LOG_DIR/ipam-celery-worker.log
[Install]
WantedBy=multi-user.target
EOF
# --- Celery Beat ---
cat > /etc/systemd/system/$SYSTEMD_CELERY_BEAT.service <<EOF
[Unit]
Description=IPAM Celery Beat Scheduler
After=$SYSTEMD_CELERY_WORKER.service network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=$BACKEND_DIR
Environment=PYTHONPATH=$BACKEND_DIR
ExecStart=$BACKEND_DIR/venv/bin/celery -A app.tasks.celery_app beat --loglevel=info
Restart=always
RestartSec=5
StandardOutput=append:$LOG_DIR/ipam-celery-beat.log
StandardError=append:$LOG_DIR/ipam-celery-beat.log
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
# enable 开机自启
systemctl enable $SYSTEMD_BACKEND >/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 ""
}
# 启动 Celerysystemdworker + 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