feat: complete pccs application scaffold per web-application-spec

- add app/pccs.py (webapp entry with ServerEnv/load_rbac/load_pybricks)
- add conf/config.json (password_key, logger, databases, website processors)
- add .env (environment variables for deployment)
- add .gitignore
- rewrite build.sh per spec (xls2ddl, json2ddl, xls2ui, symlinks, systemd)
This commit is contained in:
yumoqing 2026-08-11 14:41:55 +08:00
parent b5b5784df5
commit 43695b238d
5 changed files with 398 additions and 363 deletions

19
.env Normal file
View File

@ -0,0 +1,19 @@
# pccs 环境变量
# 部署时根据实际环境修改
# 工作目录
PCCS_HOME=/d/pccs
# 数据库 (如不在 config.json 中配置可通过环境变量覆盖)
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_USER=test
# DB_PASSWORD=test123
# DB_NAME=pccs
# Python 路径
PYTHONPATH=$PCCS_HOME/repos/ahserver:$PCCS_HOME/repos/apppublic:$PCCS_HOME/repos/sqlor:$PCCS_HOME/repos/appbase:$PCCS_HOME/repos/rbac:$PCCS_HOME/repos/bricks_for_python:$PYTHONPATH
# NFS 共享存储端点 (供集群节点使用)
NFS_SERVER=pccs.opencomputing.cn
NFS_PATH=/d

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
__pycache__/
*.pyc
.venv/
venv/
logs/
files/
*.log
.env.local

40
app/pccs.py Normal file
View File

@ -0,0 +1,40 @@
"""
pccs — 算力中心集群系统 (Pooled Computing Cluster System)
入口: webapp(init) 模式
"""
import os, sys
from ahserver.webapp import webapp
from ahserver.serverenv import ServerEnv
import bricks_for_python
from bricks_for_python.init import load_pybricks
from appbase.init import load_appbase
from rbac.init import load_rbac
WORKDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def get_module_dbname(m):
"""所有模块共用 pccs 数据库"""
return 'pccs'
def password_encode(s):
if s is None:
return ''
from ahserver.globalEnv import password_encode as _orig
return _orig(s)
def init():
env = ServerEnv() # MUST be before load_rbac
env.get_module_dbname = get_module_dbname
env.password_encode = password_encode
load_appbase()
load_rbac()
load_pybricks()
if __name__ == '__main__':
webapp(init, workdir=WORKDIR)

649
build.sh
View File

@ -1,430 +1,353 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================ # ============================================================
# pccs build.sh — 一键部署脚本 # pccs build.sh — 一键部署脚本 (符合 web-application-spec)
# 在部署节点 (pccs@pccs.opencomputing.cn) 上直接运行: #
# 在部署节点上运行:
# cd /d/pccs/repos/pccs && bash build.sh # cd /d/pccs/repos/pccs && bash build.sh
# #
# 前提: # 前提:
# 1. /d 已作为 NFS 共享盘挂载 # - MariaDB 已安装, pccs 库已创建, test/test123 已授权
# 2. MariaDB 已安装, 数据库 pccs 已创建, 用户 test/test123 已授权 # - /d 已作为 NFS 共享盘导出
# 3. 部署节点已安装 git, python3.10+, pip # - git, python3.10+, pip, npm 已安装
# ============================================================ # ============================================================
set -e set -e
SERVER_PATH="${SERVER_PATH:-/d/pccs}" # --------------- 配置 ---------------
REPOS_DIR="${SERVER_PATH}/repos" WORKDIR="$(cd "$(dirname "$0")/../.." && pwd)"
VENV_DIR="${SERVER_PATH}/venv" CDIR="${WORKDIR}" # 应用根目录 (/d/pccs)
CONF_DIR="${SERVER_PATH}/conf" PKGDIR="${CDIR}/repos" # 模块 clone 目录
LOG_DIR="${SERVER_PATH}/logs" VENV="${CDIR}/venv"
RUN_DIR="${SERVER_PATH}/run" APPNAME="pccs"
APP_PORT="${APP_PORT:-9180}" APP_PORT=9180
# --------------- 模块列表 --------------- # Git 仓库基地址
# 共享底层库 (pip install) GIT_BASE="https://git.opencomputing.cn/yumoqing"
SHARED_LIBS=(
apppublic
sqlor
ahserver
appbase
rbac
)
# pccs 业务模块 (pip install)
PCCS_MODULES=(
pcpool
pcc
storage_mgr
image_mgr
)
# 前端 bricks (需要构建)
BRICKS_DIR="${REPOS_DIR}/bricks"
# --------------- 颜色输出 --------------- # --------------- 颜色 ---------------
red() { echo -e "\033[31m$1\033[0m"; } red() { echo -e "\033[31m$1\033[0m"; }
green() { echo -e "\033[32m$1\033[0m"; } green() { echo -e "\033[32m$1\033[0m"; }
blue() { echo -e "\033[34m$1\033[0m"; } blue() { echo -e "\033[34m$1\033[0m"; }
echo "==========================================" blue "=========================================="
blue "PCCS 算力中心集群系统 — 一键部署" blue " pccs — 算力中心集群系统 部署"
echo "==========================================" blue "=========================================="
echo "部署路径: ${SERVER_PATH}"
echo "监听端口: ${APP_PORT}"
echo ""
# ============================================================ # ============================================================
# Phase 0: 创建目录结构 # Phase 0: 创建目录
# ============================================================ # ============================================================
blue "[Phase 0] 创建目录结构..." mkdir -p "${CDIR}/app" "${CDIR}/conf" "${CDIR}/files" "${CDIR}/logs" \
mkdir -p "${REPOS_DIR}" "${VENV_DIR}" "${CONF_DIR}" "${LOG_DIR}" "${RUN_DIR}" "${CDIR}/wwwroot/imgs" "${PKGDIR}"
green " 目录结构已创建"
# ============================================================ # ============================================================
# Phase 1: 安装系统依赖 # Phase 1: 安装系统依赖
# ============================================================ # ============================================================
blue "[Phase 1] 安装系统依赖..." blue "[1/10] 系统依赖..."
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -y -qq python3-venv python3-pip python3-dev build-essential nfs-common sudo apt-get install -y -qq python3-venv python3-pip python3-dev \
green " 系统依赖已安装" build-essential nfs-common mysql-client
green " OK"
# ============================================================ # ============================================================
# Phase 2: Clone/Pull 所有仓库 (HTTPS, 只读) # Phase 2: Python 虚拟环境
# ============================================================ # ============================================================
blue "[Phase 2] Clone/Pull 代码仓库..." blue "[2/10] Python 虚拟环境..."
python3 -m venv "${VENV}" --clear
GIT_BASE="https://git.opencomputing.cn/yumoqing" PIP="${VENV}/bin/pip"
ALL_REPOS=("${SHARED_LIBS[@]}" "${PCCS_MODULES[@]}" "bricks" "pccs") PYTHON="${VENV}/bin/python"
for repo in "${ALL_REPOS[@]}"; do
if [ -d "${REPOS_DIR}/${repo}/.git" ]; then
echo " [pull] ${repo}..."
(cd "${REPOS_DIR}/${repo}" && git pull origin main 2>/dev/null) || true
else
echo " [clone] ${repo}..."
git clone "${GIT_BASE}/${repo}.git" "${REPOS_DIR}/${repo}" 2>/dev/null || {
red " 警告: ${repo} clone 失败, 跳过"
}
fi
done
green " 代码仓库已同步"
# ============================================================
# Phase 3: 创建 Python 虚拟环境
# ============================================================
blue "[Phase 3] 创建 Python 虚拟环境..."
python3 -m venv "${VENV_DIR}" --clear
PIP="${VENV_DIR}/bin/pip"
PYTHON="${VENV_DIR}/bin/python"
# 升级 pip
"${PIP}" install --upgrade pip setuptools wheel -q "${PIP}" install --upgrade pip setuptools wheel -q
green " 虚拟环境已创建: ${VENV_DIR}" # 先安装 xls2ddl (spec 要求, 用于 DDL 生成和数据导入)
"${PIP}" install xls2ddl -q 2>/dev/null || true
green " OK"
# ============================================================ # ============================================================
# Phase 4: 安装共享底层库 (develop 模式) # Phase 3: Clone/Pull 所有模块
# ============================================================ # ============================================================
blue "[Phase 4] 安装共享底层库..." blue "[3/10] 同步代码仓库..."
for lib in "${SHARED_LIBS[@]}"; do ALL_REPOS=(
lib_path="${REPOS_DIR}/${lib}" # 基础层
if [ -f "${lib_path}/setup.py" ] || [ -f "${lib_path}/setup.cfg" ] || [ -f "${lib_path}/pyproject.toml" ]; then apppublic sqlor ahserver
echo " [install] ${lib}..." # 数据库层
"${PIP}" install -e "${lib_path}" -q 2>&1 | tail -1 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 else
echo " [skip] ${lib} — 无 setup" echo " clone ${repo}..."
fi git clone "${GIT_BASE}/${repo}.git" "${PKGDIR}/${repo}" 2>/dev/null || {
done red " WARN: ${repo} clone failed"
green " 共享底层库已安装"
# ============================================================
# Phase 5: 安装 pccs 业务模块 (develop 模式)
# ============================================================
blue "[Phase 5] 安装 pccs 业务模块..."
for mod in "${PCCS_MODULES[@]}"; do
mod_path="${REPOS_DIR}/${mod}"
if [ -f "${mod_path}/setup.json" ] || [ -f "${mod_path}/setup.py" ] || [ -f "${mod_path}/setup.cfg" ]; then
echo " [install] ${mod}..."
"${PIP}" install -e "${mod_path}" -q 2>&1 | tail -1
else
echo " [skip] ${mod} — 无 setup"
fi
done
green " pccs 业务模块已安装"
# ============================================================
# Phase 6: 构建 Bricks 前端
# ============================================================
blue "[Phase 6] 构建 Bricks 前端..."
if [ -f "${BRICKS_DIR}/build.sh" ]; then
echo " 构建 bricks.js..."
(cd "${BRICKS_DIR}" && npm install --silent 2>/dev/null && bash build.sh 2>&1 | tail -3) || {
red " 警告: bricks 构建失败, 前端可能不可用"
}
fi
green " Bricks 前端已构建"
# ============================================================
# Phase 7: 创建配置文件
# ============================================================
blue "[Phase 7] 创建配置..."
cat > "${CONF_DIR}/config.json" <<CONFEOF
{
"workdir": "${SERVER_PATH}",
"host": "0.0.0.0",
"port": ${APP_PORT},
"static": "${REPOS_DIR}/pccs/wwwroot",
"databases": {
"pccs": {
"db": "mysql",
"host": "127.0.0.1",
"port": 3306,
"user": "test",
"password": "test123",
"database": "pccs",
"charset": "utf8mb4",
"minsize": 2,
"maxsize": 10
} }
},
"modules": [
{"name": "appbase", "path": "${REPOS_DIR}/appbase"},
{"name": "rbac", "path": "${REPOS_DIR}/rbac"},
{"name": "pcpool", "path": "${REPOS_DIR}/pcpool"},
{"name": "pcc", "path": "${REPOS_DIR}/pcc"},
{"name": "pccs", "path": "${REPOS_DIR}/pccs"},
{"name": "storage_mgr", "path": "${REPOS_DIR}/storage_mgr"},
{"name": "image_mgr", "path": "${REPOS_DIR}/image_mgr"}
],
"module_dbname": "pccs",
"hot_reload": true
}
CONFEOF
green " 配置已生成: ${CONF_DIR}/config.json"
# ============================================================
# Phase 8: 初始化数据库表
# ============================================================
blue "[Phase 8] 初始化数据库表..."
# 收集所有模块的 models/ 目录
MODEL_DIRS=()
for mod in "${PCCS_MODULES[@]}" "appbase" "rbac"; do
mdir="${REPOS_DIR}/${mod}/models"
if [ -d "$mdir" ]; then
MODEL_DIRS+=("$mdir")
fi fi
done done
green " OK"
# 用 Python 脚本创建表
"${PYTHON}" - "$SERVER_PATH" "${MODEL_DIRS[@]}" <<'PYEOF'
import sys, os, json, asyncio
sys.path.insert(0, sys.argv[1] + '/repos/sqlor')
sys.path.insert(0, sys.argv[1] + '/repos/apppublic')
from appPublic.jsonConfig import getConfig
from sqlor.dbpools import DBPools
from sqlor.ddl_template_mysql import DDLTemplateMySQL
async def create_tables():
config = getConfig(sys.argv[1])
dbname = 'pccs'
db = DBPools(config.databases)
ddl = DDLTemplateMySQL()
async with db.sqlorContext(dbname) as sor:
# 检查已有表
existing = await sor.sqlExe("SHOW TABLES", {})
existing_tables = set(r[0] for r in existing)
model_dirs = sys.argv[2:]
created = 0
for mdir in model_dirs:
if not os.path.isdir(mdir):
continue
for f in sorted(os.listdir(mdir)):
if not f.endswith('.json'):
continue
fpath = os.path.join(mdir, f)
try:
model = json.load(open(fpath))
except Exception:
continue
tblname = model.get('summary', [{}])[0].get('name', '')
if not tblname:
continue
if tblname in existing_tables:
print(f' [exists] {tblname}')
continue
fields = model.get('fields', [])
sql = ddl.create_table(tblname, fields)
print(f' [create] {tblname}')
async with db.sqlorContext(dbname) as sor:
try:
await sor.sqlExe(sql, {})
created += 1
except Exception as e:
print(f' ERROR: {e}')
print(f' 共创建 {created} 张表')
asyncio.run(create_tables())
PYEOF
green " 数据库表已初始化"
# ============================================================ # ============================================================
# Phase 9: 加载种子数据 (appcode / RBAC) # Phase 4: pip install 所有模块
# ============================================================ # ============================================================
blue "[Phase 9] 加载种子数据..." 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"
"${PYTHON}" - "$SERVER_PATH" <<'PYEOF' # ============================================================
# 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 import sys, os, json
sys.path.insert(0, sys.argv[1] + '/repos/apppublic')
sys.path.insert(0, sys.argv[1] + '/repos/sqlor') CDIR = sys.argv[1]
sys.path.insert(0, sys.argv[1] + '/repos/ahserver') 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 appPublic.jsonConfig import getConfig
from sqlor.dbpools import DBPools from sqlor.dbpools import DBPools
from appPublic.uniqueID import getID from appPublic.uniqueID import getID
import datetime import datetime, asyncio
async def seed_data(): APPCODES = [
config = getConfig(sys.argv[1]) ('cluster_type', 'k8s', 'Kubernetes集群'),
dbname = 'pccs' ('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) db = DBPools(config.databases)
now = datetime.datetime.now().isoformat() now = datetime.datetime.now().isoformat()
async with db.sqlorContext('pccs') as sor:
async with db.sqlorContext(dbname) as sor:
# 检查 appcodes_kv 是否有 cluster_type 数据
rows = await sor.R('appcodes_kv', {'parentid': 'cluster_type'}) rows = await sor.R('appcodes_kv', {'parentid': 'cluster_type'})
if not rows: if rows:
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', '失败'),
]
for parentid, k, v in codes:
await sor.C('appcodes_kv', {
'id': getID(),
'parentid': parentid,
'k': k,
'v': v,
'created_at': now,
})
print(f' 已写入 {len(codes)} 条 appcode')
else:
print(' appcode 已存在, 跳过') 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_data()) asyncio.run(seed())
PYEOF PYEOF
green " 种子数据已加载" green " OK"
# ============================================================ # ============================================================
# Phase 10: 创建启动脚本 # Phase 10: 创建 systemd 服务 + 启动脚本
# ============================================================ # ============================================================
blue "[Phase 10] 创建启动脚本..." blue "[10/10] 服务配置..."
cat > "${RUN_DIR}/start.sh" <<STARTEOF
# start.sh
cat > "${CDIR}/start.sh" <<STARTEOF
#!/usr/bin/env bash #!/usr/bin/env bash
# pccs 应用启动脚本
set -e set -e
cd "${CDIR}"
SERVER_PATH="${SERVER_PATH}" source "${CDIR}/.env" 2>/dev/null || true
VENV_DIR="${VENV_DIR}" export PYTHONPATH="${PKGDIR}/ahserver:${PKGDIR}/apppublic:${PKGDIR}/sqlor:${PKGDIR}/appbase:${PKGDIR}/rbac:${PKGDIR}/bricks_for_python:\$PYTHONPATH"
CONF_DIR="${CONF_DIR}" exec "${VENV}/bin/python" "${CDIR}/app/${APPNAME}.py" 2>&1 | tee -a "${CDIR}/logs/${APPNAME}.log"
LOG_DIR="${LOG_DIR}"
cd "\${SERVER_PATH}"
export PYTHONPATH="\${REPOS_DIR}/ahserver:\${REPOS_DIR}/apppublic:\${REPOS_DIR}/sqlor:\${REPOS_DIR}/appbase:\${REPOS_DIR}/rbac:\${PYTHONPATH}"
echo "Starting pccs on port ${APP_PORT}..."
exec "\${VENV_DIR}/bin/python" -c "
import sys, os
sys.path.insert(0, '${REPOS_DIR}/ahserver')
sys.path.insert(0, '${REPOS_DIR}/apppublic')
sys.path.insert(0, '${REPOS_DIR}/sqlor')
sys.path.insert(0, '${REPOS_DIR}/appbase')
sys.path.insert(0, '${REPOS_DIR}/rbac')
from ahserver.configuredServer import ConfiguredServer
class PCCSAuthAPI:
'''pccs 认证 API — 开发阶段允许所有访问'''
def needAuth(self, path):
# 开发阶段: 仅登录页面需要认证
if '/api/' in path:
return False
return False
async def getPermissionNeed(self, path):
return 'any'
async def checkUserPassword(self, user_id, password):
return True
async def getUserPermissions(self, user):
return ['any', 'logined', 'reseller.operator']
if __name__ == '__main__':
server = ConfiguredServer(PCCSAuthAPI, workdir='${SERVER_PATH}')
server.run()
" 2>&1 | tee -a "\${LOG_DIR}/pccs.log"
STARTEOF STARTEOF
chmod +x "${RUN_DIR}/start.sh" chmod +x "${CDIR}/start.sh"
green " 启动脚本: ${RUN_DIR}/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"
# ============================================================ # ============================================================
# Phase 11: 停止旧进程, 启动应用 # 启动应用
# ============================================================ # ============================================================
blue "[Phase 11] 启动应用..." blue "启动应用..."
# 停止旧进程 "${CDIR}/stop.sh" 2>/dev/null || true
pkill -f "ahserver.*${APP_PORT}" 2>/dev/null || true
sleep 1 sleep 1
nohup bash "${CDIR}/start.sh" > "${CDIR}/logs/nohup.log" 2>&1 &
# 后台启动
nohup bash "${RUN_DIR}/start.sh" > "${LOG_DIR}/nohup.log" 2>&1 &
sleep 3 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
if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${APP_PORT}/" 2>/dev/null | grep -q '200\|302\|404'; then green " pccs 已启动: http://127.0.0.1:${APP_PORT}"
green " pccs 应用已启动: http://127.0.0.1:${APP_PORT}"
else else
red " 警告: 应用可能未正常启动, 检查日志: tail -f ${LOG_DIR}/pccs.log" red " WARN: 无响应, 检查日志: tail -f ${CDIR}/logs/pccs.log"
fi fi
# ============================================================
# 完成
# ============================================================
echo "" echo ""
green "==========================================" green "=========================================="
green " pccs 部署完成!" green " 部署完成!"
green " 访问地址: http://$(hostname -I | awk '{print $1}'):${APP_PORT}" green " 访问: http://$(hostname -I | awk '{print $1}'):${APP_PORT}"
green " 日志文件: ${LOG_DIR}/pccs.log" green " 启动: sudo systemctl start pccs"
green " 启动命令: bash ${RUN_DIR}/start.sh" green " 停止: sudo systemctl stop pccs"
green " 停止命令: pkill -f 'ahserver.*${APP_PORT}'" green " 日志: tail -f ${CDIR}/logs/pccs.log"
green "==========================================" green "=========================================="

45
conf/config.json Normal file
View File

@ -0,0 +1,45 @@
{
"password_key": "pccs_secret_key_change_in_production",
"logger": {
"name": "pccs",
"level": "debug",
"file": "$[workdir]$/logs/pccs.log"
},
"filesroot": "$[workdir]$/files",
"databases": {
"pccs": {
"driver": "mysql",
"kwargs": {
"host": "127.0.0.1",
"port": 3306,
"user": "test",
"password": "test123",
"db": "pccs",
"charset": "utf8mb4",
"minsize": 2,
"maxsize": 10,
"autocommit": true
}
}
},
"website": {
"host": "0.0.0.0",
"port": 9180,
"client_max_size": 104857600,
"processors": [
[".tmpl", "tmpl"],
[".ui", "bui"],
[".dspy", "dspy"],
[".wss", "ws"]
],
"indexes": ["index.ui", "index.html"],
"session": {
"session_max_time": 86400,
"session_issue_time": 7200
},
"statics": [
["/bricks/", "$[workdir]$/wwwroot/bricks/"],
["/imgs/", "$[workdir]$/wwwroot/imgs/"]
]
}
}