)
BISHENG 后端运维脚本开发规范手写迁移脚本的正确姿势scripts/CLAUDE.md 全解【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bishengBISHENG 开源仓库在后端维护了大量手动执行的运维脚本数据回填、权限修复、一次性迁移这些脚本与线上服务共享同一套配置与基础设施。本文基于 src/backend/scripts/CLAUDE.md 编写系统梳理该目录下的强制约定——从工作目录、PYTHONPATH引导、解释器探测到共享settings单例、initialize_app_context基础设施初始化与bypass_tenant_filter跨租户处理并辅以仓库内真实脚本set_admin、backfill_channel_member_rebac_grants等作为源码级佐证。读完你将能够按生产级标准编写、运行并文档化 BISHENG 后端的一次性运维脚本。一、scripts 目录的定位与总览src/backend/scripts/存放的是手动维护、迁移与一次性运维脚本与src/backend/AGENTS.md中的通用编码约定互补。它不是一个被框架自动发现的模块而是运营人员在变更、升级、修复数据时按需执行的工具箱。当前目录下已沉淀了超过 30 个脚本涵盖权限/ReBAC 相关set_admin.py提升超级管理员、backfill_channel_member_rebac_grants.py频道成员 ReBAC 授权回填、clean_department_space_user_group_grants.py清理部门空间用户组历史授权、migrate_channel_permissions_for_relation_models.py、backfill_relation_model_move_permissions.py等配置迁移migrate_workstation_models_to_workbench.py工作台模型列表迁移数据维护backfill_department_parent_tuples.py、backfill_user_tenant_associations.py、fix_tenant_id_root_leak.py、scan_orphan_tenant_mounts.py等排查诊断diagnose_milvus_collections.py、inspect_milvus_schema.py、probe_ocr_image_pipeline.py等。这些脚本普遍遵循.py实现 可选.sh包装器README.md索引的组织方式具体约定如下文所述。二、工作目录约定一切从src/backend/出发所有脚本必须从src/backend/后端根目录执行。这是整个规范体系的根基——shell 包装器中的相对路径scripts/foo.py、bisheng/xxx/...以及下文PYTHONPATH./约定都依赖这一前提从其他任何目录运行都会导致 import 失败。cd src/backend/ bash scripts/your_script.sh [args...]三、Python 导入路径PYTHONPATH./与内置 sys.path 引导后端源码以普通目录而非已安装的包的形式存在于仓库中。为了让from bisheng.xxx import ...能解析到本地源码树shell 包装器必须在调用 Python 前设置PYTHONPATH./这样无需pip install -e .即可直接运行#!/bin/bash set -e export PYTHONPATH./ python scripts/your_script.py $对于打算直接用python scripts/foo.py运行无 shell 包装器的脚本Python 文件自身应引导sys.path使其从任意 cwd 都能工作。仓库中所有脚本文件顶部都有这段标准引导代码见 set_admin.pyimport os, sys _BACKEND_ROOT os.path.abspath(os.path.join(os.path.dirname(__file__), ..)) if _BACKEND_ROOT not in sys.path: sys.path.insert(0, _BACKEND_ROOT) from bisheng.core.database import get_async_db_session # noqa: E402其中__file__指向scripts/目录向上取父目录即src/backend/。这样脚本既能在.sh包装器下靠PYTHONPATH工作也能在直接python调用时自举路径。四、Python 解释器解析短形式与自动探测形式规范允许两种模式a短形式——仅适用于已知会先激活虚拟环境的运营人员export PYTHONPATH./ python scripts/foo.pyb自动探测形式新脚本推荐——无论是否激活了 venv 都能工作if [ -x .venv/bin/python ]; then PYTHON_BIN.venv/bin/python elif command -v python /dev/null 21; then PYTHON_BIN$(command -v python) elif command -v python3 /dev/null 21; then PYTHON_BIN$(command -v python3) else echo Python interpreter not found. 2 exit 1 fi ${PYTHON_BIN} scripts/foo.py $该目录下的 set_admin.sh 与migrate_workstation_models_to_workbench.sh正是此模式的真实范例——后者在此基础上还实现了 dry-run/apply 双模式参数转发见第六节。五、文件布局与命名文件用途scripts/name.py实现本体必须可用python scripts/name.py直接运行scripts/name.sh可选 shell 包装器负责PYTHONPATH、解释器探测与参数转发scripts/sql/被脚本引用的临时 SQL 脚本scripts/README.md面向使用者的索引新增脚本时必须补充条目命名统一使用snake_case如set_admin.sh而非set-admin.sh。注意实际目录中还存在scripts/sql/子目录存放迁移用 SQL以及AGENTS.md、CLAUDE.md等协作文档均在规范框架之内。六、参数处理argparse $转发 dry-run 默认Python 脚本使用argparse同时承担参数校验与--help输出shell 包装器原样转发所有参数${PYTHON_BIN} scripts/foo.py $两条铁律任何破坏性操作dry-run 是安全默认必须显式传入--apply或等价开关才真正写库。这一点在仓库脚本中贯彻得极其彻底migrate_workstation_models_to_workbench.sh 以run_mode${1:-check}实现默认 check、传入 apply 才执行backfill_channel_member_rebac_grants.py 定义--channel-id限定范围、--apply落库dry-run 时打印would backfill并在汇总 JSON 中输出would_backfill计数clean_department_space_user_group_grants.py 的模块级 docstring 明确警告不可逆删除即收回该用户组成员的访问务必先看 dry-run 输出再--apply。退出码语义化0 成功非零 失败并对不同失败类别使用不同退出码便于包装器分支处理。例如set_admin.py中用户不存在/被禁用返回1而 OpenFGA tuple 写入失败返回2见 set_admin.py。七、运行时环境初始化与线上服务完全一致的环境脚本必须运行在与线上服务完全相同的环境下——相同的配置文件、相同的settings、相同初始化的基础设施客户端。任何不一致都是静默且危险的脚本可能连到了与业务不同的 DB/Milvus/Redis或某个基础设施客户端缺失导致深层调用失败。7.1 配置文件先export config再用共享settings单例服务与每个脚本都通过os.getenv(config, config.yaml)解析配置见 config_service.py。调用脚本前必须导出与运行中服务API/Celery 进程一致的config否则会加载错误的 YAML错误的 DB / Milvus / Redis 端点export configconfig.yaml # 必须与 API/Celery 进程的值一致 export PYTHONPATH./ python scripts/your_script.py同时必须导入共享的 settings 单例绝不要自己重新解析 YAMLfrom bisheng.common.services.config_service import settings # YAML → env → DB → Redis与服务完全一致config_service.py末尾的settings ConfigService.load_settings_from_yaml(config_file)config_service.py正是这一单例它继承自Settings加载 YAML 后还支持init_config()将默认配置写入 DB、get_all_config()按 Redis100s TTL→ DB 的链路热读可变更配置。这也是仓库中backfill_channel_member_rebac_grants.py等脚本统一from bisheng.common.services.config_service import settings的原因。7.2 应用上下文只有 DB 会话是免费的API 服务在 FastAPI lifespan 中通过initialize_app_context(configsettings)bisheng/main.py构建运行时上下文。裸脚本不会自动获得该上下文——只有惰性注册的数据库上下文存在。因此纯 DB 读写无需任何初始化即可工作任何需要其他引擎的操作——OpenFGA/ReBACPermissionService.authorize、Redis缓存、Milvus、Elasticsearch——都会失败典型报错FGAClient not available直到你自行初始化完整上下文。如果脚本涉及数据库以外的任何东西就要镜像 lifespan启动时初始化收尾时务必关闭from bisheng.common.services.config_service import settings from bisheng.core.context.manager import close_app_context, initialize_app_context async def _main() - int: await initialize_app_context(configsettings) # DB OpenFGA Redis Milvus ES与服务一致 try: return await run(...) finally: await close_app_context() # 释放连接池/客户端出错也要执行 asyncio.run(_main())正确的参考实现backfill_channel_member_rebac_grants.py 的_main()中先await initialize_app_context(configsettings)finally中close_app_context()并gc.collect()后asyncio.sleep(0)兜底clean_department_space_user_group_grants.py同样如此。经验法则只碰 DB →get_async_db_session()就够见第八节要碰 FGA / Redis / Milvus / ES → 必须先调用initialize_app_context。八、数据库与租户上下文bypass_tenant_filter是跨租户脚本的必需品脚本运行在FastAPI 请求生命周期之外因此自动租户过滤没有活动租户上下文。如果脚本要读写租户相关的表必须from bisheng.core.context.tenant import bypass_tenant_filter with bypass_tenant_filter(): # 这里的跨租户读写 ...没有bypass_tenant_filter()时每次查询都会被注入WHERE tenant_id NULL返回零行——这是多租户架构见 docs/architecture/12-multi-tenant.md下运维脚本最常见的假成功陷阱。实际用法示例set_admin.py 注释说明得很清楚User表没有tenant_id列但UserRole有自动注入的租户过滤会让查询返回空因此整个查询块放进bypass_tenant_filter()clean_department_space_user_group_grants.py 声明自己是跨租户维护脚本运行在bypass_tenant_filter()下扫描所有租户的部门空间backfill_channel_member_rebac_grants.py 更进一步指出 ContextVar 会跨 await 传播所以整个backfill任务都包在with bypass_tenant_filter():内嵌套的所有查询成员读取、FGA 名称补充、PermissionService.authorize中的主体展开、binding 配置写入都继承该旁路。数据库访问方式上异步工作用get_async_db_session()asyncio.run(main())同步用get_sync_db_session()不要在同一个事务里混用两种会话。九、文档要求让运维人员仅凭 README 即可发现脚本每个新脚本必须具备模块级 docstring说明它做什么、为什么存在、如何运行如适用需包含 dry-run/apply 的区别在scripts/README.md对应小节补充简短条目至少包含一个示例调用命令。运维人员应能仅凭README.md完成脚本发现。查看 scripts/README.md 可以看到这一要求已落实为模板每个脚本条目都包含Behavior行为说明 Usage可直接复制的命令 Options参数表。例如migrate_workstation_models_to_workbench.py条目给出了--apply双模式用法与仅写入默认租户tenant_id 1、幂等合并等行为细节。十、参考脚本布局set_admin 全解析规范给出的最小参考骨架对应仓库中的set_admin系列。scripts/set_admin.sh完整实现#!/bin/bash set -e [ -z $1 ] { echo Usage: $0 user_id 2; exit 1; } export PYTHONPATH./ # ... 解释器探测 ... ${PYTHON_BIN} scripts/set_admin.py $scripts/set_admin.py完整实现模块级 docstring含使用示例。 from __future__ import annotations import argparse, asyncio, os, sys _BACKEND_ROOT os.path.abspath(os.path.join(os.path.dirname(__file__), ..)) if _BACKEND_ROOT not in sys.path: sys.path.insert(0, _BACKEND_ROOT) # 这里导入 bisheng.* 模块 async def run(args) - int: ... def main() - int: parser argparse.ArgumentParser(description__doc__) # 添加参数 args parser.parse_args() return asyncio.run(run(args)) if __name__ __main__: sys.exit(main())真实实现比骨架更进一步示范了规范各条的落地方式docstring 即使用文档set_admin.py的模块 docstring 写明PYTHONPATH./ .venv/bin/python scripts/set_admin.py user_id与bash scripts/set_admin.sh user_id两种调用方式并逐条列出行为校验用户 → 写入userrole→ 同步 OpenFGA tuple幂等第二次对同一用户运行是安全的——userrole行已存在则跳过OpenFGA tuple 走 upsert 重写RBAC 与 ReBAC 双写先向遗留 RBAC 的userrole表插入(user_id, role_idAdminRole)再通过LegacyRBACSyncService.sync_user_role_change写入 OpenFGA tuple(user:{id}, super_admin, system:global)保证 ReBAC 校验也通过——这正是该平台权限迁移架构见 docs/architecture/10-permission-rbac.md 与 006-permission-migration中遗留 RBAC 与 ReBAC 并存的体现语义化退出码FGA 写入失败返回2并打警告提示排查 OpenFGA 连通性同时说明遗留 RBAC 回退仍可用但 ReBAC 校验在 tuple 落地前不会返回超管。十一、与后端工程规范的关系scripts/CLAUDE.md是src/backend/AGENTS.md的补充。编写脚本时还应注意上层规范中与脚本强相关的约束日志项目统一使用 logurufrom loguru import logger占位符是str.format风格{}/{!r}严禁 printf 的%s/%r/%d——loguru 不做百分号插值占位符会被原样打印而参数被静默丢弃规范明确指出这真的坑过 dry-run 脚本绝不要把logger.exception(...)降级成logger.error也不要向 loguru 传exc_info该参数不存在任何 kwarg 都会触发str.format()可能在内层抛出KeyError。错误处理绝不静默吞异常——except: pass被禁止关键路径用logger.exception记录后raise确属非关键的 best-effort缓存淘汰、遥测上报才允许窄范围捕获并附一行为何可忽略的注释。clean_department_space_user_group_grants.py等脚本对批量循环中的单条失败正是采用记日志并继续批次、最后按失败数决定退出码的模式。数据迁移边界任何对存量行的读写backfill、transform、dedup、purge、seed、SELECT→UPDATE/INSERT都属于scripts/或 DBA runbook 的带外运维流程绝不允许写进 Alembic revisionAGENTS.mdrevision 只做 DDL。这也是本目录存在的根本原因。十二、速查清单新脚本上线前逐条核对检查项要求工作目录必须从src/backend/运行导入路径.sh设export PYTHONPATH./.py自带 sys.path 引导解释器推荐.venv/bin/python优先的自动探测命名.py/.sh均用snake_case参数Python 用argparse含--helpshell 用$原样转发安全破坏性操作默认 dry-run显式--apply才写退出码语义化0 成功、非零分类失败配置先export config...与服务一致只导入共享settings单例不自己解析 YAML基础设施只碰 DB →get_async_db_session()碰 FGA/Redis/Milvus/ES →initialize_app_contextclose_app_context租户跨租户读写必须with bypass_tenant_filter():否则查询被注入tenant_id NULL返回零行文档模块级 docstringwhat/why/how dry-run 区别scripts/README.md条目含示例命令日志loguru{}风格占位符禁止 printf 风格与exc_info禁止静默吞异常运行示例cd src/backend/ bash scripts/name.sh或cd src/backend/ export configconfig.yaml PYTHONPATH./ .venv/bin/python scripts/name.py按此清单核对后新脚本即可与 BISHENG 的 API、Celery 服务共享同一套配置与基础设施安全地完成数据迁移与修复任务。【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考