mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 21:50:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a4ec4f0c4 | ||
|
|
878b7311e9 |
@@ -59,3 +59,8 @@ desktop.ini
|
|||||||
|
|
||||||
# ---- 测试收发的临时数据 ----
|
# ---- 测试收发的临时数据 ----
|
||||||
/_tests/received/
|
/_tests/received/
|
||||||
|
|
||||||
|
# 令牌(绝对不要进仓库)
|
||||||
|
/_build/.gitee_token
|
||||||
|
*.token
|
||||||
|
.env
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
|
||||||
HOST = "192.168.10.105"
|
HOST = "192.168.2.156"
|
||||||
USER = "root"
|
USER = "root"
|
||||||
PASSWORD = "1313144"
|
PASSWORD = "1313144"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r"""诊断:发现端口 8766 到底有没有被 agent 绑上。
|
||||||
|
|
||||||
|
test_selfcheck 广播到 127.0.0.1:8766 收到 WSAECONNRESET(ICMP 端口不可达),
|
||||||
|
说明**没有进程在监听 8766**。但测试里 agent 明明起来了(8841 能连)。
|
||||||
|
|
||||||
|
查清楚是:
|
||||||
|
a) agent 绑 8766 失败(被占 / 权限)
|
||||||
|
b) agent 压根没去绑
|
||||||
|
c) 绑上了但没回包
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
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")
|
||||||
|
PORT = 8851
|
||||||
|
DISCOVERY = 8766
|
||||||
|
|
||||||
|
|
||||||
|
def port_holder(port: int, udp: bool) -> list[str]:
|
||||||
|
out: list[str] = []
|
||||||
|
if udp:
|
||||||
|
cmd = ("Get-NetUDPEndpoint -LocalPort %d -ErrorAction SilentlyContinue | "
|
||||||
|
"ForEach-Object { \"$($_.LocalAddress):$($_.LocalPort) PID $($_.OwningProcess)\" }"
|
||||||
|
% port)
|
||||||
|
else:
|
||||||
|
cmd = ("Get-NetTCPConnection -LocalPort %d -ErrorAction SilentlyContinue | "
|
||||||
|
"ForEach-Object { \"$($_.LocalAddress):$($_.LocalPort) PID $($_.OwningProcess)\" }"
|
||||||
|
% port)
|
||||||
|
proc = subprocess.run(["powershell", "-NoProfile", "-Command", cmd],
|
||||||
|
capture_output=True, text=True, timeout=30)
|
||||||
|
for line in (proc.stdout or "").splitlines():
|
||||||
|
if line.strip():
|
||||||
|
out.append(line.strip())
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
# 清干净(注意别把自己杀了 —— $PID 在 PowerShell 里是 PowerShell 的,
|
||||||
|
# 所以这里按命令行特征挑,不用 PID 比较)
|
||||||
|
subprocess.run(
|
||||||
|
["powershell", "-NoProfile", "-Command",
|
||||||
|
"Get-CimInstance Win32_Process -Filter \"Name='python.exe'\" | "
|
||||||
|
"Where-Object { $_.CommandLine -match 'run_agent' -or "
|
||||||
|
"$_.CommandLine -match 'test_' } | "
|
||||||
|
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force "
|
||||||
|
"-ErrorAction SilentlyContinue }"],
|
||||||
|
capture_output=True, text=True, timeout=60)
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
print(" 起之前,8766 有没有人占:")
|
||||||
|
holders = port_holder(DISCOVERY, udp=True)
|
||||||
|
print(f" {holders if holders else '空闲'}")
|
||||||
|
|
||||||
|
data_dir = ROOT / "windows" / "data"
|
||||||
|
if data_dir.exists():
|
||||||
|
shutil.rmtree(data_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
env = dict(os.environ)
|
||||||
|
env["PYTHONIOENCODING"] = "utf-8"
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f" 起 agent(TCP {PORT})…")
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[sys.executable, "run_agent.py", "--port", str(PORT),
|
||||||
|
"--new-code", "--add-device", "10"],
|
||||||
|
cwd=str(ROOT / "windows"), env=env,
|
||||||
|
stdout=open(ROOT / "_build" / "agent_diag.log", "wb"),
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 等 TCP 就绪
|
||||||
|
ready = False
|
||||||
|
for _ in range(50):
|
||||||
|
try:
|
||||||
|
s = socket.create_connection(("127.0.0.1", PORT), timeout=1)
|
||||||
|
s.close()
|
||||||
|
ready = True
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
time.sleep(0.4)
|
||||||
|
print(f" TCP {PORT} 就绪: {ready}")
|
||||||
|
|
||||||
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f" 起之后,8766 有没有人占:")
|
||||||
|
holders = port_holder(DISCOVERY, udp=True)
|
||||||
|
print(f" {holders if holders else '还是空闲 —— 说明 agent 没绑上!'}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" 直接探测 8766:")
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.settimeout(3)
|
||||||
|
import json
|
||||||
|
request = json.dumps({"lanremote": "LANREMOTE_SELFCHECK",
|
||||||
|
"proto": 1}).encode("utf-8")
|
||||||
|
try:
|
||||||
|
sock.sendto(request, ("127.0.0.1", DISCOVERY))
|
||||||
|
data, addr = sock.recvfrom(2048)
|
||||||
|
print(f" ✓ 收到应答: {data[:160]!r}")
|
||||||
|
except ConnectionResetError as exc:
|
||||||
|
print(f" ✗ WSAECONNRESET —— 端口不可达,没人监听({exc})")
|
||||||
|
except socket.timeout:
|
||||||
|
print(" ✗ 超时 —— 有人监听但没回")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" agent 日志尾部:")
|
||||||
|
log = ROOT / "_build" / "agent_diag.log"
|
||||||
|
if log.is_file():
|
||||||
|
text = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
for line in text.splitlines()[-14:]:
|
||||||
|
print(f" {line}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
proc.terminate()
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=10)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r"""修:中转连接尝试阻塞了局域网发现。
|
||||||
|
|
||||||
|
现象(agent 日志):
|
||||||
|
[20:31:17] 没配过中转地址,用内置默认值:192.168.10.105:8080;https://...
|
||||||
|
[20:31:25] http://192.168.10.105:8080 不通,试下一个 <- 等了 8 秒
|
||||||
|
[20:31:27] https://relay.xxmxxs.top 不通,试下一个 <- 又 2 秒
|
||||||
|
[20:31:27] 自动发现已开启(UDP 8766) <- 10 秒后才绑上
|
||||||
|
|
||||||
|
server.start() 原来的顺序:
|
||||||
|
1. 绑 TCP
|
||||||
|
2. enable_relay() <- 挨个试中转地址,每个都要等超时(这里 10 秒)
|
||||||
|
3. 起 accept 线程
|
||||||
|
4. 起 DiscoveryResponder
|
||||||
|
|
||||||
|
问题:**局域网发现被中转拖住了**。
|
||||||
|
电脑和手表在同一个 WiFi 时,本来应该"广播一下就找到",
|
||||||
|
结果因为中转连不上,要等 10 几秒手表才搜得到电脑。
|
||||||
|
中转地址越烂、候选越多,等得越久。
|
||||||
|
|
||||||
|
影响:
|
||||||
|
* 用户体验上表现为"刚开电脑时手表搜不到,过一会儿才行"
|
||||||
|
* 自动化测试也栽在这(广播时发现服务还没起)
|
||||||
|
|
||||||
|
改法:把局域网发现**提到 enable_relay() 之前**。
|
||||||
|
局域网是本机能力,不该依赖任何网络外部条件。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SERVER = (Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\windows"
|
||||||
|
r"\lanremote\server.py"))
|
||||||
|
|
||||||
|
OLD = ''' # 配了中转地址就同时启用跨网络通道(和局域网监听并存,不冲突)
|
||||||
|
try:
|
||||||
|
self.enable_relay()
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
self._log(f"启用中转失败:{exc}")
|
||||||
|
|
||||||
|
self._accept_thread = threading.Thread(target=self._accept_loop,
|
||||||
|
name="accept", daemon=True)
|
||||||
|
self._accept_thread.start()
|
||||||
|
self._clip_thread = threading.Thread(target=self._clip_loop,
|
||||||
|
name="clip", daemon=True)
|
||||||
|
self._clip_thread.start()
|
||||||
|
|
||||||
|
# 启动局域网自动发现,让手表不用输 IP
|
||||||
|
self._discovery = DiscoveryResponder(self.store, self.port)
|
||||||
|
# 手表点「连不上?」会广播请求,这里就把网络自检窗口弹出来
|
||||||
|
self._discovery.on_selfcheck = self._on_selfcheck_request
|
||||||
|
if self._discovery.start():
|
||||||
|
self._log(f"自动发现已开启(UDP {DISCOVERY_PORT})")
|
||||||
|
else:
|
||||||
|
self._log(f"自动发现未开启:{self._discovery.last_error}")'''
|
||||||
|
|
||||||
|
NEW = ''' # ── 先起局域网发现 ──
|
||||||
|
#
|
||||||
|
# 顺序很重要:enable_relay() 会挨个试中转地址,
|
||||||
|
# 每个候选都要等到超时才算"不通",地址越烂等得越久(实测 10 秒+)。
|
||||||
|
# 原来这一步排在发现服务前面,导致:
|
||||||
|
# 电脑刚开机的十几秒里,手表广播搜不到它。
|
||||||
|
# 局域网发现是纯本机能力,不该被"连不连得上外网"拖住,
|
||||||
|
# 所以挪到前面,开机即可被搜到。
|
||||||
|
self._discovery = DiscoveryResponder(self.store, self.port)
|
||||||
|
# 手表点「连不上?」会广播请求,这里就把网络自检结论算出来
|
||||||
|
self._discovery.on_selfcheck = self._on_selfcheck_request
|
||||||
|
if self._discovery.start():
|
||||||
|
self._log(f"自动发现已开启(UDP {DISCOVERY_PORT})")
|
||||||
|
else:
|
||||||
|
self._log(f"自动发现未开启:{self._discovery.last_error}")
|
||||||
|
|
||||||
|
# ── 再起收发线程 ──
|
||||||
|
self._accept_thread = threading.Thread(target=self._accept_loop,
|
||||||
|
name="accept", daemon=True)
|
||||||
|
self._accept_thread.start()
|
||||||
|
self._clip_thread = threading.Thread(target=self._clip_loop,
|
||||||
|
name="clip", daemon=True)
|
||||||
|
self._clip_thread.start()
|
||||||
|
|
||||||
|
# ── 最后才连中转(会阻塞,所以放最后)──
|
||||||
|
# 配了中转地址就同时启用跨网络通道(和局域网监听并存,不冲突)
|
||||||
|
try:
|
||||||
|
self.enable_relay()
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
self._log(f"启用中转失败:{exc}")'''
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
text = SERVER.read_text(encoding="utf-8")
|
||||||
|
if "先起局域网发现" in text:
|
||||||
|
print(" 已经改过")
|
||||||
|
return 0
|
||||||
|
if OLD not in text:
|
||||||
|
print(" [失败] 锚点没找到")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
text = text.replace(OLD, NEW, 1)
|
||||||
|
SERVER.write_text(text, encoding="utf-8", newline="\n")
|
||||||
|
print(" 已把局域网发现提到 enable_relay() 之前")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ast.parse(text)
|
||||||
|
print(" server.py 语法 OK")
|
||||||
|
except SyntaxError as exc:
|
||||||
|
print(f" 语法错误: 行 {exc.lineno}: {exc.msg}")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r"""改 Gitee 仓库的简介 / 主页,并核对发行版。
|
||||||
|
|
||||||
|
用法(令牌只走命令行,不落盘):
|
||||||
|
python _build/gitee_meta.py <令牌>
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
OWNER, REPO = "xmz7891", "ykxmz"
|
||||||
|
API = f"https://gitee.com/api/v5/repos/{OWNER}/{REPO}"
|
||||||
|
|
||||||
|
DESCRIPTION = (
|
||||||
|
"教室电脑手表遥控 · 老师用手表管教室电脑:放课件、投图、"
|
||||||
|
"屏蔽游戏网站、下课关机。支持局域网直连与跨网络中继"
|
||||||
|
)
|
||||||
|
HOMEPAGE = "https://relay.xxmxxs.top"
|
||||||
|
|
||||||
|
|
||||||
|
def call(url, payload=None, method="GET", timeout=60):
|
||||||
|
data = None
|
||||||
|
headers = {"User-Agent": "ykxmz-meta"}
|
||||||
|
if payload is not None:
|
||||||
|
data = json.dumps(payload).encode("utf-8")
|
||||||
|
headers["Content-Type"] = "application/json;charset=UTF-8"
|
||||||
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||||
|
body = resp.read().decode("utf-8", "replace")
|
||||||
|
return resp.status, (json.loads(body) if body.strip() else {})
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(" 用法: python _build/gitee_meta.py <令牌>")
|
||||||
|
return 1
|
||||||
|
token = sys.argv[1]
|
||||||
|
|
||||||
|
print("===== 1. 改仓库简介 =====")
|
||||||
|
try:
|
||||||
|
status, reply = call(
|
||||||
|
API,
|
||||||
|
{"access_token": token, "name": REPO,
|
||||||
|
"description": DESCRIPTION, "homepage": HOMEPAGE},
|
||||||
|
method="PATCH")
|
||||||
|
if status in (200, 201):
|
||||||
|
print(f" ✓ 简介已更新")
|
||||||
|
print(f" {reply.get('description', '')}")
|
||||||
|
print(f" 主页: {reply.get('homepage', '')}")
|
||||||
|
else:
|
||||||
|
print(f" ✗ HTTP {status}: {reply}")
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode("utf-8", "replace")[:300]
|
||||||
|
print(f" ✗ HTTP {exc.code}: {detail}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("===== 2. 核对发行版 =====")
|
||||||
|
try:
|
||||||
|
_s, releases = call(f"{API}/releases?access_token={token}&per_page=5")
|
||||||
|
if not isinstance(releases, list):
|
||||||
|
print(f" 读不到发行版列表: {releases}")
|
||||||
|
for rel in releases[:3]:
|
||||||
|
print(f" {rel.get('tag_name')} {rel.get('name')}")
|
||||||
|
for asset in rel.get("assets", []):
|
||||||
|
size = asset.get("size", 0) / 1024 / 1024
|
||||||
|
print(f" {asset.get('browser_download_url', '')}"
|
||||||
|
.replace("https://gitee.com", ""))
|
||||||
|
print(f" 大小 {size:.2f} MB "
|
||||||
|
f"下载次数 {asset.get('download_count', 0)}")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(f" 查发行版失败: {exc}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f" 仓库: https://gitee.com/{OWNER}/{REPO}")
|
||||||
|
print(f" 发行版: https://gitee.com/{OWNER}/{REPO}/releases")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r"""在 Gitee 上创建发行版(Release)并上传三个文件。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python _build/gitee_release.py <令牌> [版本号]
|
||||||
|
|
||||||
|
令牌只从命令行参数读,**不写进任何文件、不进仓库**。
|
||||||
|
|
||||||
|
Gitee 的两个接口:
|
||||||
|
建发行版 POST /api/v5/repos/{owner}/{repo}/releases
|
||||||
|
传附件 POST /api/v5/repos/{owner}/{repo}/releases/{id}/attach_files
|
||||||
|
|
||||||
|
SSH 密钥只能推代码,建发行版必须用这个令牌。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import mimetypes
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
|
OWNER = "xmz7891"
|
||||||
|
REPO = "ykxmz"
|
||||||
|
API = f"https://gitee.com/api/v5/repos/{OWNER}/{REPO}"
|
||||||
|
|
||||||
|
|
||||||
|
def api(url: str, payload: dict | None = None, method: str = "GET",
|
||||||
|
timeout: int = 60):
|
||||||
|
data = None
|
||||||
|
headers = {"User-Agent": "ykxmz-release"}
|
||||||
|
if payload is not None:
|
||||||
|
data = json.dumps(payload).encode("utf-8")
|
||||||
|
headers["Content-Type"] = "application/json;charset=UTF-8"
|
||||||
|
request = urllib.request.Request(url, data=data, headers=headers,
|
||||||
|
method=method)
|
||||||
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||||
|
body = response.read().decode("utf-8", "replace")
|
||||||
|
return response.status, (json.loads(body) if body.strip() else {})
|
||||||
|
|
||||||
|
|
||||||
|
def upload_file(url: str, path: Path, token: str, timeout: int = 600):
|
||||||
|
"""multipart 上传单个文件。"""
|
||||||
|
boundary = "----ykxmz" + uuid.uuid4().hex
|
||||||
|
content_type = (mimetypes.guess_type(path.name)[0]
|
||||||
|
or "application/octet-stream")
|
||||||
|
head = (
|
||||||
|
f"--{boundary}\r\n"
|
||||||
|
f'Content-Disposition: form-data; name="access_token"\r\n\r\n'
|
||||||
|
f"{token}\r\n"
|
||||||
|
f"--{boundary}\r\n"
|
||||||
|
f'Content-Disposition: form-data; name="file"; '
|
||||||
|
f'filename="{path.name}"\r\n'
|
||||||
|
f"Content-Type: {content_type}\r\n\r\n"
|
||||||
|
).encode("utf-8")
|
||||||
|
tail = f"\r\n--{boundary}--\r\n".encode("utf-8")
|
||||||
|
body = head + path.read_bytes() + tail
|
||||||
|
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url, data=body, method="POST",
|
||||||
|
headers={
|
||||||
|
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
||||||
|
"Content-Length": str(len(body)),
|
||||||
|
"User-Agent": "ykxmz-release",
|
||||||
|
})
|
||||||
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||||
|
text = response.read().decode("utf-8", "replace")
|
||||||
|
return response.status, (json.loads(text) if text.strip() else {})
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(" 用法: python _build/gitee_release.py <令牌> [版本号]")
|
||||||
|
return 1
|
||||||
|
token = sys.argv[1]
|
||||||
|
version = sys.argv[2] if len(sys.argv) > 2 else "1.6.0"
|
||||||
|
tag = f"v{version}"
|
||||||
|
|
||||||
|
files = [
|
||||||
|
ROOT / "dist" / f"ykxmz-agent-{version}.zip",
|
||||||
|
ROOT / "dist" / f"ykxmz-server-{version}.zip",
|
||||||
|
ROOT / "dist" / f"ykxmz-watch-{version}.apk",
|
||||||
|
]
|
||||||
|
for path in files:
|
||||||
|
if not path.is_file():
|
||||||
|
print(f" [失败] 找不到 {path}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(f" 仓库: {OWNER}/{REPO}")
|
||||||
|
print(f" 标签: {tag}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ---- 1. 建发行版 ----
|
||||||
|
body = f"""## ykxmz {version}
|
||||||
|
|
||||||
|
教室电脑手表遥控。
|
||||||
|
|
||||||
|
老师抬一下手腕就能管教室里那台电脑 —— 放课件、放音频、投图、
|
||||||
|
看看谁在打游戏、下课关机,不用走回讲台。
|
||||||
|
|
||||||
|
### 这次下载哪个
|
||||||
|
|
||||||
|
| 你是 | 下这个 |
|
||||||
|
|---|---|
|
||||||
|
| 教室电脑装被控端 | `ykxmz-agent-{version}.zip` |
|
||||||
|
| 要跨网络(办公室管教室) | `ykxmz-server-{version}.zip` |
|
||||||
|
| 手表装控制端 | `ykxmz-watch-{version}.apk` |
|
||||||
|
|
||||||
|
只在一个教室用的话,第三件(中转服务器)可以先不装,
|
||||||
|
手表和电脑连同一个 WiFi 就能直连。
|
||||||
|
|
||||||
|
### 怎么用
|
||||||
|
|
||||||
|
装完看包里的 `README.txt`,或者仓库首页的说明。
|
||||||
|
|
||||||
|
### 这一版改了什么
|
||||||
|
|
||||||
|
- 重写说明文档,按老师 / 电教 / 开发者 / 普通用户分开讲
|
||||||
|
- 手表端内置根证书,修部分手表连中转时的证书校验失败
|
||||||
|
- 中转站加下载区
|
||||||
|
- 网络稳定性:修 MTU 问题,公网可用率从 48% 提到 95% 以上
|
||||||
|
"""
|
||||||
|
print(" 建发行版…")
|
||||||
|
try:
|
||||||
|
status, reply = api(
|
||||||
|
f"{API}/releases",
|
||||||
|
{"access_token": token, "tag_name": tag, "name": f"ykxmz {version}",
|
||||||
|
"body": body, "target_commitish": "main", "prerelease": False},
|
||||||
|
method="POST")
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode("utf-8", "replace")[:400]
|
||||||
|
print(f" [失败] HTTP {exc.code}: {detail}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if status not in (200, 201):
|
||||||
|
print(f" [失败] HTTP {status}: {reply}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
release_id = reply.get("id")
|
||||||
|
print(f" ✓ 发行版已建,id={release_id}")
|
||||||
|
print(f" {reply.get('html_url', '')}")
|
||||||
|
|
||||||
|
# 如果标签已经存在(重复发布),走"找已有发行版再传附件"的路
|
||||||
|
if not release_id:
|
||||||
|
print(" [提示] 没拿到 id,尝试按标签查已有的发行版…")
|
||||||
|
try:
|
||||||
|
_s, existing = api(f"{API}/releases/tags/{tag}?access_token={token}")
|
||||||
|
release_id = existing.get("id")
|
||||||
|
print(f" 已有发行版 id={release_id}")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(f" [失败] 查不到: {exc}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# ---- 2. 传附件 ----
|
||||||
|
print()
|
||||||
|
print(" 上传文件:")
|
||||||
|
attach = f"{API}/releases/{release_id}/attach_files"
|
||||||
|
for path in files:
|
||||||
|
size_mb = path.stat().st_size / 1024 / 1024
|
||||||
|
try:
|
||||||
|
status, reply = upload_file(attach, path, token)
|
||||||
|
if status in (200, 201):
|
||||||
|
print(f" ✓ {path.name} ({size_mb:.2f} MB)")
|
||||||
|
else:
|
||||||
|
print(f" ✗ {path.name}: HTTP {status} {reply}")
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode("utf-8", "replace")[:200]
|
||||||
|
print(f" ✗ {path.name}: HTTP {exc.code} {detail}")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(f" ✗ {path.name}: {exc}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f" 完成:https://gitee.com/{OWNER}/{REPO}/releases/tag/{tag}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -17,7 +17,7 @@ import zipfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
VERSION = "1.6.0"
|
VERSION = "1.6.2"
|
||||||
NAME = f"ykxmz-{VERSION}"
|
NAME = f"ykxmz-{VERSION}"
|
||||||
|
|
||||||
AGENT_SRC = ROOT / "dist_exe" / "ykxmz"
|
AGENT_SRC = ROOT / "dist_exe" / "ykxmz"
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r"""一条命令完成发布。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python _build/release.py 1.7.0 # 升版本 + 构建 + 打包 + 推送 + 建发行版
|
||||||
|
python _build/release.py 1.7.0 --no-readme # 明说这版没改 README
|
||||||
|
|
||||||
|
它干这些事:
|
||||||
|
1. 升版本号(三端 + 文档 + 测试断言)
|
||||||
|
2. 重建 EXE 和 APK
|
||||||
|
3. 打三个成品
|
||||||
|
4. 跑一遍关键测试
|
||||||
|
5. git 提交 + 推送
|
||||||
|
6. 在 Gitee 建发行版,把三个文件传上去
|
||||||
|
7. 覆盖仓库简介(只在第一次需要,重复设成一样也无害)
|
||||||
|
|
||||||
|
令牌从 _build/.gitee_token 读(那个文件在 .gitignore 里,不会进仓库)。
|
||||||
|
第一次用:把令牌写进去就行。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
|
TOKEN_FILE = ROOT / "_build" / ".gitee_token"
|
||||||
|
GIT = r"C:\Program Files\Git\bin\git.exe"
|
||||||
|
GIT_SSH = r"C:\Program Files\Git\usr\bin\ssh.exe"
|
||||||
|
|
||||||
|
|
||||||
|
def sh(args, cwd=ROOT, check=False, timeout=1800, env=None):
|
||||||
|
merged = dict(os.environ)
|
||||||
|
if env:
|
||||||
|
merged.update(env)
|
||||||
|
proc = subprocess.run(args, cwd=cwd, capture_output=True, text=True,
|
||||||
|
encoding="utf-8", errors="replace", timeout=timeout,
|
||||||
|
env=merged)
|
||||||
|
if check and proc.returncode != 0:
|
||||||
|
raise RuntimeError(f"{args[0]} 失败:{proc.stdout}\n{proc.stderr}")
|
||||||
|
return proc
|
||||||
|
|
||||||
|
|
||||||
|
def step(n, title):
|
||||||
|
print()
|
||||||
|
print(f" {'=' * 56}")
|
||||||
|
print(f" {n}. {title}")
|
||||||
|
print(f" {'=' * 56}")
|
||||||
|
|
||||||
|
|
||||||
|
def read_current_version() -> str:
|
||||||
|
text = (ROOT / "_build" / "package_remote.py").read_text(encoding="utf-8")
|
||||||
|
for line in text.splitlines():
|
||||||
|
if line.startswith("VERSION = "):
|
||||||
|
return line.split('"')[1]
|
||||||
|
return "0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(" 用法: python _build/release.py <新版本号>")
|
||||||
|
print(f" 当前版本: {read_current_version()}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
new_version = sys.argv[1]
|
||||||
|
old_version = read_current_version()
|
||||||
|
note_readme = "--no-readme" in sys.argv
|
||||||
|
|
||||||
|
if not TOKEN_FILE.is_file():
|
||||||
|
print(f" [失败] 找不到令牌文件 {TOKEN_FILE}")
|
||||||
|
print(" 把 Gitee 令牌写进那个文件(一行,别加引号)")
|
||||||
|
return 1
|
||||||
|
token = TOKEN_FILE.read_text(encoding="utf-8").strip()
|
||||||
|
if len(token) < 20:
|
||||||
|
print(f" [失败] 令牌看起来不对(长度 {len(token)})")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(f" 发布 {old_version} -> {new_version}")
|
||||||
|
print(f" README: {'这次改了' if note_readme else '没改'}")
|
||||||
|
|
||||||
|
# ---- 1. 升版本 ----
|
||||||
|
step(1, "升版本号")
|
||||||
|
proc = sh([sys.executable, "_build/bump_version_generic.py",
|
||||||
|
old_version, new_version])
|
||||||
|
print(proc.stdout[-1200:])
|
||||||
|
if proc.returncode != 0:
|
||||||
|
print(proc.stderr[-500:])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# ---- 2. 重建 ----
|
||||||
|
step(2, "重建 EXE 和 APK")
|
||||||
|
sh([GIT, "--version"]) # 探活,无关紧要
|
||||||
|
for args in (["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass",
|
||||||
|
"-File", "android\\build_apk.ps1"],
|
||||||
|
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass",
|
||||||
|
"-File", "windows\\build_exe.ps1"]):
|
||||||
|
proc = sh(args, timeout=2400)
|
||||||
|
tail = (proc.stdout or "").strip().splitlines()[-2:]
|
||||||
|
for line in tail:
|
||||||
|
print(f" {line}")
|
||||||
|
if proc.returncode != 0:
|
||||||
|
print(f" [失败] {args[-1]}")
|
||||||
|
print((proc.stderr or "")[-600:])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# ---- 3. 打包 ----
|
||||||
|
step(3, "打三个成品")
|
||||||
|
proc = sh([sys.executable, "_build/package_remote.py"])
|
||||||
|
print(proc.stdout[-1400:])
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# ---- 4. 关键测试 ----
|
||||||
|
step(4, "跑关键测试")
|
||||||
|
for test in ("audit_encoding.py", "test_net_tune.py", "test_chunked_body.py",
|
||||||
|
"test_admin_js.py"):
|
||||||
|
proc = sh([sys.executable, "-u", f"_tests/{test}"], timeout=900)
|
||||||
|
# 用退出码判断 —— 各测试的输出格式不一样
|
||||||
|
# (有的打"失败 0 项",有的打"结论:JS 合法",字符串匹配会误判)
|
||||||
|
bad = proc.returncode != 0
|
||||||
|
print(f" {test:28} {'✗' if bad else '✓'}")
|
||||||
|
if bad:
|
||||||
|
print((proc.stdout or "")[-800:])
|
||||||
|
print((proc.stderr or "")[-400:])
|
||||||
|
return 1
|
||||||
|
# 打包测试要起真的中转 + 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. 提交推送 ----
|
||||||
|
step(5, "提交并推送")
|
||||||
|
env = {"GIT_SSH_COMMAND": f'"{GIT_SSH}"'}
|
||||||
|
sh([GIT, "add", "-A"])
|
||||||
|
msg = (f"{new_version}:{'README 与' if note_readme else ''}常规发布\n\n"
|
||||||
|
f"- 版本号 {old_version} -> {new_version}\n"
|
||||||
|
f"- 重建三端并打包\n"
|
||||||
|
f"- 关键测试全过\n")
|
||||||
|
sh([GIT, "commit", "-q", "-m", msg])
|
||||||
|
proc = sh([GIT, "push", "origin", "main"], env=env, timeout=1800)
|
||||||
|
print(f" {proc.stdout.strip()[-300:]}")
|
||||||
|
if proc.returncode != 0:
|
||||||
|
print(proc.stderr[-600:])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# ---- 6. 发行版 ----
|
||||||
|
step(6, "建 Gitee 发行版")
|
||||||
|
proc = sh([sys.executable, "_build/gitee_release.py", token, new_version],
|
||||||
|
timeout=2400)
|
||||||
|
print(proc.stdout[-1200:])
|
||||||
|
if proc.returncode != 0:
|
||||||
|
print(proc.stderr[-600:])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f" ✓ {new_version} 发布完成")
|
||||||
|
print(f" https://gitee.com/xmz7891/ykxmz/releases/tag/v{new_version}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -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())
|
||||||
@@ -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())
|
||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 1.7 MiB |
@@ -23,7 +23,7 @@ def _latest_apk() -> Path:
|
|||||||
(ROOT / "dist").glob("*watch*.apk"),
|
(ROOT / "dist").glob("*watch*.apk"),
|
||||||
key=lambda p: p.stat().st_mtime, reverse=True)
|
key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
return candidates[0] if candidates else \
|
return candidates[0] if candidates else \
|
||||||
ROOT / "dist" / "ykxmz-watch-1.6.0.apk"
|
ROOT / "dist" / "ykxmz-watch-1.6.2.apk"
|
||||||
|
|
||||||
|
|
||||||
APK = _latest_apk()
|
APK = _latest_apk()
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ ROOT = Path(__file__).resolve().parent.parent
|
|||||||
# 被控端 -> 一个 zip
|
# 被控端 -> 一个 zip
|
||||||
# 服务器端 -> 一个 zip
|
# 服务器端 -> 一个 zip
|
||||||
# 控制端 -> 直接给 APK(不打 zip)
|
# 控制端 -> 直接给 APK(不打 zip)
|
||||||
AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.6.0.zip"
|
AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.6.2.zip"
|
||||||
SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.6.0.zip"
|
SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.6.2.zip"
|
||||||
WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.6.0.apk"
|
WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.6.2.apk"
|
||||||
TOOLCHAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain")
|
TOOLCHAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain")
|
||||||
JAVAC = TOOLCHAIN / "jdk17" / "bin" / "javac.exe"
|
JAVAC = TOOLCHAIN / "jdk17" / "bin" / "javac.exe"
|
||||||
JAVA = TOOLCHAIN / "jdk17" / "bin" / "java.exe"
|
JAVA = TOOLCHAIN / "jdk17" / "bin" / "java.exe"
|
||||||
@@ -89,7 +89,7 @@ def main() -> int:
|
|||||||
for label, path in (("被控端", agent_dir / "README-agent.txt"),
|
for label, path in (("被控端", agent_dir / "README-agent.txt"),
|
||||||
("服务器端", relay_dir / "README-server.txt"),
|
("服务器端", relay_dir / "README-server.txt"),
|
||||||
("控制端(旁边那份)",
|
("控制端(旁边那份)",
|
||||||
ROOT / "dist" / "ykxmz-watch-1.6.0-说明.txt")):
|
ROOT / "dist" / "ykxmz-watch-1.6.2-说明.txt")):
|
||||||
raw = path.read_bytes() if path.is_file() else b""
|
raw = path.read_bytes() if path.is_file() else b""
|
||||||
check(f"{label}说明有 UTF-8 BOM",
|
check(f"{label}说明有 UTF-8 BOM",
|
||||||
raw[:3] == b"\xef\xbb\xbf", raw[:3])
|
raw[:3] == b"\xef\xbb\xbf", raw[:3])
|
||||||
|
|||||||
@@ -402,7 +402,6 @@ New-Item -ItemType Directory -Force -Path $out | Out-Null
|
|||||||
(Join-Path $root "android\src\com\lanremote\watch\DiscoveryCore.java") `
|
(Join-Path $root "android\src\com\lanremote\watch\DiscoveryCore.java") `
|
||||||
(Join-Path $root "android\src\com\lanremote\watch\Transport.java") `
|
(Join-Path $root "android\src\com\lanremote\watch\Transport.java") `
|
||||||
(Join-Path $root "android\src\com\lanremote\watch\RelayTransport.java") `
|
(Join-Path $root "android\src\com\lanremote\watch\RelayTransport.java") `
|
||||||
# TlsCompat:内置根证书,修老手表连中转时的 SSLHandshakeException
|
|
||||||
(Join-Path $root "android\src\com\lanremote\watch\TlsCompat.java") `
|
(Join-Path $root "android\src\com\lanremote\watch\TlsCompat.java") `
|
||||||
(Join-Path $root "android\src\com\lanremote\watch\RemoteClient.java") `
|
(Join-Path $root "android\src\com\lanremote\watch\RemoteClient.java") `
|
||||||
(Join-Path $root "_tests\java\JavaClientTest.java") 2>&1 | Out-Null
|
(Join-Path $root "_tests\java\JavaClientTest.java") 2>&1 | Out-Null
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.lanremote.watch"
|
package="com.lanremote.watch"
|
||||||
android:versionCode="5"
|
android:versionCode="5"
|
||||||
android:versionName="1.6.0">
|
android:versionName="1.6.2">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ $d8 = Join-Path $bt "d8.bat"
|
|||||||
|
|
||||||
$work = Join-Path $android "build"
|
$work = Join-Path $android "build"
|
||||||
$outDir = Join-Path $root "dist"
|
$outDir = Join-Path $root "dist"
|
||||||
$apkName = "ykxmz-watch-1.6.0.apk"
|
$apkName = "ykxmz-watch-1.6.2.apk"
|
||||||
|
|
||||||
function Step($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan }
|
function Step($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan }
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ $unsigned = Join-Path $work "app-unsigned.apk"
|
|||||||
--min-sdk-version 24 `
|
--min-sdk-version 24 `
|
||||||
--target-sdk-version 34 `
|
--target-sdk-version 34 `
|
||||||
--version-code 5 `
|
--version-code 5 `
|
||||||
--version-name "1.6.0" `
|
--version-name "1.6.2" `
|
||||||
--auto-add-overlay
|
--auto-add-overlay
|
||||||
if ($LASTEXITCODE -ne 0) { throw "aapt2 link 失败" }
|
if ($LASTEXITCODE -ne 0) { throw "aapt2 link 失败" }
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
软件名称:ykxmz
|
软件名称:ykxmz
|
||||||
版本:1.6.0
|
版本:1.6.2
|
||||||
作者:小麦子
|
作者:小麦子
|
||||||
|
|
||||||
装好之后看这一页:怎么确认它在跑、怎么改设置、
|
装好之后看这一页:怎么确认它在跑、怎么改设置、
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
八、版本记录
|
八、版本记录
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|
||||||
1.6.0
|
1.6.2
|
||||||
* 跨网络支持:Cloudflare 具名隧道,固定域名
|
* 跨网络支持:Cloudflare 具名隧道,固定域名
|
||||||
* 中转服务器:单设备单配对码、管理页可删设备
|
* 中转服务器:单设备单配对码、管理页可删设备
|
||||||
* 手表端:自动判断局域网/中转并无缝切换
|
* 手表端:自动判断局域网/中转并无缝切换
|
||||||
@@ -209,6 +209,6 @@
|
|||||||
|
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
作者:小麦子
|
作者:小麦子
|
||||||
软件:ykxmz 1.6.0
|
软件:ykxmz 1.6.2
|
||||||
许可条款见《许可协议》(LICENSE.txt)
|
许可条款见《许可协议》(LICENSE.txt)
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
软件名称:ykxmz
|
软件名称:ykxmz
|
||||||
版本:1.6.0
|
版本:1.6.2
|
||||||
作者:小麦子
|
作者:小麦子
|
||||||
|
|
||||||
请你**在安装之前**花三分钟读完这一页。里面有几条很要紧的
|
请你**在安装之前**花三分钟读完这一页。里面有几条很要紧的
|
||||||
@@ -152,6 +152,6 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
|
|||||||
|
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
作者:小麦子
|
作者:小麦子
|
||||||
软件:ykxmz 1.6.0
|
软件:ykxmz 1.6.2
|
||||||
继续安装,即表示你已阅读并同意《许可协议》。
|
继续安装,即表示你已阅读并同意《许可协议》。
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
软件名称:ykxmz(远程管理工具)
|
软件名称:ykxmz(远程管理工具)
|
||||||
版本:1.6.0
|
版本:1.6.2
|
||||||
作者:小麦子
|
作者:小麦子
|
||||||
生效日期:2026 年 10 月 5 日
|
生效日期:2026 年 10 月 5 日
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
|
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
作者:小麦子
|
作者:小麦子
|
||||||
软件:ykxmz 1.6.0
|
软件:ykxmz 1.6.2
|
||||||
|
|
||||||
如果你不同意本协议的任何一条,请不要安装或使用本软件。
|
如果你不同意本协议的任何一条,请不要安装或使用本软件。
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@ from collections import deque
|
|||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
VERSION = "1.6.0"
|
VERSION = "1.6.2"
|
||||||
|
|
||||||
# ---------------- 参数(都能用环境变量覆盖)----------------
|
# ---------------- 参数(都能用环境变量覆盖)----------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""局域网遥控 · Windows 被控端。"""
|
"""局域网遥控 · Windows 被控端。"""
|
||||||
|
|
||||||
__version__ = "1.6.0"
|
__version__ = "1.6.2"
|
||||||
APP_NAME = "局域网遥控 被控端"
|
APP_NAME = "局域网遥控 被控端"
|
||||||
|
|||||||
@@ -253,6 +253,20 @@ class RelayLink:
|
|||||||
self._log("没配置中转地址,跳过")
|
self._log("没配置中转地址,跳过")
|
||||||
return False
|
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
|
self.candidates = candidates
|
||||||
# 先探一遍,挑第一个通的
|
# 先探一遍,挑第一个通的
|
||||||
chosen = self._pick_candidate(candidates)
|
chosen = self._pick_candidate(candidates)
|
||||||
@@ -260,6 +274,14 @@ class RelayLink:
|
|||||||
if len(candidates) > 1:
|
if len(candidates) > 1:
|
||||||
self._log(f"中转地址候选 {len(candidates)} 个,选用 {chosen}")
|
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.device_id = self._ensure_device_id()
|
||||||
self.code = str(self.store.pairing_code() or "")
|
self.code = str(self.store.pairing_code() or "")
|
||||||
|
|
||||||
|
|||||||
+26
-14
@@ -88,7 +88,10 @@ class Session:
|
|||||||
# 几十毫秒,而且地址永远不变)
|
# 几十毫秒,而且地址永远不变)
|
||||||
# 电脑在外面 -> 第一个连不上,自动切第二个(Cloudflare 隧道,
|
# 电脑在外面 -> 第一个连不上,自动切第二个(Cloudflare 隧道,
|
||||||
# 600 毫秒左右,地址固定 relay.xxmxxs.top)
|
# 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:
|
class RemoteServer:
|
||||||
@@ -149,12 +152,23 @@ class RemoteServer:
|
|||||||
self._sock = sock
|
self._sock = sock
|
||||||
self._running = True
|
self._running = True
|
||||||
|
|
||||||
# 配了中转地址就同时启用跨网络通道(和局域网监听并存,不冲突)
|
# ── 先起局域网发现 ──
|
||||||
try:
|
#
|
||||||
self.enable_relay()
|
# 顺序很重要:enable_relay() 会挨个试中转地址,
|
||||||
except Exception as exc: # noqa: BLE001
|
# 每个候选都要等到超时才算"不通",地址越烂等得越久(实测 10 秒+)。
|
||||||
self._log(f"启用中转失败:{exc}")
|
# 原来这一步排在发现服务前面,导致:
|
||||||
|
# 电脑刚开机的十几秒里,手表广播搜不到它。
|
||||||
|
# 局域网发现是纯本机能力,不该被"连不连得上外网"拖住,
|
||||||
|
# 所以挪到前面,开机即可被搜到。
|
||||||
|
self._discovery = DiscoveryResponder(self.store, self.port)
|
||||||
|
# 手表点「连不上?」会广播请求,这里就把网络自检结论算出来
|
||||||
|
self._discovery.on_selfcheck = self._on_selfcheck_request
|
||||||
|
if self._discovery.start():
|
||||||
|
self._log(f"自动发现已开启(UDP {DISCOVERY_PORT})")
|
||||||
|
else:
|
||||||
|
self._log(f"自动发现未开启:{self._discovery.last_error}")
|
||||||
|
|
||||||
|
# ── 再起收发线程 ──
|
||||||
self._accept_thread = threading.Thread(target=self._accept_loop,
|
self._accept_thread = threading.Thread(target=self._accept_loop,
|
||||||
name="accept", daemon=True)
|
name="accept", daemon=True)
|
||||||
self._accept_thread.start()
|
self._accept_thread.start()
|
||||||
@@ -162,14 +176,12 @@ class RemoteServer:
|
|||||||
name="clip", daemon=True)
|
name="clip", daemon=True)
|
||||||
self._clip_thread.start()
|
self._clip_thread.start()
|
||||||
|
|
||||||
# 启动局域网自动发现,让手表不用输 IP
|
# ── 最后才连中转(会阻塞,所以放最后)──
|
||||||
self._discovery = DiscoveryResponder(self.store, self.port)
|
# 配了中转地址就同时启用跨网络通道(和局域网监听并存,不冲突)
|
||||||
# 手表点「连不上?」会广播请求,这里就把网络自检窗口弹出来
|
try:
|
||||||
self._discovery.on_selfcheck = self._on_selfcheck_request
|
self.enable_relay()
|
||||||
if self._discovery.start():
|
except Exception as exc: # noqa: BLE001
|
||||||
self._log(f"自动发现已开启(UDP {DISCOVERY_PORT})")
|
self._log(f"启用中转失败:{exc}")
|
||||||
else:
|
|
||||||
self._log(f"自动发现未开启:{self._discovery.last_error}")
|
|
||||||
|
|
||||||
self._log(f"服务已启动,监听 0.0.0.0:{self.port}")
|
self._log(f"服务已启动,监听 0.0.0.0:{self.port}")
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ VSVersionInfo(
|
|||||||
[
|
[
|
||||||
StringStruct("CompanyName", "ykxmz"),
|
StringStruct("CompanyName", "ykxmz"),
|
||||||
StringStruct("FileDescription", "ykxmz"),
|
StringStruct("FileDescription", "ykxmz"),
|
||||||
StringStruct("FileVersion", "1.6.0.0"),
|
StringStruct("FileVersion", "1.6.2.0"),
|
||||||
StringStruct("InternalName", "ykxmz"),
|
StringStruct("InternalName", "ykxmz"),
|
||||||
StringStruct("LegalCopyright", "ykxmz"),
|
StringStruct("LegalCopyright", "ykxmz"),
|
||||||
StringStruct("OriginalFilename", "ykxmz.exe"),
|
StringStruct("OriginalFilename", "ykxmz.exe"),
|
||||||
StringStruct("ProductName", "ykxmz"),
|
StringStruct("ProductName", "ykxmz"),
|
||||||
StringStruct("ProductVersion", "1.6.0.0"),
|
StringStruct("ProductVersion", "1.6.2.0"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user