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:
+154
-135
@@ -352,11 +352,88 @@ RELAY_README = f"""========================================================
|
||||
"""
|
||||
|
||||
|
||||
WATCH_README = f"""========================================================
|
||||
控制端(安卓手表) 版本 {VERSION}
|
||||
========================================================
|
||||
|
||||
【装】
|
||||
|
||||
把 ykxmz-watch-{VERSION}.apk 传到手表上安装。
|
||||
手表如果提示"未知来源",在设置里允许一下。
|
||||
|
||||
|
||||
【连】
|
||||
|
||||
1) 打开 App,首页有四个东西:
|
||||
中转设置(跨网络用) 填服务器端给的地址
|
||||
局域网连接(同一个WiFi)同一个 WiFi 时走这条,最快
|
||||
输入配对码连接 经中转连
|
||||
服务器:还没测 点右边「检查服务器」看延迟
|
||||
|
||||
2) 电脑和手表在同一个 WiFi -> 点「局域网连接」,自动搜到电脑,点一下就连
|
||||
电脑在外面 -> 点「输入配对码连接」,填电脑上那个 6 位码
|
||||
|
||||
3) 连上后是 10 个标签页:
|
||||
媒体 屏幕 文件 语音 电脑 网络 系统 教室 效率 娱乐
|
||||
|
||||
|
||||
【连不上时看这个】
|
||||
|
||||
App 会把错误翻译成人话:
|
||||
"证书校验失败" -> 手表系统太旧或网络被劫持
|
||||
"证书时间不对" -> 把手表的日期时间调成自动获取
|
||||
"连接超时" -> 网络慢或被防火墙挡
|
||||
"电脑拒绝连接" -> 电脑上 ykxmz 没开
|
||||
"域名解析不了" -> 手表没联网
|
||||
|
||||
首页的「检查服务器」能测出到中转的延迟(快/一般/慢三档配色)。
|
||||
|
||||
|
||||
【回退】
|
||||
|
||||
系统页最上面三个按钮:
|
||||
回退上一步 撤销刚才那一步
|
||||
回退全部 把能撤的都撤了
|
||||
看能回退什么 列出还能撤哪些
|
||||
|
||||
能撤的:所有开关、所有画面(全屏大字/投图/通知等)
|
||||
撤不了的:关机、重启、删文件、杀进程 —— 这些**不会**出现在回退列表里,
|
||||
免得你以为什么都能退回去。
|
||||
"""
|
||||
|
||||
|
||||
def write_readme(path: Path, text: str) -> None:
|
||||
"""说明文档统一写成 UTF-8 with BOM —— Windows 记事本不加 BOM 会认成 GBK。"""
|
||||
path.write_bytes(b"\xef\xbb\xbf" + text.replace("\n", "\r\n").encode("utf-8"))
|
||||
|
||||
|
||||
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}")
|
||||
@@ -365,13 +442,18 @@ def main() -> int:
|
||||
print(f" [失败] 找不到 APK: {APK_SRC}")
|
||||
return 1
|
||||
|
||||
build = ROOT / "dist" / NAME
|
||||
if build.exists():
|
||||
shutil.rmtree(build, ignore_errors=True)
|
||||
dist = ROOT / "dist"
|
||||
dist.mkdir(parents=True, exist_ok=True)
|
||||
work = dist / "_build_tmp"
|
||||
if work.exists():
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
# ---------- 1. 被控端 ----------
|
||||
agent_dir = build / "1-agent"
|
||||
print(" 复制被控端…")
|
||||
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"))
|
||||
@@ -381,155 +463,92 @@ def main() -> int:
|
||||
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. 服务器端 ----------
|
||||
relay_dir = build / "2-server"
|
||||
relay_dir.mkdir(parents=True)
|
||||
print(" 复制服务器端…")
|
||||
# 文件清单写死容易漏 —— watchdog.sh 就漏过一次。
|
||||
# ================= 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, relay_dir / src.name)
|
||||
write_readme(relay_dir / "README-server.txt", RELAY_README)
|
||||
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. 控制端 ----------
|
||||
watch_dir = build / "3-watch"
|
||||
watch_dir.mkdir(parents=True)
|
||||
print(" 复制控制端…")
|
||||
shutil.copy2(APK_SRC, watch_dir / APK_SRC.name)
|
||||
write_readme(watch_dir / "README-watch.txt", f"""========================================================
|
||||
控制端(安卓手表) 版本 {VERSION}
|
||||
========================================================
|
||||
# ================= 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)
|
||||
|
||||
把 {APK_SRC.name} 传到手表上安装。
|
||||
手表如果提示"未知来源",在设置里允许一下。
|
||||
|
||||
|
||||
【连】
|
||||
|
||||
1) 打开 App,首页点「中转设置(跨网络用)」
|
||||
填服务器端给你的地址,形如 https://relay.你的域名
|
||||
(不填也能用,但只在同一个 WiFi 下)
|
||||
|
||||
2) 点「自动搜索电脑」—— 同一个局域网时能直接搜到
|
||||
搜不到就点「连不上?点这里诊断」
|
||||
|
||||
3) 输 6 位配对码(电脑上设的那个)
|
||||
|
||||
4) 连上后是 10 个标签页:
|
||||
媒体 屏幕 文件 语音 电脑 网络 系统 教室 效率 娱乐
|
||||
每个页面上半部分是常用按钮,往下滑是更多功能(共 206 个)。
|
||||
|
||||
|
||||
【回退】
|
||||
|
||||
系统页最上面三个按钮:
|
||||
回退上一步 撤销刚才那一步
|
||||
回退全部 把能撤的都撤了
|
||||
看能回退什么 列出还能撤哪些
|
||||
|
||||
能撤的:所有开关(15 个)、所有画面(26 个,全屏大字/投图/通知等)
|
||||
撤不了的:关机、重启、删文件、杀进程 —— 这些**不会**出现在回退列表里,
|
||||
免得你以为什么都能退回去。
|
||||
""")
|
||||
|
||||
|
||||
# ---------- 4. 顶层说明 ----------
|
||||
write_readme(build / "README.txt", TOP_README)
|
||||
|
||||
# ---------- 4b. 三份正式文档(作者:小麦子)----------
|
||||
# 放顶层,和 README.txt 并排,解压第一眼就能看到。
|
||||
# 统一转成 UTF-8 BOM + CRLF —— 不带 BOM 记事本会按 GBK 读,全乱。
|
||||
doc_files = [
|
||||
("LICENSE.txt", "许可协议"),
|
||||
("BEFORE-INSTALL.txt", "安装前必读"),
|
||||
("AFTER-INSTALL.txt", "安装后须知"),
|
||||
]
|
||||
for name, label in doc_files:
|
||||
src = DOCS / name
|
||||
if not src.is_file():
|
||||
print(f" [警告] 缺少文档 {src}")
|
||||
# ================= 汇总 =================
|
||||
print()
|
||||
print(" ================ 产物 ================")
|
||||
ok = True
|
||||
for label, path, count in results:
|
||||
if not path.is_file():
|
||||
print(f" {label}: ✗ 缺失!")
|
||||
ok = False
|
||||
continue
|
||||
# 读进来(原文件是无 BOM 的 UTF-8),交给 write_readme
|
||||
# 统一加 BOM + CRLF 后写出
|
||||
write_readme(build / name, src.read_text(encoding="utf-8"))
|
||||
print(f" {label}: {name}")
|
||||
|
||||
# ---------- 5. 检查 ----------
|
||||
print()
|
||||
print(" 目录结构:")
|
||||
total = 0
|
||||
for item in sorted(build.rglob("*")):
|
||||
if item.is_file():
|
||||
total += 1
|
||||
for item in sorted(build.iterdir()):
|
||||
if item.is_dir():
|
||||
count = sum(1 for _ in item.rglob("*") if _.is_file())
|
||||
print(f" {item.name}/ ({count} 个文件)")
|
||||
else:
|
||||
print(f" {item.name}")
|
||||
print(f" 合计 {total} 个文件")
|
||||
|
||||
# 必须有的东西
|
||||
required = [
|
||||
agent_dir / "ykxmz.exe",
|
||||
relay_dir / "relay.py",
|
||||
relay_dir / "install.sh",
|
||||
watch_dir / APK_SRC.name,
|
||||
build / "README.txt",
|
||||
build / "LICENSE.txt",
|
||||
build / "BEFORE-INSTALL.txt",
|
||||
build / "AFTER-INSTALL.txt",
|
||||
]
|
||||
missing = [str(p.relative_to(build)) for p in required if not p.is_file()]
|
||||
if missing:
|
||||
print(f" [失败] 少了: {missing}")
|
||||
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(" 三个部分都在")
|
||||
|
||||
# ---------- 6. 打包 ----------
|
||||
# ================= 验证 =================
|
||||
print()
|
||||
print(" 压缩中…")
|
||||
zip_path = ROOT / "dist" / f"{NAME}.zip"
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED,
|
||||
compresslevel=6) as archive:
|
||||
for item in sorted(build.rglob("*")):
|
||||
if item.is_file():
|
||||
archive.write(item, item.relative_to(build.parent))
|
||||
size = zip_path.stat().st_size / 1024 / 1024
|
||||
print(f" {zip_path.name} {size:.2f} MB")
|
||||
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)} 个条目,完整")
|
||||
|
||||
# ---------- 7. 验证压缩包 ----------
|
||||
with zipfile.ZipFile(zip_path) as archive:
|
||||
with zipfile.ZipFile(results[0][1]) as archive:
|
||||
names = archive.namelist()
|
||||
bad = archive.testzip()
|
||||
if bad:
|
||||
print(f" [失败] 压缩包里有坏文件: {bad}")
|
||||
return 1
|
||||
has = {
|
||||
"被控端": any("ykxmz.exe" in n for n in names),
|
||||
"服务器端": any("relay.py" in n for n in names),
|
||||
"控制端": any(".apk" in n for n in names),
|
||||
"顶层说明": any("README.txt" in n for n in names),
|
||||
}
|
||||
for label, ok in has.items():
|
||||
print(f" {label}: {'✓' if ok else '✗'}")
|
||||
if not all(has.values()):
|
||||
return 1
|
||||
print(f" 共 {len(names)} 个条目,完整")
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user