#!/usr/bin/env python3 """本机/本地服务器一键部署中控(git pull + 构建 + pm2 reload)。 环境变量(可选): CONTROL_ROOT 默认 /opt/eth_hedge_sim(与策略机同仓)或本机仓库根 CONTROL_HOST 若设置则 SSH 远程执行;否则在本机执行 CONTROL_USER 默认 root CONTROL_PASS SSH 密码(远程时必填) REPO_URL 默认 https://git.bz121.com/dekun/eth_hedge_sim.git """ from __future__ import annotations import os import subprocess import sys from pathlib import Path HOST = os.environ.get("CONTROL_HOST", "").strip() USER = os.environ.get("CONTROL_USER", "root") PASSWORD = os.environ.get("CONTROL_PASS", "") ROOT = os.environ.get("CONTROL_ROOT", "").strip() REPO = os.environ.get("REPO_URL", "https://git.bz121.com/dekun/eth_hedge_sim.git") def _default_root() -> Path: # scripts/deploy_control.py -> repo root return Path(__file__).resolve().parents[1] def local_update(root: Path) -> int: script = root / "control" / "deploy" / "update.sh" if not script.is_file(): print(f"missing {script}", file=sys.stderr) return 2 if os.name == "nt": print("Run on Linux local server, or set CONTROL_HOST for SSH.", file=sys.stderr) return 2 env = {**os.environ, "DEBIAN_FRONTEND": "noninteractive"} proc = subprocess.run(["bash", str(script)], cwd=str(root), env=env) return int(proc.returncode) def remote_update(root: str) -> int: try: import paramiko except ImportError: print("pip install paramiko", file=sys.stderr) return 2 if not PASSWORD: print("Set CONTROL_PASS for remote deploy.", file=sys.stderr) return 2 remote = f""" set -euo pipefail export DEBIAN_FRONTEND=noninteractive if [[ ! -d '{root}/.git' ]]; then mkdir -p "$(dirname '{root}')" git clone '{REPO}' '{root}' fi bash '{root}/control/deploy/update.sh' curl -fsS http://127.0.0.1:5160/health || true """ client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) print(f"connecting {USER}@{HOST} ...") client.connect(HOST, username=USER, password=PASSWORD, timeout=30) print("running control update ...") _stdin, stdout, stderr = client.exec_command(remote, get_pty=True) while True: chunk = stdout.read(1024) if not chunk: break text = chunk.decode("utf-8", errors="replace") if isinstance(chunk, bytes) else chunk print(text, end="", flush=True) err_raw = stderr.read() err = err_raw.decode("utf-8", errors="replace") if isinstance(err_raw, bytes) else err_raw code = stdout.channel.recv_exit_status() if err: print(err, file=sys.stderr) client.close() print(f"exit={code}") return int(code) def main() -> int: if HOST: root = ROOT or "/opt/eth_hedge_sim" return remote_update(root) root = Path(ROOT) if ROOT else _default_root() return local_update(root) if __name__ == "__main__": raise SystemExit(main())