From 43695b238d4d02bfee915c8d130cf573c95324a4 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 11 Aug 2026 14:41:55 +0800 Subject: [PATCH] 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) --- .env | 19 ++ .gitignore | 8 + app/pccs.py | 40 +++ build.sh | 649 +++++++++++++++++++++-------------------------- conf/config.json | 45 ++++ 5 files changed, 398 insertions(+), 363 deletions(-) create mode 100644 .env create mode 100644 .gitignore create mode 100644 app/pccs.py create mode 100644 conf/config.json diff --git a/.env b/.env new file mode 100644 index 0000000..508fc2d --- /dev/null +++ b/.env @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba9186f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.pyc +.venv/ +venv/ +logs/ +files/ +*.log +.env.local diff --git a/app/pccs.py b/app/pccs.py new file mode 100644 index 0000000..00b9a4d --- /dev/null +++ b/app/pccs.py @@ -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) diff --git a/build.sh b/build.sh index c7ed9ab..918df35 100644 --- a/build.sh +++ b/build.sh @@ -1,430 +1,353 @@ #!/usr/bin/env bash # ============================================================ -# pccs build.sh — 一键部署脚本 -# 在部署节点 (pccs@pccs.opencomputing.cn) 上直接运行: +# pccs build.sh — 一键部署脚本 (符合 web-application-spec) +# +# 在部署节点上运行: # cd /d/pccs/repos/pccs && bash build.sh # # 前提: -# 1. /d 已作为 NFS 共享盘挂载 -# 2. MariaDB 已安装, 数据库 pccs 已创建, 用户 test/test123 已授权 -# 3. 部署节点已安装 git, python3.10+, pip +# - MariaDB 已安装, pccs 库已创建, test/test123 已授权 +# - /d 已作为 NFS 共享盘导出 +# - git, python3.10+, pip, npm 已安装 # ============================================================ set -e -SERVER_PATH="${SERVER_PATH:-/d/pccs}" -REPOS_DIR="${SERVER_PATH}/repos" -VENV_DIR="${SERVER_PATH}/venv" -CONF_DIR="${SERVER_PATH}/conf" -LOG_DIR="${SERVER_PATH}/logs" -RUN_DIR="${SERVER_PATH}/run" -APP_PORT="${APP_PORT:-9180}" +# --------------- 配置 --------------- +WORKDIR="$(cd "$(dirname "$0")/../.." && pwd)" +CDIR="${WORKDIR}" # 应用根目录 (/d/pccs) +PKGDIR="${CDIR}/repos" # 模块 clone 目录 +VENV="${CDIR}/venv" +APPNAME="pccs" +APP_PORT=9180 -# --------------- 模块列表 --------------- -# 共享底层库 (pip install) -SHARED_LIBS=( - apppublic - sqlor - ahserver - appbase - rbac -) -# pccs 业务模块 (pip install) -PCCS_MODULES=( - pcpool - pcc - storage_mgr - image_mgr -) -# 前端 bricks (需要构建) -BRICKS_DIR="${REPOS_DIR}/bricks" +# 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"; } -echo "==========================================" -blue "PCCS 算力中心集群系统 — 一键部署" -echo "==========================================" -echo "部署路径: ${SERVER_PATH}" -echo "监听端口: ${APP_PORT}" -echo "" +blue "==========================================" +blue " pccs — 算力中心集群系统 部署" +blue "==========================================" # ============================================================ -# Phase 0: 创建目录结构 +# Phase 0: 创建目录 # ============================================================ -blue "[Phase 0] 创建目录结构..." -mkdir -p "${REPOS_DIR}" "${VENV_DIR}" "${CONF_DIR}" "${LOG_DIR}" "${RUN_DIR}" -green " 目录结构已创建" +mkdir -p "${CDIR}/app" "${CDIR}/conf" "${CDIR}/files" "${CDIR}/logs" \ + "${CDIR}/wwwroot/imgs" "${PKGDIR}" # ============================================================ # Phase 1: 安装系统依赖 # ============================================================ -blue "[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 " 系统依赖已安装" +sudo apt-get install -y -qq python3-venv python3-pip python3-dev \ + build-essential nfs-common mysql-client +green " OK" # ============================================================ -# Phase 2: Clone/Pull 所有仓库 (HTTPS, 只读) +# Phase 2: Python 虚拟环境 # ============================================================ -blue "[Phase 2] Clone/Pull 代码仓库..." - -GIT_BASE="https://git.opencomputing.cn/yumoqing" -ALL_REPOS=("${SHARED_LIBS[@]}" "${PCCS_MODULES[@]}" "bricks" "pccs") - -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 +blue "[2/10] Python 虚拟环境..." +python3 -m venv "${VENV}" --clear +PIP="${VENV}/bin/pip" +PYTHON="${VENV}/bin/python" "${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] 安装共享底层库..." -for lib in "${SHARED_LIBS[@]}"; do - lib_path="${REPOS_DIR}/${lib}" - if [ -f "${lib_path}/setup.py" ] || [ -f "${lib_path}/setup.cfg" ] || [ -f "${lib_path}/pyproject.toml" ]; then - echo " [install] ${lib}..." - "${PIP}" install -e "${lib_path}" -q 2>&1 | tail -1 +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 " [skip] ${lib} — 无 setup" - fi -done -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" </dev/null || { + red " WARN: ${repo} clone failed" } - }, - "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 done - -# 用 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 " 数据库表已初始化" +green " OK" # ============================================================ -# 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 -sys.path.insert(0, sys.argv[1] + '/repos/apppublic') -sys.path.insert(0, sys.argv[1] + '/repos/sqlor') -sys.path.insert(0, sys.argv[1] + '/repos/ahserver') + +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 +import datetime, asyncio -async def seed_data(): - config = getConfig(sys.argv[1]) - dbname = 'pccs' +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(dbname) as sor: - # 检查 appcodes_kv 是否有 cluster_type 数据 + async with db.sqlorContext('pccs') as sor: rows = await sor.R('appcodes_kv', {'parentid': 'cluster_type'}) - if not 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: + 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_data()) +asyncio.run(seed()) PYEOF -green " 种子数据已加载" +green " OK" # ============================================================ -# Phase 10: 创建启动脚本 +# Phase 10: 创建 systemd 服务 + 启动脚本 # ============================================================ -blue "[Phase 10] 创建启动脚本..." -cat > "${RUN_DIR}/start.sh" < "${CDIR}/start.sh" <&1 | tee -a "\${LOG_DIR}/pccs.log" +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 "${RUN_DIR}/start.sh" -green " 启动脚本: ${RUN_DIR}/start.sh" +chmod +x "${CDIR}/start.sh" + +# stop.sh +cat > "${CDIR}/stop.sh" </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 </dev/null || true +green " OK" # ============================================================ -# Phase 11: 停止旧进程, 启动应用 +# 启动应用 # ============================================================ -blue "[Phase 11] 启动应用..." -# 停止旧进程 -pkill -f "ahserver.*${APP_PORT}" 2>/dev/null || true +blue "启动应用..." +"${CDIR}/stop.sh" 2>/dev/null || true sleep 1 - -# 后台启动 -nohup bash "${RUN_DIR}/start.sh" > "${LOG_DIR}/nohup.log" 2>&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\|302\|404'; then - green " pccs 应用已启动: http://127.0.0.1:${APP_PORT}" +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 " 警告: 应用可能未正常启动, 检查日志: tail -f ${LOG_DIR}/pccs.log" + red " WARN: 无响应, 检查日志: tail -f ${CDIR}/logs/pccs.log" fi -# ============================================================ -# 完成 -# ============================================================ echo "" green "==========================================" -green " pccs 部署完成!" -green " 访问地址: http://$(hostname -I | awk '{print $1}'):${APP_PORT}" -green " 日志文件: ${LOG_DIR}/pccs.log" -green " 启动命令: bash ${RUN_DIR}/start.sh" -green " 停止命令: pkill -f 'ahserver.*${APP_PORT}'" +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 "==========================================" diff --git a/conf/config.json b/conf/config.json new file mode 100644 index 0000000..68da367 --- /dev/null +++ b/conf/config.json @@ -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/"] + ] + } +}