# -*- coding: utf-8 -*- r'''修两件事: == 1. 我自己的补丁把函数调用塞进了字符串里 == fix_hardcoded_password.py 里有个兜底替换: text = text.replace(OLD, '_admin_password()') 而原代码是: {"username": "xmz7891", "password": "@<老密码>"} ^ @ 在引号**里面** 正则 `"password"\s*:\s*"OLD"` 要求引号紧贴 OLD,这里中间有个 @, 所以没匹配上;然后兜底替换就把 OLD 换成了函数调用, 结果变成**字符串字面量**: {"username": "xmz7891", "password": "@_admin_password()"} 7 个文件被这样弄坏。它们登录时拿这个当密码, 当然登不上 -> 401「请先登录」-> 管理接口测试全红。 教训:**做批量替换时,兜底的 `str.replace` 太危险** —— 它不管上下文。要么用带边界正则,要么替换后检查有没有 "函数调用被引号包起来" 这种形状。 == 2. relay 启动时没生成密码文件 == admin_password() 只在有人访问登录时才调用,所以全新部署启动后 data/admin_password.txt 一直不存在 —— 日志里也就看不到密码, 管理员不知道用什么登。 main() 里已经调了 admin_key()(同样道理),补上 admin_password()。 ''' from __future__ import annotations import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent # 被弄坏的形状:引号把函数调用包起来了 BROKEN = re.compile(r"""["']@?_admin_password\(\)["']""") GOOD = "_admin_password()" TARGETS = ["_tests/acceptance.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/test_relay_admin_login.py"] def main() -> int: changed = 0 failed = 0 print("── 1. 修被引号包起来的 _admin_password() ──") for rel in TARGETS: p = ROOT / rel if not p.is_file(): print(" ✗ %s 不存在" % rel) failed += 1 continue text = p.read_text(encoding="utf-8") if '@_admin_password()' not in text and '"_admin_password()"' not in text: print(" • %s 本来就正常" % rel) continue before = text # 只处理「引号里整个就是一个函数调用」这种 text = re.sub(r'"@_admin_password\(\)"', GOOD, text) text = re.sub(r"'@_admin_password\(\)'", GOOD, text) text = re.sub(r'"_admin_password\(\)"', GOOD, text) text = re.sub(r"'_admin_password\(\)'", GOOD, text) if text == before: print(" ✗ %s 替换没生效" % rel) failed += 1 continue # 再确认没有残留 if '@_admin_password()' in text: print(" ✗ %s 还有残留" % rel) failed += 1 continue p.write_text(text, encoding="utf-8", newline="\n") print(" ✓ %s" % rel) changed += 1 print() print("── 2. relay.py 启动时生成密码文件 ──") rp = ROOT / "relay" / "relay.py" text = rp.read_text(encoding="utf-8") if "admin_password() # 确保" in text or "pw = admin_password()" in text: print(" • 已经调过了") else: # 在 main() 里 admin_key() 那行后面补一句 old = " key = admin_key()" if old not in text: print(" ✗ 找不到 main() 里 admin_key() 那行") failed += 1 else: new = ( " key = admin_key()\n" " # 同样要**在这里**把密码也生成出来:\n" " # admin_password() 原来只在有人访问登录页时才调用,\n" " # 于是全新部署启动后 data/admin_password.txt 一直不存在,\n" " # 日志里也看不到密码 —— 管理员不知道该用什么登录。\n" " # 启动时就调一次,密码会随启动日志打印出来。\n" " admin_password()" ) text = text.replace(old, new, 1) rp.write_text(text, encoding="utf-8", newline="\n") print(" ✓ 已在 main() 里调用 admin_password()") changed += 1 print() print(" 改了 %d 处,失败 %d 处" % (changed, failed)) # 全局再扫一遍有没有「函数调用被引号包起来」的 print() print("── 3. 全仓复查 ──") bad = [] for pat in ("_tests/*.py", "_build/*.py", "relay/*.py"): for f in ROOT.glob(pat): try: t = f.read_text(encoding="utf-8", errors="replace") except Exception: # noqa: BLE001 continue for m in BROKEN.finditer(t): bad.append("%s" % f.relative_to(ROOT)) break if bad: print(" ⚠ 还有:%s" % bad) return 1 print(" ✓ 没有了") return 0 if failed == 0 else 1 if __name__ == "__main__": sys.exit(main())