From df3e4b15dbc3e2d7570ae78f3ae9201c71e89edd Mon Sep 17 00:00:00 2001 From: Orion Date: Mon, 1 Jun 2026 09:12:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E2=9C=A8=20add=20vm=20permission?= =?UTF-8?q?=20fixer=20for=20macOS=20shortcuts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 macOS 快捷指令自动化工具,包含虚拟机权限修复脚本(vm-permission-fixer.sh)及远程引导启动器(vm-permission-fixer-bootstrap.sh)。 - 在打开 Parallels Desktop 时,自动修复指定 .pvm 虚拟机包的所有权。 - 采用 macOS Keychain 安全存储和读取 sudo 密码,避免明文硬编码。 - 补充 shortcuts 模块相关的中英文说明文档,并同步更新项目主 README。 --- README.md | 25 +++- shortcuts/README.md | 130 +++++++++++++++++++++ shortcuts/vm-permission-fixer-bootstrap.sh | 32 +++++ shortcuts/vm-permission-fixer.sh | 125 ++++++++++++++++++++ 4 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 shortcuts/README.md create mode 100755 shortcuts/vm-permission-fixer-bootstrap.sh create mode 100755 shortcuts/vm-permission-fixer.sh diff --git a/README.md b/README.md index 751b07b..33498f2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Script Collection -一个面向日常自动化的小型脚本集合,当前包含 Homebrew 升级管理、Sub-Store 节点重命名处理、Cloudflare Worker 脚本。 +一个面向日常自动化的小型脚本集合,当前包含 Homebrew 升级管理、macOS 快捷指令自动化、Sub-Store 节点重命名处理、Cloudflare Worker 脚本。 ## 目录 @@ -8,7 +8,12 @@ script/ ├── homebrew/ │ ├── README.md +│ ├── brew-upgrade-manager-bootstrap.sh │ └── brew-upgrade-manager.sh +├── shortcuts/ +│ ├── README.md +│ ├── vm-permission-fixer-bootstrap.sh +│ └── vm-permission-fixer.sh ├── substore/ │ ├── README.md │ └── sntp-rename.js @@ -28,6 +33,7 @@ script/ | 目录 | 脚本 | 用途 | 运行环境 | | --- | --- | --- | --- | | `homebrew/` | `brew-upgrade-manager.sh` | 自动更新 Homebrew、升级 Formula/Cask、清理缓存 | macOS、Bash、Homebrew | +| `shortcuts/` | `vm-permission-fixer.sh` | 配合快捷指令自动修复 Parallels Desktop 虚拟机包所有权 | macOS、快捷指令、Bash、Keychain | | `substore/` | `sntp-rename.js` | 守候网络节点命名专用,在 Sub-Store 代理节点名称中注入线路标签 | Sub-Store 脚本处理器 | | `cf_worker/lskypro/` | `lsky-telegram-worker.js` | Telegram 收图后上传到 Lsky Pro,并返回 Markdown 图片链接代码块 | Cloudflare Workers、D1、Telegram Bot、Lsky Pro | | `cf_worker/tg-bot/` | `tg-bot.js` | 基于 Cloudflare Workers 的 Telegram 私聊转发与管理后台 | Cloudflare Workers、D1、Telegram Bot | @@ -51,6 +57,20 @@ HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh 详细配置见 [homebrew/README.md](homebrew/README.md)。 +### 快捷指令自动化 + +`shortcuts/` 目录包含适合 macOS 快捷指令 App 自动化调用的脚本。当前提供 [shortcuts/vm-permission-fixer.sh](shortcuts/vm-permission-fixer.sh),用于在打开 Parallels Desktop 时修复指定 `.pvm` 虚拟机包所有权。 + +快捷指令「运行 Shell 脚本」可使用远程启动器: + +```bash +curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/shortcuts/vm-permission-fixer-bootstrap.sh | bash +``` + +脚本不包含明文 sudo 密码;首次运行会弹窗输入一次并保存到 macOS Keychain,后续自动读取。 + +详细配置见 [shortcuts/README.md](shortcuts/README.md)。 + ### Sub-Store 节点标签注入 把 [substore/sntp-rename.js](substore/sntp-rename.js) 作为守候网络节点命名专用的 Sub-Store 节点处理脚本使用。默认会识别 `GTM`、`S1`、`S2`、`S3`、`S4`、`BGP`、`Anytls` 等关键词,并把标签插入到节点名末尾或 `- SNTP` 后缀之前。 @@ -69,7 +89,8 @@ HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh ## 安全提示 - 不要把 Bot Token、Cloudflare 密钥、Telegram 群组 ID、管理员 ID 或 sudo 密码提交到公开仓库。 -- `homebrew/brew-upgrade-manager.sh` 当前包含 `SUDO_PWD` 占位变量,若确需使用自动输入 sudo 密码,请只在本机私有副本中填写。 +- `homebrew/` 和 `shortcuts/` 的启动器会通过 macOS Keychain 保存并读取 sudo 密码;如需清除,请使用对应 README 中的 `security delete-generic-password` 命令。 +- 远程启动器属于“下载后执行”模式,只应从可信仓库使用;需要更严格控制时可启用 SHA256 校验。 - Cloudflare Worker 脚本会保存用户状态、消息映射、上传队列或配置项到 D1,请按实际隐私要求控制数据库访问权限。 ## 许可证 diff --git a/shortcuts/README.md b/shortcuts/README.md new file mode 100644 index 0000000..17bc8a1 --- /dev/null +++ b/shortcuts/README.md @@ -0,0 +1,130 @@ +# Shortcuts Automation Scripts + +这个目录存放适合 macOS「快捷指令」自动化调用的脚本。 + +当前包含 Parallels Desktop 虚拟机权限修复脚本:当快捷指令 App 自动化检测到「打开 Parallels Desktop」时,可通过远程启动器下载最新版主脚本并执行,避免在快捷指令里维护大段 Shell。 + +## 文件说明 + +| 文件 | 作用 | +| --- | --- | +| `vm-permission-fixer-bootstrap.sh` | 快捷指令入口脚本,下载远程主脚本、可选校验 SHA256、执行后清理临时文件 | +| `vm-permission-fixer.sh` | 主脚本,修复指定 Parallels 虚拟机包的所有权 | + +## 功能 + +- 检查目标 `.pvm` 虚拟机包是否存在。 +- 通过 `sudo -A` 执行 `chown -R`,把虚拟机包所有者改回当前用户。 +- 首次运行时弹窗输入一次 macOS 登录密码 / sudo 密码,并保存到当前用户的 macOS Keychain。 +- 后续运行时通过临时 `SUDO_ASKPASS` 脚本从 Keychain 读取 sudo 密码,不在脚本中保存明文密码。 +- 写入日志到 `~/Library/Logs/vm_permission_fix.log`。 +- 成功或失败时发送 macOS 通知。 + +## 快捷指令用法 + +在快捷指令 App 中创建个人自动化: + +1. 触发条件选择「App」。 +2. App 选择「Parallels Desktop」。 +3. 条件选择「打开」。 +4. 动作选择「运行 Shell 脚本」。 +5. Shell 选择 `/bin/bash`。 +6. 输入以下内容: + +```bash +curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/shortcuts/vm-permission-fixer-bootstrap.sh | bash +``` + +首次运行会弹窗要求输入一次 sudo 密码。保存到 Keychain 后,后续自动化会直接读取。 + +## 本地运行 + +也可以 clone 仓库后本地执行: + +```bash +cd shortcuts +chmod +x vm-permission-fixer-bootstrap.sh vm-permission-fixer.sh +./vm-permission-fixer.sh +``` + +## 配置项 + +默认虚拟机路径: + +```bash +/Users/Shared/Parallels/VM/Windows 11/Windows 11.pvm +``` + +可通过环境变量覆盖: + +```bash +VM_FIXER_VM_PATH="/path/to/Windows 11.pvm" ./vm-permission-fixer.sh +``` + +默认 Keychain service 名称: + +```bash +vm-permission-fixer-sudo-password +``` + +可通过环境变量覆盖: + +```bash +VM_FIXER_KEYCHAIN_SERVICE=my-vm-fixer-password ./vm-permission-fixer.sh +``` + +启动器默认下载地址可覆盖: + +```bash +VM_FIXER_REMOTE="https://example.com/vm-permission-fixer.sh" ./vm-permission-fixer-bootstrap.sh +``` + +## SHA256 校验 + +启动器支持通过 `VM_FIXER_SHA256` 校验下载到的主脚本: + +```bash +curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/shortcuts/vm-permission-fixer.sh | shasum -a 256 +``` + +运行时指定: + +```bash +curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/shortcuts/vm-permission-fixer-bootstrap.sh | VM_FIXER_SHA256= bash +``` + +如果哈希不匹配,启动器会停止执行。 + +## 常见问题 + +### 如何删除已保存的 sudo 密码? + +```bash +security delete-generic-password -a "$USER" -s vm-permission-fixer-sudo-password +``` + +下次运行会重新弹窗保存。 + +### Keychain 中的 sudo 密码不可用 + +通常是 macOS 登录密码变更,或 Keychain 条目内容不再正确。删除后重新运行即可: + +```bash +security delete-generic-password -a "$USER" -s vm-permission-fixer-sudo-password +``` + +### 日志在哪里? + +```bash +~/Library/Logs/vm_permission_fix.log +``` + +### 是否完全没有保存密码? + +脚本不再把 sudo 密码明文写在仓库或快捷指令里,但会按你的授权把密码保存到当前用户 Keychain。更严格的做法是配置 sudoers,只允许指定 `chown` 命令免密执行。 + +## 注意事项 + +- 远程启动器属于“下载后执行”模式,只应从可信仓库使用。 +- 第一次运行时 macOS 可能请求允许访问 Keychain,请选择允许。 +- 如果 Parallels Desktop 已经占用虚拟机包,权限修复可能失败;查看日志确认原因。 diff --git a/shortcuts/vm-permission-fixer-bootstrap.sh b/shortcuts/vm-permission-fixer-bootstrap.sh new file mode 100755 index 0000000..a488496 --- /dev/null +++ b/shortcuts/vm-permission-fixer-bootstrap.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# VM Permission Fixer bootstrap +# 作用:下载远程主脚本、执行、清理临时文件。适合放在快捷指令 App 自动化里。 + +set -euo pipefail + +REMOTE="${VM_FIXER_REMOTE:-https://git.orionc.me/orion/script/raw/branch/main/shortcuts/vm-permission-fixer.sh}" +TEMP="$(mktemp "${TMPDIR:-/tmp}/vm-permission-fixer.XXXXXX.sh")" + +cleanup() { + rm -f "$TEMP" +} +trap cleanup EXIT INT TERM + +PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin" +export PATH + +curl -f -sSL "$REMOTE" -o "$TEMP" +chmod 700 "$TEMP" + +if [[ -n "${VM_FIXER_SHA256:-}" ]]; then + actual_sha256="$(shasum -a 256 "$TEMP")" + actual_sha256="${actual_sha256%% *}" + if [[ "$actual_sha256" != "$VM_FIXER_SHA256" ]]; then + echo "远程脚本 SHA256 不匹配,已停止执行。" >&2 + echo "Expected: $VM_FIXER_SHA256" >&2 + echo "Actual: $actual_sha256" >&2 + exit 1 + fi +fi + +exec /usr/bin/env bash "$TEMP" "$@" diff --git a/shortcuts/vm-permission-fixer.sh b/shortcuts/vm-permission-fixer.sh new file mode 100755 index 0000000..b8391f2 --- /dev/null +++ b/shortcuts/vm-permission-fixer.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# 脚本名称: VM Permission Fixer +# 日志路径: ~/Library/Logs/vm_permission_fix.log + +set -u +set -o pipefail + +PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin" +export PATH + +CURRENT_USER="$(id -un)" +VM_PATH="${VM_FIXER_VM_PATH:-/Users/Shared/Parallels/VM/Windows 11/Windows 11.pvm}" +LOG_FILE="${VM_FIXER_LOG_FILE:-/Users/${CURRENT_USER}/Library/Logs/vm_permission_fix.log}" +KEYCHAIN_SERVICE="${VM_FIXER_KEYCHAIN_SERVICE:-vm-permission-fixer-sudo-password}" +ASKPASS_TEMP="$(mktemp "${TMPDIR:-/tmp}/vm-permission-fixer-askpass.XXXXXX.sh")" + +cleanup() { + rm -f "$ASKPASS_TEMP" +} +trap cleanup EXIT INT TERM + +mkdir -p "$(dirname "$LOG_FILE")" +exec 1>>"$LOG_FILE" +exec 2>&1 + +log() { + local level="$1" + local message="$2" + local timestamp + timestamp="$(date "+%Y-%m-%d %H:%M:%S")" + printf "[%s] [%-7s] %s\n" "$timestamp" "$level" "$message" +} + +notify() { + local title="$1" + local message="$2" + /usr/bin/osascript -e "display notification \"${message}\" with title \"${title}\"" >/dev/null 2>&1 || true +} + +notify_error_and_exit() { + local message="$1" + local code="${2:-1}" + log "ERROR" "$message" + notify "自动化错误" "$message" + exit "$code" +} + +print_separator() { + echo "--------------------------------" +} + +print_banner() { + echo "" + echo "===================================" + log "INIT" "自动化任务启动: 修复 Parallels Desktop 虚拟机权限" + echo "===================================" +} + +prompt_and_save_sudo_password() { + local password + log "INFO" "Keychain 未找到 sudo 密码,准备首次保存。" + + password="$( + /usr/bin/osascript <<'APPLESCRIPT' +set dialogResult to display dialog "首次使用请输入一次 macOS 登录密码 / sudo 密码,脚本会保存到 Keychain,后续自动读取。" default answer "" with hidden answer buttons {"取消", "保存"} default button "保存" with title "VM Permission Fixer" +text returned of dialogResult +APPLESCRIPT + )" || return 1 + + if [[ -z "$password" ]]; then + return 1 + fi + + /usr/bin/security add-generic-password -U -a "$CURRENT_USER" -s "$KEYCHAIN_SERVICE" -w "$password" >/dev/null + unset password +} + +setup_sudo_askpass() { + cat > "$ASKPASS_TEMP" <<'EOF' +#!/usr/bin/env bash +exec /usr/bin/security find-generic-password -a "${USER:-$(id -un)}" -s "${VM_FIXER_KEYCHAIN_SERVICE:-vm-permission-fixer-sudo-password}" -w +EOF + chmod 700 "$ASKPASS_TEMP" + export SUDO_ASKPASS="$ASKPASS_TEMP" + export VM_FIXER_KEYCHAIN_SERVICE="$KEYCHAIN_SERVICE" + + if ! /usr/bin/security find-generic-password -a "$CURRENT_USER" -s "$KEYCHAIN_SERVICE" -w >/dev/null 2>&1; then + prompt_and_save_sudo_password || notify_error_and_exit "未保存 sudo 密码,脚本终止。" + fi + + log "ACTION" "正在通过 Keychain 准备 sudo 凭据..." + if ! /usr/bin/sudo -A -v; then + log "ERROR" "Keychain 中的 sudo 密码不可用。" + log "DEBUG" "可执行以下命令删除后重试: security delete-generic-password -a \"$CURRENT_USER\" -s \"$KEYCHAIN_SERVICE\"" + notify "自动化错误" "Keychain 中的 sudo 密码不可用,请查看日志" + exit 1 + fi +} + +print_banner + +log "INFO" "当前用户: $CURRENT_USER" +log "INFO" "目标路径: $VM_PATH" + +if [[ ! -e "$VM_PATH" ]]; then + print_separator + notify_error_and_exit "找不到虚拟机文件,请检查日志" +fi + +setup_sudo_askpass + +log "ACTION" "正在尝试更改文件所有权 (chown -R)..." +if /usr/bin/sudo -A /usr/sbin/chown -R "$CURRENT_USER" "$VM_PATH"; then + log "SUCCESS" "权限修复成功。" + log "DONE" "自动化任务完成 (Exit Code: 0)" + print_separator + notify "自动化完成" "虚拟机权限已修复" +else + exit_code=$? + log "ERROR" "操作失败,Exit Code: $exit_code" + log "DEBUG" "可能原因: 密码错误 / SIP 限制 / 文件被占用 / Keychain 被锁定" + print_separator + notify "自动化错误" "权限修复失败,请查看日志" + exit "$exit_code" +fi