From 3c980ec87a74c77d09a5c32283c9d00129775510 Mon Sep 17 00:00:00 2001 From: Orion Date: Mon, 4 May 2026 01:50:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor(core):=20=E2=99=BB=EF=B8=8F=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=20brew=20=E8=84=9A=E6=9C=AC=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20sudo=20=E5=AF=86=E7=A0=81=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E5=B9=B6=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除 Homebrew 升级脚本中硬编码的 sudo 密码处理逻辑,改为依赖用户预先认证(sudo -v),提高安全性并简化 PTY 转发逻辑。同步更新文档说明及 sntp 脚本的类型校验。 - 脚本: 移除 SUDO_PWD 相关变量及自动注入逻辑,升级版本至 v5.3 - 文档: 更新 README 建议使用 sudo -v 刷新凭据,重写交互说明 - 工具: 在 sntp-rename.js 中增加对非字符串名称的防御性校验 --- homebrew/README.md | 17 +++-- homebrew/brew-upgrade-manager.sh | 103 ++++++++++++++----------------- substore/sntp-rename.js | 3 +- 3 files changed, 54 insertions(+), 69 deletions(-) diff --git a/homebrew/README.md b/homebrew/README.md index 646b288..242442d 100644 --- a/homebrew/README.md +++ b/homebrew/README.md @@ -9,7 +9,7 @@ - 自动检查并安装 `buo/cask-upgrade`,用于提供 `brew cu`。 - 使用 `brew upgrade --formula` 升级命令行 Formula。 - 使用 `brew cu -yaq` 升级 Cask GUI 应用。 -- 使用 Python PTY 包装 `brew cu`,用于处理 sudo 密码输入和终端尺寸问题。 +- 使用 Python PTY 包装 `brew cu`,用于处理交互输入转发和终端尺寸问题。 - 执行 `brew cleanup --prune=all` 清理旧版本与缓存。 ## 依赖 @@ -57,19 +57,16 @@ HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh 优先级为:命令行 `--width` 高于 `HB_TERMINAL_WIDTH`,再高于自动检测值。 -## sudo 密码 +## sudo 认证 -脚本中有一个 `SUDO_PWD` 占位变量: +脚本不会保存、传递或自动注入 sudo 密码。如果你的 `brew cu` 流程会触发 sudo,建议在运行脚本前先刷新 sudo 凭据: ```bash -SUDO_PWD="" +sudo -v +./brew-upgrade-manager.sh ``` -如果你的 `brew cu` 流程会触发 sudo,脚本会把这个变量传给内置 Python PTY 逻辑。请注意: - -- 不要把真实密码提交到仓库。 -- 更建议在私有副本、本机自动化环境或临时运行前填写。 -- 如果不需要自动输入 sudo,保持空字符串即可。 +如果 sudo 凭据过期,脚本内的 PTY 逻辑只会把你的键盘输入转发给子进程,不会嗅探 `Password:` 提示,也不会替你填写密码。 ## 执行流程 @@ -102,7 +99,7 @@ brew tap buo/cask-upgrade ### sudo 卡住 -确认 `SUDO_PWD` 是否为空,以及当前命令是否确实需要 sudo。出于安全考虑,不建议把密码长期保存在仓库文件里。 +先在交互终端运行 `sudo -v`,确认 sudo 凭据有效后再执行脚本。非交互环境中请避免依赖脚本内输入密码。 ## 注意事项 diff --git a/homebrew/brew-upgrade-manager.sh b/homebrew/brew-upgrade-manager.sh index 5e1e4f8..20a1917 100644 --- a/homebrew/brew-upgrade-manager.sh +++ b/homebrew/brew-upgrade-manager.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash -# Homebrew 智能升级脚本(强制 Cask 更新增强版 v5.2 - 修复 PTY 终端尺寸导致 Ruby 渲染崩溃问题) -# ❗❗️❗️️使用前请搜索 SUDO_PWD 并添加自己的 root 密码!!! +# Homebrew 智能升级脚本(强制 Cask 更新增强版 v5.3 - 移除 sudo 密码注入) # ================== 脚本环境设置 ================== @@ -104,9 +103,6 @@ separator print_header "Step 4: Executing comprehensive upgrades (Formulae & Casks)" echo -e "${NC}" -# 使用前请先修改这里的密码!! -SUDO_PWD="" - # 1. 升级命令行工具 (Formulae) echo -e "\n${CYAN}>>> [1/2] Upgrading CLI Formulae (brew upgrade --formula)...${NC}" brew upgrade --formula @@ -121,79 +117,70 @@ export HOMEBREW_COLOR=1 # 强制将 Bash 计算好的真实终端宽度传递给底层,防止 Ruby 绘表时发生 negative argument 崩溃 export COLUMNS="$TERMINAL_WIDTH" -# 使用 Python 原生 PTY 自建极简轮询引擎 +# 使用 Python 原生 PTY 自建极简转发引擎。它只转发用户输入,不保存、不注入 sudo 密码。 python3 -c ' -import pty, os, sys, select, fcntl, termios, struct +import pty, os, sys, select, fcntl, termios, struct, tty -# 将传入的明文密码转换为底层的原始字节流,并在末尾追加换行符模拟按下回车键 -pwd = sys.argv[1].encode() + b"\n" -cmd = sys.argv[2:] - -# 在操作系统底层分叉出一个携带完整伪终端(PTY)特性的子进程 +cmd = sys.argv[1:] pid, fd = pty.fork() if pid == 0: - # --- 逻辑分支:子进程空间 --- - # 使用 execvp 顶替当前进程,正式开始执行 brew cu 命令 os.execvp(cmd[0], cmd) else: - # --- 逻辑分支:父进程空间 (监控端) --- - - # --- 核心修复:物理终端尺寸投影 --- - # 尝试捕获外层真实物理终端的行列尺寸,并硬塞进伪终端的文件描述符中 try: - # 打包一个空的 4 短整型结构体准备接收数据 s = struct.pack("HHHH", 0, 0, 0, 0) - # 通过 ioctl 系统调用,向操作系统的标准输出请求 TIOCGWINSZ (获取窗口大小) winsize = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s) - # 将获取到的真实尺寸,立刻通过 TIOCSWINSZ (设置窗口大小) 写入刚刚生成的伪终端 fd 中 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize) except Exception: - # 如果当前环境不是交互式终端(如 crontab 后台运行),捕获异常静默跳过, - # 此时外层 bash 注入的 export COLUMNS="$TERMINAL_WIDTH" 将作为完美兜底 pass + stdin_fd = sys.stdin.fileno() + stdout_fd = sys.stdout.fileno() + forward_stdin = sys.stdin.isatty() + old_stdin_attrs = None + if forward_stdin: + old_stdin_attrs = termios.tcgetattr(stdin_fd) + tty.setraw(stdin_fd) + exit_code = 0 - while True: - try: - # 开启 IO 多路复用轮询,设置 0.1 秒的极短超时时间防止物理死锁 - rfds, _, _ = select.select([fd], [], [], 0.1) + try: + while True: + try: + watch = [fd] + if forward_stdin: + watch.append(stdin_fd) + rfds, _, _ = select.select(watch, [], [], 0.1) - # 如果伪终端有数据吐出 - if fd in rfds: - # 以 8KB 为块,读取原始字节流 (Raw Bytes) - data = os.read(fd, 8192) - # 读到空字节代表通道已被操作系统关闭 - if not data: - break + if fd in rfds: + data = os.read(fd, 8192) + if not data: + break + os.write(stdout_fd, data) - # 在纯净字节流中精准狙击 sudo 发出的密码请求信号 - if b"assword:" in data or b"Password:" in data: - # 发现目标,将密码字节流强行注入到伪终端标准输入中 - os.write(fd, pwd) + if forward_stdin and stdin_fd in rfds: + data = os.read(stdin_fd, 8192) + if data: + os.write(fd, data) - # 将读取到的原汁原味的字节流直接刷入真实屏幕,确保 Emoji 不乱码 - os.write(sys.stdout.fileno(), data) - - except OSError: - # 捕获对侧进程已死导致的 EIO 错误,安全打破循环 - break - - # 主动进程心跳侦测:非阻塞探查子进程生死 - try: - wpid, wstatus = os.waitpid(pid, os.WNOHANG) - if wpid == pid: - if os.WIFEXITED(wstatus): - exit_code = os.WEXITSTATUS(wstatus) - else: - exit_code = 1 + except OSError: break - except ChildProcessError: - break - # 将真实状态码原样返回给外层的 shell + try: + wpid, wstatus = os.waitpid(pid, os.WNOHANG) + if wpid == pid: + if os.WIFEXITED(wstatus): + exit_code = os.WEXITSTATUS(wstatus) + else: + exit_code = 1 + break + except ChildProcessError: + break + finally: + if old_stdin_attrs is not None: + termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_stdin_attrs) + sys.exit(exit_code) -' "$SUDO_PWD" brew cu -yaq +' brew cu -yaq separator printf "\n" @@ -205,4 +192,4 @@ separator printf "\n" echo -e "${GREEN}All operations completed!${NC}" -printf "\n" \ No newline at end of file +printf "\n" diff --git a/substore/sntp-rename.js b/substore/sntp-rename.js index 9e38108..0bdd77c 100644 --- a/substore/sntp-rename.js +++ b/substore/sntp-rename.js @@ -56,6 +56,7 @@ function operator(proxies) { // 3. 执行节点遍历与注入 return proxies.map(p => { const name = p.name; + if (typeof name !== 'string') return p; // A. 单次复合正则扫描提取 const match = name.match(featureRegex); @@ -83,4 +84,4 @@ function operator(proxies) { return p; }); -} \ No newline at end of file +}