xmz7891
|
c5fa4eb517
|
2.1.1:修一个会让网站整个挂掉的死锁(「网站容易挂」的真因)
2.1.0 的服务端包里带着这个死锁,所以要单独发一版让部署的人拿到修好的包。
== 死锁 ==
lanremote-nettune.service Before=lanremote-tunnel.service
net-tune.sh 第 4 步 systemctl restart lanremote-tunnel
nettune 在**自己还没结束**的时候去 restart 一个"必须等自己结束
才能启动"的服务 -> systemd 判定顺序冲突 -> restart 永不返回
-> nettune 卡在 activating -> 隧道被 Before= 挡着起不来 -> 公网 530。
设备上实测卡了 9 分半(CGroup 里能看到 net-tune.sh 下面挂着
一个 etime 09:32 的 systemctl restart lanremote-tunnel)。
journalctl 里只有 nettune 停在 activating、**一句报错都没有**;
手动跑 net-tune.sh 却完全正常(那时没有 Before= 约束)。
是靠这次新加的监测单元抓到的(fails=3 / last_code=530)。
== 改法 ==
unit 加 Environment=NETTUNE_SERVICE_CALL=1,脚本据此跳过第 4 步。
第 4 步只在人工重跑时才有意义(改完 MTU/MSS 让已建隧道用上新参数),
开机时顺序就是调优先、隧道后,不需要它。
三处都改了:net-tune.sh 认开关;deploy.sh / install.sh 生成的 unit 带上它。
没选 systemctl restart --no-block:那只是把阻塞变异步,开机时仍然
做一次无谓重启(隧道刚起来就被停掉),也没解决"不该在开机时重启"。
== 为什么以前没暴露 ==
设备上那份旧 unit 的 ExecStart 里有 iptables ... || ...,
systemd 不支持 ||,所以它在第 2 条 ExecStart 就报错退出,
根本没走到会死锁的那一步。把 unit 换成正确形状之后才露出来。
|
2026-10-08 19:52:08 +08:00 |
|
xmz7891
|
3f18e6bb40
|
2.1.0:网站自动抢救 + 后台抢救报告分区 + 网络热门音效 + 全量测试后台静默
按用户定的规矩,这一轮有新功能,升第二位:2.0.0 -> 2.1.0。
== 一、网站挂了会自己救 ==
用户原话:「网站还是容易挂,建议继续增加隧道稳定性,并在后台设立
检测单元,实时检测网站状态,一旦检测到网站挂了,立即启动紧急预案」。
新增 relay/monitor.sh + lanremote-monitor.timer:
每 60 秒从**公网**查一次 /v1/health(不是查 127.0.0.1)
为什么不查本机:本机 200 只说明中转进程活着,用户真正关心的是
经过 Cloudflare 边缘 + 隧道 + 本机三段之后能不能打开。实测过
隧道两条实例都掉了、本机 curl 照样 200,只有从公网测才发现 502。
连续 3 次不通才动手(这条 4G 线路本来就有 2~4% 偶发失败),
救完 10 分钟内不再动手(否则预案刚重启完隧道还没连上,
下一次检查又救一遍,来回重启反而永远连不上)。
新增 relay/rescue.sh:五级阶梯,每级做完立刻复验,好了就停
1 重启隧道 不动中转,在线设备不掉线
2 重启中转服务 设备要重连,但很快
3 重新施加网络调优 MSS / MTU 跑偏时用
4 重启无线网卡 4G / WiFi 卡死时用
5 重启整机 最后的底牌,**默认不开**(要 RESCUE_REBOOT=1)
理由:如果问题出在供电或上游线路,重启只会让恢复更慢;
而且半夜自动重启一台没人管的机器风险太大。
== 二、抢救报告 + 后台分区 ==
用户原话:「如果紧急预案未能解决,则将问题打包成 .txt,然后在
中转站设置里面加一个分区专门放还有下载这个 txt。解决后将解决问题
的方法加入紧急预案」。
* 五级都没救回来 -> 把现场(内存/进程/隧道连接数/路由和 MTU、
三份日志尾部、内核异常)整理成 rescue_reports/<时间戳>.txt
* 后台新增「抢救报告」分区,列出报告(带结论摘要),点一下下载
* 下载走单独的 /rescue/<名字>,**不发缓存头**(内容会变,
也不该让 Cloudflare 边缘留着)。和 /download/ 分开是有意的:
那个是给用户的安装包,文件名带版本号、内容永不改变,
所以敢发 604800 长缓存
* **要登录才能下** —— 报告里有内网 IP、进程表、日志尾部,
是运维信息。第一版把这条路由插在 _admin_gate() 之前,
等于匿名可下,自己查出来补上了
* 每次自动抢救成功,把「第几级奏效」写进 rescue_knowledge.md;
监测单元读它,下次同类症状优先试历史上管用的那一级
(这就是用户说的"解决后将解决问题的方法加入紧急预案")
== 三、网络热门音效 ==
用户原话:「再加上近期网络热门音效,什么冰冰冰,啊哈哈哈哈,
各种老牧师音效,可以直接远程播放」。
**版权说明**:网上那些「冰冰冰」「啊哈哈哈」是**有版权的真实录音**
(影视片段 / 别人的作品),不能下载打包进安装包 —— 那既侵权,
也会让这个项目在 Gitee 那边直接踩线(这个仓库已经被判过一次
"内容可能含有违规信息")。
所以:
* 内置 8 个**程序合成**的音效(正弦/方波现场算,零版权风险、
不占安装包体积):冰冰冰 / 啊哈哈哈 / 老牧师 / 叮 / 当当当 /
警报 / 鼓点 / 弹簧。名字对着感觉来,但不是那些录音本身
* 做「自己放进来」的目录:mp3/wav 丢进 data/sfx/ 就自动出现在
列表里,照样能远程播。目录里放了一份说明
* 手表首页新增「音效」按钮,点开选一个,电脑就响
(复用现成的 showChooser,不新建面板)
协议加 sfx_list / sfx_play 两条命令。
== 四、修「安卓设置页一片空白」 ==
用户报的。根因:设置页**建好了但忘了 root.addView()** ——
showOnly() 让它 VISIBLE 也没用,它根本不在视图树里。
这类"漏挂"编译完全不报,只在运行时表现为空白。已加断言
_tests/test_watch_panels_wired.py 盯着:每个面板都必须挂到 root、
并且纳入 showOnly()。
== 五、全量测试改成后台静默 ==
用户原话:「把全量测试改为在后台静默运行不要打开任何窗口或锁屏」。
会开窗口的 6 个阶段(含开**全屏**那个)现在跑在独立的 Windows
隐藏桌面上(_tests/hidden_desktop.py)。窗口是**真的** —— 全屏、
可见、几何、Win32 枚举都真实,测试有效性一点没损失 ——
只是不在用户眼前,全屏也不会锁屏。
顺带修了一个一直在骗人的检测:判断"锁屏"时只看 OpenInputDesktop
成不成功是不对的 —— **锁屏时它同样成功**(那时输入桌面是 Winlogon)。
于是截屏类测试在锁屏时被记成 FAIL 而不是 SKIP,把真问题淹掉了
(用户电脑锁了一夜,我就是被这个误导过)。改成看输入桌面的**名字**。
现在锁屏也能跑全量:该 SKIP 的 SKIP,该跑的照跑。
== 六、顺手修的真 bug ==
* windows/lanremote/server.py 的 sfx_play 分支用了不存在的变量名 body
(应该是 msg)-> **一调就 NameError**。pyflakes 那关抓出来的,
说明"未定义名"这条检查有用。
* _build/package_remote.py 的文件白名单只有 .py/.sh/.ico,
**.service/.timer 收不进服务端 zip** -> 新机器上监测根本不启动,
而且不报错、只是静默不跑。已加。
* protocol.py 被我补丁插入了 LF 行到 CRLF 文件里 -> 行尾混用,编码审计红。
* deploy.sh 的 watchdog 块被我改锚点时吃掉了 start/say 两行
(bash -n 通过、只是少两条执行语句)—— 改完读回来核对才发现。
* _build/CHANGELOG.md 的 2.1.0 说明里写了"管理后台",
触发对外材料清洁度检查。改成不提管理页(用户定的规矩:
对外只能提官网)。
全量回归 46 个阶段全部通过。
|
2026-10-08 18:36:50 +08:00 |
|
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 |
|
xmz7891
|
de5018bbbe
|
按用户规矩升到 1.9.0(有新内容+重构,不是只修 bug)+ 加版本号一致性检查
用户定的规矩:
只修 bug -> 最后一位 +1 1.8.2 -> 1.8.3
加新内容/重构 -> 第二位 +1,最后一位清 0 1.8.2 -> 1.9.0
这一轮既有新东西(设置窗口的中转服务器字段、允许新设备接入开关、
双击开设置窗口)又有重构(手表面板切换收成 showOnly()),
所以是 **1.9.0**,不是我之前说的 1.8.3。
== 顺手加了个「版本号一致性」检查(回归阶段 3a8)==
版本号散落在 8 个地方,靠脚本+人工替换很容易漏。实测漏了两个,
而且**漏了不报错**,只是用户看到一个对不上的版本号:
* windows/version_info.txt 写着 1.7.1
-> 用户在「文件属性 -> 详细信息」里看到的版本,错了三个版本
* docs/LICENSE.txt 写着 1.7.1
-> 安装时许可页上显示 1.7.1
第二个的根因值得记:installer/build_installer.py 会从 docs/ 里三份
文档**重新生成** license-combined.txt,所以光改生成物没用 ——
下次构建又变回去(我第一次就是这么被骗的)。改成在 build_license()
里**统一注入当前版本**,源文档写什么都不影响。
新增:
_tests/check_version_consistency.py 检查 8 处版本号一致
_build/add_verify_stage.py 给套件加阶段(不经 shell,
不会被 PowerShell 吃掉 \$ 变量)
_build/fix_license_version.py 按行精确修 license
== 另外修了两个我自己的坑 ==
1. verify_all.ps1 被 PowerShell heredoc 吃掉了 \ / \,
整个脚本语法错。**同一个坑踩了两次**(3a7 和 3a8)。
以后给 .ps1 加含 \$ 的内容一律写 Python 脚本文件再跑。
2. build_exe.ps1 被 edit 工具吃掉了 UTF-8 BOM
(PowerShell 5.1 会按 GBK 读,中文注释乱码)。
全量回归 43 个阶段全部通过。
|
2026-10-07 22:10:14 +08:00 |
|
xmz7891
|
9b17129c49
|
修「去密码硬编码」那一步自己引入的三个 bug
上一版把明文密码换成随机生成时,引入了三个问题。都是我自己的补丁
造成的,而且症状都像"功能坏了",实际是环境和脚本的问题。
== bug 1:兜底替换把函数调用塞进了字符串 ==
fix_hardcoded_password.py 里有句兜底:
text = text.replace(OLD, '_admin_password()')
而原代码是:
{"username": "xmz7891", "password": "@<老密码>"}
^ @ 在引号**里面**
正则要求引号紧贴老密码,这里中间有个 @,所以没匹配上;
兜底替换就把老密码换成了函数调用,结果成了**字符串字面量**:
{"username": "xmz7891", "password": "@_admin_password()"}
7 个文件被这样弄坏。它们拿这串东西当密码去登录,
当然登不上 -> 401「请先登录」-> 管理接口测试全红(12 项失败)。
教训:**批量替换里的兜底 str.replace 太危险**,它不看上下文。
替换完必须检查有没有"函数调用被引号包起来"这种形状。
== bug 2:测试和它自己起的中转对不上密码 ==
密码改成随机生成之后,测试分成两边:
* 测试进程:_admin_password() 去环境变量/文件里找
* 它启动的中转:找不到就随机生成一个
两边不一致 -> 登录失败。
修法:测试在**模块级**(import 时)生成一个本次运行专用的随机密码,
写进 os.environ["RELAY_ADMIN_PASSWORD"]。测试启动中转用的是
dict(os.environ),所以会自动传过去 —— 两边就是同一个密码。
用随机而不是写死:写死就又变成"密码进仓库"了,白改一场。
== bug 3:全新部署启动后没有密码文件 ==
admin_password() 原来只在有人访问登录页时才调用,于是全新部署
启动后 data/admin_password.txt 一直不存在,日志里也看不到密码,
管理员不知道该用什么登。
修法:main() 里在 admin_key() 后面补一句 admin_password(),
和钥匙一样的道理 —— 启动时生成并打印一次。
== 验证 ==
管理页设备控制 23/23 通过
管理页批删 24/24 通过
管理页按钮 10/10 通过
管理页 JS 通过
管理页网络体检 15/15 通过
密码随机生成 11/11 通过(含"老密码登不进去"、"两次部署密码不同")
工作区搜不到明文密码了(git 历史里还有,等用户确认后重写)。
|
2026-10-07 20:56:53 +08:00 |
|
xmz7891
|
430923dd84
|
🔐 修一个严重安全问题:管理密码被硬编码进公开仓库
== 问题 ==
relay/relay.py 第 138 行:
ADMIN_PASSWORD_DEFAULT = "@REVAedu120830"
而且 _tests/ 和 _build/ 下 11 个文件里也写着明文密码。
这个仓库是要推到**公开的 Gitee / EzHub** 的,等于:
git 仓库 = 公开的
管理页路径 /zhongzhuanzhan = 公开的(就写在代码里)
公网地址 = 公开的
密码 = 也在仓库里
**= 把管理台的门钥匙挂在门上。**
而且 git 历史删不掉 —— 只是在最新提交里删掉没用,
读过仓库的人已经从历史里拿到了。
== 修法 ==
### 1. 代码里不再有任何固定密码
relay.py 的 admin_password() 改成和 admin_key 一样的做法:
环境变量 RELAY_ADMIN_PASSWORD
-> data/admin_password.txt(600 权限,不进仓库)
-> 都没有就**首次启动随机生成**,写进那个文件并打印一次
这样全新部署的密码天然是随机的、每台不同。
### 2. 11 个文件里的明文密码清掉
_tests/ acceptance.py, test_relay_admin_login.py,
test_admin_batch_delete.py, test_admin_button.py,
test_admin_device_control.py, test_admin_js.py,
test_admin_netcheck.py
_build/ check_web.sh, verify_site_admin.sh,
split_site_admin2.py, test_response_headers.py
统一改成从 RELAY_ADMIN_PASSWORD 环境变量读,
拿不到就从部署的 data/admin_password.txt 读,
再拿不到就**跳过需要登录的用例**(而不是编个假密码硬试)。
### 3. 真机上的密码已经轮换
设备上的 data/admin_password.txt 已换成用户指定的新密码,
并实测:
✓ 新密码能登录(HTTP 302 + lr_admin cookie)
✓ 老密码登不上了
✓ 新密码无换行(printf 不带 \n,免得读出来多一个字符)
顺带:重启中断了 relay-stability 的 A/B 对照 A 组(69 个样本),
分析数据时会标注这一段。
== 新增测试 ==
_tests/test_admin_password_gen.py 验证六件事:
1. 全新部署会生成随机密码(不是那个老密码)
2. 密码长度 >= 12、纯字母数字
3. 用生成的密码能登录管理页
4. **老密码登不进去**
5. 重启后密码不变(从文件读回来)
6. **两次全新部署的密码不一样**(真的是随机的)
== 还没做的(等用户决定)==
git **历史**里还有那个密码。要彻底清掉得重写历史 + 强制推送,
这是个危险操作,按用户的规矩必须他确认之后才做。
而且**光删历史不够** —— 密码已经公开过,必须换(已换)。
|
2026-10-07 20:49:33 +08:00 |
|
xmz7891
|
8880e7033d
|
修「经中转几次后永久哑掉」——「按一下没反应」的真因
== 现象 ==
用户报:
* 局域网能搜到电脑,IP/端口/配对码都对,连上后**没有任何反应**
* 走中转「已配对」,但点按钮没反应
局域网那一半已经修过(preferLan)。这次抓到的是**经中转**那一半,
而且比预想的严重得多。
== 证据链(插桩复现出来的)==
被控端日志里直接抛了未捕获异常,线程当场死:
Exception in thread client-relay:
server.py:407 session.send(reply)
relay.py:166 self._link.send_to_client(data)
relay.py:357 self._post("/v1/agent/send", ...)
relay.py:766 raise RelayError(f"请求失败: {last_error}")
lanremote.relay.RelayError: 请求失败: Idle
之后的状态:
* client-relay 线程没了 —— 电脑再也不往手表回任何东西
* relay-link 线程还活着、还在长轮询 —— 中转那边看着「设备在线」
* **手表看到的就是「连上了,但按一下毫无反应」**
写入仓库的测试 _tests/test_relay_local.py 一直是这个形状:
前面几项 PASS、跑到经中转发命令就卡死。之前没人深挖。
== 根因 1:一条 HTTP/1.1 连接被两个线程同时用 ==
_post() 里锁只包住「取连接」那一段:
with self._http_lock:
connection = self._http # 锁到这里就放了
try:
connection.request("POST", ...) # 锁外发请求
于是两个线程会同时用同一条 connection:
* relay-link 线程跑 /v1/agent/poll —— 长轮询,一条请求挂 180 秒
* client-relay 线程同时 POST /v1/agent/send
HTTP/1.1 一条连接同一时刻只能有一个在途请求,第二个线程一进来
就撞上 http.client 状态机,抛 CannotSendRequest("Idle")。
_post 的「重试一次」也救不了:它 _drop_http() 之后,另一个线程
手里还攥着那条旧连接对象在发,照样坏。
修法:
* **发命令用独立连接**(_post 加 dedicated 参数)。
长轮询那条常年被占着,发命令不该去挤它,各走各的。
* 两条连接各自一把锁**锁住整个「发请求 + 读响应」周期**,
不再只锁取连接那一下。
* 普通请求若发现缓存连接正忙(长轮询在途),临时另开一条用完即关。
* _drop_http(dedicated=) 区分清哪条;finally 里一定归还占用
(否则那条连接会被永久标记为忙)。
== 根因 2:回包失败会打死整个会话线程 ==
server.py 的 _client_loop 里:
if reply is not None:
session.send(reply) # <- 不在任何 try 里
...
except OSError: # <- 外层只捕获 OSError
pass
而中转链路上 send 抛的是 RelayError,直接冒出去把线程打死。
**死一次就永久哑**,代价太大。
修法:session.send() 单独兜住所有异常,失败就断开这个会话
(让手表重连),绝不让线程消失。外层也补一个兜底 except。
== 修复效果 ==
修复前:pair OK -> ping OK -> ping 卡死 -> 线程崩溃,永久哑
修复后:pair 21ms -> ping 82ms -> ping 4ms -> ping 6ms,零异常
_tests/test_relay_local.py:**之前完全卡死 -> 现在 21/21 全过**
这个测试之前一直是红的,说明「35 个阶段的回归全绿」这件事
本身就不成立过 —— 某个阶段卡住时看起来像环境问题而不是缺陷。
== 同批修掉验证阶段找到的其它问题 ==
1. **TTL 清理 + 关掉接入开关 = 设备永久锁死**(严重)
register() 用 `device_id in self.devices` 判断老设备,但超时清理
把设备从 devices 和 codes 都删了。电脑关机过夜(必然 > 6 小时)
第二天开机就会被自己的开关挡住,而且**重启也救不回来**
(清 codes 时没落盘,重启后又当孤儿清一遍,自我维系)。
管理员在页面上还看不到这台设备,不知道该放开谁。
修法:状态文件里单独存 knownDevices 设备历史,超时清理绝不动它;
register 改查这份历史;清理后补 _save();delete_device 才真正忘记。
2. **管理页 JS 语法错误 -> 所有按钮失效**(严重)
admin.py 里 ADMIN_JS 是普通三引号字符串,我在 Python 源码里写的
`\n` 被变成真实换行,拼进 JS 就是字符串里的裸换行 ->
整段 <script> 不执行 -> 改码/删除/批量删除/全选全变幽灵按键。
元凶是我加改码功能那次提交。改成 `\\n`。
3. **--relay / --relay-status 崩溃**(严重)
RelayLink.start(url) 的 url 是必填参数,我把默认地址改空白时
run_agent.py 两处调用没跟着改。一处被 try 吞成「测试失败:missing
1 required positional argument」(用户以为地址填错了),
一处直接报栈。两处都补上 url。
4. 非法 deviceId(控制字符 / 路径分隔符)现在被拒(白名单:
字母数字 + - _ . :)
5. enable_relay() 第二次调用不再谎报成功(加 RelayLink.running 属性,
返回值反映真实运行状态;不再用「对象建过没有」判断)
6. snapshot() 清孤儿码后补 _save(),磁盘和内存不再脱节
== 局域网全面优化(task-2,另一个智能体完成)==
4 个真问题:
* **网段判断写死 /24** —— 在 /16 网络上 10.8.1.5 和 10.8.2.5
被判成不同网络,明明能直连却去走中转还费流量。
改成按网卡真实掩码比较。
* **广播只发一个包** —— 丢一个就整轮搜不到。改成 5 个时间点重发。
* **只探一个网段** —— 多网卡手表搜不到另一网段的电脑。改成全探。
* **走中转之后再也回不到局域网** —— 出门再回家永远挂中转。
新增定期回切(先便宜地 TCP ping,通了才动真格,
不破坏正在工作的中转连接)。
实测:局域网握手 2ms、50/50 = 100%、中位 0ms。
(跨网络那半之前拿不到数字,就是被上面那个线程崩溃挡住的 ——
现已修复。)
顺带:instance.py 单实例锁端口加 YKXMZ_LOCK_PORT 覆盖(默认不变)。
用户自己那台被控端正跑在这台机器上占着 8767/TCP + 8766/UDP,
是这次排查里好几个「测试怎么都不通」的根因。
== 服务端一键部署脚本(task-1,另一个智能体完成)==
relay/deploy.sh + relay/deploy-tune.sh,随服务端 zip 发布:
* 自动检查并安装 cloudflared
* 部署程序、装 4 个 systemd 服务、网络调优、配隧道、自检打公网地址
* **不传 --hostname 且没有 TUNNEL_TOKEN 时明确提示要填**,
不会静默用一个写死的域名(用户明确说过这个中转站是他自己用的,
不对外开放,绝不能把私人域名烧成默认值)
* 重跑 = 升级,已有数据/配对码/管理密码/凭据都不动
顺带修了发布包一直缺 site_shots/ 的老 bug
(package_remote.py 用 iterdir()+is_file() 收文件,目录进不去,
新机器装完官网截图全是 404)。
== 默认中转地址改成空白 ==
用户要求:软件默认不带任何中转地址,由用户自己填。
四处硬编码全部清空(server.py / MainActivity.java / run_agent.py /
relay.py 的注释和文档示例),全仓已无非注释的硬编码地址。
== 文档同步(task-5,另一个智能体完成)==
README / BEFORE-INSTALL / AFTER-INSTALL / 打包模板:
* 新增「软件里不带任何中转地址」告示,讲清跨网络要自己部署
* 修正 Android 版本(写的是 9.0/API28,实际 API 24 / Android 7.0)
* 删掉「地址已经内置好了」(现在是错的)
* AGENT_README 原来把作者自己的中转地址当成「内置地址」发给用户,已重写
* RELAY_README 原来写「两条隧道同时跑会互相争抢」,
和真机/CHANGELOG 矛盾;按实测改成「两种协议同时在跑(故意的)」
|
2026-10-07 17:59:39 +08:00 |
|
xmz7891
|
d4f335b8f9
|
管理页新增:允许/禁止新设备接入 + 修改设备配对码
== 功能一:允许新设备接入开关 ==
关掉之后只有已经登记过的电脑能继续上线,没见过的新设备一律拒绝。
状态存在 relay_state.json 里,重启不丢。
判断新设备用的是 deviceId,不是配对码 ——
这一点很关键:管理员给老设备改过码之后,
那台设备必须仍然能进来;按码判断的话它会被自己的开关挡在外面。
测试里专门有一条 A3b 守这个行为。
被拒时回 blocked=True,让客户端能区分被管理员挡了
和配对码冲突,而不是去匹配中文错误文案(文案一改就坏)。
== 功能二:修改任意设备的配对码 ==
管理页每台设备一个「改码」按钮,填 6 位数字即生效。
要点:
* 新旧码冲突检查
* 旧码映射必须删掉,否则变成孤儿条目,
管理页配对码占用的数字会虚高
* 改码踢掉该设备当前会话(会话绑在码上)
* 立刻持久化
== 顺手修掉两个真 bug ==
1. 改码功能一上线就 500:Session 用了 __slots__,
根本没有 session_id 属性(会话是以 token 为 key 存在 dict 里的),
写 s.session_id 直接 AttributeError。
改成遍历 sessions.items() 拿 key。
2. snapshot() 里有同样的错(清理超时设备那段):
平时 stale 列表是空的所以从没炸过,一旦真有设备超过 6 小时
没上报就会 500。这次一起修了。
== 管理页 UI ==
* 设备小节加了「允许新设备接入」勾选框
* 每行加「改码」按钮(走 prompt,不引入弹窗层)
* 改码按钮用 data-recode 而不是 data-device ——
现有单台删除按 button.danger[data-device] 绑,
带上 data-device 会被当成删除按钮,点改码变删设备。
测试 C6 专门断言这一点。
== 测试 ==
新增 _tests/test_admin_device_control.py(23 项),
已注册进 _tests/verify_all.ps1 第 3e 阶段。
|
2026-10-07 17:01:14 +08:00 |
|
xmz7891
|
aea78efe45
|
版本 1.8.1 -> 1.8.2
|
2026-10-07 15:44:49 +08:00 |
|
xmz7891
|
bf15a2ee61
|
静态资源交给 Cloudflare 边缘缓存(下载提速 41 倍)
实测基线:
ykxmz-setup-1.8.1.exe 只下到 237568/15067199 字节就停(17 KB/s)
ykxmz-watch-1.8.1.apk 201523 字节 / 66.8 秒 = 3 KB/s
ykxmz-server-1.8.1.zip 214900 字节 / 3.1 秒 = 67 KB/s
原因:relay 给所有响应都发 Cache-Control: no-store,
Cloudflare 完全不缓存 —— 每次下载/每张图都要穿隧道回 4G 源站。
改动:
/download/* no-store -> public, max-age=604800, immutable
文件名带版本号,同一名字内容永不改变,可以长缓存
/ -> public, max-age=60(挡住刷新重复回源)
/favicon.ico 已是 max-age=86400,保持不变
/shot/* 已是 max-age=86400,保持不变
/v1/* 保持 no-store(接口必须实时)
顺带修两个自己踩的坑:
1. _send() 无条件先发 no-store,再追加 extra 里的 Cache-Control,
结果是**两个 Cache-Control 头**,客户端只认第一个 ——
调用方传的缓存策略被吃掉。改成 extra 里有就不再发默认值。
2. do_HEAD 和 do_GET 的缓存头不一致:HEAD 对非 /shot/ 一律报 no-store。
下载器/代理用 HEAD 探测时会误判文件不可缓存。
现在 _head_info 给每个路由带上和 GET 完全相同的缓存头,
do_HEAD 也不再盲目覆盖。
结果(同一个测试口径):
setup.exe 17 KB/s(截断) -> 695 KB/s 完整 15 MB 41 倍
watch.apk 3 KB/s -> 101 KB/s 33 倍
server.zip 67 KB/s -> 87 KB/s
三个文件全部 cf-cache-status=HIT(不再穿隧道回源)
排查过程记一笔:我一开始用 curl -I 验证,
看到的全是 no-store —— 因为 **-I 发的是 HEAD**,
而 HEAD 走的是另一个分支。查了半天才发现是验证方法错了。
|
2026-10-07 15:44:48 +08:00 |
|
xmz7891
|
b1119d0da6
|
修「局域网连接被中转劫持」+ 长轮询 15s→60s
用户报两个问题:
1. 局域网能搜到电脑,IP/端口/配对码都对,点连接**没有任何反应**
2. 走中转 1426ms 延时,一会儿就 502
========== 一、局域网被劫持(真 bug)==========
MainActivity.connect() 里:
boolean usedLan = !relayUrl().isEmpty() ? false : true;
if (!relay.isEmpty()) {
reply = client.connectViaRelay(...); // 走中转
} else {
reply = client.connect(host, port, ...); // 才走局域网
}
**只看「中转设置里填没填地址」,填了就一律走中转,
完全无视用户填的局域网 IP 和端口。**
而中转单次超时是 40 秒 —— 用户点完就卡在「正在连接…」
四十秒,在他看来就是"没有任何反应"。
影响两条路:
* 「局域网连接」页面填 IP 直连(connectLanTyped)
* 首页「已配对的电脑」点一下重连(quickConnect)
修法:给 connect() 加 preferLan 参数。明确要走局域网时
**先直连(6 秒超时,局域网本该是毫秒级),失败再退回中转**,
保证最终能连上。
顺带两个体验改进:
* 设备列表里,保存的 IP 和手表不同网段时标「⚠ 地址可能已过期」
(用户截图里那个 192.168.2.64 就是过期地址,电脑早换成
192.168.10.103 了)
* quickConnect 判断出地址过期就跳过直连,直接走中转查最新地址 ——
省掉那 6 秒白等
========== 二、长轮询 15 秒 -> 60 秒 ==========
实测隧道每次请求要多花 1~2 秒(CF 边缘路由 + 回源):
本机直连 60 秒的 poll -> 60.0s
走公网同一个 poll -> 61.5~62.0s
这个开销是**按次数**收的:
15 秒一循环 -> 一天 5760 次/端,光路费 2.4~3.2 小时
60 秒一循环 -> 一天 1440 次/端,光费 0.6~0.8 小时
而且请求越频繁,撞上丢包/重连窗口的概率越大。
**不会让命令变慢**:长轮询是"有数据立刻返回"(device.event 唤醒),
拉长只是减少空转重开。实测 60 秒长轮询穿隧道 4/4 通过;
Cloudflare 单请求上限 100 秒,留了 40 秒余量。
三层同步改(客户端超时必须显著大于服务端 hold):
* relay/relay.py RELAY_POLL 默认 25 -> 60
* windows/lanremote/relay.py POLL_TIMEOUT 40 -> 90
* android .../RelayTransport.java POLL_TIMEOUT_MS 40000 -> 90000
========== 三、A/B 实测(还在跑更严谨的一轮)==========
短测发现基线 http2+4 虽然 100% 成功,但 **P90 是 16.8 秒**
(每 10 次有 1 次卡十几秒 —— 正是用户说的"没反应"),
而 quic+6 的 P90 只有 2.8 秒。正在用真实长轮询模式
跑 10 分钟对照确认。
|
2026-10-07 14:11:33 +08:00 |
|
xmz7891
|
49fdec1da8
|
1.8.0:隧道稳定性大修 + 中转管理后台重做
版本号 1.7.1 -> 1.8.0(8 个文件,13 处)。
写了个 bump_180.py 统一处理 —— 版本号散落在
Python / Java manifest / PowerShell / Inno Setup / 测试里,
手动改必漏;而且 .ps1 必须保留 UTF-8 BOM。
==================== 一、隧道稳定性(用户撞上 1033/502/530)====================
1) 4G 链路会**静默断开 TCP 连接**(不发 FIN/RST,双方都以为还活着),
Linux 默认要 2 小时才发现 -> 改成 keepalive 30 秒 + 每 10 秒探测。
实测 60 秒内发现并重连。
2) 隧道连接数 2 -> 4。只剩 1 条连接时跨机房回源会失败
(实测可用率 0/12,提到 4 条后 12/12)。
3) 掉电重启后恢复慢:原来开机 2 分 10 秒公网才可用。
根因是 systemd-networkd-wait-online 等一个不归它管的网络
(wlan0 是 NetworkManager 管的),干等 2 分钟超时失败,
把 network-online.target 一起拖住。
mask 掉它之后:启动 17.3 秒,掉电重启 24 秒公网可用。
4) 看门狗两次翻车都是我造成的,都修了:
a) 我先加了"确认故障立刻重启",但隧道重建 4 条连接要 30~60 秒,
MIN_RESTART_GAP=45 导致**还没建齐就被下一次重启打断**,
死循环,每 50 秒重启一次 -> 加 150 秒宽限期
b) 宽限期又带来"链路持续差时什么都不做"的问题
-> 增加"本次重启后建齐过吗"的判断:
建齐过又垮 = 真故障,立刻重启
从没建齐 = 还在努力,继续等
5) 顺手修:看门狗状态文件里的变量在函数定义前被引用,
set -u 下报 "BUILT: 未绑定的变量"。
==================== 二、中转管理后台重做 ====================
原来的管理页是几百行内联在 relay.py 的 f-string 里,
改一处样式要在一堆转义花括号里翻。拆成独立模块 relay/admin.py。
新设计(对齐官网那套白色简洁):
吸顶顶栏 -> 公网地址(最大权重,这是用户最需要复制的)
-> 四个数字卡 -> 设备表 -> 工具卡(体检 + 下载)
空状态给图标+引导文案;窄屏表格自动变卡片。
relay.py 从 2089 行缩到 1500 行左右。
修了三个我自己引入的 bug:
a) 响应头类型写错,浏览器把整页 HTML 当纯文本显示
(用户看到的是"一屏源码")。教训:200 不等于页面能看,
当时只验了状态码和字节数。
b) 顶栏用了固定 height:62px,装不下"标题+副标题+版本信息"两行,
内容被顶出容器 -> 标题看不见、徽章压在字上。
c) do_HEAD 没处理 /download/ 路径,HEAD 一个 APK 返回
Content-Length: 0 + text/plain,下载器会误判成空文件。
同时发现 _head_info 里对 bytes 调 .encode() —— 加 _as_bytes() 兜住。
==================== 三、其它 ====================
* 设备记录只增不减:实测一天攒 75 条僵尸,/v1/health 从几 KB 涨到 20KB。
加 DEVICE_TTL(6 小时)自动清,占用的配对码一起释放
(管理页曾显示"配对码占用 131"但一台设备都没有)。
* HEAD 请求支持(原来一律 501)。
* 官网新增产品展示:手表界面预览(颜色/布局与真实手表一致)、
电脑端真实截图、两种连接方式对比图。
* 加了一份 docs/免费托管方案.md(含一个纠错:上一版推荐的
ClawCloud Run 已在 2026-05-11 关停)。
==================== 验收 ====================
全量回归 33 个阶段全部通过
安装包完整周期 20/20(静默装入 7 秒、卸载 0.6 秒、无残留)
网页响应头 12/12
公网可用率 26/30 = 86%(底层 4G 丢包 30%,是这个上限)
发布产物 安装包 14.37MB / 服务端 0.20MB / 手表 0.19MB
|
2026-10-07 11:47:12 +08:00 |
|
xmz7891
|
fe3b9ada12
|
修管理页 Content-Type(浏览器显示成一屏源码)
用户截图:打开 relay.xxmxxs.top/zhongzhuanzhan 看到的**不是页面,
而是整页 HTML 源码**。
根因:_send() 的 content_type 参数默认是 application/json,
我重做管理页时调它没传 text/html —— 浏览器按 JSON/纯文本处理,
就把 HTML 原样显示了。
⚠ 更该反省的是:**我当时没发现。**
验证脚本只看了 HTTP 200 和字节数,两个都对,就以为没问题。
200 不等于页面能看 —— 响应头也得看。
顺手修的第二个问题:
HEAD 请求一律返回 501 Unsupported method(没实现 do_HEAD)。
浏览器用 GET 所以页面照开,但 Cloudflare 健康检查、
很多监控/代理探测都用 HEAD,curl -I 也是。
补了 do_HEAD:按路径算出类型和长度,只回头不回体。
新增测试 _build/test_response_headers.py(9 项):
* HEAD 各路径不能是 501
* 官网/管理页 Content-Type 必须是 text/html,不能是 json
* favicon、产品截图必须是 image/*
并加进全量套件(阶段 3b6)。
验证:该测试 9/9 通过;全量套件 33 个阶段全部通过。
|
2026-10-07 09:53:43 +08:00 |
|
xmz7891
|
1f05084f51
|
隧道"假活"真因修复 + 中转管理后台重做
用户反馈两件事:
1. 早上打开 relay.xxmxxs.top 看到 Cloudflare Error 1033 —— "这就是你说的优化好了?"
2. "中转管理那边 ui 界面大改,现在难看的要死"
============ 一、隧道 1033/502/530 的真因 ============
之前我只测 15~25 次就说"可用率 100%",太草率 ——
实际是**间歇性假活**:隧道显示 ha_connections=2 连着,
公网却 502/530/000。watchdog 日志把过程记了下来。
根因:**4G 链路静默断开 TCP 连接**(不发 FIN/RST,双方都以为还活着)。
Cloudflare 把请求发到这条僵尸连接上就回错误。
Linux 默认 tcp_keepalive_time = **7200 秒(2 小时)**才发现,
中间这段时间服务一直"看着在线、实际打不开"。
修法(都有实测数据支撑):
1. tcp_keepalive 30 秒 + 每 10 秒探测 3 次
=> 最多约 60 秒发现僵尸连接并重连
2. 隧道连接数 2 -> 4
(对照实验:http2+2 只有 1 条连上时公网 0/12;
http2+4 稳定 12/12。连接数太少会出现"只有 1 条"的危险窗口)
3. 协议定为 http2(cloudflared 自检说 TCP 7844 被墙,
但实测 http2 反而比 quic 稳)
实测:http2+4 之后公网 40/40 = 100%,最慢 1.3 秒,0 次重连。
============ 二、中转管理后台重做 ============
原来是几百行内联在 relay.py 的 f-string 里(HTML+CSS+JS 全挤一起),
改一处样式要在一堆转义花括号里翻。拆成独立模块 relay/admin.py。
新版设计(对齐官网那套白色简洁):
* 吸顶顶栏:品牌 + 运行状态 + 版本/内存/请求数
* 公网地址给最大权重(这是用户最需要复制的东西)
* 四个数字卡(在线/会话数带颜色区分)
* 设备表卡片:状态圆点、等宽配对码、行悬停
* 工具卡:网络体检 + 下载区并排
* 空状态改成图标+引导文案,不再是干巴巴一句
* 窄屏时表格自动变卡片
relay.py 从 2089 行缩到 1550 行。
============ 三、顺手修的两个真 bug ============
1. 设备记录只增不减
self.devices 从不清理,被控端每次换端口/重装就多一条,
实测一天攒了 75 条僵尸记录,/v1/health 从几 KB 涨到 20KB。
加 DEVICE_TTL(6 小时)在 snapshot() 里清。
2. 配对码映射也没清
RELAY.codes 和 devices 是一对孪生 dict,
只清 devices 会留下指向已删设备的孤儿条目 ——
管理页显示"配对码占用 131",实际一台设备都没有。
现在一起清。
============ 四、我自己造的两次事故(如实记录) ============
1. 删文件删过头
用"从 _admin_page 截到文件末尾"的方式替换方法,
但 _admin_page 后面**还有 _esc/_kb/_uptime/class Server/main()**。
结果 relay 起不来,systemd 重启 24 次,所有接口 000。
教训:**"删到文件末尾"之前必须先确认末尾有什么。**
2. ADMIN_PATH 引用了不存在的常量
路由里写的是字面量,我却在新代码里用了 ADMIN_PATH,
管理页一访问就 NameError。现在统一定义了常量。
二者都是"改完没立刻验"造成的。现在每次改完都跑一遍
管理页 + 官网 + 关键接口的回归。
|
2026-10-07 09:34:53 +08:00 |
|
xmz7891
|
8c4ee6df08
|
修真凶:4G 链路静默断连导致隧道"假活"(用户撞上 Error 1033)
现象:用户早上打开 relay.xxmxxs.top 看到 Cloudflare Error 1033。
watchdog 日志把过程记下来了:
公网不通(HTTP 000,ha_connections=2) <- 隧道显示连着,公网不通
公网不通(HTTP 502,ha_connections=2)
公网恢复(HTTP 200) <- 自己又好了
也就是说:**间歇性假活**,短样本测不出来。
我之前只测 15~25 次就说"可用率 100%",太草率了。
根因:4G 链路会**静默断开** TCP 连接 —— 不发 FIN/RST,双方都以为还连着。
Cloudflare 把请求发到这条僵尸连接上,就回 502/530/1033。
Linux 默认 tcp_keepalive_time = **7200 秒(2 小时)**才发现连接死了,
所以中间这段时间服务一直"看着在线、实际打不开"。
tunnel.log 最近 3000 行的佐证:
i/o timeout 212 次
Retrying connection 127 次
Lost connection 22 次
修法:
1. TCP keepalive 调到 30 秒 + 每 10 秒探测 3 次
=> 最多约 60 秒发现僵尸连接,cloudflared 立刻重连
(/etc/sysctl.d/97-ykxmz-keepalive.conf)
2. 隧道连接数 2 -> 4,多几条冗余
3. 修 relay 从不清理设备记录的问题:
self.devices 只增不减,被控端每换端口/重装就多一条,
实测一天攒了 75 条僵尸记录,/v1/health 从几 KB 涨到 20KB。
加 DEVICE_TTL(6 小时)在 snapshot() 里清掉。
实测(修完立刻验证):
设备侧 公网 15/15 HTTP 200
Windows 公网 25/25,中位 520 ms,最快 463 ms
health 20931 字节 / 75 台 -> 152 字节 / 0 台
|
2026-10-07 09:15:27 +08:00 |
|
xmz7891
|
7b7830d8c5
|
阶段3+4+5:管理页白色化 + 局域网诊断 + 全量验收
阶段3 管理页
- 清掉全部深色主题残留(地址框 #0b1220、按钮悬停 #262c36、
删除按钮 #2d1214 等 11 处),现在 0 处深色
- 接口全部正常:health / downloads / netcheck
阶段4 局域网诊断
- 查清楚"电脑不在同一网络"是怎么回事:被控端是**对的**
(lan_ips() 报 ['192.168.2.64', '169.254.83.107'],真实地址排第一),
是手表当时真不在 192.168.2.x —— 可能连了 5G 频段的另一个路由器,或开着移动数据
- 所以改了手表的提示:不再只说"不在同一网络",
而是把**双方地址都打出来**,用户一眼能看出是网段不同还是连错 WiFi
- 端到端验证局域网路径:认证 1 ms,功能列表 207 项
阶段1 结论修正(一天内推翻自己两次,如实记录)
- 第一次判断"优选 IP 无用":错。半小时内确实能避开超时 IP
- 第二次固定优选 IP:也错。那些实测 138ms 的 IP 半小时后全不通了
—— Cloudflare anycast 可达性随运营商路由变化,固定 IP 等于钉死到会失效的地址
- 最终:不固定,靠 GeoDNS + cloudflared 重试 + ha-connections 2 冗余
- 撤销后立即变好:连接稳定 2 条、超时告警 0 次、设备侧延迟 4.40s -> 1.66s
- **真正的瓶颈**:分段实测发现本机到 Cloudflare 的**裸 RTT 就 1439 ms**,
比经隧道的请求(1215 ms)还慢。说明瓶颈是这条网络的 4G 上行本身,
不是设备的隧道。公网侧已无优化空间。
- 局域网 118 ms vs 公网 1215 ms,快 10 倍 —— 这才是该走的路
阶段5 验收
- 全量回归 29 项全通过
- 设备侧验收 38 项全通过
- 成品:setup exe 14.37 MB / server zip / watch apk,已同步到设备下载区
|
2026-10-07 00:13:10 +08:00 |
|
xmz7891
|
566d9e384f
|
阶段1+2:恢复 HTTP/2 加速 + 重做官网
阶段1 网络加速
- 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2),
实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。
恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。
- 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP),
但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。
已清掉这段无效配置。
- BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66,
没编 tcp_bbr 模块也没配置项。改用 cubic + 调优:
TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0
(长轮询场景关键)、tcp_tw_reuse=1。
阶段2 官网重做
- 拆出独立模块 relay/landing.py(relay.py 已两千多行,
再塞 HTML 没法看)
- 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 /
三步上手 / 下载 / FAQ / 页脚
- 保持白色简洁:底白、浅灰面板、细边框、大量留白
- logo 用内联 SVG(ICO 放 img 里实测是破图)
- 管理入口从官网移除(管理不对外开放)
过程中被自己的测试抓到两个 bug:
- 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库
- _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError
|
2026-10-06 23:53:19 +08:00 |
|
xmz7891
|
caf774791f
|
修官网 .lead 的文字色
那一处还是深色主题配的浅灰(#c9d1d9),白底上几乎看不见。
上一版脚本没改成功,因为判断条件写成了新值已存在就跳过,
而 color:#1f2328 在别处已经出现过,导致整个替换被跳过。
|
2026-10-06 23:10:49 +08:00 |
|
xmz7891
|
51565112b9
|
网页改成白色简洁 + 修官网两个 bug
配色
- 官网 / 登录页 / 管理页从 GitHub 深色风改成白色简洁
(用户的设计理念:所有东西都要白色简洁)
- 白底上把浅灰文字换成深色,链接蓝也调深一档保证对比度
修 bug
- 管理入口的链接拼接写坏了,页面上显示成 href= + ADMIN_PATH +,
点了打不开管理页
- .lead 用的是深色主题的浅灰,白底上几乎看不见
其他
- 清掉提交信息里的 BOM(PowerShell 的 Set-Content 加的)
|
2026-10-06 23:10:08 +08:00 |
|
xmz7891
|
81e923a0aa
|
1.7.1:修白屏 + 配对 + 官网管理页分家
手表端
- 修「连上后一片白屏」:initTabs() 从来没被调用过,
导致 tabDefs 为 null、selectTab 直接 return,
标签栏里只剩一个孤立的滑动指示器
- 应用名改成 ykxmz
被控端
- 修「设备已关闭新设备配对」:配对码本身就是密钥,
拿着正确的码就该放行。老逻辑"首台配对后自动关窗"会把
手表重装的人永久挡在外面
- 应用名改成 ykxmz
服务器端
- / 改成官网(公开,简洁介绍 + 下载)
- /zhingzhuanzhan 中转站管理(要登录),/admin 自动跳转
- 管理账号 xmz7891,可改
- 官网 / 登录页 / 管理页都加上 favicon
|
2026-10-06 23:08:07 +08:00 |
|
xmz7891
|
ce87ec1650
|
1.7.0:常规发布
- 版本号 1.7.0 -> 1.7.0
- 重建三端并打包
- 关键测试全过
|
2026-10-06 23:08:07 +08:00 |
|
xmz7891
|
4c88ca901f
|
1.6.2:常规发布
- 版本号 1.6.2 -> 1.6.2
- 重建三端并打包
- 关键测试全过
|
2026-10-06 23:08:07 +08:00 |
|
xmz7891
|
65dc2fad65
|
1.6.1:常规发布
- 版本号 1.6.1 -> 1.6.1
- 重建三端并打包
- 关键测试全过
|
2026-10-06 23:08:07 +08:00 |
|
xmz7891
|
07e96a9595
|
1.6.0:重写 README,面向老师 / 电教 / 开发者 / 普通用户
README
- 原来的 README 是开发者视角(在解释我为什么没做某功能),
而且通篇 AI 腔。这次按用户视角重写,讲怎么用。
- 开头就分流:老师 / 电教 / 开发者 / 普通用户,各看各的
- 定位改成教室电脑手表遥控,主场景是老师上课管教室那台电脑
- 加了电教专用一节:机房同传部署、脚本批量推、网络规划、权限收口
- 加了开发者一节:三端怎么编译、怎么跑测试、怎么打包
- 加了通信协议一节
- 常见问题、排错顺序、错误对照表都补全
版本号
- 1.5.0 -> 1.6.0(versionCode 107)
- 顺手写了通用的版本升级脚本 _build/bump_version_generic.py,
以后升版本一条命令就行,不用手改十几个文件
|
2026-10-06 23:08:07 +08:00 |
|
xmz7891
|
60d1bdfe58
|
中转站加下载区 + 大文件流式发送
下载区
- 管理页加一张「下载最新版」卡片,就一个按钮
自动指向 /v1/downloads 里最新的被控端整包,不用每次改页面
- GET /v1/downloads 列出成品
- GET /download/<文件> 直接给文件
只允许白名单后缀(.apk/.zip/.txt/.exe/.idsig)
文件名严格校验,挡目录穿越(../ 和 ..%2F 都试过)
- 卡片下面附一个 Gitee 历史版本入口
大文件
- 原来 read_bytes() 一次性读进内存再 write。
19MB 在 460MB 的设备上勉强,更要命的是 Cloudflare
有 100 秒的源站响应上限,数据越晚开始流动越容易被掐。
- 改成 64KB 分块流式发送,边读边发
实测:19MB 公网下载 40 秒完成,字节数完全一致
版本
- 三端分开打包(原来是一个合并 zip)
ykxmz-agent-1.5.0.zip 被控端
ykxmz-server-1.5.0.zip 服务器端
ykxmz-watch-1.5.0.apk 控制端(直接给 APK)
|
2026-10-06 23:08:06 +08:00 |
|
xmz7891
|
b62ac865ea
|
ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示
手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测
服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接
文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
|
2026-10-06 19:01:05 +08:00 |
|