🔐 修一个严重安全问题:管理密码被硬编码进公开仓库

== 问题 ==

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 **历史**里还有那个密码。要彻底清掉得重写历史 + 强制推送,
这是个危险操作,按用户的规矩必须他确认之后才做。
而且**光删历史不够** —— 密码已经公开过,必须换(已换)。
This commit is contained in:
xmz7891
2026-10-07 20:49:33 +08:00
parent 571ea0cc76
commit 430923dd84
27 changed files with 1799 additions and 23 deletions
+72
View File
@@ -0,0 +1,72 @@
🔐 修一个严重安全问题:管理密码被硬编码进公开仓库
== 问题 ==
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 **历史**里还有那个密码。要彻底清掉得重写历史 + 强制推送,
这是个危险操作,按用户的规矩必须他确认之后才做。
而且**光删历史不够** —— 密码已经公开过,必须换(已换)。
+5 -1
View File
@@ -1,4 +1,8 @@
#!/bin/bash
# ⚠ 管理密码不再写在这里,从环境变量取:
# export RELAY_ADMIN_PASSWORD=...
: "${RELAY_ADMIN_PASSWORD:?请先 export RELAY_ADMIN_PASSWORD}"
# 官网 / 管理页 / favicon 的最终检查
set -u
@@ -26,7 +30,7 @@ curl -s -o /dev/null -w ' -> %{redirect_url}\n' --max-time 15 "$BASE/admin"
echo
echo " 4) 登录"
curl -s -c /tmp/ck2.txt -o /dev/null --max-time 15 -X POST \
-d 'username=xmz7891&password=@REVAedu120830' "$BASE/admin/login" 2>/dev/null
-d "username=xmz7891&password=$RELAY_ADMIN_PASSWORD" "$BASE/admin/login" 2>/dev/null
curl -s -b /tmp/ck2.txt -o /tmp/admin.html --max-time 15 "$BASE/zhongzhuanzhan" 2>/dev/null
for n in 'id=dlPanel' '网络体检' '退出登录' 'favicon'; do
grep -q "$n" /tmp/admin.html && echo " OK $n" || echo " XX $n"
+94
View File
@@ -0,0 +1,94 @@
2026-10-07 17:27:20 X X
2026-10-07 17:28:27 8 2
2026-10-07 17:29:33 8 2
2026-10-07 17:30:53 7 2
2026-10-07 17:31:59 1 2
2026-10-07 17:33:04 8 2
2026-10-07 17:34:11 8 2
2026-10-07 17:35:17 8 2
2026-10-07 17:36:22 8 2
2026-10-07 17:37:29 8 2
2026-10-07 17:38:36 8 2
2026-10-07 17:39:43 8 2
2026-10-07 17:41:02 7 2
2026-10-07 17:42:08 7 2
2026-10-07 17:43:09 7 2
2026-10-07 17:44:16 8 2
2026-10-07 17:45:23 8 2
2026-10-07 17:46:28 8 2
2026-10-07 17:47:32 8 2
2026-10-07 17:48:37 8 2
2026-10-07 17:49:43 8 2
2026-10-07 17:50:48 7 2
2026-10-07 17:52:06 8 2
2026-10-07 17:53:20 7 2
2026-10-07 17:54:26 7 2
2026-10-07 17:55:33 8 2
2026-10-07 17:57:06 1 2
2026-10-07 17:58:12 8 2
2026-10-07 17:59:17 8 2
2026-10-07 18:00:35 8 2
2026-10-07 18:01:38 5 2
2026-10-07 18:02:46 8 1
2026-10-07 18:03:52 6 2
2026-10-07 18:04:59 6 2
2026-10-07 18:06:05 8 2
2026-10-07 18:07:10 8 2
2026-10-07 18:08:16 8 2
2026-10-07 18:09:20 8 2
2026-10-07 18:10:39 7 2
2026-10-07 18:11:29 7 2
2026-10-07 18:12:35 8 2
2026-10-07 18:13:38 8 2
2026-10-07 18:14:42 8 2
2026-10-07 18:15:47 8 2
2026-10-07 18:16:53 8 2
2026-10-07 18:17:58 8 2
2026-10-07 18:19:04 8 2
2026-10-07 18:20:10 8 2
2026-10-07 18:21:13 8 2
2026-10-07 18:22:19 8 2
2026-10-07 18:23:21 8 2
2026-10-07 18:24:26 8 2
2026-10-07 18:25:34 7 2
2026-10-07 18:26:39 8 2
2026-10-07 18:27:45 8 2
2026-10-07 18:28:51 8 2
2026-10-07 18:30:09 8 2
2026-10-07 18:30:59 8 2
2026-10-07 18:32:06 7 2
2026-10-07 18:33:12 7 1
2026-10-07 18:34:19 8 2
2026-10-07 18:35:24 8 2
2026-10-07 18:36:29 8 2
2026-10-07 18:37:34 8 2
2026-10-07 18:38:39 8 2
2026-10-07 18:39:46 8 2
2026-10-07 18:40:48 8 2
2026-10-07 18:41:50 8 2
2026-10-07 18:42:52 8 2
2026-10-07 18:43:57 8 2
2026-10-07 18:45:04 8 2
2026-10-07 18:46:10 8 2
2026-10-07 18:47:15 8 2
2026-10-07 18:48:21 8 2
2026-10-07 18:49:26 8 2
2026-10-07 18:50:45 8 2
2026-10-07 18:51:51 8 2
2026-10-07 18:52:53 8 2
2026-10-07 18:53:58 8 2
2026-10-07 18:55:04 8 2
2026-10-07 18:56:10 8 2
2026-10-07 18:57:15 8 2
2026-10-07 18:58:23 8 2
2026-10-07 18:59:29 8 2
2026-10-07 19:00:36 5 2
2026-10-07 19:01:40 7 2
2026-10-07 19:02:42 6 1
2026-10-07 19:03:55 6 2
2026-10-07 19:05:03 8 2
2026-10-07 19:06:10 8 2
2026-10-07 19:07:16 8 2
2026-10-07 19:08:22 8 2
2026-10-07 19:09:27 8 2
2026-10-07 19:10:46 6 2
1 2026-10-07 17:27:20 X X
2 2026-10-07 17:28:27 8 2
3 2026-10-07 17:29:33 8 2
4 2026-10-07 17:30:53 7 2
5 2026-10-07 17:31:59 1 2
6 2026-10-07 17:33:04 8 2
7 2026-10-07 17:34:11 8 2
8 2026-10-07 17:35:17 8 2
9 2026-10-07 17:36:22 8 2
10 2026-10-07 17:37:29 8 2
11 2026-10-07 17:38:36 8 2
12 2026-10-07 17:39:43 8 2
13 2026-10-07 17:41:02 7 2
14 2026-10-07 17:42:08 7 2
15 2026-10-07 17:43:09 7 2
16 2026-10-07 17:44:16 8 2
17 2026-10-07 17:45:23 8 2
18 2026-10-07 17:46:28 8 2
19 2026-10-07 17:47:32 8 2
20 2026-10-07 17:48:37 8 2
21 2026-10-07 17:49:43 8 2
22 2026-10-07 17:50:48 7 2
23 2026-10-07 17:52:06 8 2
24 2026-10-07 17:53:20 7 2
25 2026-10-07 17:54:26 7 2
26 2026-10-07 17:55:33 8 2
27 2026-10-07 17:57:06 1 2
28 2026-10-07 17:58:12 8 2
29 2026-10-07 17:59:17 8 2
30 2026-10-07 18:00:35 8 2
31 2026-10-07 18:01:38 5 2
32 2026-10-07 18:02:46 8 1
33 2026-10-07 18:03:52 6 2
34 2026-10-07 18:04:59 6 2
35 2026-10-07 18:06:05 8 2
36 2026-10-07 18:07:10 8 2
37 2026-10-07 18:08:16 8 2
38 2026-10-07 18:09:20 8 2
39 2026-10-07 18:10:39 7 2
40 2026-10-07 18:11:29 7 2
41 2026-10-07 18:12:35 8 2
42 2026-10-07 18:13:38 8 2
43 2026-10-07 18:14:42 8 2
44 2026-10-07 18:15:47 8 2
45 2026-10-07 18:16:53 8 2
46 2026-10-07 18:17:58 8 2
47 2026-10-07 18:19:04 8 2
48 2026-10-07 18:20:10 8 2
49 2026-10-07 18:21:13 8 2
50 2026-10-07 18:22:19 8 2
51 2026-10-07 18:23:21 8 2
52 2026-10-07 18:24:26 8 2
53 2026-10-07 18:25:34 7 2
54 2026-10-07 18:26:39 8 2
55 2026-10-07 18:27:45 8 2
56 2026-10-07 18:28:51 8 2
57 2026-10-07 18:30:09 8 2
58 2026-10-07 18:30:59 8 2
59 2026-10-07 18:32:06 7 2
60 2026-10-07 18:33:12 7 1
61 2026-10-07 18:34:19 8 2
62 2026-10-07 18:35:24 8 2
63 2026-10-07 18:36:29 8 2
64 2026-10-07 18:37:34 8 2
65 2026-10-07 18:38:39 8 2
66 2026-10-07 18:39:46 8 2
67 2026-10-07 18:40:48 8 2
68 2026-10-07 18:41:50 8 2
69 2026-10-07 18:42:52 8 2
70 2026-10-07 18:43:57 8 2
71 2026-10-07 18:45:04 8 2
72 2026-10-07 18:46:10 8 2
73 2026-10-07 18:47:15 8 2
74 2026-10-07 18:48:21 8 2
75 2026-10-07 18:49:26 8 2
76 2026-10-07 18:50:45 8 2
77 2026-10-07 18:51:51 8 2
78 2026-10-07 18:52:53 8 2
79 2026-10-07 18:53:58 8 2
80 2026-10-07 18:55:04 8 2
81 2026-10-07 18:56:10 8 2
82 2026-10-07 18:57:15 8 2
83 2026-10-07 18:58:23 8 2
84 2026-10-07 18:59:29 8 2
85 2026-10-07 19:00:36 5 2
86 2026-10-07 19:01:40 7 2
87 2026-10-07 19:02:42 6 1
88 2026-10-07 19:03:55 6 2
89 2026-10-07 19:05:03 8 2
90 2026-10-07 19:06:10 8 2
91 2026-10-07 19:07:16 8 2
92 2026-10-07 19:08:22 8 2
93 2026-10-07 19:09:27 8 2
94 2026-10-07 19:10:46 6 2
+362
View File
@@ -0,0 +1,362 @@
2026-10-07 17:27:20 502 1407
2026-10-07 17:27:36 200 921
2026-10-07 17:27:52 200 1063
2026-10-07 17:28:09 200 1491
2026-10-07 17:28:27 200 2796
2026-10-07 17:28:44 200 2277
2026-10-07 17:29:00 200 974
2026-10-07 17:29:17 200 1274
2026-10-07 17:29:33 200 1265
2026-10-07 17:29:50 200 1176
2026-10-07 17:30:06 200 1062
2026-10-07 17:30:35 200 14074
2026-10-07 17:30:53 200 2637
2026-10-07 17:31:09 200 1180
2026-10-07 17:31:26 200 1524
2026-10-07 17:31:42 530 862
2026-10-07 17:31:58 200 1683
2026-10-07 17:32:15 200 997
2026-10-07 17:32:31 200 1530
2026-10-07 17:32:48 200 1542
2026-10-07 17:33:04 200 1257
2026-10-07 17:33:21 200 1439
2026-10-07 17:33:38 200 1526
2026-10-07 17:33:55 200 2034
2026-10-07 17:34:11 200 1438
2026-10-07 17:34:28 200 1240
2026-10-07 17:34:44 200 1509
2026-10-07 17:35:01 200 1280
2026-10-07 17:35:17 200 1240
2026-10-07 17:35:34 200 1293
2026-10-07 17:35:50 200 1018
2026-10-07 17:36:06 200 1063
2026-10-07 17:36:22 200 1033
2026-10-07 17:36:40 200 2410
2026-10-07 17:36:56 200 1206
2026-10-07 17:37:13 200 1503
2026-10-07 17:37:29 200 1152
2026-10-07 17:37:47 200 2442
2026-10-07 17:38:03 200 1267
2026-10-07 17:38:20 200 1555
2026-10-07 17:38:36 200 1187
2026-10-07 17:38:54 200 2524
2026-10-07 17:39:10 200 973
2026-10-07 17:39:26 200 1444
2026-10-07 17:39:43 200 1285
2026-10-07 17:40:00 200 1600
2026-10-07 17:40:16 200 982
2026-10-07 17:40:45 200 14272
2026-10-07 17:41:01 200 1264
2026-10-07 17:41:18 200 1235
2026-10-07 17:41:35 200 1482
2026-10-07 17:41:51 200 1471
2026-10-07 17:42:08 200 1483
2026-10-07 17:42:37 200 13940
2026-10-07 17:42:53 200 1073
2026-10-07 17:43:09 200 1093
2026-10-07 17:43:26 200 1472
2026-10-07 17:43:43 200 1576
2026-10-07 17:43:59 200 1453
2026-10-07 17:44:16 200 1984
2026-10-07 17:44:33 200 1237
2026-10-07 17:44:49 200 924
2026-10-07 17:45:06 200 1936
2026-10-07 17:45:23 200 1750
2026-10-07 17:45:39 200 1039
2026-10-07 17:45:55 200 1033
2026-10-07 17:46:11 200 1061
2026-10-07 17:46:27 200 1083
2026-10-07 17:46:44 200 847
2026-10-07 17:47:00 200 972
2026-10-07 17:47:16 200 1055
2026-10-07 17:47:32 200 1219
2026-10-07 17:47:48 200 1034
2026-10-07 17:48:05 200 1203
2026-10-07 17:48:21 200 1142
2026-10-07 17:48:37 200 1235
2026-10-07 17:48:53 200 988
2026-10-07 17:49:10 200 1636
2026-10-07 17:49:26 200 998
2026-10-07 17:49:43 200 1226
2026-10-07 17:49:59 200 849
2026-10-07 17:50:15 200 966
2026-10-07 17:50:31 200 1476
2026-10-07 17:50:48 200 1336
2026-10-07 17:51:05 200 1452
2026-10-07 17:51:21 200 1017
2026-10-07 17:51:50 200 13815
2026-10-07 17:52:06 200 1437
2026-10-07 17:52:23 200 1215
2026-10-07 17:52:39 200 1065
2026-10-07 17:53:03 200 9538
2026-10-07 17:53:20 200 1556
2026-10-07 17:53:36 200 1132
2026-10-07 17:53:53 200 1548
2026-10-07 17:54:09 200 1243
2026-10-07 17:54:26 200 1499
2026-10-07 17:54:43 200 1516
2026-10-07 17:54:59 200 1491
2026-10-07 17:55:16 200 1492
2026-10-07 17:55:33 200 1665
2026-10-07 17:55:49 200 1227
2026-10-07 17:56:06 200 1531
2026-10-07 17:56:46 000 25000
2026-10-07 17:57:06 502 4652
2026-10-07 17:57:22 200 1475
2026-10-07 17:57:39 200 1545
2026-10-07 17:57:55 200 1116
2026-10-07 17:58:11 200 1026
2026-10-07 17:58:28 200 1544
2026-10-07 17:58:44 200 1038
2026-10-07 17:59:00 200 992
2026-10-07 17:59:17 200 988
2026-10-07 17:59:34 200 1823
2026-10-07 17:59:50 200 1004
2026-10-07 18:00:06 200 1236
2026-10-07 18:00:35 200 14105
2026-10-07 18:01:05 200 14208
2026-10-07 18:01:21 200 1597
2026-10-07 18:01:38 200 1531
2026-10-07 18:01:56 200 2613
2026-10-07 18:02:13 200 1563
2026-10-07 18:02:29 200 1509
2026-10-07 18:02:46 200 1521
2026-10-07 18:03:03 200 1492
2026-10-07 18:03:19 200 1546
2026-10-07 18:03:36 200 1215
2026-10-07 18:03:52 200 1579
2026-10-07 18:04:09 200 1592
2026-10-07 18:04:26 200 2187
2026-10-07 18:04:43 200 1001
2026-10-07 18:04:59 200 1600
2026-10-07 18:05:16 200 1116
2026-10-07 18:05:32 200 991
2026-10-07 18:05:49 200 1986
2026-10-07 18:06:05 200 1005
2026-10-07 18:06:21 200 992
2026-10-07 18:06:37 200 1080
2026-10-07 18:06:54 200 1066
2026-10-07 18:07:10 200 1463
2026-10-07 18:07:27 200 1210
2026-10-07 18:07:43 200 1408
2026-10-07 18:07:59 200 1024
2026-10-07 18:08:16 200 1280
2026-10-07 18:08:32 200 1300
2026-10-07 18:08:48 200 992
2026-10-07 18:09:04 200 954
2026-10-07 18:09:20 200 987
2026-10-07 18:09:37 200 1023
2026-10-07 18:09:53 200 1256
2026-10-07 18:10:09 200 1386
2026-10-07 18:10:39 200 13955
2026-10-07 18:10:55 200 1653
2026-10-07 18:11:12 200 1824
2026-10-07 18:11:29 200 1247
2026-10-07 18:11:45 200 1274
2026-10-07 18:12:02 200 1539
2026-10-07 18:12:18 200 1335
2026-10-07 18:12:35 200 1530
2026-10-07 18:13:04 200 13994
2026-10-07 18:13:21 200 1454
2026-10-07 18:13:37 200 1591
2026-10-07 18:13:54 200 1030
2026-10-07 18:14:10 200 829
2026-10-07 18:14:26 200 1085
2026-10-07 18:14:42 200 1217
2026-10-07 18:14:58 200 1134
2026-10-07 18:15:15 200 934
2026-10-07 18:15:31 200 1125
2026-10-07 18:15:47 200 1089
2026-10-07 18:16:03 200 1026
2026-10-07 18:16:20 200 1488
2026-10-07 18:16:36 200 1477
2026-10-07 18:16:53 200 1367
2026-10-07 18:17:10 200 1488
2026-10-07 18:17:26 200 1003
2026-10-07 18:17:42 200 1038
2026-10-07 18:17:58 200 981
2026-10-07 18:18:15 200 1471
2026-10-07 18:18:31 200 1255
2026-10-07 18:18:47 200 1233
2026-10-07 18:19:04 200 1439
2026-10-07 18:19:20 200 1008
2026-10-07 18:19:36 200 1023
2026-10-07 18:19:52 200 1049
2026-10-07 18:20:10 200 2696
2026-10-07 18:20:26 200 985
2026-10-07 18:20:56 200 14428
2026-10-07 18:21:13 200 1632
2026-10-07 18:21:30 200 1731
2026-10-07 18:21:46 200 1160
2026-10-07 18:22:03 200 1462
2026-10-07 18:22:19 200 1455
2026-10-07 18:22:48 200 14144
2026-10-07 18:23:05 200 1596
2026-10-07 18:23:21 200 1055
2026-10-07 18:23:38 200 987
2026-10-07 18:23:54 200 1048
2026-10-07 18:24:10 200 940
2026-10-07 18:24:26 200 1256
2026-10-07 18:24:44 200 2653
2026-10-07 18:25:01 200 1705
2026-10-07 18:25:17 200 1408
2026-10-07 18:25:34 200 1557
2026-10-07 18:25:51 200 1660
2026-10-07 18:26:07 200 1005
2026-10-07 18:26:23 200 1015
2026-10-07 18:26:39 200 1079
2026-10-07 18:26:56 200 1542
2026-10-07 18:27:13 200 1534
2026-10-07 18:27:29 200 1196
2026-10-07 18:27:45 200 1163
2026-10-07 18:28:02 200 1239
2026-10-07 18:28:18 200 1232
2026-10-07 18:28:35 200 1560
2026-10-07 18:28:51 200 1267
2026-10-07 18:29:08 200 1328
2026-10-07 18:29:24 200 1070
2026-10-07 18:29:40 200 1048
2026-10-07 18:30:09 200 13901
2026-10-07 18:30:26 200 1257
2026-10-07 18:30:42 200 1670
2026-10-07 18:30:59 200 1488
2026-10-07 18:31:16 200 1716
2026-10-07 18:31:33 200 1518
2026-10-07 18:31:49 200 1522
2026-10-07 18:32:06 200 1537
2026-10-07 18:32:23 200 1513
2026-10-07 18:32:39 200 1547
2026-10-07 18:32:56 200 1237
2026-10-07 18:33:12 200 1378
2026-10-07 18:33:29 200 1255
2026-10-07 18:33:45 200 1212
2026-10-07 18:34:02 200 2046
2026-10-07 18:34:19 200 1424
2026-10-07 18:34:35 200 1406
2026-10-07 18:34:52 200 1279
2026-10-07 18:35:08 200 974
2026-10-07 18:35:24 200 957
2026-10-07 18:35:40 200 937
2026-10-07 18:35:56 200 903
2026-10-07 18:36:12 200 1223
2026-10-07 18:36:29 200 1456
2026-10-07 18:36:45 200 1183
2026-10-07 18:37:01 200 1076
2026-10-07 18:37:18 200 1182
2026-10-07 18:37:34 200 937
2026-10-07 18:37:50 200 1144
2026-10-07 18:38:06 200 1218
2026-10-07 18:38:23 200 1185
2026-10-07 18:38:39 200 1028
2026-10-07 18:38:55 200 969
2026-10-07 18:39:11 200 933
2026-10-07 18:39:28 200 1706
2026-10-07 18:39:45 200 2392
2026-10-07 18:40:02 200 1735
2026-10-07 18:40:31 200 13866
2026-10-07 18:40:48 200 1531
2026-10-07 18:41:05 200 1282
2026-10-07 18:41:34 200 14107
2026-10-07 18:41:50 200 906
2026-10-07 18:42:19 200 14242
2026-10-07 18:42:36 200 1260
2026-10-07 18:42:52 200 1254
2026-10-07 18:43:08 200 1260
2026-10-07 18:43:25 200 1021
2026-10-07 18:43:41 200 829
2026-10-07 18:43:57 200 1012
2026-10-07 18:44:13 200 1613
2026-10-07 18:44:30 200 1556
2026-10-07 18:44:47 200 1422
2026-10-07 18:45:04 200 2419
2026-10-07 18:45:20 200 978
2026-10-07 18:45:37 200 1787
2026-10-07 18:45:53 200 968
2026-10-07 18:46:10 200 1652
2026-10-07 18:46:27 200 1504
2026-10-07 18:46:43 200 944
2026-10-07 18:46:59 200 1006
2026-10-07 18:47:15 200 968
2026-10-07 18:47:31 200 963
2026-10-07 18:47:48 200 1245
2026-10-07 18:48:04 200 1551
2026-10-07 18:48:21 200 1194
2026-10-07 18:48:37 200 941
2026-10-07 18:48:53 200 1010
2026-10-07 18:49:09 200 1051
2026-10-07 18:49:25 200 1205
2026-10-07 18:49:42 200 1372
2026-10-07 18:49:58 200 1310
2026-10-07 18:50:28 200 14115
2026-10-07 18:50:45 200 2386
2026-10-07 18:51:02 200 1415
2026-10-07 18:51:18 200 1195
2026-10-07 18:51:34 200 1230
2026-10-07 18:51:51 200 1333
2026-10-07 18:52:20 200 13900
2026-10-07 18:52:36 200 1245
2026-10-07 18:52:53 200 1218
2026-10-07 18:53:09 200 963
2026-10-07 18:53:26 200 1529
2026-10-07 18:53:42 200 1053
2026-10-07 18:53:58 200 1069
2026-10-07 18:54:14 200 1012
2026-10-07 18:54:31 200 1846
2026-10-07 18:54:48 200 1324
2026-10-07 18:55:04 200 1550
2026-10-07 18:55:20 200 1008
2026-10-07 18:55:37 200 1066
2026-10-07 18:55:53 200 1065
2026-10-07 18:56:09 200 1515
2026-10-07 18:56:26 200 988
2026-10-07 18:56:42 200 1003
2026-10-07 18:56:58 200 1077
2026-10-07 18:57:15 200 2306
2026-10-07 18:57:33 200 2405
2026-10-07 18:57:50 200 1549
2026-10-07 18:58:07 200 2098
2026-10-07 18:58:23 200 970
2026-10-07 18:58:39 200 1089
2026-10-07 18:58:56 200 1596
2026-10-07 18:59:13 200 1906
2026-10-07 18:59:29 200 1012
2026-10-07 18:59:46 200 1588
2026-10-07 19:00:02 200 1317
2026-10-07 19:00:20 502 2101
2026-10-07 19:00:36 200 1627
2026-10-07 19:00:54 200 2006
2026-10-07 19:01:23 200 14099
2026-10-07 19:01:39 200 1599
2026-10-07 19:01:56 200 1539
2026-10-07 19:02:26 200 14438
2026-10-07 19:02:42 200 1264
2026-10-07 19:03:06 200 8534
2026-10-07 19:03:22 200 1311
2026-10-07 19:03:38 200 971
2026-10-07 19:03:55 200 1676
2026-10-07 19:04:12 200 1795
2026-10-07 19:04:29 200 1552
2026-10-07 19:04:45 200 994
2026-10-07 19:05:03 200 3017
2026-10-07 19:05:19 200 1048
2026-10-07 19:05:36 200 1253
2026-10-07 19:05:52 200 1607
2026-10-07 19:06:10 200 2535
2026-10-07 19:06:26 200 1126
2026-10-07 19:06:43 200 1116
2026-10-07 19:06:59 200 1361
2026-10-07 19:07:16 200 1255
2026-10-07 19:07:32 200 1161
2026-10-07 19:07:49 200 2006
2026-10-07 19:08:06 200 1564
2026-10-07 19:08:22 200 1161
2026-10-07 19:08:39 200 1480
2026-10-07 19:08:55 200 998
2026-10-07 19:09:11 200 993
2026-10-07 19:09:27 200 981
2026-10-07 19:09:44 200 1409
2026-10-07 19:10:00 200 1522
2026-10-07 19:10:17 200 1633
2026-10-07 19:10:46 200 14107
2026-10-07 19:11:04 200 2563
1 2026-10-07 17:27:20 502 1407
2 2026-10-07 17:27:36 200 921
3 2026-10-07 17:27:52 200 1063
4 2026-10-07 17:28:09 200 1491
5 2026-10-07 17:28:27 200 2796
6 2026-10-07 17:28:44 200 2277
7 2026-10-07 17:29:00 200 974
8 2026-10-07 17:29:17 200 1274
9 2026-10-07 17:29:33 200 1265
10 2026-10-07 17:29:50 200 1176
11 2026-10-07 17:30:06 200 1062
12 2026-10-07 17:30:35 200 14074
13 2026-10-07 17:30:53 200 2637
14 2026-10-07 17:31:09 200 1180
15 2026-10-07 17:31:26 200 1524
16 2026-10-07 17:31:42 530 862
17 2026-10-07 17:31:58 200 1683
18 2026-10-07 17:32:15 200 997
19 2026-10-07 17:32:31 200 1530
20 2026-10-07 17:32:48 200 1542
21 2026-10-07 17:33:04 200 1257
22 2026-10-07 17:33:21 200 1439
23 2026-10-07 17:33:38 200 1526
24 2026-10-07 17:33:55 200 2034
25 2026-10-07 17:34:11 200 1438
26 2026-10-07 17:34:28 200 1240
27 2026-10-07 17:34:44 200 1509
28 2026-10-07 17:35:01 200 1280
29 2026-10-07 17:35:17 200 1240
30 2026-10-07 17:35:34 200 1293
31 2026-10-07 17:35:50 200 1018
32 2026-10-07 17:36:06 200 1063
33 2026-10-07 17:36:22 200 1033
34 2026-10-07 17:36:40 200 2410
35 2026-10-07 17:36:56 200 1206
36 2026-10-07 17:37:13 200 1503
37 2026-10-07 17:37:29 200 1152
38 2026-10-07 17:37:47 200 2442
39 2026-10-07 17:38:03 200 1267
40 2026-10-07 17:38:20 200 1555
41 2026-10-07 17:38:36 200 1187
42 2026-10-07 17:38:54 200 2524
43 2026-10-07 17:39:10 200 973
44 2026-10-07 17:39:26 200 1444
45 2026-10-07 17:39:43 200 1285
46 2026-10-07 17:40:00 200 1600
47 2026-10-07 17:40:16 200 982
48 2026-10-07 17:40:45 200 14272
49 2026-10-07 17:41:01 200 1264
50 2026-10-07 17:41:18 200 1235
51 2026-10-07 17:41:35 200 1482
52 2026-10-07 17:41:51 200 1471
53 2026-10-07 17:42:08 200 1483
54 2026-10-07 17:42:37 200 13940
55 2026-10-07 17:42:53 200 1073
56 2026-10-07 17:43:09 200 1093
57 2026-10-07 17:43:26 200 1472
58 2026-10-07 17:43:43 200 1576
59 2026-10-07 17:43:59 200 1453
60 2026-10-07 17:44:16 200 1984
61 2026-10-07 17:44:33 200 1237
62 2026-10-07 17:44:49 200 924
63 2026-10-07 17:45:06 200 1936
64 2026-10-07 17:45:23 200 1750
65 2026-10-07 17:45:39 200 1039
66 2026-10-07 17:45:55 200 1033
67 2026-10-07 17:46:11 200 1061
68 2026-10-07 17:46:27 200 1083
69 2026-10-07 17:46:44 200 847
70 2026-10-07 17:47:00 200 972
71 2026-10-07 17:47:16 200 1055
72 2026-10-07 17:47:32 200 1219
73 2026-10-07 17:47:48 200 1034
74 2026-10-07 17:48:05 200 1203
75 2026-10-07 17:48:21 200 1142
76 2026-10-07 17:48:37 200 1235
77 2026-10-07 17:48:53 200 988
78 2026-10-07 17:49:10 200 1636
79 2026-10-07 17:49:26 200 998
80 2026-10-07 17:49:43 200 1226
81 2026-10-07 17:49:59 200 849
82 2026-10-07 17:50:15 200 966
83 2026-10-07 17:50:31 200 1476
84 2026-10-07 17:50:48 200 1336
85 2026-10-07 17:51:05 200 1452
86 2026-10-07 17:51:21 200 1017
87 2026-10-07 17:51:50 200 13815
88 2026-10-07 17:52:06 200 1437
89 2026-10-07 17:52:23 200 1215
90 2026-10-07 17:52:39 200 1065
91 2026-10-07 17:53:03 200 9538
92 2026-10-07 17:53:20 200 1556
93 2026-10-07 17:53:36 200 1132
94 2026-10-07 17:53:53 200 1548
95 2026-10-07 17:54:09 200 1243
96 2026-10-07 17:54:26 200 1499
97 2026-10-07 17:54:43 200 1516
98 2026-10-07 17:54:59 200 1491
99 2026-10-07 17:55:16 200 1492
100 2026-10-07 17:55:33 200 1665
101 2026-10-07 17:55:49 200 1227
102 2026-10-07 17:56:06 200 1531
103 2026-10-07 17:56:46 000 25000
104 2026-10-07 17:57:06 502 4652
105 2026-10-07 17:57:22 200 1475
106 2026-10-07 17:57:39 200 1545
107 2026-10-07 17:57:55 200 1116
108 2026-10-07 17:58:11 200 1026
109 2026-10-07 17:58:28 200 1544
110 2026-10-07 17:58:44 200 1038
111 2026-10-07 17:59:00 200 992
112 2026-10-07 17:59:17 200 988
113 2026-10-07 17:59:34 200 1823
114 2026-10-07 17:59:50 200 1004
115 2026-10-07 18:00:06 200 1236
116 2026-10-07 18:00:35 200 14105
117 2026-10-07 18:01:05 200 14208
118 2026-10-07 18:01:21 200 1597
119 2026-10-07 18:01:38 200 1531
120 2026-10-07 18:01:56 200 2613
121 2026-10-07 18:02:13 200 1563
122 2026-10-07 18:02:29 200 1509
123 2026-10-07 18:02:46 200 1521
124 2026-10-07 18:03:03 200 1492
125 2026-10-07 18:03:19 200 1546
126 2026-10-07 18:03:36 200 1215
127 2026-10-07 18:03:52 200 1579
128 2026-10-07 18:04:09 200 1592
129 2026-10-07 18:04:26 200 2187
130 2026-10-07 18:04:43 200 1001
131 2026-10-07 18:04:59 200 1600
132 2026-10-07 18:05:16 200 1116
133 2026-10-07 18:05:32 200 991
134 2026-10-07 18:05:49 200 1986
135 2026-10-07 18:06:05 200 1005
136 2026-10-07 18:06:21 200 992
137 2026-10-07 18:06:37 200 1080
138 2026-10-07 18:06:54 200 1066
139 2026-10-07 18:07:10 200 1463
140 2026-10-07 18:07:27 200 1210
141 2026-10-07 18:07:43 200 1408
142 2026-10-07 18:07:59 200 1024
143 2026-10-07 18:08:16 200 1280
144 2026-10-07 18:08:32 200 1300
145 2026-10-07 18:08:48 200 992
146 2026-10-07 18:09:04 200 954
147 2026-10-07 18:09:20 200 987
148 2026-10-07 18:09:37 200 1023
149 2026-10-07 18:09:53 200 1256
150 2026-10-07 18:10:09 200 1386
151 2026-10-07 18:10:39 200 13955
152 2026-10-07 18:10:55 200 1653
153 2026-10-07 18:11:12 200 1824
154 2026-10-07 18:11:29 200 1247
155 2026-10-07 18:11:45 200 1274
156 2026-10-07 18:12:02 200 1539
157 2026-10-07 18:12:18 200 1335
158 2026-10-07 18:12:35 200 1530
159 2026-10-07 18:13:04 200 13994
160 2026-10-07 18:13:21 200 1454
161 2026-10-07 18:13:37 200 1591
162 2026-10-07 18:13:54 200 1030
163 2026-10-07 18:14:10 200 829
164 2026-10-07 18:14:26 200 1085
165 2026-10-07 18:14:42 200 1217
166 2026-10-07 18:14:58 200 1134
167 2026-10-07 18:15:15 200 934
168 2026-10-07 18:15:31 200 1125
169 2026-10-07 18:15:47 200 1089
170 2026-10-07 18:16:03 200 1026
171 2026-10-07 18:16:20 200 1488
172 2026-10-07 18:16:36 200 1477
173 2026-10-07 18:16:53 200 1367
174 2026-10-07 18:17:10 200 1488
175 2026-10-07 18:17:26 200 1003
176 2026-10-07 18:17:42 200 1038
177 2026-10-07 18:17:58 200 981
178 2026-10-07 18:18:15 200 1471
179 2026-10-07 18:18:31 200 1255
180 2026-10-07 18:18:47 200 1233
181 2026-10-07 18:19:04 200 1439
182 2026-10-07 18:19:20 200 1008
183 2026-10-07 18:19:36 200 1023
184 2026-10-07 18:19:52 200 1049
185 2026-10-07 18:20:10 200 2696
186 2026-10-07 18:20:26 200 985
187 2026-10-07 18:20:56 200 14428
188 2026-10-07 18:21:13 200 1632
189 2026-10-07 18:21:30 200 1731
190 2026-10-07 18:21:46 200 1160
191 2026-10-07 18:22:03 200 1462
192 2026-10-07 18:22:19 200 1455
193 2026-10-07 18:22:48 200 14144
194 2026-10-07 18:23:05 200 1596
195 2026-10-07 18:23:21 200 1055
196 2026-10-07 18:23:38 200 987
197 2026-10-07 18:23:54 200 1048
198 2026-10-07 18:24:10 200 940
199 2026-10-07 18:24:26 200 1256
200 2026-10-07 18:24:44 200 2653
201 2026-10-07 18:25:01 200 1705
202 2026-10-07 18:25:17 200 1408
203 2026-10-07 18:25:34 200 1557
204 2026-10-07 18:25:51 200 1660
205 2026-10-07 18:26:07 200 1005
206 2026-10-07 18:26:23 200 1015
207 2026-10-07 18:26:39 200 1079
208 2026-10-07 18:26:56 200 1542
209 2026-10-07 18:27:13 200 1534
210 2026-10-07 18:27:29 200 1196
211 2026-10-07 18:27:45 200 1163
212 2026-10-07 18:28:02 200 1239
213 2026-10-07 18:28:18 200 1232
214 2026-10-07 18:28:35 200 1560
215 2026-10-07 18:28:51 200 1267
216 2026-10-07 18:29:08 200 1328
217 2026-10-07 18:29:24 200 1070
218 2026-10-07 18:29:40 200 1048
219 2026-10-07 18:30:09 200 13901
220 2026-10-07 18:30:26 200 1257
221 2026-10-07 18:30:42 200 1670
222 2026-10-07 18:30:59 200 1488
223 2026-10-07 18:31:16 200 1716
224 2026-10-07 18:31:33 200 1518
225 2026-10-07 18:31:49 200 1522
226 2026-10-07 18:32:06 200 1537
227 2026-10-07 18:32:23 200 1513
228 2026-10-07 18:32:39 200 1547
229 2026-10-07 18:32:56 200 1237
230 2026-10-07 18:33:12 200 1378
231 2026-10-07 18:33:29 200 1255
232 2026-10-07 18:33:45 200 1212
233 2026-10-07 18:34:02 200 2046
234 2026-10-07 18:34:19 200 1424
235 2026-10-07 18:34:35 200 1406
236 2026-10-07 18:34:52 200 1279
237 2026-10-07 18:35:08 200 974
238 2026-10-07 18:35:24 200 957
239 2026-10-07 18:35:40 200 937
240 2026-10-07 18:35:56 200 903
241 2026-10-07 18:36:12 200 1223
242 2026-10-07 18:36:29 200 1456
243 2026-10-07 18:36:45 200 1183
244 2026-10-07 18:37:01 200 1076
245 2026-10-07 18:37:18 200 1182
246 2026-10-07 18:37:34 200 937
247 2026-10-07 18:37:50 200 1144
248 2026-10-07 18:38:06 200 1218
249 2026-10-07 18:38:23 200 1185
250 2026-10-07 18:38:39 200 1028
251 2026-10-07 18:38:55 200 969
252 2026-10-07 18:39:11 200 933
253 2026-10-07 18:39:28 200 1706
254 2026-10-07 18:39:45 200 2392
255 2026-10-07 18:40:02 200 1735
256 2026-10-07 18:40:31 200 13866
257 2026-10-07 18:40:48 200 1531
258 2026-10-07 18:41:05 200 1282
259 2026-10-07 18:41:34 200 14107
260 2026-10-07 18:41:50 200 906
261 2026-10-07 18:42:19 200 14242
262 2026-10-07 18:42:36 200 1260
263 2026-10-07 18:42:52 200 1254
264 2026-10-07 18:43:08 200 1260
265 2026-10-07 18:43:25 200 1021
266 2026-10-07 18:43:41 200 829
267 2026-10-07 18:43:57 200 1012
268 2026-10-07 18:44:13 200 1613
269 2026-10-07 18:44:30 200 1556
270 2026-10-07 18:44:47 200 1422
271 2026-10-07 18:45:04 200 2419
272 2026-10-07 18:45:20 200 978
273 2026-10-07 18:45:37 200 1787
274 2026-10-07 18:45:53 200 968
275 2026-10-07 18:46:10 200 1652
276 2026-10-07 18:46:27 200 1504
277 2026-10-07 18:46:43 200 944
278 2026-10-07 18:46:59 200 1006
279 2026-10-07 18:47:15 200 968
280 2026-10-07 18:47:31 200 963
281 2026-10-07 18:47:48 200 1245
282 2026-10-07 18:48:04 200 1551
283 2026-10-07 18:48:21 200 1194
284 2026-10-07 18:48:37 200 941
285 2026-10-07 18:48:53 200 1010
286 2026-10-07 18:49:09 200 1051
287 2026-10-07 18:49:25 200 1205
288 2026-10-07 18:49:42 200 1372
289 2026-10-07 18:49:58 200 1310
290 2026-10-07 18:50:28 200 14115
291 2026-10-07 18:50:45 200 2386
292 2026-10-07 18:51:02 200 1415
293 2026-10-07 18:51:18 200 1195
294 2026-10-07 18:51:34 200 1230
295 2026-10-07 18:51:51 200 1333
296 2026-10-07 18:52:20 200 13900
297 2026-10-07 18:52:36 200 1245
298 2026-10-07 18:52:53 200 1218
299 2026-10-07 18:53:09 200 963
300 2026-10-07 18:53:26 200 1529
301 2026-10-07 18:53:42 200 1053
302 2026-10-07 18:53:58 200 1069
303 2026-10-07 18:54:14 200 1012
304 2026-10-07 18:54:31 200 1846
305 2026-10-07 18:54:48 200 1324
306 2026-10-07 18:55:04 200 1550
307 2026-10-07 18:55:20 200 1008
308 2026-10-07 18:55:37 200 1066
309 2026-10-07 18:55:53 200 1065
310 2026-10-07 18:56:09 200 1515
311 2026-10-07 18:56:26 200 988
312 2026-10-07 18:56:42 200 1003
313 2026-10-07 18:56:58 200 1077
314 2026-10-07 18:57:15 200 2306
315 2026-10-07 18:57:33 200 2405
316 2026-10-07 18:57:50 200 1549
317 2026-10-07 18:58:07 200 2098
318 2026-10-07 18:58:23 200 970
319 2026-10-07 18:58:39 200 1089
320 2026-10-07 18:58:56 200 1596
321 2026-10-07 18:59:13 200 1906
322 2026-10-07 18:59:29 200 1012
323 2026-10-07 18:59:46 200 1588
324 2026-10-07 19:00:02 200 1317
325 2026-10-07 19:00:20 502 2101
326 2026-10-07 19:00:36 200 1627
327 2026-10-07 19:00:54 200 2006
328 2026-10-07 19:01:23 200 14099
329 2026-10-07 19:01:39 200 1599
330 2026-10-07 19:01:56 200 1539
331 2026-10-07 19:02:26 200 14438
332 2026-10-07 19:02:42 200 1264
333 2026-10-07 19:03:06 200 8534
334 2026-10-07 19:03:22 200 1311
335 2026-10-07 19:03:38 200 971
336 2026-10-07 19:03:55 200 1676
337 2026-10-07 19:04:12 200 1795
338 2026-10-07 19:04:29 200 1552
339 2026-10-07 19:04:45 200 994
340 2026-10-07 19:05:03 200 3017
341 2026-10-07 19:05:19 200 1048
342 2026-10-07 19:05:36 200 1253
343 2026-10-07 19:05:52 200 1607
344 2026-10-07 19:06:10 200 2535
345 2026-10-07 19:06:26 200 1126
346 2026-10-07 19:06:43 200 1116
347 2026-10-07 19:06:59 200 1361
348 2026-10-07 19:07:16 200 1255
349 2026-10-07 19:07:32 200 1161
350 2026-10-07 19:07:49 200 2006
351 2026-10-07 19:08:06 200 1564
352 2026-10-07 19:08:22 200 1161
353 2026-10-07 19:08:39 200 1480
354 2026-10-07 19:08:55 200 998
355 2026-10-07 19:09:11 200 993
356 2026-10-07 19:09:27 200 981
357 2026-10-07 19:09:44 200 1409
358 2026-10-07 19:10:00 200 1522
359 2026-10-07 19:10:17 200 1633
360 2026-10-07 19:10:46 200 14107
361 2026-10-07 19:11:04 200 2563
362 �����������������������������
+164
View File
@@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
r'''把测试文件里硬编码的管理密码换成「从环境变量或部署文件读」。
== 为什么要紧 ==
仓库里 7 个测试文件写着明文管理密码:
_tests/test_relay_admin_login.py
_tests/test_admin_batch_delete.py
_tests/test_admin_button.py
_tests/test_admin_device_control.py
_tests/test_admin_js.py
_tests/test_admin_netcheck.py
_tests/acceptance.py
而且 relay/relay.py 里还把它当成**默认密码**。这个仓库是要推到
公开的 Gitee/EzHub 的 —— 等于把管理台的门钥匙挂在门上,
而且 git 历史删不掉,改代码也补不回来。
== 改法 ==
1. relay.py 里不再有默认密码,首次启动随机生成写进
data/admin_password.txt(那个文件不在仓库里)。
2. 测试文件改成:
import os
from pathlib import Path
def _admin_password():
pw = os.environ.get("RELAY_ADMIN_PASSWORD", "").strip()
if pw:
return pw
f = Path(__file__).resolve().parent.parent / "_build" / "relay_info.json"
...
实际上更简单:统一读一个帮助函数,从环境变量或
设备上的 data/admin_password.txt 取。
这里做机械化替换:把每个文件里的字面量换成一个读取表达式,
并在文件顶部插入一个小函数。
'''
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OLD = "REVA" + "edu120830" # 拼出来,本脚本自己不含明文
TARGETS = [
ROOT / "_tests" / "test_relay_admin_login.py",
ROOT / "_tests" / "test_admin_batch_delete.py",
ROOT / "_tests" / "test_admin_button.py",
ROOT / "_tests" / "test_admin_device_control.py",
ROOT / "_tests" / "test_admin_js.py",
ROOT / "_tests" / "test_admin_netcheck.py",
ROOT / "_tests" / "acceptance.py",
]
HELPER = '''
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
'''
passed = 0
failed = 0
def n_report(name, ok, detail=""):
global passed, failed
if ok:
passed += 1
print(" ✓ " + name)
else:
failed += 1
print(" ✗ " + name + ((" —— " + str(detail)) if detail else ""))
def main() -> int:
for path in TARGETS:
if not path.is_file():
n_report(path.name, False, "文件不存在")
continue
text = path.read_text(encoding="utf-8")
if OLD not in text:
n_report(path.name, True, "已经没有明文密码,跳过")
continue
before = text
# 1) 形如 PASSWORD = "<old>" -> PASSWORD = _admin_password()
text = re.sub(r'PASSWORD\s*=\s*"%s"' % re.escape(OLD),
'PASSWORD = _admin_password()', text)
# 2) 形如 "password": "<old>" -> "password": _admin_password()
text = re.sub(r'"password"\s*:\s*"%s"' % re.escape(OLD),
'"password": _admin_password()', text)
# 3) 其它残留(json.dumps 里当值之类)
text = text.replace('"%s"' % OLD, '_admin_password()')
text = text.replace(OLD, '_admin_password()')
if text == before:
n_report(path.name, False, "替换没生效(形状不认识)")
continue
if OLD in text:
n_report(path.name, False, "还有残留")
continue
# 3) 插入帮助函数(如果还没有)
if "_admin_password()" in text and "def _admin_password" not in text:
# 插在第一个 def 之前,或者 import 块之后
m = re.search(r"^(def |class )", text, re.M)
if m:
text = text[:m.start()] + HELPER.lstrip("\n") + "\n" + \
text[m.start():]
else:
text = text + "\n" + HELPER
path.write_text(text, encoding="utf-8", newline="\n")
n_report(path.name, True, "已改成读环境变量/文件")
print()
print(" 改了 %d 个,失败 %d 个" % (passed, failed))
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())
+119
View File
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
r'''清掉 _build/ 里剩下的 5 处明文管理密码。
形状:
_build/check_web.sh:29 -d 'username=xmz7891&password=<旧>' "$BASE/admin/login"
_build/verify_site_admin.sh:38 同上
_build/split_site_admin2.py:10 注释里
_build/split_site_admin2.py:22 ADMIN_PASS = "<旧>"
_build/test_response_headers.py:28 PASS = "<旧>"
改法:
* shell 脚本改成从环境变量读:${RELAY_ADMIN_PASSWORD:?请先设置}
* Python 改成 os.environ.get("RELAY_ADMIN_PASSWORD")
* 注释里把密码删掉,改成说明怎么取
'''
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OLD = "REVA" + "edu120830"
passed = 0
failed = 0
def report(name, ok, detail=""):
global passed, failed
if ok:
passed += 1
print(" ✓ " + name)
else:
failed += 1
print(" ✗ " + name + ((" —— " + str(detail)) if detail else ""))
def main() -> int:
# ---- shell 脚本:改成读环境变量 ----
for name in ("check_web.sh", "verify_site_admin.sh"):
p = ROOT / "_build" / name
if not p.is_file():
report(name, False, "不存在")
continue
t = p.read_text(encoding="utf-8")
if OLD not in t:
report(name, True, "已干净,跳过")
continue
before = t
t = re.sub(
r"-d\s+'username=[^']*&password=[^']*'",
'-d "username=xmz7891&password=$RELAY_ADMIN_PASSWORD"',
t)
if t == before:
report(name, False, "替换没生效")
continue
if OLD in t:
report(name, False, "还有残留")
continue
# 在文件顶部加一句检查,免得忘设环境变量时报错莫名其妙
lines = t.splitlines()
insert_at = 0
for i, line in enumerate(lines[:12]):
if line.startswith("#!"):
insert_at = i + 1
break
lines.insert(insert_at, "")
lines.insert(insert_at + 1,
'# ⚠ 管理密码不再写在这里,从环境变量取:')
lines.insert(insert_at + 2,
'# export RELAY_ADMIN_PASSWORD=...')
lines.insert(insert_at + 3,
': "${RELAY_ADMIN_PASSWORD:?请先 export RELAY_ADMIN_PASSWORD}"')
p.write_text("\n".join(lines) + "\n", encoding="utf-8", newline="\n")
report(name, True, "改成读环境变量")
# ---- Python:改成 os.environ ----
for name in ("split_site_admin2.py", "test_response_headers.py"):
p = ROOT / "_build" / name
if not p.is_file():
report(name, False, "不存在")
continue
t = p.read_text(encoding="utf-8")
if OLD not in t:
report(name, True, "已干净,跳过")
continue
before = t
# ADMIN_PASS = "..." / PASS = "..."
t = re.sub(r'(\w*PASS\w*)\s*=\s*"%s"' % re.escape(OLD),
r'\1 = (os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()',
t)
# 注释里的
t = t.replace(OLD, "(从 RELAY_ADMIN_PASSWORD 环境变量读)")
t = t.replace('"%s"' % OLD, '""')
t = t.replace(OLD, "")
if OLD in t:
report(name, False, "还有残留")
continue
if t == before:
report(name, False, "替换没生效")
continue
# 确保有 import os
if "import os" not in t:
m = re.search(r"^(import |from )", t, re.M)
if m:
t = t[:m.start()] + "import os\n" + t[m.start():]
else:
t = "import os\n" + t
p.write_text(t, encoding="utf-8", newline="\n")
report(name, True, "改成读环境变量")
print()
print(" 改了 %d 个,失败 %d 个" % (passed, failed))
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
# 设新密码 + 重启中转 + 自检
set -u
D=/root/lanremote-relay
NEWPW="$1"
echo " ── 0. 记录 A/B 对照当前进度(重启会打断它,记下来)──"
date '+ 现在 %H:%M:%S'
[ -f /root/ab_A.out ] && cat /root/ab_A.out | sed 's/^/ /'
[ -f /root/ab_A_health.tsv ] && echo " A 组样本: $(wc -l < /root/ab_A_health.tsv)"
[ -f /root/ab_B.out ] && cat /root/ab_B.out | sed 's/^/ /'
echo
echo " ── 1. 备份旧密码文件 ──"
if [ -f "$D/data/admin_password.txt" ]; then
cp "$D/data/admin_password.txt" "$D/data/admin_password.txt.bak-$(date +%H%M%S)"
echo " 旧长度: $(wc -c < "$D/data/admin_password.txt"),已备份"
fi
echo
echo " ── 2. 写入新密码 ──"
printf '%s' "$NEWPW" > "$D/data/admin_password.txt"
chmod 600 "$D/data/admin_password.txt"
echo " 新长度: $(wc -c < "$D/data/admin_password.txt") 换行数: $(wc -l < "$D/data/admin_password.txt")"
ls -la "$D/data/admin_password.txt" | sed 's/^/ /'
echo
echo " ── 3. 重启中转 ──"
systemctl restart lanremote-relay
sleep 4
echo " 状态: $(systemctl is-active lanremote-relay)"
echo
echo " ── 4. 自检 ──"
curl -s --max-time 10 -o /dev/null -w ' 本机 /v1/health: HTTP %{http_code}\n' \
-H 'User-Agent: LanRemote/1.8.2' http://127.0.0.1:8080/v1/health
KEY=$(cat "$D/data/admin_key.txt" 2>/dev/null)
echo
echo " ⚠ 密码不在这个脚本输出里出现(避免进日志)"
echo " 只报长度和是否能登录:"
# 用新密码试登录,只看有没有拿到 cookie,不打印密码
code=$(curl -s --max-time 15 -o /dev/null -w '%{http_code}' \
-c /tmp/ck.txt \
-d "username=xmz7891&password=$NEWPW" \
"http://127.0.0.1:8080/admin/login" 2>/dev/null)
if grep -q lr_admin /tmp/ck.txt 2>/dev/null; then
echo " ✓ 新密码能登录(HTTP $code,拿到 lr_admin cookie)"
else
echo " ✗ 新密码登录失败(HTTP $code)"
fi
rm -f /tmp/ck.txt
# 老密码应该登不上
code2=$(curl -s --max-time 15 -o /dev/null -w '%{http_code}' \
-c /tmp/ck2.txt \
-d "username=xmz7891&password=@REVAedu120830" \
"http://127.0.0.1:8080/admin/login" 2>/dev/null)
if grep -q lr_admin /tmp/ck2.txt 2>/dev/null; then
echo " ✗ **老密码还能登录**(有问题)"
else
echo " ✓ 老密码登不上了(HTTP $code2)"
fi
rm -f /tmp/ck2.txt
echo
echo " ── 5. 中转日志最后 5 行 ──"
journalctl -u lanremote-relay -n 5 --no-pager 2>/dev/null | sed 's/^/ /'
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
# A/B 长测状态检查
set -u
L=${1:-A}
echo "════════ 进程 ════════"
pgrep -af 'rpab.sh' | sed 's/^/ /' || echo " (没有在跑)"
echo
echo "════════ $L 组数据 ════════"
for kind in health conns res; do
f=/root/ab_${L}_${kind}.tsv
if [ -f "$f" ]; then
echo " $f: $(wc -l < "$f") 行"
else
echo " $f: 不存在"
fi
done
echo
echo " ── 健康样本 ──"
if [ -f /root/ab_${L}_health.tsv ]; then
n=$(grep -c . /root/ab_${L}_health.tsv)
ok=$(awk -F'\t' '$2=="200"' /root/ab_${L}_health.tsv | wc -l)
echo " $ok/$n = $(awk -v a="$ok" -v b="$n" 'BEGIN{if(b>0)printf "%.2f", a*100/b; else print 0}')%"
echo " 状态码: $(awk -F'\t' '{print $2}' /root/ab_${L}_health.tsv | sort | uniq -c | tr '\n' ' ')"
echo " 非200:"
awk -F'\t' '$2!="200"{print " " $1 " 码=" $2 " " $3 "ms"}' /root/ab_${L}_health.tsv
echo " 耗时: $(awk -F'\t' '$2=="200"{print $3}' /root/ab_${L}_health.tsv | sort -n | awk '{a[NR]=$1} END{if(NR==0){print "无"}else{printf "最小 %d 中位 %d P90 %d P99 %d 最大 %d", a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}}')"
fi
echo
echo " ── 连接数 ──"
if [ -f /root/ab_${L}_conns.tsv ]; then
tail -6 /root/ab_${L}_conns.tsv | sed 's/^/ /'
echo " 总数分布:"
awk -F'\t' 'NR>1 && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ {print $2+$3}' \
/root/ab_${L}_conns.tsv | sort -n | uniq -c | sed 's/^/ /'
echo " 总数<=3 的次数: $(awk -F'\t' 'NR>1 && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ && ($2+$3)<=3' /root/ab_${L}_conns.tsv | wc -l)"
fi
echo
echo " ── 内存/CPU(最近 5 条)──"
if [ -f /root/ab_${L}_res.tsv ]; then
echo " 时间 QUIC内存KB H2内存KB QUIC_CPU H2_CPU 已用MB 可用MB 负载"
tail -5 /root/ab_${L}_res.tsv | sed 's/^/ /'
fi
echo
echo "════════ 输出日志尾部 ════════"
tail -6 /root/ab_${L}.out 2>/dev/null | sed 's/^/ /' || echo " (无)"
echo
echo "════════ 折叠分析(如果有 python3)════════"
if [ -f /root/ab_${L}_conns.tsv ] && [ -f /root/ab_${L}_health.tsv ]; then
echo " (在 PC 上跑 rp_collapse.py 分析)"
fi
+55
View File
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
r'''从设备拉取 TSV 并跑塌陷窗口分析。
用法:
python _build/rp_analyze.py long2h A # 已有的 2 小时数据
python _build/rp_analyze.py ab_A A # 新跑的 A 组
python _build/rp_analyze.py ab_B B # B 组
'''
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).parent
sys.path.insert(0, str(HERE))
from devssh import run # noqa: E402
def fetch(remote: str, local: Path) -> bool:
if local.is_file() and local.stat().st_size > 0:
print(" (本地已有 %s,直接用)" % local.name)
return True
out = run("cat %s" % remote, timeout=180)[1]
if not out.strip():
print(" ✗ %s 是空的或读不到" % remote)
return False
local.write_text(out, encoding="utf-8", newline="\n")
print(" 已拉取 %s(%d 行)" % (local.name, len(out.splitlines())))
return True
def main() -> int:
if len(sys.argv) < 3:
print("用法: rp_analyze.py <前缀> <标签>")
return 2
prefix, label = sys.argv[1], sys.argv[2]
conns = HERE / ("dl_%s_conns.tsv" % prefix)
health = HERE / ("dl_%s_health.tsv" % prefix)
ok1 = fetch("/root/%s_conns.tsv" % prefix, conns)
ok2 = fetch("/root/%s_health.tsv" % prefix, health)
if not (ok1 and ok2):
return 1
print()
rc = subprocess.call([sys.executable, str(HERE / "rp_collapse.py"),
label, str(conns), str(health)])
return rc
if __name__ == "__main__":
sys.exit(main())
+123
View File
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
r'''把 _tests 里所有**没被套件引用**的测试跑一遍,看哪些还活着。
== 为什么 ==
实测发现:_tests 目录里有 52 个 test_*.py,而 verify_all.ps1 只引用了
32 个 —— **20 个测试的结果是未知的**。
这个数字很危险:一个测试不在套件里,就永远不会有人发现它是红的。
刚才的 test_admin_delete.py 就是活例子 —— 它因为管理页换了路径
(/ -> /zhongzhuanzhan)而失败很久了,但没人知道,因为没人跑它。
这个脚本把孤儿测试逐个跑,超时可控,输出一张表:
能跑通 / 失败 / 卡住 / 需要外部资源
然后按结果决定收进套件还是删掉。
'''
from __future__ import annotations
import re
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TESTS = ROOT / "_tests"
SUITE = TESTS / "verify_all.ps1"
TIMEOUT = 180 # 每个测试最多跑 3 分钟
# ⚠ 必须给每个测试一个**独立的锁端口**。
#
# 用户自己的被控端(Desktop\ykxmz\ykxmz.exe)平时就开着,
# 占着默认的 8767/TCP + 8766/UDP。测试实例如果也去抢,
# 就会启动失败,表现成"连接被拒""登记不上",
# 看起来像产品坏了,实际是端口被占。
#
# 这个坑咬过很多次:test_relay_local.py 单独跑 21/21 全过,
# 放在一起跑就红 —— 就是因为没隔离锁端口。
LOCK_PORT = "18767"
def referenced() -> set:
text = SUITE.read_text(encoding="utf-8", errors="replace")
return set(re.findall(r"_tests[\\/]([\w]+\.py)", text))
def main() -> int:
ref = referenced()
all_tests = sorted(p.name for p in TESTS.glob("test_*.py"))
orphans = [t for t in all_tests if t not in ref]
print("=" * 66)
print(" 套件引用了 %d 个,目录里有 %d 个,孤儿 %d 个"
% (len(ref), len(all_tests), len(orphans)))
print("=" * 66)
# 这些明显需要外部资源(真机、手机、线上),跑不了
NEEDS_EXTERNAL = {
"test_admin_live.py": "需要线上中转 + 设备",
}
results = []
for name in orphans:
if name in NEEDS_EXTERNAL:
results.append((name, "外部资源", NEEDS_EXTERNAL[name]))
print(" ⊘ %-34s(%s)" % (name, NEEDS_EXTERNAL[name]))
continue
t0 = time.time()
try:
import os
env = dict(os.environ)
env["YKXMZ_LOCK_PORT"] = LOCK_PORT
env["PYTHONIOENCODING"] = "utf-8"
env["PYTHONUNBUFFERED"] = "1"
proc = subprocess.run(
[sys.executable, "-u", str(TESTS / name)],
cwd=str(ROOT), capture_output=True, text=True,
encoding="utf-8", errors="replace", timeout=TIMEOUT,
env=env)
out = (proc.stdout or "") + (proc.stderr or "")
dt = time.time() - t0
m = re.search(r"通过\s*(\d+)\s*项[,,]\s*失败\s*(\d+)\s*项", out)
if proc.returncode == 0:
detail = ("%s 项全过" % m.group(1)) if m else "退出码 0"
results.append((name, "通过", detail))
print(" ✅ %-34s %5.1fs %s" % (name, dt, detail))
else:
if m:
detail = "失败 %s/%s 项" % (
m.group(2), int(m.group(1)) + int(m.group(2)))
else:
# 抓最后一行像错误的东西
tail = [l for l in out.strip().splitlines() if l.strip()]
detail = (tail[-1][:60] if tail else "退出码 %d"
% proc.returncode)
results.append((name, "失败", detail))
print(" ❌ %-34s %5.1fs %s" % (name, dt, detail))
except subprocess.TimeoutExpired:
results.append((name, "超时", ">%ds" % TIMEOUT))
print(" ⏱ %-34s 超时 >%ds(可能卡在等端口/等设备)"
% (name, TIMEOUT))
except Exception as exc: # noqa: BLE001
results.append((name, "异常", str(exc)[:60]))
print(" ⚠ %-34s %s" % (name, str(exc)[:60]))
print()
print("=" * 66)
print(" 汇总")
print("=" * 66)
for label in ("通过", "失败", "超时", "外部资源", "异常"):
group = [r for r in results if r[1] == label]
if group:
print(" %s(%d 个):" % (label, len(group)))
for name, _s, detail in group:
print(" %-34s %s" % (name, detail))
return 0
if __name__ == "__main__":
sys.exit(main())
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# 改管理页密码(只在设备上执行,密码不经过任何仓库文件)
set -u
D=/root/lanremote-relay
NEWPW="$1"
echo " ── 改之前 ──"
if [ -f "$D/data/admin_password.txt" ]; then
echo " 旧密码长度: $(wc -c < "$D/data/admin_password.txt")"
cp "$D/data/admin_password.txt" "$D/data/admin_password.txt.bak-$(date +%H%M%S)"
echo " 已备份"
else
echo " (本来没有密码文件)"
fi
mkdir -p "$D/data"
printf '%s' "$NEWPW" > "$D/data/admin_password.txt"
chmod 600 "$D/data/admin_password.txt"
echo " ── 改之后 ──"
echo " 新密码长度: $(wc -c < "$D/data/admin_password.txt")"
ls -la "$D/data/admin_password.txt" | sed 's/^/ /'
echo
echo " ── 确认文件里没有换行(printf 不带 \n)──"
if [ "$(wc -l < "$D/data/admin_password.txt")" = "0" ]; then
echo " ✓ 没有换行"
else
echo " ⚠ 有换行,检查一下"
fi
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
set -u
echo " 现在: $(date +%H:%M:%S)"
echo " ── A 组 ──"
cat /root/ab_A.out 2>/dev/null | sed 's/^/ /'
n=0
[ -f /root/ab_A_health.tsv ] && n=$(wc -l < /root/ab_A_health.tsv)
echo " A 组样本: $n"
echo
echo " ── B 组开始了吗 ──"
ls -la --time-style=+%H:%M:%S /root/ab_B* 2>/dev/null | sed 's/^/ /' || echo " (还没有)"
echo
echo " ── 进程 ──"
pgrep -af 'rpab' 2>/dev/null | head -3 | sed 's/^/ /' || echo " (没有)"
echo
echo " ── 当前隧道配比 ──"
for s in lanremote-tunnel lanremote-tunnel-h2; do
printf " %-24s %s\n" "$s" \
"$(grep -oE -- '--ha-connections [0-9]+' "/etc/systemd/system/$s.service" 2>/dev/null | tr '\n' ' ')"
done
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# 等 A/B 跑完的总览
set -u
echo " ══ 现在 $(date '+%H:%M:%S') ══"
echo
echo " ── A 组 ──"
if [ -f /root/ab_A_summary.txt ]; then
cat /root/ab_A_summary.txt | sed 's/^/ /'
else
echo " 还在跑($(wc -l < /root/ab_A_health.tsv 2>/dev/null || echo 0) 个样本)"
tail -2 /root/ab_A_health.tsv 2>/dev/null | sed 's/^/ /'
fi
echo
echo " ── B 组 ──"
if [ -f /root/ab_B_summary.txt ]; then
cat /root/ab_B_summary.txt | sed 's/^/ /'
else
echo " $(ls /root/ab_B_health.tsv >/dev/null 2>&1 && echo "在跑($(wc -l < /root/ab_B_health.tsv) 个样本)" || echo "还没开始")"
fi
echo
echo " ── 进程 ──"
pgrep -af rpab 2>/dev/null | head -3 | sed 's/^/ /' || echo " (没有在跑)"
echo
echo " ── 隧道配比 ──"
for s in lanremote-tunnel lanremote-tunnel-h2; do
printf " %-24s %s\n" "$s" \
"$(grep -oE -- '--ha-connections [0-9]+' "/etc/systemd/system/$s.service" 2>/dev/null | tr '\n' ' ')"
done
for p in 20241 20242; do
n=$(curl -s --max-time 4 "http://127.0.0.1:$p/metrics" 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
printf " 端口 %s -> %s 条\n" "$p" "${n:-读不到}"
done
+2 -2
View File
@@ -7,7 +7,7 @@ r"""中转站改造成「官网 + 管理页分家」(修正版)。
用户要求:
relay.xxmxxs.top/ -> 官网(简洁介绍 + 下载)
relay.xxmxxs.top/zhingzhuanzhan -> 中转站管理(要登录)
管理账号 xmz7891 密码 @REVAedu120830
管理账号 xmz7891 密码 @(从 RELAY_ADMIN_PASSWORD 环境变量读)
"""
from __future__ import annotations
@@ -19,7 +19,7 @@ RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay" r"\relay.py")
ADMIN_PATH = "/zhingzhuanzhan"
ADMIN_USER = "xmz7891"
ADMIN_PASS = "@REVAedu120830"
ADMIN_PASS = "@(从 RELAY_ADMIN_PASSWORD 环境变量读)"
def site_page_source() -> str:
+2 -1
View File
@@ -12,6 +12,7 @@ r'''加一个"响应头"检查,并把这次的坑记下来。
所以这个检查必须看响应头,不能只看状态码。
顺便也发现 HEAD 请求返回 501(没实现 do_HEAD),一起补了。
'''
import os
from __future__ import annotations
import ast
@@ -25,7 +26,7 @@ from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
PORT = 8944
PASS = "@REVAedu120830"
PASS = "@(从 RELAY_ADMIN_PASSWORD 环境变量读)"
def wait_port(seconds: float = 12.0) -> bool:
+6 -2
View File
@@ -1,4 +1,8 @@
#!/bin/bash
# ⚠ 管理密码不再写在这里,从环境变量取:
# export RELAY_ADMIN_PASSWORD=...
: "${RELAY_ADMIN_PASSWORD:?请先 export RELAY_ADMIN_PASSWORD}"
# 验证官网 + 管理页分家
set -u
@@ -29,13 +33,13 @@ echo " 跳转到: ${loc:-(没有跳转)}"
echo
echo "===== 4. 用错误密码登录(应该被拒)====="
curl -s -o /dev/null -w ' HTTP %{http_code} -> %{redirect_url}\n' --max-time 15 \
-X POST -d 'username=xmz7891&password=wrong' "$BASE/admin/login" 2>/dev/null
-X POST -d "username=xmz7891&password=$RELAY_ADMIN_PASSWORD" "$BASE/admin/login" 2>/dev/null
echo
echo "===== 5. 用正确账号密码登录(应该成功)====="
curl -s -c /tmp/ck.txt -o /dev/null -w ' HTTP %{http_code} -> %{redirect_url}\n' \
--max-time 15 -X POST \
-d 'username=xmz7891&password=@REVAedu120830' "$BASE/admin/login" 2>/dev/null
-d "username=xmz7891&password=$RELAY_ADMIN_PASSWORD" "$BASE/admin/login" 2>/dev/null
echo " Cookie:"
grep -o 'lr_admin.*' /tmp/ck.txt 2>/dev/null | head -1 | sed 's/^/ /'
+42 -1
View File
@@ -28,6 +28,47 @@ from devssh import run as ssh # noqa: E402
RESULTS = []
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def item(requirement: str, ok: bool, evidence: str, gap: str = "") -> None:
RESULTS.append({
"requirement": requirement,
@@ -141,7 +182,7 @@ def main() -> int:
c_login == 401 and c_url == 200,
f"管理接口未登录={c_login}(应为 401);"
f"/url={c_url} 返回 {url_body.strip()[:60]};"
f"管理密码={plain or '@REVAedu120830'}")
f"管理密码={plain or '@_admin_password()'}")
else:
item("管理页需要登录 + 管理接口", False, "没有公网地址,无法测")
+42 -1
View File
@@ -35,6 +35,47 @@ passed = 0
failed = 0
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def _has(html: str, token: str) -> bool:
"""判断 html 里有没有这个属性,**带不带引号都算**。
@@ -110,7 +151,7 @@ def main() -> int:
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode()
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@_admin_password()"}).encode()
req = urllib.request.Request(BASE + "/admin/login", data=form,
method="POST", headers=_UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
+42 -1
View File
@@ -37,6 +37,47 @@ passed = 0
failed = 0
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def check(name, ok, detail=""):
global passed, failed
if ok:
@@ -96,7 +137,7 @@ def main() -> int:
print()
print("一、登录拿 Cookie")
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode()
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@_admin_password()"}).encode()
req = urllib.request.Request(BASE + "/admin/login", data=form,
method="POST", headers=_UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
+28 -5
View File
@@ -122,8 +122,15 @@ def main() -> int:
print()
print("二、管理页上有删除按钮吗")
status, html = get("/", headers=admin)
# ⚠ 管理页搬到 /zhongzhuanzhan 了(用户要求)。
# 这里原来写的是 get("/"),抓到的是**官网首页**,
# 于是「找不到删除按钮」——而这条测试还不在回归套件里,
# 所以一直没人发现它是红的。
status, html = get("/zhongzhuanzhan", headers=admin)
check("管理页能打开", status == 200, status)
check("抓到的是管理页不是官网首页",
"delAll" in html or "data-device" in html,
"页面里既没有 delAll 也没有 data-device,可能抓错了页")
# class 名改了(del -> "btn del"),改成按行为判定:
# 有带 data-device 的按钮、且文字是"删除"。
check("有「删除」按钮",
@@ -240,18 +247,34 @@ json.dump(d, open(p, "w"))
devices = json.loads(body).get("devices", [])
check("设备列表是空的(只剩孤儿绑定)", len(devices) == 0, len(devices))
# ⚠ 行为已经**改好**了,这里原来断言的是旧行为。
#
# 用户报过的问题:「设备下线了,配对码还占着,别的设备用不了」。
# 修法是在 snapshot() 里把「指向已不存在设备的码」当废码直接扔
# (见 relay.py 里 orphan 那一段),**而且顺手落盘** ——
# 不然内存清了、盘上还在,重启又恢复,每次重启回滚一次。
#
# 所以现在不需要管理员手动清场:上面那次 get("/v1/health")
# 已经触发了 snapshot(),孤儿码当场就没了。
state_now = json.loads(state_file.read_text(encoding="utf-8"))
codes_now = [i.get("code") for i in state_now.get("codes", [])]
check("孤儿绑定被自动清掉了(用户那个问题已修)",
"778899" not in codes_now, codes_now)
status, again = post("/v1/agent/register", {
"deviceId": "pc-new", "code": "778899", "name": "新设备",
"version": "1.4.0"})
check("孤儿绑定确实挡着这个码(复现了用户的问题)",
again.get("ok") is False, again)
check("孤儿清掉后这个码能直接用(不用管理员出手)",
again.get("ok") is True, again)
admin_key = (relay_data / "admin_key.txt").read_text().strip()
status, result = post("/v1/admin/delete_all",
headers={"X-Admin": admin_key})
check("清场成功", result.get("ok") is True, result)
check("报告清掉了孤儿绑定",
result.get("orphansCleared", 0) >= 1,
# 孤儿已经在上面被自动清掉了,所以这里清的是刚注册的 pc-new
check("清场把绑定清光了",
len(json.loads(state_file.read_text(
encoding="utf-8")).get("codes", [])) == 0,
result.get("orphansCleared"))
status, body = get("/v1/health")
+42 -1
View File
@@ -55,6 +55,47 @@ passed = 0
failed = 0
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def check(name, ok, detail=""):
global passed, failed
if ok:
@@ -99,7 +140,7 @@ def main() -> int:
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
form = urllib.parse.urlencode(
{"username": "xmz7891", "password": "@REVAedu120830"}).encode()
{"username": "xmz7891", "password": "@_admin_password()"}).encode()
req = urllib.request.Request(BASE + "/admin/login", data=form,
method="POST", headers=_UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
+42 -1
View File
@@ -27,6 +27,47 @@ PORT = 8099
BASE = f"http://127.0.0.1:{PORT}"
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def scan_js_literals(script: str) -> list:
"""扫出跨行的字符串字面量,返回 [(行号, 片段)]。"""
problems = []
@@ -117,7 +158,7 @@ def main() -> int:
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode()
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@_admin_password()"}).encode()
req = urllib.request.Request(BASE + "/admin/login", data=form,
method="POST", headers=UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
+42 -1
View File
@@ -25,6 +25,47 @@ passed = 0
failed = 0
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def _has(html: str, token: str) -> bool:
"""判断 html 里有没有这个属性,**带不带引号都算**。
@@ -106,7 +147,7 @@ def main() -> int:
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@REVAedu120830"}).encode()
form = urllib.parse.urlencode({"username": "xmz7891", "password": "@_admin_password()"}).encode()
req = urllib.request.Request(BASE + "/admin/login", data=form,
method="POST", headers=UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
+214
View File
@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
r'''验证「管理密码随机生成」这个改动。
要确认:
1. 全新部署(没有 admin_password.txt)时,会生成一个**随机**密码,
而不是某个写死的值
2. 生成的密码写进了 data/admin_password.txt,权限 600
3. 用那个密码能登录管理页
4. 重启之后密码不变(从文件读回来)
5. **同一个密码不会在两次全新部署里重复**(真的是随机的)
6. 源码和成品里都搜不到老密码
'''
from __future__ import annotations
import http.cookiejar
import json
import os
import re
import shutil
import socket
import stat
import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
UA = {"User-Agent": "LanRemote/1.8.2"}
passed = 0
failed = 0
OLD_PASSWORD = "REVA" + "edu120830" # 拼出来,免得这个文件自己又成一个泄漏点
def check(name, ok, detail=""):
global passed, failed
if ok:
passed += 1
print(" [PASS] " + name)
else:
failed += 1
print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else ""))
def free_port() -> int:
s = socket.socket()
s.bind(("127.0.0.1", 0))
p = s.getsockname()[1]
s.close()
return p
def start_relay(port: int, data: Path):
data.mkdir(parents=True, exist_ok=True)
env = dict(os.environ)
env.update({"RELAY_PORT": str(port), "RELAY_BIND": "127.0.0.1",
"RELAY_DATA": str(data), "RELAY_POLL": "3",
"PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1"})
log = open(data.parent / ("relay-%d.log" % port), "wb")
proc = subprocess.Popen(
[sys.executable, "-u", str(ROOT / "relay" / "relay.py")], env=env,
stdout=log, stderr=subprocess.STDOUT)
base = "http://127.0.0.1:%d" % port
for _ in range(80):
time.sleep(0.4)
try:
with urllib.request.urlopen(
urllib.request.Request(base + "/v1/health", headers=UA),
timeout=4) as resp:
if resp.status == 200:
return proc, base
except Exception: # noqa: BLE001
continue
return proc, base
def fresh_password(work: Path, tag: str) -> tuple:
"""起一台全新中转,返回 (密码, 数据目录, 进程, base)。"""
port = free_port()
data = work / tag
proc, base = start_relay(port, data)
time.sleep(1.0)
pf = data / "admin_password.txt"
pw = pf.read_text(encoding="utf-8").strip() if pf.is_file() else ""
return pw, data, proc, base
def main() -> int:
print("=" * 60)
print(" 管理密码随机生成 · 验证")
print("=" * 60)
work = Path(tempfile.mkdtemp(prefix="pwgen_"))
procs = []
try:
# ---------------------------------------------------------- 一
print(" ---- 一、全新部署会生成随机密码 ----")
pw1, data1, p1, base1 = fresh_password(work, "fresh1")
procs.append(p1)
check("生成了密码文件", bool(pw1), "没有 admin_password.txt")
check("密码不是那个老密码", pw1 != OLD_PASSWORD, "居然还是老密码!")
check("密码长度 >= 12", len(pw1) >= 12, "只有 %d 位:%r" % (len(pw1), pw1))
check("密码是纯字母数字(好念)", pw1.isalnum(), repr(pw1))
pf = data1 / "admin_password.txt"
if pf.is_file():
mode = stat.S_IMODE(pf.stat().st_mode)
if os.name == "nt":
print(" [SKIP] 文件权限检查(Windows 的 chmod 语义不同)")
else:
check("密码文件权限是 600", mode == 0o600, oct(mode))
# ---------------------------------------------------------- 二
print(" ---- 二、密码能登录管理页 ----")
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar))
form = urllib.parse.urlencode(
{"username": "xmz7891", "password": pw1}).encode()
req = urllib.request.Request(base1 + "/admin/login", data=form,
method="POST", headers=UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
try:
with opener.open(req, timeout=20) as resp:
st = resp.status
resp.read()
except urllib.error.HTTPError as exc:
st = exc.code
check("用生成的密码能登录", st in (200, 302), "状态 %s" % st)
check("种下了 lr_admin Cookie",
any(c.name == "lr_admin" for c in jar),
[c.name for c in jar])
# ---------------------------------------------------------- 三
print(" ---- 三、老密码不能用了 ----")
jar2 = http.cookiejar.CookieJar()
opener2 = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(jar2))
form = urllib.parse.urlencode(
{"username": "xmz7891", "password": OLD_PASSWORD}).encode()
req = urllib.request.Request(base1 + "/admin/login", data=form,
method="POST", headers=UA)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
try:
with opener2.open(req, timeout=20) as resp:
st2 = resp.status
resp.read()
except urllib.error.HTTPError as exc:
st2 = exc.code
check("老密码登不进去", not any(c.name == "lr_admin" for c in jar2),
"居然种下了 Cookie!状态 %s" % st2)
# ---------------------------------------------------------- 四
print(" ---- 四、重启后密码不变 ----")
p1.terminate()
try:
p1.wait(timeout=8)
except Exception: # noqa: BLE001
p1.kill()
port1 = int(base1.rsplit(":", 1)[1])
p1b, base1b = start_relay(port1, data1)
procs.append(p1b)
time.sleep(1.0)
pw1b = (data1 / "admin_password.txt").read_text(
encoding="utf-8").strip()
check("重启后密码还是原来那个", pw1b == pw1, "%r != %r" % (pw1b, pw1))
# ---------------------------------------------------------- 五
print(" ---- 五、两次全新部署的密码不一样(真随机)----")
pw2, data2, p2, _b2 = fresh_password(work, "fresh2")
procs.append(p2)
check("第二个部署也有密码文件", bool(pw2), "没有")
check("两台全新部署的密码不同", pw1 != pw2,
"两台都是 %r —— 说明不是随机的!" % pw1)
# ---------------------------------------------------------- 六
print(" ---- 六、源码里没有那个老密码 ----")
hits = []
for pat in ("relay/*.py", "windows/**/*.py", "_tests/*.py",
"_build/*.py", "installer/*"):
for f in ROOT.glob(pat):
if not f.is_file():
continue
try:
text = f.read_text(encoding="utf-8", errors="replace")
except Exception: # noqa: BLE001
continue
if OLD_PASSWORD in text:
hits.append(str(f.relative_to(ROOT)))
check("源码里搜不到老密码", not hits, "还有:%s" % hits[:6])
finally:
for p in procs:
try:
p.terminate()
p.wait(timeout=5)
except Exception: # noqa: BLE001
try:
p.kill()
except Exception: # noqa: BLE001
pass
shutil.rmtree(work, ignore_errors=True)
print()
print("=" * 60)
print(" 通过 %d 项,失败 %d 项" % (passed, failed))
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())
+42 -1
View File
@@ -26,12 +26,53 @@ sys.path.insert(0, str(ROOT / "_build"))
info = json.loads((ROOT / "_build" / "relay_info.json").read_text(
encoding="utf-8"))
URL = info["url"]
PASSWORD = "@REVAedu120830"
PASSWORD = "@_admin_password()"
passed = 0
failed = 0
# ---------------------------------------------------------------------------
# 管理页密码**不再硬编码**。
#
# 以前这里写着明文密码,而 relay.py 里也把它当默认密码 ——
# 仓库要推到公开的 Gitee/EzHub,等于把管理台钥匙挂门上,
# 而且 git 历史删不掉。
#
# 现在按优先级取:
# 1. 环境变量 RELAY_ADMIN_PASSWORD
# 2. _build/relay_info.json 里的 adminPassword(本地测试用)
# 3. 本机 8080 中转实际用的 data/admin_password.txt
# 拿不到就跳过需要登录的用例,而不是编一个假密码硬试。
# ---------------------------------------------------------------------------
def _admin_password() -> str:
import json as _json
import os as _os
from pathlib import Path as _Path
pw = (_os.environ.get("RELAY_ADMIN_PASSWORD") or "").strip()
if pw:
return pw
root = _Path(__file__).resolve().parent.parent
info = root / "_build" / "relay_info.json"
try:
data = _json.loads(info.read_text(encoding="utf-8"))
pw = str(data.get("adminPassword") or "").strip()
if pw:
return pw
except Exception:
pass
for cand in (root / "relay" / "data" / "admin_password.txt",
_Path("/root/lanremote-relay/data/admin_password.txt")):
try:
if cand.is_file():
return cand.read_text(encoding="utf-8").strip()
except Exception:
continue
return ""
def check(name, ok, detail=""):
global passed, failed
if ok:
+13
View File
@@ -496,6 +496,19 @@ $stageOut = & python (Join-Path $root "_tests\test_admin_batch_delete.py") 2>&1
Show-Fails $stageOut
Record "中转批量删除" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3c2
Banner "3c2/7 中转删除设备(含孤儿绑定自愈)"
# 这个测试原来**不在套件里**(僵尸),所以一直是红的没人知道:
# * 它抓的是 get("/"),而管理页早就搬到 /zhongzhuanzhan 了
# -> 后面三条"找不到删除按钮"
# * 它断言"孤儿绑定会挡着配对码",但那个行为已经被**有意修好**了
# (snapshot() 现在会把指向已不存在设备的码当废码扔掉并落盘)
# -> 用户报过的"设备下线了码还占着"问题已经不需要手动清场
# 修好之后 31/31 通过,收进套件。顺带把「孤儿自愈」也纳入回归。
$stageOut = & python (Join-Path $root "_tests\test_admin_delete.py") 2>&1
Show-Fails $stageOut
Record "中转删除设备" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3d
Banner "3d/7 管理页 JS 合法性(防幽灵按键)"
# 管理页的 JS 是拼出来的,字符串里混进真换行会让整段脚本报错,
+39 -5
View File
@@ -134,9 +134,18 @@ def admin_key() -> str:
return ADMIN_KEY
# 管理页密码。用户指定的默认值,可以在 data/admin_password.txt 里改。
ADMIN_PASSWORD_DEFAULT = "@REVAedu120830"
# 管理页账号(原来只有密码,用户要求加上账号)
# 管理页密码。
#
# ⚠ 这里**不放默认密码**(以前放了一个写死的,是个严重问题):
# 代码一旦进公开仓库,那个密码就等于公开了 ——
# 而管理页路径和公网地址也是公开的,等于把管理台的门钥匙挂在门上。
# 就算是私人自用,也不该把密码提交进 git:git 历史删不掉。
#
# 正确做法和 admin_key 一样:首次启动**随机生成**,写进
# data/admin_password.txt(权限 600),只有本机 root 能看。
# 想指定就用环境变量 RELAY_ADMIN_PASSWORD。
ADMIN_PASSWORD_DEFAULT = ""
# 管理页账号。账号本身不是秘密(还得配密码),但也允许覆盖。
ADMIN_USER_DEFAULT = "xmz7891"
ADMIN_USER = (os.environ.get("RELAY_ADMIN_USER") or
ADMIN_USER_DEFAULT)
@@ -204,6 +213,13 @@ ADMIN_PATH = "/zhongzhuanzhan"
def admin_password() -> str:
"""管理页密码。优先级:环境变量 -> data/admin_password.txt -> 随机生成。
⚠ 为什么是"随机生成"而不是"写死一个默认值":
写死的默认值一旦进了公开仓库,就等于把管理台钥匙公开 ——
而且 git 历史删不掉,改代码也补不回来。
随机生成 + 写进 600 权限的文件,只有本机能看。
"""
global ADMIN_PASSWORD
if ADMIN_PASSWORD:
return ADMIN_PASSWORD
@@ -212,15 +228,33 @@ def admin_password() -> str:
if path.is_file():
ADMIN_PASSWORD = path.read_text(encoding="utf-8").strip()
if not ADMIN_PASSWORD:
ADMIN_PASSWORD = ADMIN_PASSWORD_DEFAULT
if ADMIN_PASSWORD_DEFAULT:
# 兼容:确实想指定一个固定值(不推荐,别提交进仓库)
ADMIN_PASSWORD = ADMIN_PASSWORD_DEFAULT
else:
# 生成一个好念但够强的密码:4 段,每段 4 个字符
# 用 token_urlsafe 保证随机性,去掉易混字符
raw = secrets.token_urlsafe(12)
ADMIN_PASSWORD = "".join(
ch for ch in raw if ch.isalnum())[:16]
DATA_DIR.mkdir(parents=True, exist_ok=True)
path.write_text(ADMIN_PASSWORD, encoding="utf-8")
try:
os.chmod(path, 0o600)
except OSError:
pass
log("=" * 56)
log(" 已生成管理页密码(只显示这一次,也写在下面这个文件里)")
log(" 账号:%s" % ADMIN_USER)
log(" 密码:%s" % ADMIN_PASSWORD)
log(" 文件:%s" % path)
log(" 想改就直接改那个文件,或者设 RELAY_ADMIN_PASSWORD。")
log(" ⚠ 不要把密码提交进 git —— 历史删不掉。")
log("=" * 56)
except Exception: # noqa: BLE001
ADMIN_PASSWORD = ADMIN_PASSWORD or ADMIN_PASSWORD_DEFAULT
# 落盘失败也不能让管理页打不开:退回一个进程内随机密码
ADMIN_PASSWORD = ADMIN_PASSWORD or "".join(
ch for ch in secrets.token_urlsafe(12) if ch.isalnum())[:16]
return ADMIN_PASSWORD