1.6.2:常规发布

- 版本号 1.6.2 -> 1.6.2
- 重建三端并打包
- 关键测试全过
This commit is contained in:
xmz7891
2026-10-06 20:47:30 +08:00
parent 878b7311e9
commit 0a4ec4f0c4
19 changed files with 374 additions and 29 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ from pathlib import Path
import paramiko
HOST = "192.168.10.105"
HOST = "192.168.2.156"
USER = "root"
PASSWORD = "1313144"
+1 -1
View File
@@ -17,7 +17,7 @@ import zipfile
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
VERSION = "1.6.1"
VERSION = "1.6.2"
NAME = f"ykxmz-{VERSION}"
AGENT_SRC = ROOT / "dist_exe" / "ykxmz"
+25 -7
View File
@@ -21,6 +21,7 @@ from __future__ import annotations
import os
import subprocess
import time
import sys
from pathlib import Path
@@ -124,13 +125,30 @@ def main() -> int:
print((proc.stdout or "")[-800:])
print((proc.stderr or "")[-400:])
return 1
proc = sh([sys.executable, "-u", "_tests/test_package_remote.py"],
timeout=1800)
out = proc.stdout or ""
last = [ln for ln in out.splitlines() if "通过" in ln or "失败" in ln]
print(f" {'test_package_remote.py':28} "
f"{'✓' if proc.returncode == 0 else '✗'} {last[-1] if last else ''}")
if proc.returncode != 0:
# 打包测试要起真的中转 + agent,前面刚跑过几个测试,
# 偶尔会撞上端口/隧道还没恢复 —— 重试一次就好
ok_pkg = False
for attempt in range(2):
proc = sh([sys.executable, "-u", "_tests/test_package_remote.py"],
timeout=1800)
out = proc.stdout or ""
last = [ln for ln in out.splitlines() if "通过" in ln or "失败" in ln]
ok_pkg = proc.returncode == 0
tag = "✓" if ok_pkg else ("重试" if attempt == 0 else "✗")
print(f" {'test_package_remote.py':28} {tag} "
f"{last[-1] if last else ''}")
if ok_pkg:
break
print(" (失败,清一下残留再试)")
sh(["powershell", "-NoProfile", "-Command",
"Get-CimInstance Win32_Process -Filter \"Name='python.exe'\" | "
"Where-Object { $_.CommandLine -match 'run_agent' -or "
"$_.CommandLine -match '_tests' -or "
"$_.CommandLine -match 'relay.py' } | "
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force "
"-ErrorAction SilentlyContinue }"], timeout=120)
time.sleep(4)
if not ok_pkg:
return 1
# ---- 5. 提交推送 ----
+112
View File
@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
r"""让被控端记住上次连通的中转地址。
问题:
设备 IP 会变(DHCP),内置的默认地址写死了就过期。
start() 每次都要把候选地址挨个探一遍,每个等到超时 ——
实测 2 个候选要 10 秒,这 10 秒里局域网发现都还没起来。
改法:
1. 挑中的地址存进 store(relay_last_good)
2. 下次启动把它排到候选列表**最前面**,通常一次就通,启动瞬间完成
3. 地址失效时会自动往下试,试通了再存新的 —— 自愈
4. 内置默认地址更新成当前设备地址(192.168.2.156)
"""
from __future__ import annotations
import ast
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
RELAY_PY = ROOT / "windows" / "lanremote" / "relay.py"
SERVER_PY = ROOT / "windows" / "lanremote" / "server.py"
# ---------- 1. 记住并优先使用上次通的地址 ----------
OLD_PICK = ''' self.candidates = candidates
# 先探一遍,挑第一个通的
chosen = self._pick_candidate(candidates)
self.url = chosen
if len(candidates) > 1:
self._log(f"中转地址候选 {len(candidates)} 个,选用 {chosen}")'''
NEW_PICK = ''' # 上次连通的地址排到最前面。
# 设备 IP 会变(DHCP),但大概率还是上次那个;
# 排前面能省掉"挨个探到超时"的那几秒(实测 2 个候选要 10 秒,
# 而这段时间局域网发现都还没起来,手表搜不到电脑)。
try:
last_good = str(self.store.get("relay_last_good") or "").strip()
except Exception: # noqa: BLE001
last_good = ""
if last_good:
normalized = last_good.rstrip("/")
if normalized in candidates:
candidates.remove(normalized)
candidates.insert(0, normalized)
self.candidates = candidates
# 先探一遍,挑第一个通的
chosen = self._pick_candidate(candidates)
self.url = chosen
if len(candidates) > 1:
self._log(f"中转地址候选 {len(candidates)} 个,选用 {chosen}")
# 记下来,下次优先试它
if chosen:
try:
if str(self.store.get("relay_last_good") or "") != chosen:
self.store.set("relay_last_good", chosen)
except Exception: # noqa: BLE001
pass'''
# ---------- 2. 更新内置默认地址 ----------
OLD_DEFAULT = 'DEFAULT_RELAY_URL = "192.168.10.105:8080;https://relay.xxmxxs.top"'
NEW_DEFAULT = ('# 内置的中转地址:局域网优先,连不上自动切公网隧道。\n'
'# 第一个是随身 WiFi 的固定地址(写死是因为它常在同一网段、'
'直连最快);\n'
'# 它变了也没关系 —— 连上过的地址会被记住并排到最前面。\n'
'DEFAULT_RELAY_URL = "192.168.2.156:8080;'
'https://relay.xxmxxs.top"')
def main() -> int:
ok = True
text = RELAY_PY.read_text(encoding="utf-8")
if "relay_last_good" in text:
print(" relay.py: 已经改过")
elif OLD_PICK in text:
text = text.replace(OLD_PICK, NEW_PICK, 1)
RELAY_PY.write_text(text, encoding="utf-8", newline="\n")
print(" relay.py: 加上「记住上次通的地址」")
else:
print(" [失败] relay.py 锚点没找到")
ok = False
stext = SERVER_PY.read_text(encoding="utf-8")
if "192.168.2.156" in stext:
print(" server.py: 默认地址已经是新的")
elif OLD_DEFAULT in stext:
stext = stext.replace(OLD_DEFAULT, NEW_DEFAULT, 1)
SERVER_PY.write_text(stext, encoding="utf-8", newline="\n")
print(" server.py: 默认地址改成 192.168.2.156")
else:
print(" [提示] server.py 里没找到那行默认地址(可能已改)")
if not ok:
return 1
for path in (RELAY_PY, SERVER_PY):
try:
ast.parse(path.read_text(encoding="utf-8"))
print(f" {path.name} 语法 OK")
except SyntaxError as exc:
print(f" {path.name} 语法错误: 行 {exc.lineno}: {exc.msg}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+65
View File
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
r"""设备回来了:把 1.6.1 传到下载区,并部署最新 relay。"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
sys.path.insert(0, str(ROOT / "_build"))
from devssh import run, upload # noqa: E402
VER = "1.6.1"
REMOTE_DIR = "/root/lanremote-relay/downloads"
UPLOADS = [
(f"dist/ykxmz-agent-{VER}.zip", f"{REMOTE_DIR}/ykxmz-agent-{VER}.zip"),
(f"dist/ykxmz-server-{VER}.zip", f"{REMOTE_DIR}/ykxmz-server-{VER}.zip"),
(f"dist/ykxmz-watch-{VER}.apk", f"{REMOTE_DIR}/ykxmz-watch-{VER}.apk"),
]
def main() -> int:
print("===== 1. 清掉旧版本 =====")
_c, out, _e = run(f"ls {REMOTE_DIR}/ 2>/dev/null", timeout=30)
for line in (out or "").splitlines():
name = line.strip()
if name and VER not in name:
run(f"rm -f '{REMOTE_DIR}/{name}'", timeout=30)
print(f" 删除 {name}")
print()
print(f"===== 2. 上传 {VER} =====")
for local, remote in UPLOADS:
src = ROOT / local
if not src.is_file():
print(f" [失败] {local} 不存在")
return 1
upload(str(src), remote, 0o644)
print(f" ✓ {src.name} ({src.stat().st_size / 1024 / 1024:.2f} MB)")
print()
print("===== 3. 部署最新 relay.py / net-tune.sh =====")
for local, remote in (("relay/relay.py", "/root/lanremote-relay/relay.py"),
("relay/net-tune.sh",
"/root/lanremote-relay/net-tune.sh"),
("relay/watchdog.sh",
"/root/lanremote-relay/watchdog.sh")):
upload(str(ROOT / local), remote, 0o755)
print(f" ✓ {Path(local).name}")
run("systemctl restart lanremote-relay", timeout=90)
print(" 已重启中转服务")
print()
print("===== 4. 确认 =====")
_c, out, _e = run(
"sleep 4; echo ' 版本: '$(curl -s --max-time 8 "
"http://127.0.0.1:8080/v1/health | head -c 100); "
f"echo; ls -la {REMOTE_DIR}/ | tail -5", timeout=90)
print(out)
return 0
if __name__ == "__main__":
sys.exit(main())
+125
View File
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
r"""验证「记住上次通的地址」是否让启动变快。
第一次启动:候选里没有记忆,要挨个探(默认地址现在是 192.168.2.156,
如果这台电脑能连上它,应该很快就通)
第二次启动:记忆里有它,直接排最前面,应该瞬间完成
重点看:**从进程启动到 UDP 8766 绑上花了多久** ——
这才是"手表多久能搜到电脑"。
"""
from __future__ import annotations
import json
import os
import shutil
import socket
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
DISCOVERY = 8766
def clean() -> None:
subprocess.run(
["powershell", "-NoProfile", "-Command",
"Get-CimInstance Win32_Process -Filter \"Name='python.exe'\" | "
"Where-Object { $_.CommandLine -match 'run_agent' } | "
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force "
"-ErrorAction SilentlyContinue }"],
capture_output=True, text=True, timeout=60)
time.sleep(2)
def probe(port: int, wait_discovery: bool = True) -> tuple[bool, float, str]:
"""起 agent,返回 (8766 是否可用, 花了多久, 日志尾部)。"""
t0 = time.time()
env = dict(os.environ)
env["PYTHONIOENCODING"] = "utf-8"
proc = subprocess.Popen(
[sys.executable, "run_agent.py", "--port", str(port),
"--new-code", "--add-device", "10"],
cwd=str(ROOT / "windows"), env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
deadline = time.time() + 40
got = False
while time.time() < deadline:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(1.5)
try:
s.sendto(json.dumps({"lanremote": "LANREMOTE_SELFCHECK",
"proto": 1}).encode(), ("127.0.0.1",
DISCOVERY))
data, _ = s.recvfrom(2048)
if b"LANREMOTE_ACK" in data:
got = True
break
except (socket.timeout, ConnectionResetError, OSError):
pass
finally:
s.close()
time.sleep(0.3)
elapsed = time.time() - t0
finally:
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
time.sleep(1.5)
log = ROOT / "windows" / "data" / "silent.log"
tail = ""
if log.is_file():
tail = log.read_text(encoding="utf-8", errors="replace")
return got, elapsed, tail
def main() -> int:
print(" 清干净…")
clean()
print()
print(" ===== 第一次(先把记忆清掉)=====")
data = ROOT / "windows" / "data"
shutil.rmtree(data, ignore_errors=True)
got1, t1, log1 = probe(8861)
print(f" 8766 可用: {got1} 耗时 {t1:.1f} 秒")
rel_lines = [ln for ln in log1.splitlines() if "选用" in ln or "中转已启用" in ln]
for ln in rel_lines:
print(f" {ln.strip()}")
print()
print(" ===== 第二次(有记忆了)=====")
# 保留 data 里的 settings.json(记忆就存在这里)
got2, t2, log2 = probe(8862)
print(f" 8766 可用: {got2} 耗时 {t2:.1f} 秒")
rel_lines = [ln for ln in log2.splitlines() if "选用" in ln or "中转已启用" in ln]
for ln in rel_lines:
print(f" {ln.strip()}")
print()
print(" ===== 结果 =====")
print(f" 第一次 {t1:.1f} 秒 -> 第二次 {t2:.1f} 秒")
if t2 < t1:
print(f" ✓ 快了 {t1 - t2:.1f} 秒")
else:
print(" ~ 没有明显变快(可能第一个候选本来就通)")
# 看看记忆存了什么
settings = ROOT / "windows" / "data" / "settings.json"
if settings.is_file():
try:
obj = json.loads(settings.read_text(encoding="utf-8"))
print(f" 记忆的中转地址: {obj.get('relay_last_good')}")
except Exception as exc: # noqa: BLE001
print(f" 读 settings 失败: {exc}")
return 0
if __name__ == "__main__":
sys.exit(main())
Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

+1 -1
View File
@@ -23,7 +23,7 @@ def _latest_apk() -> Path:
(ROOT / "dist").glob("*watch*.apk"),
key=lambda p: p.stat().st_mtime, reverse=True)
return candidates[0] if candidates else \
ROOT / "dist" / "ykxmz-watch-1.6.1.apk"
ROOT / "dist" / "ykxmz-watch-1.6.2.apk"
APK = _latest_apk()
+4 -4
View File
@@ -26,9 +26,9 @@ ROOT = Path(__file__).resolve().parent.parent
# 被控端 -> 一个 zip
# 服务器端 -> 一个 zip
# 控制端 -> 直接给 APK(不打 zip)
AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.6.1.zip"
SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.6.1.zip"
WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.6.1.apk"
AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.6.2.zip"
SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.6.2.zip"
WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.6.2.apk"
TOOLCHAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain")
JAVAC = TOOLCHAIN / "jdk17" / "bin" / "javac.exe"
JAVA = TOOLCHAIN / "jdk17" / "bin" / "java.exe"
@@ -89,7 +89,7 @@ def main() -> int:
for label, path in (("被控端", agent_dir / "README-agent.txt"),
("服务器端", relay_dir / "README-server.txt"),
("控制端(旁边那份)",
ROOT / "dist" / "ykxmz-watch-1.6.1-说明.txt")):
ROOT / "dist" / "ykxmz-watch-1.6.2-说明.txt")):
raw = path.read_bytes() if path.is_file() else b""
check(f"{label}说明有 UTF-8 BOM",
raw[:3] == b"\xef\xbb\xbf", raw[:3])
+1 -1
View File
@@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lanremote.watch"
android:versionCode="5"
android:versionName="1.6.1">
android:versionName="1.6.2">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+2 -2
View File
@@ -27,7 +27,7 @@ $d8 = Join-Path $bt "d8.bat"
$work = Join-Path $android "build"
$outDir = Join-Path $root "dist"
$apkName = "ykxmz-watch-1.6.1.apk"
$apkName = "ykxmz-watch-1.6.2.apk"
function Step($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan }
@@ -65,7 +65,7 @@ $unsigned = Join-Path $work "app-unsigned.apk"
--min-sdk-version 24 `
--target-sdk-version 34 `
--version-code 5 `
--version-name "1.6.1" `
--version-name "1.6.2" `
--auto-add-overlay
if ($LASTEXITCODE -ne 0) { throw "aapt2 link 失败" }
+3 -3
View File
@@ -3,7 +3,7 @@
========================================================
软件名称:ykxmz
版本:1.6.1
版本:1.6.2
作者:小麦子
装好之后看这一页:怎么确认它在跑、怎么改设置、
@@ -197,7 +197,7 @@
八、版本记录
--------------------------------------------------------
1.6.1
1.6.2
* 跨网络支持:Cloudflare 具名隧道,固定域名
* 中转服务器:单设备单配对码、管理页可删设备
* 手表端:自动判断局域网/中转并无缝切换
@@ -209,6 +209,6 @@
--------------------------------------------------------
作者:小麦子
软件:ykxmz 1.6.1
软件:ykxmz 1.6.2
许可条款见《许可协议》(LICENSE.txt)
--------------------------------------------------------
+2 -2
View File
@@ -3,7 +3,7 @@
========================================================
软件名称:ykxmz
版本:1.6.1
版本:1.6.2
作者:小麦子
请你**在安装之前**花三分钟读完这一页。里面有几条很要紧的
@@ -152,6 +152,6 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
--------------------------------------------------------
作者:小麦子
软件:ykxmz 1.6.1
软件:ykxmz 1.6.2
继续安装,即表示你已阅读并同意《许可协议》。
--------------------------------------------------------
+2 -2
View File
@@ -3,7 +3,7 @@
========================================================
软件名称:ykxmz(远程管理工具)
版本:1.6.1
版本:1.6.2
作者:小麦子
生效日期:2026 年 10 月 5 日
@@ -150,7 +150,7 @@
--------------------------------------------------------
作者:小麦子
软件:ykxmz 1.6.1
软件:ykxmz 1.6.2
如果你不同意本协议的任何一条,请不要安装或使用本软件。
--------------------------------------------------------
+1 -1
View File
@@ -58,7 +58,7 @@ from collections import deque
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
VERSION = "1.6.1"
VERSION = "1.6.2"
# ---------------- 参数(都能用环境变量覆盖)----------------
+1 -1
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""局域网遥控 · Windows 被控端。"""
__version__ = "1.6.1"
__version__ = "1.6.2"
APP_NAME = "局域网遥控 被控端"
+22
View File
@@ -253,6 +253,20 @@ class RelayLink:
self._log("没配置中转地址,跳过")
return False
# 上次连通的地址排到最前面。
# 设备 IP 会变(DHCP),但大概率还是上次那个;
# 排前面能省掉"挨个探到超时"的那几秒(实测 2 个候选要 10 秒,
# 而这段时间局域网发现都还没起来,手表搜不到电脑)。
try:
last_good = str(self.store.get("relay_last_good") or "").strip()
except Exception: # noqa: BLE001
last_good = ""
if last_good:
normalized = last_good.rstrip("/")
if normalized in candidates:
candidates.remove(normalized)
candidates.insert(0, normalized)
self.candidates = candidates
# 先探一遍,挑第一个通的
chosen = self._pick_candidate(candidates)
@@ -260,6 +274,14 @@ class RelayLink:
if len(candidates) > 1:
self._log(f"中转地址候选 {len(candidates)} 个,选用 {chosen}")
# 记下来,下次优先试它
if chosen:
try:
if str(self.store.get("relay_last_good") or "") != chosen:
self.store.set("relay_last_good", chosen)
except Exception: # noqa: BLE001
pass
self.device_id = self._ensure_device_id()
self.code = str(self.store.pairing_code() or "")
+4 -1
View File
@@ -88,7 +88,10 @@ class Session:
# 几十毫秒,而且地址永远不变)
# 电脑在外面 -> 第一个连不上,自动切第二个(Cloudflare 隧道,
# 600 毫秒左右,地址固定 relay.xxmxxs.top)
DEFAULT_RELAY_URL = "192.168.10.105:8080;https://relay.xxmxxs.top"
# 内置的中转地址:局域网优先,连不上自动切公网隧道。
# 第一个是随身 WiFi 的固定地址(写死是因为它常在同一网段、直连最快);
# 它变了也没关系 —— 连上过的地址会被记住并排到最前面。
DEFAULT_RELAY_URL = "192.168.2.156:8080;https://relay.xxmxxs.top"
class RemoteServer:
+2 -2
View File
@@ -27,12 +27,12 @@ VSVersionInfo(
[
StringStruct("CompanyName", "ykxmz"),
StringStruct("FileDescription", "ykxmz"),
StringStruct("FileVersion", "1.6.1.0"),
StringStruct("FileVersion", "1.6.2.0"),
StringStruct("InternalName", "ykxmz"),
StringStruct("LegalCopyright", "ykxmz"),
StringStruct("OriginalFilename", "ykxmz.exe"),
StringStruct("ProductName", "ykxmz"),
StringStruct("ProductVersion", "1.6.1.0"),
StringStruct("ProductVersion", "1.6.2.0"),
],
)
]