From 023d4a67faaaf8b7feab29aba3bb04cc4ee87983 Mon Sep 17 00:00:00 2001 From: ymq Date: Fri, 28 Aug 2026 21:15:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(remote=5Fenv):=20=E6=89=B9=E9=87=8Fpip?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=97=B6=E9=99=8D=E7=BA=A7=E9=80=90=E5=8C=85?= =?UTF-8?q?=E5=AE=89=E8=A3=85(=E9=83=A8=E5=88=86=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E4=BC=98=E4=BA=8E=E5=85=A8=E8=B4=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_service/remote_env_init.py | 47 +++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/pipeline_service/remote_env_init.py b/pipeline_service/remote_env_init.py index 29539d3..50c31bc 100644 --- a/pipeline_service/remote_env_init.py +++ b/pipeline_service/remote_env_init.py @@ -312,14 +312,55 @@ async def install_remote_deps(env: dict, specs: list, pip_index: str, installed = list(specs) failed = [] else: - installed = [s for s in specs if all( - not s.lower().startswith(re.split(r"[\[<>=!~\s]", f, 1)[0].lower()) - for f in failed)] + # 批量失败 → 逐包降级:一次 SSH 会话内逐包安装(pip 幂等,已装的秒过), + # 部分成功优于全败。 + return await _install_per_package(env, specs, pip_index, rel, timeout) return {"ok": rc == 0, "installed": installed, "failed": failed, "venv": "~/" + rel + "/.venv-skills", "tail": out[-800:]} +async def _install_per_package(env: dict, specs: list, pip_index: str, + rel: str, timeout: int) -> dict: + """逐包安装(批量失败后的降级路径)。返回格式与 install_remote_deps 一致。""" + payload = base64.b64encode(json.dumps(specs).encode()).decode() + index_arg = ('-i ' + shlex.quote(pip_index) + ' ') if pip_index else '' + script = ( + 'RD="$HOME/' + rel + '"; VENV="$RD/.venv-skills"; ' + '[ -x "$VENV/bin/pip" ] || { echo "VENV_MISSING"; exit 4; }; ' + 'set -f; ' + 'echo "' + payload + '" | base64 -d | python3 -c ' + '"import json,sys;print(chr(10).join(json.load(sys.stdin)))" | ' + 'while IFS= read -r pkg; do ' + ' [ -n "$pkg" ] || continue; ' + ' if "$VENV/bin/pip" install --no-input --disable-pip-version-check ' + + index_arg + '"$pkg" >/dev/null 2>&1; then echo "OK:$pkg"; ' + ' else echo "FAILED:$pkg"; fi; ' + 'done' + ) + ssh_cmd = ["ssh"] + _ssh_common_args(env) + [_ssh_target(env), script] + try: + proc = await asyncio.create_subprocess_exec( + *ssh_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + stdout, _stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout + 30) + except asyncio.TimeoutError: + return {"ok": False, "error": f"逐包安装超时({timeout}s),可重跑"} + except Exception as e: + return {"ok": False, "error": "SSH 执行失败: " + str(e)[:300]} + + out = stdout.decode("utf-8", "replace") + installed, failed = [], [] + for line in out.splitlines(): + if line.startswith("OK:"): + installed.append(line[3:]) + elif line.startswith("FAILED:"): + failed.append(line[7:]) + return {"ok": len(failed) == 0 and len(installed) > 0, + "installed": installed, "failed": failed, + "venv": "~/" + rel + "/.venv-skills", + "mode": "per-package", "tail": out[-500:]} + + # ── 总编排 ──────────────────────────────────────────────────── async def init_remote_env(env: dict, org_id: str = "", sor=None,