359 lines
12 KiB
Bash
359 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
# ============================================================
|
|
# pccs build.sh — 一键部署脚本 (符合 web-application-spec)
|
|
#
|
|
# 在部署节点上运行:
|
|
# cd /d/pccs/repos/pccs && bash build.sh
|
|
#
|
|
# 前提:
|
|
# - MariaDB 已安装, pccs 库已创建, test/test123 已授权
|
|
# - /d 已作为 NFS 共享盘导出
|
|
# - git, python3.10+, pip, npm 已安装
|
|
# ============================================================
|
|
set -e
|
|
|
|
# --------------- 配置 ---------------
|
|
WORKDIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
CDIR="${WORKDIR}" # 应用根目录 (/d/pccs)
|
|
PKGDIR="${CDIR}/repos" # 模块 clone 目录
|
|
VENV="${CDIR}/venv"
|
|
APPNAME="pccs"
|
|
APP_PORT=9180
|
|
|
|
# Git 仓库基地址
|
|
GIT_BASE="https://git.opencomputing.cn/yumoqing"
|
|
|
|
# --------------- 颜色 ---------------
|
|
red() { echo -e "\033[31m$1\033[0m"; }
|
|
green() { echo -e "\033[32m$1\033[0m"; }
|
|
blue() { echo -e "\033[34m$1\033[0m"; }
|
|
|
|
blue "=========================================="
|
|
blue " pccs — 算力中心集群系统 部署"
|
|
blue "=========================================="
|
|
|
|
# ============================================================
|
|
# Phase 0: 创建目录
|
|
# ============================================================
|
|
mkdir -p "${CDIR}/app" "${CDIR}/conf" "${CDIR}/files" "${CDIR}/logs" \
|
|
"${CDIR}/wwwroot/imgs" "${PKGDIR}"
|
|
|
|
# ============================================================
|
|
# Phase 1: 安装系统依赖
|
|
# ============================================================
|
|
blue "[1/10] 系统依赖..."
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq python3-venv python3-pip python3-dev \
|
|
build-essential nfs-common
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 2: Python 虚拟环境
|
|
# ============================================================
|
|
blue "[2/10] Python 虚拟环境..."
|
|
python3 -m venv "${VENV}" --clear
|
|
PIP="${VENV}/bin/pip"
|
|
PYTHON="${VENV}/bin/python"
|
|
"${PIP}" install --upgrade pip setuptools wheel -q
|
|
# 配置 pip 国内镜像 (阿里云)
|
|
"${PIP}" config set global.index-url https://mirrors.aliyun.com/pypi/simple/ 2>/dev/null || true
|
|
"${PIP}" install --upgrade pip setuptools wheel -q
|
|
# 安装 xls2ddl (DDL 生成工具)
|
|
"${PIP}" install xls2ddl -q --timeout=30 2>/dev/null || {
|
|
red " WARN: xls2ddl install failed (PyPI unreachable?), skip"
|
|
}
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 3: Clone/Pull 所有模块
|
|
# ============================================================
|
|
blue "[3/10] 同步代码仓库..."
|
|
ALL_REPOS=(
|
|
# 基础层
|
|
apppublic sqlor ahserver
|
|
# 数据库层
|
|
appbase rbac
|
|
# 前端
|
|
bricks bricks_for_python
|
|
# 业务模块
|
|
pcpool pcc storage_mgr image_mgr
|
|
# 主应用
|
|
pccs
|
|
)
|
|
for repo in "${ALL_REPOS[@]}"; do
|
|
if [ -d "${PKGDIR}/${repo}/.git" ]; then
|
|
(cd "${PKGDIR}/${repo}" && git pull origin main 2>/dev/null) || true
|
|
else
|
|
echo " clone ${repo}..."
|
|
git clone "${GIT_BASE}/${repo}.git" "${PKGDIR}/${repo}" 2>/dev/null || {
|
|
red " WARN: ${repo} clone failed"
|
|
}
|
|
fi
|
|
done
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 4: pip install 所有模块
|
|
# ============================================================
|
|
blue "[4/10] 安装 Python 模块..."
|
|
MODULES=(
|
|
apppublic sqlor ahserver
|
|
appbase rbac
|
|
bricks_for_python
|
|
pcpool pcc storage_mgr image_mgr
|
|
)
|
|
for m in "${MODULES[@]}"; do
|
|
mp="${PKGDIR}/${m}"
|
|
if [ -f "${mp}/setup.py" ] || [ -f "${mp}/setup.cfg" ] || [ -f "${mp}/pyproject.toml" ]; then
|
|
"${PIP}" install -e "${mp}" -q 2>&1 | tail -1
|
|
fi
|
|
done
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 5: 数据库 DDL — json2ddl 生成建表 SQL
|
|
# ============================================================
|
|
blue "[5/10] 数据库 DDL 生成..."
|
|
# 收集所有有 models/ 的模块
|
|
DB_MODULES=(appbase rbac pcpool pcc storage_mgr image_mgr)
|
|
|
|
for m in "${DB_MODULES[@]}"; do
|
|
mdir="${PKGDIR}/${m}/models"
|
|
if [ -d "$mdir" ]; then
|
|
has_json=$(ls "$mdir"/*.json 2>/dev/null | wc -l)
|
|
has_xlsx=$(ls "$mdir"/*.xlsx 2>/dev/null | wc -l)
|
|
if [ "$has_json" -gt 0 ]; then
|
|
echo " [${m}] json2ddl → DDL"
|
|
(cd "$mdir" && "${VENV}/bin/json2ddl" mysql . > /tmp/pccs_${m}.ddl.sql 2>/dev/null) || true
|
|
mysql -u test -ptest123 pccs < /tmp/pccs_${m}.ddl.sql 2>/dev/null || true
|
|
fi
|
|
if [ "$has_xlsx" -gt 0 ]; then
|
|
echo " [${m}] xls2ddl → DDL"
|
|
(cd "$mdir" && xls2ddl mysql . > /tmp/pccs_${m}_xlsx.ddl.sql 2>/dev/null) || true
|
|
mysql -u test -ptest123 pccs < /tmp/pccs_${m}_xlsx.ddl.sql 2>/dev/null || true
|
|
fi
|
|
fi
|
|
done
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 6: 导入初始数据 (data/ 目录)
|
|
# ============================================================
|
|
blue "[6/10] 初始数据导入..."
|
|
for m in "${DB_MODULES[@]}"; do
|
|
ddir="${PKGDIR}/${m}/data"
|
|
if [ -d "$ddir" ]; then
|
|
for xlsx in "$ddir"/*.xlsx; do
|
|
[ -f "$xlsx" ] || continue
|
|
echo " [${m}] dbloader → ${xlsx}"
|
|
"${VENV}/bin/dbloader" "${CDIR}" pccs "$xlsx" 2>/dev/null || true
|
|
done
|
|
fi
|
|
done
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 7: CRUD 生成 — xls2ui
|
|
# ============================================================
|
|
blue "[7/10] CRUD 界面生成..."
|
|
for m in "${DB_MODULES[@]}"; do
|
|
jdir="${PKGDIR}/${m}/json"
|
|
mdir="${PKGDIR}/${m}/models"
|
|
odir="${PKGDIR}/${m}/wwwroot"
|
|
if [ -d "$jdir" ] && [ -d "$mdir" ]; then
|
|
echo " [${m}] xls2ui..."
|
|
(cd "$jdir" && "${VENV}/bin/xls2ui" -m "$mdir" -o "$odir" "$m" *.json 2>/dev/null) || true
|
|
fi
|
|
done
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 8: 创建 wwwroot 软链接
|
|
# ============================================================
|
|
blue "[8/10] 创建 wwwroot 软链接..."
|
|
for m in "${DB_MODULES[@]}"; do
|
|
wdir="${PKGDIR}/${m}/wwwroot"
|
|
if [ -d "$wdir" ]; then
|
|
ln -sfn "$wdir" "${CDIR}/wwwroot/${m}" 2>/dev/null || true
|
|
fi
|
|
done
|
|
# pccs 自身的 wwwroot
|
|
ln -sfn "${PKGDIR}/pccs/wwwroot" "${CDIR}/wwwroot/${APPNAME}" 2>/dev/null || true
|
|
|
|
# Bricks 前端
|
|
if [ -f "${PKGDIR}/bricks/build.sh" ]; then
|
|
blue "[bricks] 构建前端..."
|
|
(cd "${PKGDIR}/bricks" && npm install --silent 2>/dev/null && bash build.sh 2>&1 | tail -3) || true
|
|
ln -sfn "${PKGDIR}/bricks/dist" "${CDIR}/wwwroot/bricks" 2>/dev/null || true
|
|
fi
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 9: 种子应用数据 (appcode / RBAC)
|
|
# ============================================================
|
|
blue "[9/10] 种子数据..."
|
|
"${PYTHON}" - "${CDIR}" "${PKGDIR}" <<'PYEOF'
|
|
import sys, os, json
|
|
|
|
CDIR = sys.argv[1]
|
|
PKGDIR = sys.argv[2]
|
|
|
|
sys.path.insert(0, os.path.join(PKGDIR, 'apppublic'))
|
|
sys.path.insert(0, os.path.join(PKGDIR, 'sqlor'))
|
|
sys.path.insert(0, os.path.join(PKGDIR, 'ahserver'))
|
|
|
|
from appPublic.jsonConfig import getConfig
|
|
from sqlor.dbpools import DBPools
|
|
from appPublic.uniqueID import getID
|
|
import datetime, asyncio
|
|
|
|
APPCODES = [
|
|
('cluster_type', 'k8s', 'Kubernetes集群'),
|
|
('cluster_type', 'slurm', 'Slurm集群'),
|
|
('cluster_type', 'ray', 'Ray集群'),
|
|
('cluster_status', 'deploying', '部署中'),
|
|
('cluster_status', 'running', '运行中'),
|
|
('cluster_status', 'failed', '失败'),
|
|
('cluster_status', 'stopped', '已停止'),
|
|
('cluster_status', 'destroyed', '已销毁'),
|
|
('node_role', 'control', '控制节点'),
|
|
('node_role', 'compute', '算力节点'),
|
|
('node_role', 'storage', '存储节点'),
|
|
('node_status', 'joining', '加入中'),
|
|
('node_status', 'active', '活跃'),
|
|
('node_status', 'draining', '排空中'),
|
|
('node_status', 'removed', '已移除'),
|
|
('node_type', 'control', '控制节点'),
|
|
('node_type', 'compute', '算力节点'),
|
|
('node_type', 'storage', '存储节点'),
|
|
('storage_type', 'nfs', 'NFS'),
|
|
('storage_type', 'glusterfs', 'GlusterFS'),
|
|
('storage_type', 'cephfs', 'CephFS'),
|
|
('storage_status', 'online', '在线'),
|
|
('storage_status', 'offline', '离线'),
|
|
('storage_status', 'maintenance', '维护中'),
|
|
('storage_access_mode', 'rw', '读写'),
|
|
('storage_access_mode', 'ro', '只读'),
|
|
('export_status', 'active', '活跃'),
|
|
('export_status', 'inactive', '停用'),
|
|
('mount_status', 'unmounted', '未挂载'),
|
|
('mount_status', 'mounted', '已挂载'),
|
|
('mount_status', 'failed', '失败'),
|
|
('mirror_source_type', 'public_mirror', '公共镜像'),
|
|
('mirror_source_type', 'private_registry', '私有仓库'),
|
|
('mirror_source_type', 'hub', '官方Hub'),
|
|
('mirror_region', 'cn', '中国'),
|
|
('mirror_region', 'us', '美国'),
|
|
('mirror_region', 'eu', '欧洲'),
|
|
('mirror_status', 'active', '活跃'),
|
|
('mirror_status', 'inactive', '停用'),
|
|
('mirror_status', 'error', '异常'),
|
|
('registry_type', 'docker-registry', 'Docker Registry'),
|
|
('registry_type', 'harbor', 'Harbor'),
|
|
('registry_status', 'deploying', '部署中'),
|
|
('registry_status', 'running', '运行中'),
|
|
('registry_status', 'failed', '失败'),
|
|
('registry_status', 'destroyed', '已销毁'),
|
|
('image_type', 'vm', '虚拟机镜像'),
|
|
('image_type', 'base', '基础镜像'),
|
|
('image_type', 'app', '应用镜像'),
|
|
('image_type', 'custom', '自定义镜像'),
|
|
('sync_status', 'pending', '待同步'),
|
|
('sync_status', 'syncing', '同步中'),
|
|
('sync_status', 'synced', '已同步'),
|
|
('sync_status', 'failed', '失败'),
|
|
]
|
|
|
|
async def seed():
|
|
config = getConfig(CDIR)
|
|
db = DBPools(config.databases)
|
|
now = datetime.datetime.now().isoformat()
|
|
async with db.sqlorContext('pccs') as sor:
|
|
rows = await sor.R('appcodes_kv', {'parentid': 'cluster_type'})
|
|
if rows:
|
|
print(' appcode 已存在, 跳过')
|
|
return
|
|
for parentid, k, v in APPCODES:
|
|
await sor.C('appcodes_kv', {
|
|
'id': getID(), 'parentid': parentid,
|
|
'k': k, 'v': v, 'created_at': now,
|
|
})
|
|
print(f' 已写入 {len(APPCODES)} 条 appcode')
|
|
|
|
asyncio.run(seed())
|
|
PYEOF
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# Phase 10: 创建 systemd 服务 + 启动脚本
|
|
# ============================================================
|
|
blue "[10/10] 服务配置..."
|
|
|
|
# start.sh
|
|
cat > "${CDIR}/start.sh" <<STARTEOF
|
|
#!/usr/bin/env bash
|
|
set -e
|
|
cd "${CDIR}"
|
|
source "${CDIR}/.env" 2>/dev/null || true
|
|
export PYTHONPATH="${PKGDIR}/ahserver:${PKGDIR}/apppublic:${PKGDIR}/sqlor:${PKGDIR}/appbase:${PKGDIR}/rbac:${PKGDIR}/bricks_for_python:\$PYTHONPATH"
|
|
exec "${VENV}/bin/python" "${CDIR}/app/${APPNAME}.py" 2>&1 | tee -a "${CDIR}/logs/${APPNAME}.log"
|
|
STARTEOF
|
|
chmod +x "${CDIR}/start.sh"
|
|
|
|
# stop.sh
|
|
cat > "${CDIR}/stop.sh" <<STOPEOF
|
|
#!/usr/bin/env bash
|
|
pkill -f "${CDIR}/app/${APPNAME}.py" 2>/dev/null && echo "pccs stopped" || echo "pccs not running"
|
|
STOPEOF
|
|
chmod +x "${CDIR}/stop.sh"
|
|
|
|
# systemd service
|
|
sudo tee /etc/systemd/system/pccs.service > /dev/null <<SERVEOF
|
|
[Unit]
|
|
Description=PCCS 算力中心集群系统
|
|
After=network.target mariadb.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=$(whoami)
|
|
Group=$(id -gn)
|
|
WorkingDirectory=${CDIR}
|
|
ExecStart=${CDIR}/start.sh
|
|
ExecStop=${CDIR}/stop.sh
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
StandardOutput=append:${CDIR}/logs/pccs.log
|
|
StandardError=append:${CDIR}/logs/pccs.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVEOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable pccs 2>/dev/null || true
|
|
green " OK"
|
|
|
|
# ============================================================
|
|
# 启动应用
|
|
# ============================================================
|
|
blue "启动应用..."
|
|
"${CDIR}/stop.sh" 2>/dev/null || true
|
|
sleep 1
|
|
nohup bash "${CDIR}/start.sh" > "${CDIR}/logs/nohup.log" 2>&1 &
|
|
sleep 3
|
|
|
|
if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${APP_PORT}/" 2>/dev/null | grep -q '200\|404'; then
|
|
green " pccs 已启动: http://127.0.0.1:${APP_PORT}"
|
|
else
|
|
red " WARN: 无响应, 检查日志: tail -f ${CDIR}/logs/pccs.log"
|
|
fi
|
|
|
|
echo ""
|
|
green "=========================================="
|
|
green " 部署完成!"
|
|
green " 访问: http://$(hostname -I | awk '{print $1}'):${APP_PORT}"
|
|
green " 启动: sudo systemctl start pccs"
|
|
green " 停止: sudo systemctl stop pccs"
|
|
green " 日志: tail -f ${CDIR}/logs/pccs.log"
|
|
green "=========================================="
|