feat: sync shared-home MCP scripts on control-plane startup
租户 config 的 mcp_servers.* 指向共享 home 路径,但宿主共享 home 里原先没有 MCP 脚本,宿主 app-server 启动 MCP 即文件 不存在、握手断掉,面板 mcp-servers-list 三服务器 toolsError 全是 handshake failed;14:43 的手工拷入能修一次,但下次 MCP 脚本改动后面板又会跑旧版。 本批只在服务端 main() 启动时同步一次:清单唯一定义在 cloud/shared_home_mcp.py(7 文件:playwright_mcp/browser/ remote、websearch_mcp/websearch、imagegen_mcp/imagegen,含 同目录兄弟模块),解析优先用 host-config-host.json 的 codex_home,兜底用 tenants_root 父目录/codex-home;缺失 拷入、不同刷新(原子替换)、相同不动;源缺失/目标不可写 只记 warning,绝不阻塞启动。create_server() 不做同步, 避免单测写向生产共享路径。 被否决的落点:worker _tenant_dirs(只在下个任务派发时跑, 面板要等下一个任务才更新,且不修共享路径本身);deploy host_deploy.sh(会在 shell 里复制第二份清单,且重启不 部署时不刷新);面板请求时同步(把读请求变成写请求, 失败会把面板读数变成 502 风险)。
This commit is contained in:
1 parent
671b4bedef
commit
b977a9dee8
3 files changed
+401
No files matched your search
@@ -107,6 +107,7 @@ from cloud.wait_ops import poll_task_statuses as _wait_poll # noqa: F401
|
||||
# /wait 本身走首变轮询(_wait_poll_first),见 _task_wait。
|
||||
from cloud.wait_ops import report_handoff as _handoff_report
|
||||
from cloud.wait_ops import worktree_status as _wait_worktree_status
|
||||
from cloud.shared_home_mcp import sync_shared_home_mcp as _sync_shared_home_mcp
|
||||
from cloud.automations import ensure_automations_tables as _auto_ensure
|
||||
from cloud.automations import list_automations as _auto_list
|
||||
from cloud.todos import create_todo as _todo_create
|
||||
@@ -425,6 +426,30 @@ def _local_worker_codex_home():
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_shared_home_mcp(server=None):
|
||||
"""共享 home 解析:供服务端启动时的 MCP 脚本同步用。
|
||||
|
||||
优先级(生产路径绝不硬编码在这里,全部来自已有解析):
|
||||
1. _local_worker_codex_home():读 host-config-host.json 的
|
||||
codex_home,生产即共享 home;
|
||||
2. 兜底:tenants_root 的父目录 + codex-home(tenants_root
|
||||
本身来自 server 属性 / CODEX_TENANTS_ROOT / 默认值)。
|
||||
任一解析不出时返回 None,调用方跳过同步、不因此报错。
|
||||
注意:本函数只解析路径,不做文件同步;文件清单与同步
|
||||
语义只在 cloud/shared_home_mcp.py 里。
|
||||
"""
|
||||
local = _local_worker_codex_home()
|
||||
if local:
|
||||
return local
|
||||
try:
|
||||
root = _tenants_root_for(server)
|
||||
except Exception:
|
||||
return None
|
||||
if not root:
|
||||
return None
|
||||
return os.path.join(os.path.dirname(root), "codex-home")
|
||||
|
||||
|
||||
def _tenant_work_dir(server, project_id):
|
||||
"""项目对应的租户工作目录:tenants_root/<安全项目名>/work,
|
||||
skills/list 的 cwds 过滤用它(不传 cwd 时 app-server 会
|
||||
@@ -10309,6 +10334,30 @@ def main(argv=None):
|
||||
args = parser.parse_args(argv)
|
||||
server = create_server(args.db, args.host, args.port,
|
||||
backend=args.db_backend)
|
||||
# 共享 home MCP 脚本同步:只在 main() 启动时做,不在
|
||||
# create_server() 里做——create_server 是单测入口,单测若
|
||||
# 在此同步会按 host-config-host.json 写向生产共享路径。
|
||||
# systemd 两个控制面都跑 python3 -m cloud.server,走的
|
||||
# 都是 main(),每次部署重启都会同步一次;同步失败只记
|
||||
# warning,绝不阻塞启动(sync 本身也不抛异常,外层再
|
||||
# 兜一层防解析等意外)。Docker 任务不受影响:容器内
|
||||
# 该共享路径是租户 home 的挂载,宿主补齐不进容器。
|
||||
try:
|
||||
_shared_home = _resolve_shared_home_mcp(server)
|
||||
if _shared_home:
|
||||
_sync_result = _sync_shared_home_mcp(_shared_home)
|
||||
if _sync_result.get("copied") or _sync_result.get("refreshed"):
|
||||
_server_log.info(
|
||||
"shared home MCP synced: copied=%s refreshed=%s",
|
||||
_sync_result.get("copied"),
|
||||
_sync_result.get("refreshed"))
|
||||
if _sync_result.get("error"):
|
||||
_server_log.warning(
|
||||
"shared home MCP sync failed (non-fatal): %s",
|
||||
_sync_result.get("error"))
|
||||
except Exception as exc:
|
||||
_server_log.warning(
|
||||
"shared home MCP sync failed (non-fatal): %s", exc)
|
||||
start_rate_limit_sweeper(server.rate_limiter, args.db)
|
||||
print(
|
||||
"cloud-control-plane listening on %s:%d db=%s"
|
||||
|
||||
Reference in new issue
Block a user