157 lines
5.4 KiB
Python
157 lines
5.4 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
光湖直连同步器 · 本地→新加坡
|
|||
|
|
绕过代理,直连新加坡 Gatekeeper,分 chunk 传输文件到代码仓库。
|
|||
|
|
用法: python3 sync-to-sg.py --file brain/xxx.hdlp --target brain/xxx.hdlp --msg "feat: xxx"
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import base64
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
import subprocess
|
|||
|
|
import sys
|
|||
|
|
import time
|
|||
|
|
|
|||
|
|
GK_URL = "http://43.156.237.110:3911/exec"
|
|||
|
|
GK_KEY = "zy_gtw_c38be341d65043eee0ba51a214a267832a9ae46dd73e423c"
|
|||
|
|
REPO_PATH = "/opt/zhuyuan/guanghulab"
|
|||
|
|
CHUNK_SIZE = 6000 # bytes,新加坡 gatekeeper 安全上限
|
|||
|
|
CMD_TIMEOUT = 30 # 每条命令超时秒数
|
|||
|
|
|
|||
|
|
|
|||
|
|
def gk_exec(cmd: str) -> dict:
|
|||
|
|
"""通过 Gatekeeper 执行命令,绕过代理直连"""
|
|||
|
|
env = os.environ.copy()
|
|||
|
|
env.pop("http_proxy", None)
|
|||
|
|
env.pop("https_proxy", None)
|
|||
|
|
env.pop("HTTP_PROXY", None)
|
|||
|
|
env.pop("HTTPS_PROXY", None)
|
|||
|
|
|
|||
|
|
result = subprocess.run(
|
|||
|
|
["curl", "-s", "--connect-timeout", str(CMD_TIMEOUT),
|
|||
|
|
"-X", "POST", GK_URL,
|
|||
|
|
"-H", "Content-Type: application/json",
|
|||
|
|
"-H", f"Authorization: Bearer {GK_KEY}",
|
|||
|
|
"-d", json.dumps({"cmd": cmd})],
|
|||
|
|
capture_output=True, text=True, timeout=CMD_TIMEOUT + 5, env=env
|
|||
|
|
)
|
|||
|
|
try:
|
|||
|
|
return json.loads(result.stdout)
|
|||
|
|
except json.JSONDecodeError:
|
|||
|
|
return {"ok": False, "error": f"gatekeeper 返回非 JSON: {result.stdout[:200]}", "stderr": result.stderr}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def sync_file(local_path: str, target_path: str, commit_msg: str) -> bool:
|
|||
|
|
"""同步单个文件到新加坡仓库"""
|
|||
|
|
# 读文件
|
|||
|
|
with open(local_path, "rb") as f:
|
|||
|
|
content = f.read()
|
|||
|
|
expected_bytes = len(content)
|
|||
|
|
b64 = base64.b64encode(content).decode("ascii")
|
|||
|
|
print(f"[sync] {local_path} → {target_path} ({expected_bytes} bytes, {len(b64)} b64 chars)")
|
|||
|
|
|
|||
|
|
# 清空临时目录
|
|||
|
|
r = gk_exec(f"rm -f /tmp/sync_chunk_* /tmp/sync_b64.txt")
|
|||
|
|
if not r.get("ok"):
|
|||
|
|
print(f"[error] 清空临时目录失败: {r}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 分 chunk 上传
|
|||
|
|
chunks = [b64[i:i+CHUNK_SIZE] for i in range(0, len(b64), CHUNK_SIZE)]
|
|||
|
|
print(f"[sync] 分 {len(chunks)} chunks 上传...")
|
|||
|
|
|
|||
|
|
for i, chunk in enumerate(chunks):
|
|||
|
|
# base64 编码 chunk 内容避免 shell 转义问题
|
|||
|
|
chunk_b64 = base64.b64encode(chunk.encode()).decode()
|
|||
|
|
cmd = f'echo "{chunk_b64}" | base64 -d >> /tmp/sync_b64.txt'
|
|||
|
|
r = gk_exec(cmd)
|
|||
|
|
if not r.get("ok"):
|
|||
|
|
print(f"[error] chunk {i+1}/{len(chunks)} 上传失败: {r}")
|
|||
|
|
return False
|
|||
|
|
print(f" chunk {i+1}/{len(chunks)} ✓")
|
|||
|
|
|
|||
|
|
# 解码并写入
|
|||
|
|
full_target = f"{REPO_PATH}/{target_path}"
|
|||
|
|
target_dir = os.path.dirname(full_target)
|
|||
|
|
write_cmd = (
|
|||
|
|
f"mkdir -p {target_dir} && "
|
|||
|
|
f"base64 -d /tmp/sync_b64.txt > {full_target} && "
|
|||
|
|
f"echo BYTES=$(wc -c < {full_target})"
|
|||
|
|
)
|
|||
|
|
r = gk_exec(write_cmd)
|
|||
|
|
if not r.get("ok"):
|
|||
|
|
print(f"[error] 写入失败: {r}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 校验字节数
|
|||
|
|
stdout = r.get("stdout", "")
|
|||
|
|
if f"BYTES={expected_bytes}" not in stdout:
|
|||
|
|
actual = stdout.replace("BYTES=", "").strip()
|
|||
|
|
print(f"[error] 字节校验失败: 期望={expected_bytes}, 实际={actual}")
|
|||
|
|
return False
|
|||
|
|
print(f"[sync] 写入+校验通过 ({expected_bytes} bytes)")
|
|||
|
|
|
|||
|
|
# Git 提交(pull→commit→push)
|
|||
|
|
escaped_msg = commit_msg.replace('"', '\\"').replace("'", "\\'")
|
|||
|
|
git_cmd = (
|
|||
|
|
f"cd {REPO_PATH} && "
|
|||
|
|
f"git config user.email zhuyuan@guanghulab.com && "
|
|||
|
|
f"git config user.name '铸渊 ICE-GL-ZY001' && "
|
|||
|
|
f"git pull --rebase origin main 2>&1; "
|
|||
|
|
f"git add {target_path} && "
|
|||
|
|
f"GIT_OUT=$(git commit -m \"{escaped_msg}\" 2>&1); echo \"COMMIT: $GIT_OUT\"; "
|
|||
|
|
f"if echo \"$GIT_OUT\" | grep -q 'nothing to commit'; then echo 'NO_CHANGE'; else git push origin main 2>&1; fi"
|
|||
|
|
)
|
|||
|
|
r = gk_exec(git_cmd)
|
|||
|
|
stdout = r.get("stdout", "")
|
|||
|
|
stderr = r.get("stderr", "")
|
|||
|
|
|
|||
|
|
if "NO_CHANGE" in stdout:
|
|||
|
|
print(f"[sync] 文件无变更,跳过 commit+push")
|
|||
|
|
elif r.get("ok"):
|
|||
|
|
print(f"[sync] git pull + commit + push 成功")
|
|||
|
|
if stdout:
|
|||
|
|
print(f" 输出: {stdout[:300]}")
|
|||
|
|
else:
|
|||
|
|
# git 可能返回非零但实际成功了(committer 警告等)
|
|||
|
|
if "COMMIT:" in stdout and "nothing to commit" not in stdout:
|
|||
|
|
print(f"[sync] git 操作完成(有警告但 commit 已执行)")
|
|||
|
|
else:
|
|||
|
|
print(f"[error] git 操作失败: stdout={stdout[:300]}, stderr={stderr[:200]}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 清理
|
|||
|
|
gk_exec("rm -f /tmp/sync_chunk_* /tmp/sync_b64.txt")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
parser = argparse.ArgumentParser(description="光湖直连同步器 · 本地→新加坡")
|
|||
|
|
parser.add_argument("--file", required=True, help="本地文件路径")
|
|||
|
|
parser.add_argument("--target", required=True, help="目标路径(相对于仓库根)")
|
|||
|
|
parser.add_argument("--msg", required=True, help="commit message")
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
if not os.path.exists(args.file):
|
|||
|
|
print(f"[error] 文件不存在: {args.file}")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
print(f"═" * 50)
|
|||
|
|
print(f"光湖直连同步器 · 本地→新加坡 BS-SG-001")
|
|||
|
|
print(f"═" * 50)
|
|||
|
|
|
|||
|
|
start = time.time()
|
|||
|
|
ok = sync_file(args.file, args.target, args.msg)
|
|||
|
|
elapsed = time.time() - start
|
|||
|
|
|
|||
|
|
if ok:
|
|||
|
|
print(f"✅ 同步完成 ({elapsed:.1f}s)")
|
|||
|
|
else:
|
|||
|
|
print(f"❌ 同步失败 ({elapsed:.1f}s)")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|