Files
ykxmz/_build/deploy_release.py
T
xmz7891 4295b482a1 2.0.0:安装时可设中转服务器 + 手表端新增设置页 + 中转默认留空
按用户定的版本规矩,这一轮既有新内容又有重构,所以升第二位:
1.9.0 -> 2.0.0(最后一位清 0)。

== 一、安装时就能设中转服务器(用户要求)==

安装向导那一页现在是两个输入框:配对码 + 中转服务器。
装的时候是"第一次设置",最该一次把地址填好,不然装完还得去
双击 exe 找设置窗口,很容易漏。

只填域名就行(自动补 https://),留空 = 只走局域网。
批量静默部署同样支持:
    ykxmz-setup-2.0.0.exe /VERYSILENT /CODE=246810 /RELAY=relay.你的域名

为此新增了一个 --relay-url 参数:
    --relay       连接一次、打印状态、然后退出(人工排查用)
    --relay-url   只写进配置,不连接、不退出(安装流程用)
用错那个的话,安装包的命令链会在中间就 return,后面的 --silent
根本跑不到 —— 所以必须分开。

== 二、手表端新增「设置」页(用户要求)==

用户原话:"安卓端加一个设置,各种设置就在这里面调"。
以前设置散在首页(只有一个"中转设置"按钮),现在统一收进设置页:
    * 中转服务器(填写 / 一键清空)
    * 记住的配对码(显示 / 清除)
    * 已配对的电脑(看数量 / 清空,带二次确认)
    * 关于(版本号从 APK 读,不会和代码对不上)

== 三、中转地址默认留空 + 一次性迁移(用户报的问题)==

用户原话:"安卓端为什么还是默认填的我的中转站,以后改成手动填"。

代码里的默认值早就是空的了,但用户看到的还是那个地址 ——
因为**老版本 1.5.0 把作者的域名硬编码成了默认值**,第一次运行就
存进了 SharedPreferences。装新 APK 不会清偏好,所以那个值一直留着。
用户其实从来没"选"过这个地址,是程序硬塞的。

所以加了 migrateLegacyRelay():存的值如果正好等于当时内置的域名,
就当成没设过、清掉。只清完全相等的,用户手填的不会动。

== 四、两个测试自身的毛病 ==

* test_lan_optimize 那条"没有把旧公网域名当默认值"太粗 ——
  它查的是源码里任何位置,于是迁移注释里引用那行老代码就把它判红了。
  改成先剥掉注释再查(断言要测的是"有没有把它当默认值")。

* test_image_modes 偶发红:它开真实 Tk 全屏窗口,在全量套件里跑
  偶尔起不来(单独跑 19/19 全过)。加了有界重试。
  偶发红的测试会让"全量通过"失去意义。

== 五、新增防复发断言 ==

_tests/test_relay_default_blank.py(15 项,回归阶段 3a3b):
    * 默认值必须是空串
    * 除了迁移清单,源码里不许有写死的中转域名
    * 迁移必须真的被调用(不是只定义)
    * 迁移必须是 remove,不能换成另一个硬编码值
    * Windows 端的默认也得是空

理由:用户说过"这个中转站我就自己用,不对外" —— 值得用断言钉死。

== 六、顺手修的 ==

* release_all.py 崩在 Gitee 那一步:Gitee 的
  GET /releases/tags/{tag} 在"标签存在、但还没发行版"时返回的是
  **HTTP 200 + JSON null**(不是 404),于是 .get("id") 直接
  AttributeError。发 1.9.0 时踩到,已改成容忍 None。

* installer/build_installer.py 里 license-combined.txt 是从
  docs/ 三份文档重新生成的,所以光改生成物没用(下次构建又变回去)。
  改成在 build_license() 里统一注入当前版本。

* ExE 文件属性里的版本(version_info.txt)从 1.7.1 起就一直没跟上。

全量回归 43 个阶段全部通过。
2026-10-07 23:02:44 +08:00

124 lines
4.2 KiB
Python

# -*- coding: utf-8 -*-
r'''把某个版本的成品和官网代码传到中转服务器。
用法:
python _build/deploy_release.py 1.9.0
python _build/deploy_release.py 1.9.0 --code-only # 只更新官网代码
python _build/deploy_release.py 1.9.0 --files-only # 只传成品文件
== 为什么写成脚本文件而不是 python -c ==
这一轮我在 PowerShell 里用 `python -c @"..."@` 写带引号和 $ 的
Python 代码,失败了四五次(PowerShell 会插值、会吃掉 $、嵌套引号
转义一层套一层)。凡是超过两行的 Python,一律写成 .py 文件再跑。
== 官网上"最新版"是怎么来的 ==
relay.py 的 _list_downloads() 按 **mtime 倒序** 返回文件,
landing.py 的 _version() 取**第一个**能匹配到 x.y.z 的文件名。
所以"上传哪个版本,官网就显示哪个版本" —— 不用改任何代码。
按顺序传 1.9.0、再传 2.0.0,官网就会先显示 1.9.0、再显示 2.0.0。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "_build"))
from devssh import upload, run # noqa: E402
REMOTE_DIR = "/root/lanremote-relay"
DL_DIR = REMOTE_DIR + "/downloads"
# 要传的成品(和 dist/ 里的文件名一致)
ARTIFACTS = [
"ykxmz-setup-{v}.exe",
"ykxmz-watch-{v}.apk",
"ykxmz-watch-{v}-说明.txt",
"ykxmz-server-{v}.zip",
]
# 官网/服务端代码
CODE = ["relay.py", "admin.py", "landing.py", "showcase.py"]
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 到中转服务器" % version)
print("=" * 62)
# ---------------- 成品 ----------------
if not code_only:
print()
print(" ── 1. 上传成品到 downloads/ ──")
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/%s" % (DL_DIR, name))
print(" ✓ %-34s %10d 字节" % (name, local.stat().st_size))
# ---------------- 代码 ----------------
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" % (REMOTE_DIR, name))
print(" ✓ %s" % name)
print()
print(" ── 3. 语法检查 + 重启 ──")
out = run(
"cd %s && python3 - <<'PY'\n"
"import ast\n"
"for f in %r:\n"
" ast.parse(open(f, encoding='utf-8').read())\n"
"print('语法 OK')\n"
"PY" % (REMOTE_DIR, tuple(CODE)),
timeout=90)
print(" " + out[1].strip().splitlines()[-1])
out = run("systemctl restart lanremote-relay && sleep 4 && "
"systemctl is-active lanremote-relay && "
"grep -m1 '^VERSION' %s/relay.py" % REMOTE_DIR, timeout=120)
for ln in out[1].strip().splitlines():
print(" " + ln)
# ---------------- 复核 ----------------
print()
print(" ── 4. 复核:官网现在显示哪个版本 ──")
out = run(
"curl -s -H 'Cache-Control: no-cache' http://127.0.0.1:8080/v1/downloads",
timeout=60)
print(" " + out[1].strip()[:400])
out = run("curl -s -H 'Cache-Control: no-cache' http://127.0.0.1:8080/ "
"| grep -o '最新版 [0-9.]*' | head -1", timeout=60)
print(" 官网首页角标: " + out[1].strip())
print()
print(" 提示:官网 / 走 max-age=60,公网可能需要等一分钟才刷新;")
print(" 下载链接带 604800 缓存,换版本号正好会换 URL。")
return 0
if __name__ == "__main__":
sys.exit(main())