mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 13:40:45 +00:00
ykxmz 1.5.0:手表端 TLS 证书兼容 + 三端分开打包 + 下载区
手表端
- 内置 GTS Root R4 / GlobalSign Root CA
修 SSLHandshakeException: Chain validation failed
- 连接错误翻译成人话:证书 / 时间 / 解析 / 超时分别给提示
- 经中转连上后用服务器报的地址刷新已配对记录
- 版本号 1.3.0 -> 1.5.0(AndroidManifest 之前漏改了)
服务器端
- 支持 chunked 请求体,修每 16 秒一次的连接错乱与大面积 502
- 网络调优 net-tune.sh(开机自动应用)
- 新增下载区,管理页加下载卡片
- 隧道改 HTTP/2 + 双连接
打包方式变更
- 从一个合并 zip 改成三个独立产物:
ykxmz-agent-*.zip 被控端
ykxmz-server-*.zip 服务器端
ykxmz-watch-*.apk 控制端
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""把打包改成"三端分开出"。
|
||||
|
||||
用户要求:
|
||||
"三端分别打包,控制端不用打包成 zip,直接上传 apk"
|
||||
|
||||
所以产物是三个文件(原来是一个大 zip):
|
||||
dist/ykxmz-agent-1.5.0.zip 被控端(Windows)
|
||||
dist/ykxmz-server-1.5.0.zip 服务器端(随身 WiFi / Linux)
|
||||
dist/ykxmz-watch-1.5.0.apk 控制端(手表,直接给 APK 不打 zip)
|
||||
|
||||
三个包里都带上三份文档(许可协议 / 安装前必读 / 安装后须知),
|
||||
这样用户拿到哪一个都知道自己在装什么、有什么红线。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||
PKG = ROOT / "_build" / "package_remote.py"
|
||||
|
||||
# 从 "def main() -> int:" 起,到文件末尾的 if __name__ 之前,全换成新的
|
||||
NEW_MAIN = '''def _zip_dir(zip_path: Path, source: Path, arc_root: str) -> int:
|
||||
"""把一个目录打成一个 zip(内部带一层 arc_root 目录名)。"""
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
count = 0
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED,
|
||||
compresslevel=6) as archive:
|
||||
for item in sorted(source.rglob("*")):
|
||||
if item.is_file():
|
||||
archive.write(item, Path(arc_root) / item.relative_to(source))
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def _copy_docs(target: Path) -> int:
|
||||
"""把三份正式文档拷到目标目录(UTF-8 BOM + CRLF)。"""
|
||||
done = 0
|
||||
for name in ("LICENSE.txt", "BEFORE-INSTALL.txt", "AFTER-INSTALL.txt"):
|
||||
src = DOCS / name
|
||||
if src.is_file():
|
||||
write_readme(target / name, src.read_text(encoding="utf-8"))
|
||||
done += 1
|
||||
else:
|
||||
print(f" [警告] 缺少文档 {src}")
|
||||
return done
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not AGENT_SRC.is_dir():
|
||||
print(f" [失败] 找不到被控端: {AGENT_SRC}")
|
||||
return 1
|
||||
if not APK_SRC.is_file():
|
||||
print(f" [失败] 找不到 APK: {APK_SRC}")
|
||||
return 1
|
||||
|
||||
dist = ROOT / "dist"
|
||||
dist.mkdir(parents=True, exist_ok=True)
|
||||
work = dist / "_build_tmp"
|
||||
if work.exists():
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
results = []
|
||||
|
||||
# ================= 1. 被控端 =================
|
||||
print(" [1/3] 被控端(Windows)")
|
||||
agent_name = f"ykxmz-agent-{VERSION}"
|
||||
agent_dir = work / agent_name
|
||||
shutil.copytree(AGENT_SRC, agent_dir,
|
||||
ignore=shutil.ignore_patterns("data", "received",
|
||||
"*.idsig", "*.log"))
|
||||
for tool in TOOLS:
|
||||
if not (agent_dir / tool).is_file():
|
||||
src = ROOT / "tools" / tool
|
||||
if src.is_file():
|
||||
shutil.copy2(src, agent_dir / tool)
|
||||
write_readme(agent_dir / "README-agent.txt", AGENT_README)
|
||||
for junk in ("先看这个.txt", "使用说明.txt"):
|
||||
target = agent_dir / junk
|
||||
if target.is_file():
|
||||
target.unlink()
|
||||
_copy_docs(agent_dir)
|
||||
if not (agent_dir / "ykxmz.exe").is_file():
|
||||
print(" [失败] 被控端里没有 ykxmz.exe")
|
||||
return 1
|
||||
agent_zip = dist / f"{agent_name}.zip"
|
||||
n = _zip_dir(agent_zip, agent_dir, agent_name)
|
||||
results.append(("被控端", agent_zip, n))
|
||||
|
||||
# ================= 2. 服务器端 =================
|
||||
print(" [2/3] 服务器端(随身 WiFi / Linux)")
|
||||
server_name = f"ykxmz-server-{VERSION}"
|
||||
server_dir = work / server_name
|
||||
server_dir.mkdir(parents=True)
|
||||
# 文件清单写死容易漏 —— watchdog.sh / net-tune.sh 都漏过一次。
|
||||
# 改成:relay 目录里所有 .py / .sh 都带上。
|
||||
wanted = sorted(
|
||||
[f for f in RELAY_SRC.iterdir()
|
||||
if f.suffix in (".py", ".sh") and f.is_file()],
|
||||
key=lambda f: f.name)
|
||||
for src in wanted:
|
||||
shutil.copy2(src, server_dir / src.name)
|
||||
write_readme(server_dir / "README-server.txt", RELAY_README)
|
||||
_copy_docs(server_dir)
|
||||
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh"):
|
||||
if not (server_dir / must).is_file():
|
||||
print(f" [警告] 服务器端少了 {must}")
|
||||
server_zip = dist / f"{server_name}.zip"
|
||||
n = _zip_dir(server_zip, server_dir, server_name)
|
||||
results.append(("服务器端", server_zip, n))
|
||||
|
||||
# ================= 3. 控制端(直接给 APK)=================
|
||||
print(" [3/3] 控制端(手表)—— 不打 zip,直接给 APK")
|
||||
# 顺带把说明也放一份在旁边,方便看
|
||||
watch_note = dist / f"ykxmz-watch-{VERSION}-说明.txt"
|
||||
write_readme(watch_note, WATCH_README)
|
||||
results.append(("控制端", APK_SRC, 1))
|
||||
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
# ================= 汇总 =================
|
||||
print()
|
||||
print(" ================ 产物 ================")
|
||||
ok = True
|
||||
for label, path, count in results:
|
||||
if not path.is_file():
|
||||
print(f" {label}: ✗ 缺失!")
|
||||
ok = False
|
||||
continue
|
||||
size = path.stat().st_size / 1024 / 1024
|
||||
extra = f"({count} 个文件)" if count > 1 else ""
|
||||
print(f" {label:6} {path.name:34} {size:6.2f} MB {extra}")
|
||||
if not ok:
|
||||
return 1
|
||||
|
||||
# ================= 验证 =================
|
||||
print()
|
||||
print(" 验证压缩包内容:")
|
||||
for label, path, _count in results:
|
||||
if path.suffix != ".zip":
|
||||
continue
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
names = archive.namelist()
|
||||
bad = archive.testzip()
|
||||
if bad:
|
||||
print(f" [失败] {path.name} 里有坏文件: {bad}")
|
||||
return 1
|
||||
print(f" {path.name}: {len(names)} 个条目,完整")
|
||||
|
||||
with zipfile.ZipFile(results[0][1]) as archive:
|
||||
names = archive.namelist()
|
||||
if not any("ykxmz.exe" in n for n in names):
|
||||
print(" [失败] 被控端包里没有 ykxmz.exe")
|
||||
return 1
|
||||
with zipfile.ZipFile(results[1][1]) as archive:
|
||||
names = archive.namelist()
|
||||
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh"):
|
||||
if not any(must in n for n in names):
|
||||
print(f" [失败] 服务器端包里没有 {must}")
|
||||
return 1
|
||||
|
||||
print()
|
||||
print(" 三个产物齐全")
|
||||
return 0
|
||||
|
||||
|
||||
'''
|
||||
|
||||
# 手表说明单独抽出来(原来嵌在 main 里)
|
||||
WATCH_README_BLOCK = '''WATCH_README = f"""========================================================
|
||||
控制端(安卓手表) 版本 {VERSION}
|
||||
========================================================
|
||||
|
||||
【装】
|
||||
|
||||
把 ykxmz-watch-{VERSION}.apk 传到手表上安装。
|
||||
手表如果提示"未知来源",在设置里允许一下。
|
||||
|
||||
|
||||
【连】
|
||||
|
||||
1) 打开 App,首页有四个东西:
|
||||
中转设置(跨网络用) 填服务器端给的地址
|
||||
局域网连接(同一个WiFi)同一个 WiFi 时走这条,最快
|
||||
输入配对码连接 经中转连
|
||||
服务器:还没测 点右边「检查服务器」看延迟
|
||||
|
||||
2) 电脑和手表在同一个 WiFi -> 点「局域网连接」,自动搜到电脑,点一下就连
|
||||
电脑在外面 -> 点「输入配对码连接」,填电脑上那个 6 位码
|
||||
|
||||
3) 连上后是 10 个标签页:
|
||||
媒体 屏幕 文件 语音 电脑 网络 系统 教室 效率 娱乐
|
||||
|
||||
|
||||
【连不上时看这个】
|
||||
|
||||
App 会把错误翻译成人话:
|
||||
"证书校验失败" -> 手表系统太旧或网络被劫持
|
||||
"证书时间不对" -> 把手表的日期时间调成自动获取
|
||||
"连接超时" -> 网络慢或被防火墙挡
|
||||
"电脑拒绝连接" -> 电脑上 ykxmz 没开
|
||||
"域名解析不了" -> 手表没联网
|
||||
|
||||
首页的「检查服务器」能测出到中转的延迟(快/一般/慢三档配色)。
|
||||
|
||||
|
||||
【回退】
|
||||
|
||||
系统页最上面三个按钮:
|
||||
回退上一步 撤销刚才那一步
|
||||
回退全部 把能撤的都撤了
|
||||
看能回退什么 列出还能撤哪些
|
||||
|
||||
能撤的:所有开关、所有画面(全屏大字/投图/通知等)
|
||||
撤不了的:关机、重启、删文件、杀进程 —— 这些**不会**出现在回退列表里,
|
||||
免得你以为什么都能退回去。
|
||||
"""
|
||||
|
||||
|
||||
'''
|
||||
|
||||
WATCH_PATTERN = re.compile(r'WATCH_README = f""".*?"""\n\n\n', re.S)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
text = PKG.read_text(encoding="utf-8")
|
||||
|
||||
if "ykxmz-agent-" in text:
|
||||
print(" 已经改过")
|
||||
return 0
|
||||
|
||||
# 1. 把 WATCH_README 提到顶层的常量
|
||||
# 原来它是嵌在 main() 里 write_readme 的第三个参数
|
||||
old_watch = re.search(
|
||||
r' write_readme\(watch_dir / "README-watch\.txt", f"""'
|
||||
r'.*?"""\)\n', text, re.S)
|
||||
if old_watch:
|
||||
text = text[:old_watch.start()] + text[old_watch.end():]
|
||||
print(" 摘掉了 main 里内嵌的手表说明")
|
||||
else:
|
||||
print(" [提示] 没找到内嵌的手表说明(可能已改)")
|
||||
|
||||
# 2. 插入 WATCH_README 常量(放在 RELAY_README 之后)
|
||||
anchor = 'def write_readme(path: Path, text: str) -> None:'
|
||||
if "WATCH_README =" not in text and anchor in text:
|
||||
text = text.replace(anchor, WATCH_README_BLOCK + anchor, 1)
|
||||
print(" 加了 WATCH_README 常量")
|
||||
|
||||
# 3. 换掉 main()
|
||||
start = text.find("def main() -> int:")
|
||||
end = text.find('if __name__ == "__main__":')
|
||||
if start < 0 or end < 0 or end < start:
|
||||
print(" [失败] 找不到 main() 的边界")
|
||||
return 1
|
||||
text = text[:start] + NEW_MAIN + text[end:]
|
||||
print(" 替换了 main()(改成出三个产物)")
|
||||
|
||||
PKG.write_text(text, encoding="utf-8", newline="\n")
|
||||
|
||||
import ast
|
||||
try:
|
||||
ast.parse(text)
|
||||
print(" package_remote.py 语法 OK")
|
||||
except SyntaxError as exc:
|
||||
print(f" 语法错误: 行 {exc.lineno}: {exc.msg}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user