301 lines
12 KiB
Bash
301 lines
12 KiB
Bash
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# pccs build.sh — 一键部署 (符合部署目录规范)
|
||
#
|
||
# 目标结构:
|
||
# /d/pccs/
|
||
# ├── app/ ← 应用入口
|
||
# ├── conf/ ← config.json
|
||
# ├── pkgs/ ← 所有模块 clone
|
||
# ├── wwwroot/ ← 软链接
|
||
# ├── py3/ ← venv
|
||
# ├── logs/
|
||
# ├── files/
|
||
# └── scripts/
|
||
#
|
||
# 运行: cd /d/pccs && bash build.sh
|
||
# ============================================================
|
||
set -e
|
||
|
||
# --------------- 路径 ---------------
|
||
# 假设 build.sh 放在 /d/pccs/build.sh (从 pccs repo 复制)
|
||
CDIR="$(cd "$(dirname "$0")" && pwd)" # /d/pccs
|
||
PKGDIR="${CDIR}/pkgs"
|
||
VENV="${CDIR}/py3"
|
||
APPNAME="pccs"
|
||
APP_PORT=9180
|
||
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 部署 — ${CDIR}"
|
||
blue "=========================================="
|
||
|
||
# ============================================================
|
||
# 0: 目录
|
||
# ============================================================
|
||
mkdir -p "${CDIR}/app" "${CDIR}/conf" "${CDIR}/files" "${CDIR}/logs" \
|
||
"${CDIR}/wwwroot/imgs" "${CDIR}/scripts" "${PKGDIR}"
|
||
|
||
# 复制 app/pccs.py 和 conf/config.json 到顶层
|
||
if [ -d "${PKGDIR}/pccs" ]; then
|
||
cp -f "${PKGDIR}/pccs/app/pccs.py" "${CDIR}/app/pccs.py" 2>/dev/null || true
|
||
cp -f "${PKGDIR}/pccs/conf/config.json" "${CDIR}/conf/config.json" 2>/dev/null || true
|
||
fi
|
||
|
||
# ============================================================
|
||
# 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"
|
||
|
||
# ============================================================
|
||
# 2: venv
|
||
# ============================================================
|
||
blue "[2/10] Python 虚拟环境..."
|
||
python3 -m venv "${VENV}" --clear
|
||
PIP="${VENV}/bin/pip"
|
||
PYTHON="${VENV}/bin/python"
|
||
"${PIP}" config set global.index-url https://mirrors.aliyun.com/pypi/simple/ 2>/dev/null || true
|
||
"${PIP}" install --upgrade pip setuptools wheel -q
|
||
"${PIP}" install xls2ddl -q --timeout=30 2>/dev/null || red " WARN: xls2ddl skip"
|
||
green " OK: ${VENV}"
|
||
|
||
# ============================================================
|
||
# 3: Clone/Pull
|
||
# ============================================================
|
||
blue "[3/10] 同步仓库..."
|
||
ALL=(apppublic sqlor ahserver appbase rbac bricks
|
||
pcpool pcc storage_mgr image_mgr pccs)
|
||
for repo in "${ALL[@]}"; 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}"
|
||
fi
|
||
done
|
||
|
||
# 确保 app/conf 文件存在
|
||
cp -f "${PKGDIR}/pccs/app/pccs.py" "${CDIR}/app/pccs.py" 2>/dev/null || true
|
||
cp -f "${PKGDIR}/pccs/conf/config.json" "${CDIR}/conf/config.json" 2>/dev/null || true
|
||
|
||
# 更新 config.json 路径占位符
|
||
if [ -f "${CDIR}/conf/config.json" ]; then
|
||
sed -i "s|\$\[workdir\]\$|${CDIR}|g" "${CDIR}/conf/config.json"
|
||
fi
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 4: pip install
|
||
# ============================================================
|
||
blue "[4/10] 安装模块..."
|
||
MODULES=(apppublic sqlor ahserver appbase rbac
|
||
pcpool pcc storage_mgr image_mgr)
|
||
|
||
_pip_failed=0
|
||
for m in "${MODULES[@]}"; do
|
||
mp="${PKGDIR}/${m}"
|
||
[ -f "${mp}/setup.py" ] || [ -f "${mp}/setup.cfg" ] || [ -f "${mp}/pyproject.toml" ] || continue
|
||
echo " pip install ${m}..."
|
||
"${PIP}" install "${mp}" -q 2>&1 && echo " OK" || { red " FAILED: ${m}"; _pip_failed=1; }
|
||
done
|
||
if [ $_pip_failed -eq 1 ]; then
|
||
red " Some modules failed to install"
|
||
fi
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 5: DDL
|
||
# ============================================================
|
||
blue "[5/10] DDL 建表..."
|
||
for m in appbase rbac pcpool pcc storage_mgr image_mgr; do
|
||
mdir="${PKGDIR}/${m}/models"
|
||
[ -d "$mdir" ] || continue
|
||
for jsonf in "$mdir"/*.json; do
|
||
[ -f "$jsonf" ] || continue
|
||
tbl=$(python3 -c "import json;d=json.load(open('$jsonf'));print(d['summary'][0]['name'])" 2>/dev/null) || continue
|
||
exists=$(mysql -u test -ptest123 pccs -sNe "SHOW TABLES LIKE '$tbl'" 2>/dev/null)
|
||
if [ -z "$exists" ]; then
|
||
echo " [${m}] create table ${tbl}..."
|
||
"${PYTHON}" -c "
|
||
import sys,json
|
||
sys.path[:0]=['${PKGDIR}/apppublic','${PKGDIR}/sqlor','${PKGDIR}/ahserver']
|
||
from appPublic.jsonConfig import getConfig
|
||
from sqlor.dbpools import DBPools
|
||
from sqlor.ddl_template_mysql import DDLTemplateMySQL
|
||
import asyncio
|
||
async def go():
|
||
config=getConfig('${CDIR}')
|
||
db=DBPools(config.databases)
|
||
ddl=DDLTemplateMySQL()
|
||
m=json.load(open('${jsonf}'))
|
||
sql=ddl.create_table(m['summary'][0]['name'],m.get('fields',[]))
|
||
async with db.sqlorContext('pccs') as s:
|
||
await s.sqlExe(sql,{})
|
||
print(' done')
|
||
asyncio.run(go())
|
||
" 2>&1 | tail -1
|
||
fi
|
||
done
|
||
done
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 6: xls2ui
|
||
# ============================================================
|
||
blue "[6/10] CRUD 生成..."
|
||
for m in appbase rbac pcpool pcc storage_mgr image_mgr; do
|
||
jdir="${PKGDIR}/${m}/json"
|
||
mdir="${PKGDIR}/${m}/models"
|
||
odir="${PKGDIR}/${m}/wwwroot"
|
||
[ -d "$jdir" ] && [ -d "$mdir" ] || continue
|
||
echo " [${m}] xls2ui..."
|
||
(cd "$jdir" && "${VENV}/bin/xls2ui" -m "$mdir" -o "$odir" "$m" *.json 2>/dev/null) || true
|
||
done
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 7: wwwroot symlinks
|
||
# ============================================================
|
||
blue "[7/10] 软链接..."
|
||
for m in appbase rbac pcpool pcc storage_mgr image_mgr; do
|
||
[ -d "${PKGDIR}/${m}/wwwroot" ] || continue
|
||
ln -sfn "${PKGDIR}/${m}/wwwroot" "${CDIR}/wwwroot/${m}" 2>/dev/null || true
|
||
done
|
||
ln -sfn "${PKGDIR}/pccs/wwwroot" "${CDIR}/wwwroot/pccs" 2>/dev/null || true
|
||
# 确保根级 index.html / index.ui 可访问
|
||
cp -f "${PKGDIR}/pccs/wwwroot/index.html" "${CDIR}/wwwroot/index.html" 2>/dev/null || true
|
||
cp -f "${PKGDIR}/pccs/wwwroot/index.ui" "${CDIR}/wwwroot/index.ui" 2>/dev/null || true
|
||
# bricks
|
||
if [ -f "${PKGDIR}/bricks/build.sh" ]; then
|
||
(cd "${PKGDIR}/bricks" && npm install --silent 2>/dev/null && bash build.sh 2>&1 | tail -2) || true
|
||
ln -sfn "${PKGDIR}/bricks/dist" "${CDIR}/wwwroot/bricks" 2>/dev/null || true
|
||
fi
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 7b: RBAC 权限初始化(参考 Sage setup_rbac_perms.sh)
|
||
# ============================================================
|
||
blue "[7b/10] RBAC 权限初始化..."
|
||
cp -f "${PKGDIR}/pccs/setup_rbac_perms.sh" "${CDIR}/setup_rbac_perms.sh"
|
||
cp -f "${PKGDIR}/pccs/set_role_perm.py" "${CDIR}/set_role_perm.py"
|
||
bash "${CDIR}/setup_rbac_perms.sh" 2>&1 | tail -5
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 8: 种子数据
|
||
# ============================================================
|
||
blue "[8/10] 种子数据..."
|
||
"${PYTHON}" - "${CDIR}" "${PKGDIR}" <<'PYEOF'
|
||
import sys,os,json,asyncio,datetime
|
||
CDIR=sys.argv[1]; PKGDIR=sys.argv[2]
|
||
sys.path[:0]=[os.path.join(PKGDIR,'apppublic'),os.path.join(PKGDIR,'sqlor'),os.path.join(PKGDIR,'ahserver')]
|
||
from appPublic.jsonConfig import getConfig
|
||
from sqlor.dbpools import DBPools
|
||
from appPublic.uniqueID import getID
|
||
|
||
CODES=[
|
||
('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 exists, skip')
|
||
return
|
||
for pid,k,v in CODES:
|
||
await sor.C('appcodes_kv',{'id':getID(),'parentid':pid,'k':k,'v':v,'created_at':now})
|
||
print(f' seeded {len(CODES)} codes')
|
||
asyncio.run(seed())
|
||
PYEOF
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 9: 启动脚本
|
||
# ============================================================
|
||
blue "[9/10] 启动脚本..."
|
||
cat > "${CDIR}/start.sh" <<SHEOF
|
||
#!/usr/bin/env bash
|
||
set -e
|
||
cd "${CDIR}"
|
||
exec "${VENV}/bin/python" "${CDIR}/app/pccs.py"
|
||
SHEOF
|
||
chmod +x "${CDIR}/start.sh"
|
||
|
||
cat > "${CDIR}/stop.sh" <<SHEOF
|
||
#!/usr/bin/env bash
|
||
pkill -f "${CDIR}/app/pccs.py" 2>/dev/null && echo "stopped" || echo "not running"
|
||
SHEOF
|
||
chmod +x "${CDIR}/stop.sh"
|
||
|
||
# systemd
|
||
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
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
SERVEOF
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable pccs 2>/dev/null || true
|
||
green " OK"
|
||
|
||
# ============================================================
|
||
# 10: 启动
|
||
# ============================================================
|
||
blue "[10/10] 启动..."
|
||
"${CDIR}/stop.sh" 2>/dev/null || true
|
||
sleep 1
|
||
nohup bash "${CDIR}/start.sh" > "${CDIR}/logs/nohup.log" 2>&1 &
|
||
sleep 4
|
||
|
||
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: check log: tail -50 ${CDIR}/logs/nohup.log"
|
||
tail -20 "${CDIR}/logs/nohup.log" 2>/dev/null
|
||
fi
|
||
|
||
echo ""
|
||
green "=========================================="
|
||
green " 部署完成! http://$(hostname -I | awk '{print $1}'):${APP_PORT}"
|
||
green "=========================================="
|