# -*- coding: utf-8 -*- """从 207 拉取 Dify / Langfuse / Xinference / 监控相关配置快照。""" from __future__ import annotations import json from datetime import datetime from pathlib import Path import _bootstrap # noqa: F401 def pull_remote_snapshot(manifest: dict, dest_root: Path) -> dict: """ 将远程配置写入 dest_root(通常为 bundle/tree)。 返回 summary dict(成功/失败列表)。 """ cfg = manifest.get("remote_snapshot") or {} summary = { "enabled": bool(cfg.get("enabled")), "pulled_at": datetime.now().isoformat(timespec="seconds"), "ok": [], "missing": [], "errors": [], } if not cfg.get("enabled"): summary["errors"].append("remote_snapshot.disabled") return summary try: import paramiko except ImportError as e: summary["errors"].append(f"paramiko missing: {e}") return summary host = cfg.get("host", "192.168.110.207") user = cfg.get("user", "root") password = cfg.get("pass", "") client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: client.connect(host, username=user, password=password, timeout=20) except Exception as e: summary["errors"].append(f"ssh connect failed: {e}") return summary try: sftp = client.open_sftp() for item in cfg.get("paths") or []: src = item["src"] dst_rel = item["dst"].replace("\\", "/") dst = dest_root / dst_rel try: sftp.stat(src) except OSError: summary["missing"].append(src) continue try: dst.parent.mkdir(parents=True, exist_ok=True) sftp.get(src, str(dst)) summary["ok"].append({"src": src, "dst": dst_rel, "category": item.get("category", "")}) except Exception as e: summary["errors"].append(f"{src}: {e}") for item in cfg.get("commands") or []: dst_rel = item["dst"].replace("\\", "/") dst = dest_root / dst_rel cmd = item["cmd"] try: _stdin, stdout, stderr = client.exec_command(cmd, timeout=90) out = (stdout.read() or b"") + (stderr.read() or b"") dst.parent.mkdir(parents=True, exist_ok=True) dst.write_bytes(out) summary["ok"].append( { "src": f"cmd:{item.get('name', dst.name)}", "dst": dst_rel, "category": item.get("category", ""), } ) except Exception as e: summary["errors"].append(f"cmd {item.get('name')}: {e}") sftp.close() finally: client.close() meta_path = dest_root / "platform_snapshot" / "snapshot_meta.json" meta_path.parent.mkdir(parents=True, exist_ok=True) meta_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8") return summary