Files
ykxmz/_build/deploy_lightsail_release.py
T
xmz7891 0ad3f648c6 修上一轮清理留下的副作用:占位符把运维脚本弄连不上了
上一个提交把仓库里的服务器 IP 换成了占位符,但**那些脚本本身要用这个 IP
去连服务器** —— 换掉之后它们就跑不起来了。这个提交把取值方式理清楚。

== 取值统一走 _build/lssh.py ==

    YKXMZ_LS_HOST 环境变量
    或 _build/.lightsail_host(一行 IP,已 gitignore)
    都没有就报错,并告诉你怎么设

十个运维脚本改成 `from lssh import ... HOST as LS_HOST`,
公开仓库里一个具体地址都不留。

== 顺带修的两个自己造的 bug ==

  * 用正则往 `from lssh import ...` 后面追加东西时,
    追加到了行尾的 `# noqa: E402` **注释里面** ——
    十处导入全是废的,脚本一跑就 NameError。
    改成先剥掉注释、拼好导入、再把 noqa 加回去。

  * 同一个正则还把 `from lssh import HOST as LS_HOST` 加进了
    **lssh.py 自己**,造成自我导入。

教训:**正则批量改导入语句很危险**(注释、行尾、顺序都能坑到),
这种地方宁可逐个 edit。

== 为什么要重建产物 ==

docs/AFTER-INSTALL.txt 会被打进服务端 zip,而它上一轮改过
(去掉了写死的旧管理员密码)—— 不重打的话发出去的还是旧的。

重打后确认:三个产物里都没有旧密码、没有服务器 IP。

== 验证 ==

    全量回归退出码 0,41 个阶段全 PASS
    603 个 Python 文件语法通过
    ykxmz-agent / server / watch 三个产物扫描干净
    提交前泄漏闸门通过
2026-10-09 00:02:32 +08:00

120 lines
4.3 KiB
Python

# -*- coding: utf-8 -*-
r'''把一个版本的代码和成品部署到 Lightsail。
用法:
python _build/deploy_lightsail_release.py 2.1.3
python _build/deploy_lightsail_release.py 2.1.3 --code-only
python _build/deploy_lightsail_release.py 2.1.3 --files-only
== 为什么写成文件 ==
这一轮我用 `python -c @"..."@` 传带 `$(...)` 的远端命令,
PowerShell 把 `$(systemctl ...)` 当成本机的子表达式执行了
(报 "无法将 systemctl 项识别为 cmdlet"),还吃掉引号导致
Python 语法错。凡是超过两行的远端命令,一律写成文件跑。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "_build"))
from lssh import run, upload, HOST as LS_HOST # noqa: E402
RELAY_DIR = "/opt/ykxmz-relay"
PORT = "13245"
PUBLIC = "http://%s:%s" % (LS_HOST, PORT)
CODE = ["relay.py", "admin.py", "landing.py", "showcase.py"]
ARTIFACTS = ["ykxmz-setup-{v}.exe", "ykxmz-watch-{v}.apk",
"ykxmz-watch-{v}-说明.txt", "ykxmz-server-{v}.zip"]
def main() -> int:
args = [a for a in sys.argv[1:] if a]
if not args:
print(__doc__)
return 1
version = args[0]
code_only = "--code-only" in args
files_only = "--files-only" in args
print("=" * 62)
print(" 部署 %s 到 Lightsail" % version)
print("=" * 62)
if not code_only:
print()
print(" ── 1. 传成品到 downloads/ ──")
run("mkdir -p %s/downloads" % RELAY_DIR, timeout=60, quiet=True)
for tpl in ARTIFACTS:
name = tpl.format(v=version)
local = ROOT / "dist" / name
if not local.is_file():
print(" [跳过] %s 不存在" % name)
continue
upload(str(local), "%s/downloads/%s" % (RELAY_DIR, name))
if not files_only:
print()
print(" ── 2. 传服务端代码 ──")
for name in CODE:
local = ROOT / "relay" / name
if not local.is_file():
print(" [跳过] %s 不存在" % name)
continue
upload(str(local), "%s/%s" % (RELAY_DIR, name))
print()
print(" ── 3. 语法检查 + 重启 ──")
out = run(
"cd %s && for f in relay.py admin.py landing.py showcase.py; do "
"python3 -c \"import ast,sys;ast.parse(open(sys.argv[1],"
"encoding='utf-8').read())\" $f || exit 1; done && echo PY_OK"
% RELAY_DIR, timeout=120, quiet=True)
print(" " + out[1].strip().splitlines()[-1])
out = run("sudo systemctl restart ykxmz-relay; sleep 4; "
"systemctl is-active ykxmz-relay", timeout=180, quiet=True)
print(" relay 状态: " + out[1].strip())
print()
print(" ── 4. 复核 ──")
script = (
'printf " %-18s %s\\n" 本机 "'
'$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://127.0.0.1:' + PORT + '/v1/health)"\n'
'printf " %-18s %s\\n" 公网 "'
'$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 15 '
+ PUBLIC + '/v1/health)"\n'
'printf " %-18s %s\\n" 公网首页 "'
'$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 15 '
+ PUBLIC + '/)"\n'
'printf " %-18s %s\\n" 公网延迟 "'
'$(curl -s -o /dev/null -w \'%{time_total}s\' --max-time 15 '
+ PUBLIC + '/v1/health)"\n'
'printf " %-18s %s\\n" 角标 "'
'$(curl -s --max-time 10 http://127.0.0.1:' + PORT
+ '/ | grep -o "最新版 [0-9.]*" | head -1)"\n'
'printf " %-18s %s\\n" 监听 "'
'$(ss -tln 2>/dev/null | grep ":' + PORT + ' " | wc -l) 个"\n'
'printf " %-18s %s\\n" 监测 "'
'fails=$(grep -m1 ^fails= ' + RELAY_DIR
+ '/monitor_state.txt 2>/dev/null | cut -d= -f2); '
'echo ${fails:-?} 次连续失败"\n'
'printf " %-18s %s\\n" 抢救报告 "'
'$(ls ' + RELAY_DIR + '/rescue_reports 2>/dev/null | wc -l) 份"\n'
)
out = run(script, timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(ln)
print()
print(" 公网地址:%s" % PUBLIC)
return 0
if __name__ == "__main__":
sys.exit(main())