feat(core): ✨ add vm permission fixer for macOS shortcuts
新增 macOS 快捷指令自动化工具,包含虚拟机权限修复脚本(vm-permission-fixer.sh)及远程引导启动器(vm-permission-fixer-bootstrap.sh)。 - 在打开 Parallels Desktop 时,自动修复指定 .pvm 虚拟机包的所有权。 - 采用 macOS Keychain 安全存储和读取 sudo 密码,避免明文硬编码。 - 补充 shortcuts 模块相关的中英文说明文档,并同步更新项目主 README。
This commit is contained in:
@@ -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=<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 已经占用虚拟机包,权限修复可能失败;查看日志确认原因。
|
||||
Executable
+32
@@ -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" "$@"
|
||||
Executable
+125
@@ -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
|
||||
Reference in New Issue
Block a user