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/bisheng
BISHENG 开源仓库在后端维护了大量手动执行的运维脚本(数据回填、权限修复、一次性迁移),这些脚本与线上服务共享同一套配置与基础设施。本文基于 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.py):
import 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.py(b)自动探测形式(新脚本推荐)——无论是否激活了 venv 都能工作:
if [ -x ".venv/bin/python" ]; then PYTHON_BIN=".venv/bin/python" elif command -v python >/dev/null 2>&1; then PYTHON_BIN="$(command -v python)" elif command -v python3 >/dev/null 2>&1; 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"。
- migrate_workstation_models_to_workbench.sh 以
- 退出码语义化:
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 config=config.yaml # 必须与 API/Celery 进程的值一致 export PYTHONPATH="./" python scripts/<your_script>.py同时必须导入共享的 settings 单例,绝不要自己重新解析 YAML:
from 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()按 Redis(100s 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(config=settings)(bisheng/main.py)构建运行时上下文。裸脚本不会自动获得该上下文——只有惰性注册的数据库上下文存在。因此:
- 纯 DB 读写无需任何初始化即可工作;
- 任何需要其他引擎的操作——OpenFGA/ReBAC(
PermissionService.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(config=settings) # 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(config=settings),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_id=AdminRole),再通过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的补充。编写脚本时还应注意上层规范中与脚本强相关的约束:
- 日志:项目统一使用 loguru(
from 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 revision(AGENTS.md);revision 只做 DDL。这也是本目录存在的根本原因。
十二、速查清单:新脚本上线前逐条核对
| 检查项 | 要求 |
|---|---|
| 工作目录 | 必须从src/backend/运行 |
| 导入路径 | .sh设export PYTHONPATH="./";.py自带 sys.path 引导 |
| 解释器 | 推荐.venv/bin/python优先的自动探测 |
| 命名 | .py/.sh均用snake_case |
| 参数 | Python 用argparse(含--help);shell 用"$@"原样转发 |
| 安全 | 破坏性操作默认 dry-run,显式--apply才写;退出码语义化(0 成功、非零分类失败) |
| 配置 | 先export config=...(与服务一致);只导入共享settings单例,不自己解析 YAML |
| 基础设施 | 只碰 DB →get_async_db_session();碰 FGA/Redis/Milvus/ES →initialize_app_context+close_app_context |
| 租户 | 跨租户读写必须with bypass_tenant_filter():,否则查询被注入tenant_id = NULL返回零行 |
| 文档 | 模块级 docstring(what/why/how + dry-run 区别)+scripts/README.md条目(含示例命令) |
| 日志 | loguru{}风格占位符,禁止 printf 风格与exc_info=,禁止静默吞异常 |
| 运行示例 | cd src/backend/ && bash scripts/<name>.sh,或cd src/backend/ && export config=config.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),仅供参考