@@ -1,5 +1,13 @@
#!/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 管理系统启动脚本"
@@ -12,8 +20,10 @@ GREEN='\033[0;32m'
YELLOW = '\033[1;33m'
NC = '\033[0m' # No Color
# 项目根目录
BASE_DIR = "/root/ipam"
# ============ 目录自适应(核心改动)============
# 无论脚本放哪里,都以其所在目录为项目根目录
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
BASE_DIR = " $SCRIPT_DIR "
BACKEND_DIR = " $BASE_DIR /backend "
FRONTEND_DIR = " $BASE_DIR /frontend "
@@ -21,20 +31,31 @@ 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 = *** \
-e MYSQL_ROOT_PASSWORD = ipam2024 \
-e MYSQL_DATABASE = ipam \
-e MYSQL_USER = ipam \
-e MYSQL_PASSWORD = *** \
-e MYSQL_PASSWORD = ipam2024 \
mysql:8.0 --default-authentication-plugin= mysql_native_password
}
fi
@@ -63,87 +84,151 @@ kill_port() {
fi
}
# 启动后端 API
# ============ 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
# 激活虚拟环境并启动
cd $BACKEND_DIR
source venv/bin/activate
# 检查端口并清理
# 检查并清理占用端口的旧进程
kill_port $API_PORT
# 后台启动 uvicorn
nohup uvicorn app.main:app --host 0.0.0.0 --port $API_PORT > /tmp/ipam-backend.log 2>& 1 &
BACKEND_PID = $!
# 等待服务启动
sleep 5
# 检查是否启动成功
# 通过 systemd 启动
systemctl restart $SYSTEMD_BACKEND
sleep 4
if curl -s http://localhost:$API_PORT /health > /dev/null; then
echo -e " ${ GREEN } ✅ 后端 API 启动成功 (端口 $API_PORT ) ${ NC } "
echo " PID: $BACKEND_PID "
echo " 日志: /tmp/ipam-backend.log"
echo -e " ${ GREEN } ✅ 后端 API 启动成功 (端口 $API_PORT , systemd 管理 )${ NC } "
return 0
else
echo -e " ${ RED } ❌ 后端 API 启动失败 ${ NC } "
echo " 查看日志: tail -50 /tmp /ipam-backend.log"
echo " 查看日志: journalctl -u $SYSTEMD_BACKEND -n 50 或 tail -50 $LOG_DIR /ipam-backend.log "
return 1
fi
echo ""
}
# 启动 Celery Worker
# 启动 Celery( systemd: worker + beat)
start_celery( ) {
echo "🚀 启动 Celery Worker..."
cd $BACKEND_DIR
source venv/bin/activate
# 检查是否已有 celery 进程
pkill -f "celery.*app.tasks.celery_app" 2>/dev/null
sleep 1
# 后台启动 celery
nohup celery -A app.tasks.celery_app worker --loglevel= info --concurrency= 4 > /tmp/ipam-celery.log 2>& 1 &
CELERY_PID = $!
sleep 3
if ps -p $CELERY_PID > /dev/null; then
echo -e " ${ GREEN } ✅ Celery Worker 启动成功 ${ NC } "
echo " PID: $CELERY_PID "
echo " 日志: /tmp/ipam-celery.log"
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 启动失败 ${ NC } "
echo " 查看日志: tail -50 /tmp/ipam-celery.log "
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 及关键依赖是否完整
if [ ! -d "node_modules" ] || [ ! -f "node_modules/element-plus/es/index.m js" ] ; then
echo -e " ${ YELLOW } 安装前端依赖... ${ NC } "
npm install
# 检查 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 } "
@@ -155,7 +240,6 @@ 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):
@@ -163,29 +247,28 @@ if os.path.exists(src):
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
@@ -217,8 +300,9 @@ show_status() {
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 ✅ "
echo " - Celery: 后台运行 ✅ "
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 "📖 访问地址:"
@@ -226,7 +310,11 @@ show_status() {
echo " - API 文档: http:// $( hostname -I | awk '{print $1}' ) : $API_PORT /docs "
echo ""
echo "🛑 停止服务命令:"
echo " pkill -f 'uvicorn|celery|vite' "
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 ""
}
@@ -234,7 +322,7 @@ show_status() {
main( ) {
# 检查是否是 root 用户
if [ " $EUID " -ne 0 ] ; then
echo -e " ${ RED } 请使用 root 权限运行此脚本 ${ NC } "
echo -e " ${ RED } 请使用 root 权限运行此脚本(生成 systemd 单元需要) ${ NC } "
exit 1
fi
@@ -258,6 +346,7 @@ main() {
echo ""
check_docker_containers
setup_systemd_services
start_backend
start_celery
start_frontend