Files
diary-news/scripts/deploy_pull.py
Mavis a8e93cf7c7 fix(translate): pipeline 写库用 service 返回的 engine(之前写死 tencent)
pipeline.translate_article 调 translation_service.translate() 后,
自己写 engine_label='tencent',忽略 service 实际返回的 engine。
效果:即使 service 内部按 spark 优先链路跑成功了,
DB 里 translation_engine 也只显示 tencent。

修法: 改用 tr_title.engine(or 默认 tencent 兜底)。

附带: deploy_pull.py 加 sys.stdout.reconfigure(encoding='utf-8'),
Windows GBK 终端下 Unicode 字符不会再 UnicodeEncodeError。
2026-06-10 23:31:03 +08:00

222 lines
7.8 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""部署:从 Gitea 拉取最新代码到远程服务器(免密)。
行为:
- 服务器项目目录不存在?git clone : git fetch + reset --hard
- 拉前记录当前 HEAD(用于失败回滚)
- 失败回滚:git reset --hard <之前 sha>
- 输出 commit hash / log / working tree 状态 / 跟本地 main 对比
用法:
python scripts/deploy_pull.py
python scripts/deploy_pull.py --dry-run
python scripts/deploy_pull.py --repo-dir /opt/diary-news
python scripts/deploy_pull.py --rollback <sha> # 手动回退
环境变量(覆盖默认值):
DEPLOY_HOST, DEPLOY_PORT, DEPLOY_USER,
DEPLOY_REPO_DIR, DEPLOY_REPO_URL, DEPLOY_SSH_KEY
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
from pathlib import Path
# 在 Windows GBK 终端下也能正常输出中文/Unicode
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass
import paramiko
# ===== 默认配置(适配当前项目)=====
DEFAULT_HOST = "207.57.129.228"
DEFAULT_PORT = 19717
DEFAULT_USER = "root"
DEFAULT_REPO_DIR = "/root/diary-news"
DEFAULT_REPO_URL = "http://124.223.26.33:3000/xiaji/diary-news.git"
DEFAULT_SSH_KEY = os.path.expanduser("~/.ssh/id_ed25519")
def _run(c: paramiko.SSHClient, cmd: str, timeout: int = 60) -> tuple[int, str, str]:
si, so, se = c.exec_command(cmd, timeout=timeout)
out = so.read().decode(errors="replace")
err = se.read().decode(errors="replace")
rc = so.channel.recv_exit_status()
return rc, out, err
def _connect(host: str, port: int, user: str, ssh_key: str) -> paramiko.SSHClient:
# 依次尝试 RSA / Ed25519 / ECDSA(paramiko 5 没有统一入口)
pkey: Any = None
last_err: Exception | None = None
for loader in (paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey):
try:
pkey = loader.from_private_key_file(ssh_key)
break
except Exception as e:
last_err = e
if pkey is None:
raise RuntimeError(f"无法解析 SSH key {ssh_key}: {last_err}")
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(host, port=port, username=user, pkey=pkey, timeout=30,
allow_agent=False, look_for_keys=False)
return c
def _local_head() -> str:
"""本地 HEAD sha(用作同步参考)。"""
try:
r = subprocess.run(
["git", "rev-parse", "HEAD"],
capture_output=True, text=True, cwd=str(Path(__file__).resolve().parents[1]),
check=True,
)
return r.stdout.strip()
except Exception as e:
return f"unknown:{e}"
def deploy(
host: str = DEFAULT_HOST,
port: int = DEFAULT_PORT,
user: str = DEFAULT_USER,
repo_dir: str = DEFAULT_REPO_DIR,
repo_url: str = DEFAULT_REPO_URL,
ssh_key: str = DEFAULT_SSH_KEY,
branch: str = "main",
dry_run: bool = False,
rollback: str | None = None,
) -> int:
if not Path(ssh_key).exists():
print(f"ERROR: SSH key 不存在: {ssh_key}", file=sys.stderr)
return 2
print(f"=== 连接 {user}@{host}:{port} ===")
c = _connect(host, port, user, ssh_key)
print(f"[OK] 已免密登录")
try:
# 1) 手动回退
if rollback:
print(f"\n=== 手动回退到 {rollback[:12]} ===")
rc, out, err = _run(c, f"cd {repo_dir} && git reset --hard {rollback}", timeout=30)
if rc != 0:
print(f"[FAIL] 回退失败: {err}")
return 1
print("[OK] 已回退")
rc, head, _ = _run(c, f"cd {repo_dir} && git rev-parse HEAD", timeout=10)
print(f"现在 HEAD: {head.strip()[:12]}")
return 0
# 2) 探查:目录在不在?
rc, exists, _ = _run(c, f"test -d {repo_dir}/.git && echo EXISTS || echo MISSING", timeout=10)
action = "pull" if "EXISTS" in exists else "clone"
# 3) 拉前 HEAD(用于回滚)
before_sha: str | None = None
if action == "pull":
rc, h, _ = _run(c, f"cd {repo_dir} && git rev-parse HEAD", timeout=10)
before_sha = h.strip()
print(f"拉前 HEAD: {before_sha[:12]}")
if dry_run:
print(f"DRY-RUN: 将要 {action} from {repo_url}{repo_dir}")
return 0
# 4) 拉
print(f"\n=== {action.upper()} ===")
if action == "clone":
cmd = f"git clone --depth 50 {repo_url} {repo_dir}"
else:
cmd = (
f"cd {repo_dir} && "
f"git fetch origin {branch} && "
f"git reset --hard origin/{branch}"
)
rc, out, err = _run(c, cmd, timeout=180)
if rc != 0:
print(f"[FAIL] {action} 失败 (rc={rc})")
if out.strip(): print(f"stdout:\n{out}")
if err.strip(): print(f"stderr:\n{err}")
# 回滚(只在 pull 时)
if before_sha and action == "pull":
print(f"\n=== 回退到 {before_sha[:12]} ===")
rrc, _, rerr = _run(c, f"cd {repo_dir} && git reset --hard {before_sha}", timeout=30)
if rrc == 0:
print(f"[OK] 已回退到 {before_sha[:12]}")
else:
print(f"[FAIL] 回退失败: {rerr}")
return 1
print(f"[OK] {action} 成功")
if out.strip(): print(out)
# 5) 拉后状态
rc, head_sha, _ = _run(c, f"cd {repo_dir} && git rev-parse HEAD", timeout=10)
head_sha = head_sha.strip()
print(f"\n拉后 HEAD: {head_sha[:12]}")
rc, log, _ = _run(c, f"cd {repo_dir} && git log --oneline -5", timeout=10)
print("--- 最近的 commit ---")
print(log)
rc, status, _ = _run(c, f"cd {repo_dir} && git status --porcelain", timeout=10)
if status.strip():
print(f"[WARN] working tree 不干净:\n{status}")
else:
print("[OK] working tree 干净")
# 6) 跟本地 main 对比
local = _local_head()
print(f"\n=== 同步对比 ===")
print(f"本地 HEAD: {local[:12]}")
print(f"服务器 HEAD: {head_sha[:12]}")
if local.startswith(head_sha):
print("[OK] 服务器 == 本地")
else:
print("[INFO] 服务器跟本地不完全一致(可能本地未推送,或服务器在另一条 commit 上)")
# 7) 总结
print(f"\n=== 部署报告 ===")
print(f"服务器: {user}@{host}:{port}")
print(f"项目目录: {repo_dir}")
print(f"动作: {action}")
print(f"最终 HEAD: {head_sha[:12]}")
return 0
finally:
c.close()
def main() -> None:
p = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--host", default=os.environ.get("DEPLOY_HOST", DEFAULT_HOST))
p.add_argument("--port", type=int, default=int(os.environ.get("DEPLOY_PORT", str(DEFAULT_PORT))))
p.add_argument("--user", default=os.environ.get("DEPLOY_USER", DEFAULT_USER))
p.add_argument("--repo-dir", default=os.environ.get("DEPLOY_REPO_DIR", DEFAULT_REPO_DIR))
p.add_argument("--repo-url", default=os.environ.get("DEPLOY_REPO_URL", DEFAULT_REPO_URL))
p.add_argument("--ssh-key", default=os.environ.get("DEPLOY_SSH_KEY", DEFAULT_SSH_KEY))
p.add_argument("--branch", default="main")
p.add_argument("--dry-run", action="store_true")
p.add_argument("--rollback", metavar="SHA", help="手动回退到指定 commit")
args = p.parse_args()
sys.exit(deploy(
host=args.host, port=args.port, user=args.user,
repo_dir=args.repo_dir, repo_url=args.repo_url,
ssh_key=args.ssh_key, branch=args.branch,
dry_run=args.dry_run, rollback=args.rollback,
))
if __name__ == "__main__":
main()