#!/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_DIR="$(mktemp -d "${TMPDIR:-/tmp}/vm-permission-fixer-askpass.XXXXXX")" ASKPASS_TEMP="${ASKPASS_TEMP_DIR}/askpass.sh" cleanup() { rm -rf "$ASKPASS_TEMP_DIR" } 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