Files
go-cursor-help/scripts/run/cursor_linux_id_modifier.sh
煎饼果子卷鲨鱼辣椒 82a86503e4 ```
docs(readme): 添加Windows脚本缓存清除提示

添加关于如何处理镜像/代理缓存问题的详细说明,包括在URL中追加时间戳参数的方法,
以便用户能够绕过缓存获取最新脚本。

feat(script): 更新广告信息添加Team账户选项

在各个平台的脚本中更新广告内容,新增Team绝版次数号选项,包含1000次+20刀额度的服务。
```
2026-01-14 21:42:36 +08:00

1820 lines
72 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# 设置错误处理
set -e
# 定义日志文件路径
LOG_FILE="/tmp/cursor_linux_id_modifier.log"
# 初始化日志文件
initialize_log() {
echo "========== Cursor ID 修改工具日志开始 $(date) ==========" > "$LOG_FILE"
chmod 644 "$LOG_FILE"
}
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数 - 同时输出到终端和日志文件
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}
# 记录命令输出到日志文件
log_cmd_output() {
local cmd="$1"
local msg="$2"
echo "[CMD] $(date '+%Y-%m-%d %H:%M:%S') 执行命令: $cmd" >> "$LOG_FILE"
echo "[CMD] $msg:" >> "$LOG_FILE"
eval "$cmd" 2>&1 | tee -a "$LOG_FILE"
echo "" >> "$LOG_FILE"
}
# 获取当前用户
get_current_user() {
if [ "$EUID" -eq 0 ]; then
echo "$SUDO_USER"
else
echo "$USER"
fi
}
CURRENT_USER=$(get_current_user)
if [ -z "$CURRENT_USER" ]; then
log_error "无法获取用户名"
exit 1
fi
# 定义Linux下的Cursor路径
CURSOR_CONFIG_DIR="$HOME/.config/Cursor"
STORAGE_FILE="$CURSOR_CONFIG_DIR/User/globalStorage/storage.json"
BACKUP_DIR="$CURSOR_CONFIG_DIR/User/globalStorage/backups"
# 共享ID用于配置与JS注入保持一致
CURSOR_ID_MACHINE_ID=""
CURSOR_ID_MACHINE_GUID=""
CURSOR_ID_MAC_MACHINE_ID=""
CURSOR_ID_DEVICE_ID=""
CURSOR_ID_SQM_ID=""
CURSOR_ID_FIRST_SESSION_DATE=""
CURSOR_ID_SESSION_ID=""
CURSOR_ID_MAC_ADDRESS="00:11:22:33:44:55"
# --- 新增:安装相关变量 ---
APPIMAGE_SEARCH_DIR="/opt/CursorInstall" # AppImage 搜索目录,可按需修改
APPIMAGE_PATTERN="Cursor-*.AppImage" # AppImage 文件名模式
INSTALL_DIR="/opt/Cursor" # Cursor 最终安装目录
ICON_PATH="/usr/share/icons/cursor.png"
DESKTOP_FILE="/usr/share/applications/cursor-cursor.desktop"
# --- 结束:安装相关变量 ---
# 可能的Cursor二进制路径 - 添加了标准安装路径
CURSOR_BIN_PATHS=(
"/usr/bin/cursor"
"/usr/local/bin/cursor"
"$INSTALL_DIR/cursor" # 添加标准安装路径
"$HOME/.local/bin/cursor"
"/snap/bin/cursor"
)
# 找到Cursor安装路径
find_cursor_path() {
log_info "查找Cursor安装路径..."
for path in "${CURSOR_BIN_PATHS[@]}"; do
if [ -f "$path" ] && [ -x "$path" ]; then # 确保文件存在且可执行
log_info "找到Cursor安装路径: $path"
CURSOR_PATH="$path"
return 0
fi
done
# 尝试通过which命令定位
if command -v cursor &> /dev/null; then
CURSOR_PATH=$(which cursor)
log_info "通过which找到Cursor: $CURSOR_PATH"
return 0
fi
# 尝试查找可能的安装路径 (限制搜索范围和类型)
local cursor_paths=$(find /usr /opt $HOME/.local -path "$INSTALL_DIR/cursor" -o -name "cursor" -type f -executable 2>/dev/null)
if [ -n "$cursor_paths" ]; then
# 优先选择标准安装路径
local standard_path=$(echo "$cursor_paths" | grep "$INSTALL_DIR/cursor" | head -1)
if [ -n "$standard_path" ]; then
CURSOR_PATH="$standard_path"
else
CURSOR_PATH=$(echo "$cursor_paths" | head -1)
fi
log_info "通过查找找到Cursor: $CURSOR_PATH"
return 0
fi
log_warn "未找到Cursor可执行文件"
return 1
}
# 查找并定位Cursor资源文件目录
find_cursor_resources() {
log_info "查找Cursor资源目录..."
# 可能的资源目录路径 - 添加了标准安装目录
local resource_paths=(
"$INSTALL_DIR" # 添加标准安装路径
"/usr/lib/cursor"
"/usr/share/cursor"
"$HOME/.local/share/cursor"
)
for path in "${resource_paths[@]}"; do
if [ -d "$path/resources" ]; then # 检查是否存在 resources 子目录
log_info "找到Cursor资源目录: $path"
CURSOR_RESOURCES="$path"
return 0
fi
if [ -d "$path/app" ]; then # 有些版本可能直接是 app 目录
log_info "找到Cursor资源目录 (app): $path"
CURSOR_RESOURCES="$path"
return 0
fi
done
# 如果有CURSOR_PATH尝试从它推断
if [ -n "$CURSOR_PATH" ]; then
local base_dir=$(dirname "$CURSOR_PATH")
# 检查常见的相对路径
if [ -d "$base_dir/resources" ]; then
CURSOR_RESOURCES="$base_dir"
log_info "通过二进制路径找到资源目录: $CURSOR_RESOURCES"
return 0
elif [ -d "$base_dir/../resources" ]; then # 例如在 bin 目录内
CURSOR_RESOURCES=$(realpath "$base_dir/..")
log_info "通过二进制路径找到资源目录: $CURSOR_RESOURCES"
return 0
elif [ -d "$base_dir/../lib/cursor/resources" ]; then # 另一种常见结构
CURSOR_RESOURCES=$(realpath "$base_dir/../lib/cursor")
log_info "通过二进制路径找到资源目录: $CURSOR_RESOURCES"
return 0
fi
fi
log_warn "未找到Cursor资源目录"
return 1
}
# 检查权限
check_permissions() {
if [ "$EUID" -ne 0 ]; then
log_error "请使用 sudo 运行此脚本 (安装和修改系统文件需要权限)"
echo "示例: sudo $0"
exit 1
fi
}
# --- 新增/重构:从本地 AppImage 安装 Cursor ---
install_cursor_appimage() {
log_info "开始尝试从本地 AppImage 安装 Cursor..."
local found_appimage_path=""
# 确保搜索目录存在
mkdir -p "$APPIMAGE_SEARCH_DIR"
# 查找 AppImage 文件
find_appimage() {
found_appimage_path=$(find "$APPIMAGE_SEARCH_DIR" -maxdepth 1 -name "$APPIMAGE_PATTERN" -print -quit)
if [ -z "$found_appimage_path" ]; then
return 1
else
return 0
fi
}
if ! find_appimage; then
log_warn "在 '$APPIMAGE_SEARCH_DIR' 目录下未找到 '$APPIMAGE_PATTERN' 文件。"
# --- 新增:添加文件名格式提醒 ---
log_info "请确保 AppImage 文件名格式类似: Cursor-版本号-架构.AppImage (例如: Cursor-1.0.6-aarch64.AppImage 或 Cursor-x.y.z-x86_64.AppImage)"
# --- 结束:添加文件名格式提醒 ---
# 等待用户放置文件
read -p $"请将 Cursor AppImage 文件放入 '$APPIMAGE_SEARCH_DIR' 目录,然后按 Enter 键继续..."
# 再次查找
if ! find_appimage; then
log_error "在 '$APPIMAGE_SEARCH_DIR' 中仍然找不到 '$APPIMAGE_PATTERN' 文件。安装中止。"
return 1
fi
fi
log_info "找到 AppImage 文件: $found_appimage_path"
local appimage_filename=$(basename "$found_appimage_path")
# 进入搜索目录操作,避免路径问题
local current_dir=$(pwd)
cd "$APPIMAGE_SEARCH_DIR" || { log_error "无法进入目录: $APPIMAGE_SEARCH_DIR"; return 1; }
log_info "设置 '$appimage_filename' 可执行权限..."
chmod +x "$appimage_filename" || {
log_error "设置可执行权限失败: $appimage_filename"
cd "$current_dir"
return 1
}
log_info "解压 AppImage 文件 '$appimage_filename'..."
# 创建临时解压目录
local extract_dir="squashfs-root"
rm -rf "$extract_dir" # 清理旧的解压目录(如果存在)
# 执行解压,将输出重定向避免干扰
if ./"$appimage_filename" --appimage-extract > /dev/null; then
log_info "AppImage 解压成功到 '$extract_dir'"
else
log_error "解压 AppImage 失败: $appimage_filename"
rm -rf "$extract_dir" # 清理失败的解压
cd "$current_dir"
return 1
fi
# 检查解压后的预期目录结构
local cursor_source_dir=""
if [ -d "$extract_dir/usr/share/cursor" ]; then
cursor_source_dir="$extract_dir/usr/share/cursor"
elif [ -d "$extract_dir" ]; then # 有些 AppImage 可能直接在根目录
# 进一步检查是否存在关键文件/目录
if [ -f "$extract_dir/cursor" ] && [ -d "$extract_dir/resources" ]; then
cursor_source_dir="$extract_dir"
fi
fi
if [ -z "$cursor_source_dir" ]; then
log_error "解压后的目录 '$extract_dir' 中未找到预期的 Cursor 文件结构 (例如 'usr/share/cursor' 或直接包含 'cursor' 和 'resources')。"
rm -rf "$extract_dir"
cd "$current_dir"
return 1
fi
log_info "找到 Cursor 源文件在: $cursor_source_dir"
log_info "安装 Cursor 到 '$INSTALL_DIR'..."
# 如果安装目录已存在,先删除 (确保全新安装)
if [ -d "$INSTALL_DIR" ]; then
log_warn "发现已存在的安装目录 '$INSTALL_DIR',将先移除..."
rm -rf "$INSTALL_DIR" || { log_error "移除旧安装目录失败: $INSTALL_DIR"; cd "$current_dir"; return 1; }
fi
# 创建安装目录的父目录(如果需要)并设置权限
mkdir -p "$(dirname "$INSTALL_DIR")"
# 移动解压后的内容到安装目录
if mv "$cursor_source_dir" "$INSTALL_DIR"; then
log_info "成功将文件移动到 '$INSTALL_DIR'"
# 确保安装目录及其内容归属当前用户(如果需要)
chown -R "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$INSTALL_DIR" || log_warn "设置 '$INSTALL_DIR' 文件所有权失败,可能需要手动调整"
chmod -R u+rwX,go+rX,go-w "$INSTALL_DIR" || log_warn "设置 '$INSTALL_DIR' 文件权限失败,可能需要手动调整"
else
log_error "移动文件到安装目录 '$INSTALL_DIR' 失败"
rm -rf "$extract_dir" # 确保清理
rm -rf "$INSTALL_DIR" # 清理部分移动的文件
cd "$current_dir"
return 1
fi
# 处理图标和桌面快捷方式 (从脚本执行的原始目录查找)
cd "$current_dir" # 返回原始目录查找图标等文件
local icon_source="./cursor.png"
local desktop_source="./cursor-cursor.desktop"
if [ -f "$icon_source" ]; then
log_info "安装图标..."
mkdir -p "$(dirname "$ICON_PATH")"
cp "$icon_source" "$ICON_PATH" || log_warn "无法复制图标文件 '$icon_source' 到 '$ICON_PATH'"
chmod 644 "$ICON_PATH" || log_warn "设置图标文件权限失败: $ICON_PATH"
else
log_warn "图标文件 '$icon_source' 在脚本当前目录不存在,跳过图标安装。"
log_warn "请将 'cursor.png' 文件放置在脚本目录 '$current_dir' 下并重新运行安装(如果需要图标)。"
fi
if [ -f "$desktop_source" ]; then
log_info "安装桌面快捷方式..."
mkdir -p "$(dirname "$DESKTOP_FILE")"
cp "$desktop_source" "$DESKTOP_FILE" || log_warn "无法创建桌面快捷方式 '$desktop_source' 到 '$DESKTOP_FILE'"
chmod 644 "$DESKTOP_FILE" || log_warn "设置桌面文件权限失败: $DESKTOP_FILE"
# 更新桌面数据库
log_info "更新桌面数据库..."
update-desktop-database "$(dirname "$DESKTOP_FILE")" &> /dev/null || log_warn "无法更新桌面数据库,快捷方式可能不会立即显示"
else
log_warn "桌面文件 '$desktop_source' 在脚本当前目录不存在,跳过快捷方式安装。"
log_warn "请将 'cursor-cursor.desktop' 文件放置在脚本目录 '$current_dir' 下并重新运行安装(如果需要快捷方式)。"
fi
# 创建符号链接到 /usr/local/bin
log_info "创建命令行启动链接..."
ln -sf "$INSTALL_DIR/cursor" /usr/local/bin/cursor || log_warn "无法创建命令行链接 '/usr/local/bin/cursor'"
# 清理临时文件
log_info "清理临时文件..."
cd "$APPIMAGE_SEARCH_DIR" # 返回搜索目录清理
rm -rf "$extract_dir"
log_info "正在删除原始 AppImage 文件: $found_appimage_path"
rm -f "$appimage_filename" # 删除 AppImage 文件
cd "$current_dir" # 确保返回最终目录
log_info "Cursor 安装成功!安装目录: $INSTALL_DIR"
return 0
}
# --- 结束:安装函数 ---
# 检查并关闭 Cursor 进程
check_and_kill_cursor() {
log_info "检查 Cursor 进程..."
local attempt=1
local max_attempts=5
# 函数:获取进程详细信息
get_process_details() {
local process_name="$1"
log_debug "正在获取 $process_name 进程详细信息:"
ps aux | grep -i "cursor" | grep -v grep | grep -v "cursor_linux_id_modifier.sh"
}
while [ $attempt -le $max_attempts ]; do
# 使用更精确的匹配来获取 Cursor 进程排除当前脚本和grep进程
CURSOR_PIDS=$(ps aux | grep -i "cursor" | grep -v "grep" | grep -v "cursor_linux_id_modifier.sh" | awk '{print $2}' || true)
if [ -z "$CURSOR_PIDS" ]; then
log_info "未发现运行中的 Cursor 进程"
return 0
fi
log_warn "发现 Cursor 进程正在运行"
get_process_details "cursor"
log_warn "尝试关闭 Cursor 进程..."
if [ $attempt -eq $max_attempts ]; then
log_warn "尝试强制终止进程..."
kill -9 $CURSOR_PIDS 2>/dev/null || true
else
kill $CURSOR_PIDS 2>/dev/null || true
fi
sleep 1
# 再次检查进程是否还在运行排除当前脚本和grep进程
if ! ps aux | grep -i "cursor" | grep -v "grep" | grep -v "cursor_linux_id_modifier.sh" > /dev/null; then
log_info "Cursor 进程已成功关闭"
return 0
fi
log_warn "等待进程关闭,尝试 $attempt/$max_attempts..."
((attempt++))
done
log_error "$max_attempts 次尝试后仍无法关闭 Cursor 进程"
get_process_details "cursor"
log_error "请手动关闭进程后重试"
exit 1
}
# 备份配置文件
backup_config() {
if [ ! -f "$STORAGE_FILE" ]; then
log_warn "配置文件 '$STORAGE_FILE' 不存在,跳过备份"
return 0
fi
mkdir -p "$BACKUP_DIR"
local backup_file="$BACKUP_DIR/storage.json.backup_$(date +%Y%m%d_%H%M%S)"
if cp "$STORAGE_FILE" "$backup_file"; then
chmod 644 "$backup_file"
# 确保备份文件归属正确用户
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$backup_file" || log_warn "设置备份文件所有权失败: $backup_file"
log_info "配置已备份到: $backup_file"
else
log_error "备份失败: $STORAGE_FILE"
exit 1
fi
return 0 # 明确返回成功
}
# 生成随机 ID
generate_random_id() {
# 生成32字节(64个十六进制字符)的随机数
openssl rand -hex 32
}
# 生成随机 UUID
generate_uuid() {
# 在Linux上使用uuidgen生成UUID
if command -v uuidgen &> /dev/null; then
uuidgen | tr '[:upper:]' '[:lower:]'
else
# 备选方案:使用/proc/sys/kernel/random/uuid
if [ -f /proc/sys/kernel/random/uuid ]; then
cat /proc/sys/kernel/random/uuid
else
# 最后备选方案使用openssl生成
openssl rand -hex 16 | sed 's/\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)/\\1\\2\\3\\4-\\5\\6-\\7\\8-\\9\\10-\\11\\12\\13\\14\\15\\16/'
fi
fi
}
# 规范化 machineId确保为十六进制字符串
normalize_machine_id() {
local raw="$1"
local cleaned
cleaned=$(echo "$raw" | tr -d '-' | tr '[:upper:]' '[:lower:]')
if [[ "$cleaned" =~ ^[0-9a-f]{32,}$ ]]; then
echo "$cleaned"
return 0
fi
return 1
}
# 从现有配置读取ID用于JS注入保持一致
load_ids_from_storage() {
if [ ! -f "$STORAGE_FILE" ]; then
return 1
fi
if ! command -v python3 >/dev/null 2>&1; then
log_warn "未检测到 python3无法从现有配置读取 ID"
return 1
fi
local output
output=$(python3 - "$STORAGE_FILE" <<'PY'
import json, sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
def pick(keys):
for k in keys:
v = data.get(k)
if isinstance(v, str) and v:
return v
return ""
items = {
"machineId": pick(["telemetry.machineId", "machineId"]),
"macMachineId": pick(["telemetry.macMachineId"]),
"devDeviceId": pick(["telemetry.devDeviceId", "deviceId"]),
"sqmId": pick(["telemetry.sqmId"]),
"firstSessionDate": pick(["telemetry.firstSessionDate"]),
}
for k, v in items.items():
print(f"{k}={v}")
PY
)
if [ $? -ne 0 ] || [ -z "$output" ]; then
return 1
fi
while IFS='=' read -r key value; do
case "$key" in
machineId) CURSOR_ID_MACHINE_ID="$value" ;;
macMachineId) CURSOR_ID_MAC_MACHINE_ID="$value" ;;
devDeviceId) CURSOR_ID_DEVICE_ID="$value" ;;
sqmId) CURSOR_ID_SQM_ID="$value" ;;
firstSessionDate) CURSOR_ID_FIRST_SESSION_DATE="$value" ;;
esac
done <<< "$output"
if [ -n "$CURSOR_ID_MACHINE_ID" ]; then
local normalized
if normalized=$(normalize_machine_id "$CURSOR_ID_MACHINE_ID"); then
if [ "$normalized" != "$CURSOR_ID_MACHINE_ID" ]; then
log_warn "machineId 非标准格式JS 注入将使用去除连字符后的值"
fi
CURSOR_ID_MACHINE_ID="$normalized"
else
log_warn "machineId 无法识别为十六进制JS 注入将改用新值"
CURSOR_ID_MACHINE_ID=""
fi
fi
CURSOR_ID_SESSION_ID=$(generate_uuid)
CURSOR_ID_MAC_ADDRESS="${CURSOR_ID_MAC_ADDRESS:-00:11:22:33:44:55}"
if [ -n "$CURSOR_ID_MACHINE_ID" ] && [ -n "$CURSOR_ID_MAC_MACHINE_ID" ] && [ -n "$CURSOR_ID_DEVICE_ID" ] && [ -n "$CURSOR_ID_SQM_ID" ]; then
return 0
fi
return 1
}
# 仅用于JS注入的ID生成不写配置
generate_ids_for_js_only() {
CURSOR_ID_MACHINE_ID=$(openssl rand -hex 32)
CURSOR_ID_MACHINE_GUID=$(generate_uuid)
CURSOR_ID_MAC_MACHINE_ID=$(openssl rand -hex 32)
CURSOR_ID_DEVICE_ID=$(generate_uuid)
CURSOR_ID_SQM_ID="{$(generate_uuid | tr '[:lower:]' '[:upper:]')}"
CURSOR_ID_FIRST_SESSION_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
CURSOR_ID_SESSION_ID=$(generate_uuid)
CURSOR_ID_MAC_ADDRESS="${CURSOR_ID_MAC_ADDRESS:-00:11:22:33:44:55}"
}
# 修改现有文件
modify_or_add_config() {
local key="$1"
local value="$2"
local file="$3"
if [ ! -f "$file" ]; then
log_error "配置文件不存在: $file"
return 1
fi
# 确保文件对当前执行用户root可写
chmod u+w "$file" || {
log_error "无法修改文件权限(写): $file"
return 1
}
# 创建临时文件
local temp_file=$(mktemp)
# 检查key是否存在
if grep -q "\"$key\":[[:space:]]*\"[^\"]*\"" "$file"; then
# key存在,执行替换 (更精确的匹配)
sed "s/\\(\"$key\"\\):[[:space:]]*\"[^\"]*\"/\\1: \"$value\"/" "$file" > "$temp_file" || {
log_error "修改配置失败 (替换): $key in $file"
rm -f "$temp_file"
chmod u-w "$file" # 恢复权限
return 1
}
log_debug "已替换 key '$key' 在文件 '$file' 中"
elif grep -q "}" "$file"; then
# key不存在, 在最后一个 '}' 前添加新的key-value对
# 注意:这种方式比较脆弱,如果 JSON 格式不标准或最后一行不是 '}' 会失败
sed '$ s/}/,\n "'$key'\": "'$value'\"\n}/' "$file" > "$temp_file" || {
log_error "添加配置失败 (注入): $key to $file"
rm -f "$temp_file"
chmod u-w "$file" # 恢复权限
return 1
}
log_debug "已添加 key '$key' 到文件 '$file' 中"
else
log_error "无法确定如何添加配置: $key to $file (文件结构可能不标准)"
rm -f "$temp_file"
chmod u-w "$file" # 恢复权限
return 1
fi
# 检查临时文件是否有效
if [ ! -s "$temp_file" ]; then
log_error "修改或添加配置后生成的临时文件为空: $key in $file"
rm -f "$temp_file"
chmod u-w "$file" # 恢复权限
return 1
fi
# 使用 cat 替换原文件内容
cat "$temp_file" > "$file" || {
log_error "无法写入更新后的配置到文件: $file"
rm -f "$temp_file"
# 尝试恢复权限(如果失败也无大碍)
chmod u-w "$file" || true
return 1
}
rm -f "$temp_file"
# 设置所有者和基础权限root执行时目标文件是用户家目录下的
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$file" || log_warn "设置文件所有权失败: $file"
chmod 644 "$file" || log_warn "设置文件权限失败: $file" # 用户读写,组和其他读
return 0
}
# 生成新的配置
generate_new_config() {
echo
log_warn "机器码重置选项"
# 使用菜单选择函数询问用户是否重置机器码
set +e
select_menu_option "是否需要重置机器码? (通常情况下只修改js文件即可)" "不重置 - 仅修改js文件即可|重置 - 同时修改配置文件和机器码" 0
reset_choice=$?
set -e
# 记录日志以便调试
echo "[INPUT_DEBUG] 机器码重置选项选择: $reset_choice" >> "$LOG_FILE"
# 确保配置文件目录存在
mkdir -p "$(dirname "$STORAGE_FILE")"
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$(dirname "$STORAGE_FILE")" || log_warn "设置配置目录所有权失败: $(dirname "$STORAGE_FILE")"
chmod 755 "$(dirname "$STORAGE_FILE")" || log_warn "设置配置目录权限失败: $(dirname "$STORAGE_FILE")"
# 处理用户选择 - 索引0对应"不重置"选项索引1对应"重置"选项
if [ "$reset_choice" = "1" ]; then
log_info "您选择了重置机器码"
# 检查配置文件是否存在
if [ -f "$STORAGE_FILE" ]; then
log_info "发现已有配置文件: $STORAGE_FILE"
# 备份现有配置
if ! backup_config; then # 如果备份失败,不继续修改
log_error "配置文件备份失败,中止机器码重置。"
return 1 # 返回错误状态
fi
# 生成并设置新的设备ID
local new_device_id=$(generate_uuid)
local new_machine_id=$(openssl rand -hex 32)
# 🔧 新增: serviceMachineId (用于 storage.serviceMachineId)
local new_service_machine_id=$(generate_uuid)
# 🔧 新增: firstSessionDate (重置首次会话日期)
local new_first_session_date=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
# 🔧 新增: macMachineId 和 sqmId
local new_mac_machine_id=$(openssl rand -hex 32 2>/dev/null || head -c 32 /dev/urandom | xxd -p | tr -d '\n')
local new_sqm_id="{$(generate_uuid | tr '[:lower:]' '[:upper:]')}"
CURSOR_ID_MACHINE_ID="$new_machine_id"
CURSOR_ID_MAC_MACHINE_ID="$new_mac_machine_id"
CURSOR_ID_DEVICE_ID="$new_device_id"
CURSOR_ID_SQM_ID="$new_sqm_id"
CURSOR_ID_FIRST_SESSION_DATE="$new_first_session_date"
CURSOR_ID_SESSION_ID=$(generate_uuid)
CURSOR_ID_MAC_ADDRESS="${CURSOR_ID_MAC_ADDRESS:-00:11:22:33:44:55}"
log_info "正在设置新的设备和机器ID..."
log_debug "新设备ID: $new_device_id"
log_debug "新机器ID: $new_machine_id"
log_debug "新serviceMachineId: $new_service_machine_id"
log_debug "新firstSessionDate: $new_first_session_date"
# 修改配置文件
# 🔧 修复: 添加 storage.serviceMachineId, telemetry.firstSessionDate, telemetry.macMachineId, telemetry.sqmId
local config_success=true
modify_or_add_config "deviceId" "$new_device_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "machineId" "$new_machine_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "telemetry.machineId" "$new_machine_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "telemetry.macMachineId" "$new_mac_machine_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "telemetry.devDeviceId" "$new_device_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "telemetry.sqmId" "$new_sqm_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "storage.serviceMachineId" "$new_service_machine_id" "$STORAGE_FILE" || config_success=false
modify_or_add_config "telemetry.firstSessionDate" "$new_first_session_date" "$STORAGE_FILE" || config_success=false
if [ "$config_success" = true ]; then
log_info "配置文件中的所有标识符修改成功"
log_info "📋 [详情] 已更新以下标识符:"
echo " 🔹 deviceId: ${new_device_id:0:16}..."
echo " 🔹 machineId: ${new_machine_id:0:16}..."
echo " 🔹 macMachineId: ${new_mac_machine_id:0:16}..."
echo " 🔹 sqmId: $new_sqm_id"
echo " 🔹 serviceMachineId: $new_service_machine_id"
echo " 🔹 firstSessionDate: $new_first_session_date"
# 🔧 新增: 修改 machineid 文件
log_info "🔧 [machineid] 正在修改 machineid 文件..."
local machineid_file_path="$HOME/.config/Cursor/machineid"
if [ -f "$machineid_file_path" ]; then
# 备份原始 machineid 文件
local machineid_backup="$BACKUP_DIR/machineid.backup_$(date +%Y%m%d_%H%M%S)"
cp "$machineid_file_path" "$machineid_backup" 2>/dev/null && \
log_info "💾 [备份] machineid 文件已备份: $machineid_backup"
fi
# 写入新的 serviceMachineId 到 machineid 文件
if echo -n "$new_service_machine_id" > "$machineid_file_path" 2>/dev/null; then
log_info "✅ [machineid] machineid 文件修改成功: $new_service_machine_id"
# 设置 machineid 文件为只读
chmod 444 "$machineid_file_path" 2>/dev/null && \
log_info "🔒 [保护] machineid 文件已设置为只读"
else
log_warn "⚠️ [machineid] machineid 文件修改失败"
log_info "💡 [提示] 可手动修改文件: $machineid_file_path"
fi
# 🔧 新增: 修改 .updaterId 文件(更新器设备标识符)
log_info "🔧 [updaterId] 正在修改 .updaterId 文件..."
local updater_id_file_path="$HOME/.config/Cursor/.updaterId"
if [ -f "$updater_id_file_path" ]; then
# 备份原始 .updaterId 文件
local updater_id_backup="$BACKUP_DIR/.updaterId.backup_$(date +%Y%m%d_%H%M%S)"
cp "$updater_id_file_path" "$updater_id_backup" 2>/dev/null && \
log_info "💾 [备份] .updaterId 文件已备份: $updater_id_backup"
fi
# 生成新的 updaterIdUUID格式
local new_updater_id=$(generate_uuid)
if echo -n "$new_updater_id" > "$updater_id_file_path" 2>/dev/null; then
log_info "✅ [updaterId] .updaterId 文件修改成功: $new_updater_id"
# 设置 .updaterId 文件为只读
chmod 444 "$updater_id_file_path" 2>/dev/null && \
log_info "🔒 [保护] .updaterId 文件已设置为只读"
else
log_warn "⚠️ [updaterId] .updaterId 文件修改失败"
log_info "💡 [提示] 可手动修改文件: $updater_id_file_path"
fi
else
log_error "配置文件中的部分标识符修改失败"
# 注意:即使失败,备份仍在,但配置文件可能已部分修改
return 1 # 返回错误状态
fi
else
log_warn "未找到配置文件 '$STORAGE_FILE',无法重置机器码。如果这是首次安装,这是正常的。"
# 即使文件不存在,也认为此步骤(不执行)是"成功"的,允许继续
fi
else
log_info "您选择了不重置机器码将仅修改js文件"
# 检查配置文件是否存在并备份(如果存在)
if [ -f "$STORAGE_FILE" ]; then
log_info "发现已有配置文件: $STORAGE_FILE"
if ! backup_config; then
log_error "配置文件备份失败,中止操作。"
return 1 # 返回错误状态
fi
if load_ids_from_storage; then
log_info "已从现有配置读取 IDJS 注入将保持一致"
else
log_warn "无法从现有配置读取 IDJS 注入将使用新生成的 ID不会修改配置"
generate_ids_for_js_only
fi
else
log_warn "未找到配置文件 '$STORAGE_FILE',跳过备份。"
log_warn "无法读取现有IDJS 注入将使用新生成的 ID不会修改配置"
generate_ids_for_js_only
fi
fi
echo
log_info "配置处理完成"
return 0 # 明确返回成功
}
# 查找Cursor的JS文件
find_cursor_js_files() {
log_info "查找Cursor的JS文件..."
local js_files=()
local found=false
# 确保 CURSOR_RESOURCES 已设置
if [ -z "$CURSOR_RESOURCES" ] || [ ! -d "$CURSOR_RESOURCES" ]; then
log_error "Cursor 资源目录未找到或无效 ($CURSOR_RESOURCES),无法查找 JS 文件。"
return 1
fi
log_debug "在资源目录中搜索JS文件: $CURSOR_RESOURCES"
# 在资源目录中递归搜索特定JS文件
# 注意:这些模式可能需要根据 Cursor 版本更新
local js_patterns=(
"resources/app/out/vs/workbench/api/node/extensionHostProcess.js"
"resources/app/out/main.js"
"resources/app/out/vs/code/electron-utility/sharedProcess/sharedProcessMain.js"
"resources/app/out/vs/code/node/cliProcessMain.js"
# 添加其他可能的路径模式
"app/out/vs/workbench/api/node/extensionHostProcess.js" # 如果资源目录是 app 的父目录
"app/out/main.js"
"app/out/vs/code/electron-utility/sharedProcess/sharedProcessMain.js"
"app/out/vs/code/node/cliProcessMain.js"
)
for pattern in "${js_patterns[@]}"; do
# 使用 find 在 CURSOR_RESOURCES 下查找完整路径
local files=$(find "$CURSOR_RESOURCES" -path "*/$pattern" -type f 2>/dev/null)
if [ -n "$files" ]; then
while IFS= read -r file; do
# 检查文件是否已添加
if [[ ! " ${js_files[@]} " =~ " ${file} " ]]; then
log_info "找到JS文件: $file"
js_files+=("$file")
found=true
fi
done <<< "$files"
fi
done
# 如果还没找到,尝试更通用的搜索(可能误报)
if [ "$found" = false ]; then
log_warn "在标准路径模式中未找到JS文件尝试在资源目录 '$CURSOR_RESOURCES' 中进行更广泛的搜索..."
# 查找包含特定关键字的 JS 文件
local files=$(find "$CURSOR_RESOURCES" -name "*.js" -type f -exec grep -lE 'IOPlatformUUID|x-cursor-checksum|getMachineId' {} \; 2>/dev/null)
if [ -n "$files" ]; then
while IFS= read -r file; do
if [[ ! " ${js_files[@]} " =~ " ${file} " ]]; then
log_info "通过关键字找到可能的JS文件: $file"
js_files+=("$file")
found=true
fi
done <<< "$files"
else
log_warn "在资源目录 '$CURSOR_RESOURCES' 中通过关键字也未能找到 JS 文件。"
fi
fi
if [ "$found" = false ]; then
log_error "在资源目录 '$CURSOR_RESOURCES' 中未找到任何可修改的JS文件。"
log_error "请检查 Cursor 安装是否完整,或脚本中的 JS 路径模式是否需要更新。"
return 1
fi
# 去重(理论上上面的检查已经处理,但以防万一)
IFS=" " read -r -a CURSOR_JS_FILES <<< "$(echo "${js_files[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"
log_info "找到 ${#CURSOR_JS_FILES[@]} 个唯一的JS文件需要处理。"
return 0
}
# 修改Cursor的JS文件
# 🔧 修改Cursor内核JS文件实现设备识别绕过增强版三重方案
# 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名
# 方案B: b6 定点重写 - 机器码源函数直接返回固定值
# 方案C: Loader Stub + 外置 Hook - 主/共享进程仅加载外置 Hook 文件
modify_cursor_js_files() {
log_info "🔧 [内核修改] 开始修改Cursor内核JS文件实现设备识别绕过..."
log_info "💡 [方案] 使用增强版三重方案:占位符替换 + b6 定点重写 + Loader Stub + 外置 Hook"
# 先查找需要修改的JS文件
if ! find_cursor_js_files; then
return 1
fi
if [ ${#CURSOR_JS_FILES[@]} -eq 0 ]; then
log_error "JS 文件列表为空,无法继续修改。"
return 1
fi
# 生成或复用设备标识符(优先使用配置中读取的值)
local machine_id="${CURSOR_ID_MACHINE_ID:-}"
local machine_guid="${CURSOR_ID_MACHINE_GUID:-}"
local device_id="${CURSOR_ID_DEVICE_ID:-}"
local mac_machine_id="${CURSOR_ID_MAC_MACHINE_ID:-}"
local sqm_id="${CURSOR_ID_SQM_ID:-}"
local session_id="${CURSOR_ID_SESSION_ID:-}"
local first_session_date="${CURSOR_ID_FIRST_SESSION_DATE:-}"
local mac_address="${CURSOR_ID_MAC_ADDRESS:-00:11:22:33:44:55}"
local ids_missing=false
if [ -z "$machine_id" ]; then
machine_id=$(openssl rand -hex 32)
ids_missing=true
fi
if [ -z "$machine_guid" ]; then
machine_guid=$(generate_uuid)
ids_missing=true
fi
if [ -z "$device_id" ]; then
device_id=$(generate_uuid)
ids_missing=true
fi
if [ -z "$mac_machine_id" ]; then
mac_machine_id=$(openssl rand -hex 32)
ids_missing=true
fi
if [ -z "$sqm_id" ]; then
sqm_id="{$(generate_uuid | tr '[:lower:]' '[:upper:]')}"
ids_missing=true
fi
if [ -z "$session_id" ]; then
session_id=$(generate_uuid)
ids_missing=true
fi
if [ -z "$first_session_date" ]; then
first_session_date=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
ids_missing=true
fi
if [ "$ids_missing" = true ]; then
log_warn "部分 ID 未从配置获取,已生成新值用于 JS 注入"
else
log_info "已使用配置中的设备标识符进行 JS 注入"
fi
CURSOR_ID_MACHINE_ID="$machine_id"
CURSOR_ID_MACHINE_GUID="$machine_guid"
CURSOR_ID_DEVICE_ID="$device_id"
CURSOR_ID_MAC_MACHINE_ID="$mac_machine_id"
CURSOR_ID_SQM_ID="$sqm_id"
CURSOR_ID_SESSION_ID="$session_id"
CURSOR_ID_FIRST_SESSION_DATE="$first_session_date"
CURSOR_ID_MAC_ADDRESS="$mac_address"
log_info "🔑 [准备] 设备标识符已就绪"
log_info " machineId: ${machine_id:0:16}..."
log_info " machineGuid: ${machine_guid:0:16}..."
log_info " deviceId: ${device_id:0:16}..."
log_info " macMachineId: ${mac_machine_id:0:16}..."
log_info " sqmId: $sqm_id"
# 每次执行都删除旧配置并重新生成,确保获得新的设备标识符
local ids_config_path="$HOME/.cursor_ids.json"
if [ -f "$ids_config_path" ]; then
rm -f "$ids_config_path"
log_info "🗑️ [清理] 已删除旧的 ID 配置文件"
fi
cat > "$ids_config_path" << EOF
{
"machineId": "$machine_id",
"machineGuid": "$machine_guid",
"macMachineId": "$mac_machine_id",
"devDeviceId": "$device_id",
"sqmId": "$sqm_id",
"macAddress": "$mac_address",
"sessionId": "$session_id",
"firstSessionDate": "$first_session_date",
"createdAt": "$first_session_date"
}
EOF
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$ids_config_path" 2>/dev/null || true
log_info "💾 [保存] 新的 ID 配置已保存到: $ids_config_path"
# 部署外置 Hook 文件(供 Loader Stub 加载,支持多域名备用下载)
local hook_target_path="$HOME/.cursor_hook.js"
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local hook_source_path="$script_dir/../hook/cursor_hook.js"
local hook_download_urls=(
"https://wget.la/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js"
"https://down.npee.cn/?https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js"
"https://xget.xi-xu.me/gh/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js"
"https://gh-proxy.com/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js"
"https://gh.chjina.com/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js"
)
# 支持通过环境变量覆盖下载节点(逗号分隔)
if [ -n "$CURSOR_HOOK_DOWNLOAD_URLS" ]; then
IFS=',' read -r -a hook_download_urls <<< "$CURSOR_HOOK_DOWNLOAD_URLS"
log_info " [Hook] 检测到自定义下载节点列表,将优先使用"
fi
if [ -f "$hook_source_path" ]; then
if cp "$hook_source_path" "$hook_target_path"; then
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$hook_target_path" 2>/dev/null || true
log_info "✅ [Hook] 外置 Hook 已部署: $hook_target_path"
else
log_warn "⚠️ [Hook] 本地 Hook 复制失败,尝试在线下载..."
fi
fi
if [ ! -f "$hook_target_path" ]; then
log_info " [Hook] 正在下载外置 Hook用于设备标识拦截..."
local hook_downloaded=false
local total_urls=${#hook_download_urls[@]}
if [ "$total_urls" -eq 0 ]; then
log_warn "⚠️ [Hook] 下载节点列表为空,跳过在线下载"
elif command -v curl >/dev/null 2>&1; then
local index=0
for url in "${hook_download_urls[@]}"; do
index=$((index + 1))
log_info "⏳ [Hook] ($index/$total_urls) 当前下载节点: $url"
if curl -fL --progress-bar "$url" -o "$hook_target_path"; then
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$hook_target_path" 2>/dev/null || true
log_info "✅ [Hook] 外置 Hook 已在线下载: $hook_target_path"
hook_downloaded=true
break
else
rm -f "$hook_target_path"
log_warn "⚠️ [Hook] 外置 Hook 下载失败: $url"
fi
done
elif command -v wget >/dev/null 2>&1; then
local index=0
for url in "${hook_download_urls[@]}"; do
index=$((index + 1))
log_info "⏳ [Hook] ($index/$total_urls) 当前下载节点: $url"
if wget --progress=bar:force -O "$hook_target_path" "$url"; then
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$hook_target_path" 2>/dev/null || true
log_info "✅ [Hook] 外置 Hook 已在线下载: $hook_target_path"
hook_downloaded=true
break
else
rm -f "$hook_target_path"
log_warn "⚠️ [Hook] 外置 Hook 下载失败: $url"
fi
done
else
log_warn "⚠️ [Hook] 未检测到 curl/wget无法在线下载 Hook"
fi
if [ "$hook_downloaded" != true ] && [ ! -f "$hook_target_path" ]; then
log_warn "⚠️ [Hook] 外置 Hook 全部下载失败"
fi
fi
local modified_count=0
local file_modification_status=()
# 处理每个文件:创建原始备份或从原始备份恢复
for file in "${CURSOR_JS_FILES[@]}"; do
log_info "📝 [处理] 正在处理: $(basename "$file")"
if [ ! -f "$file" ]; then
log_error "文件不存在: $file,跳过处理。"
file_modification_status+=("'$(basename "$file")': Not Found")
continue
fi
# 创建备份目录
local backup_dir="$(dirname "$file")/backups"
mkdir -p "$backup_dir" 2>/dev/null || true
local file_name=$(basename "$file")
local original_backup="$backup_dir/$file_name.original"
# 如果原始备份不存在,先创建
if [ ! -f "$original_backup" ]; then
# 检查当前文件是否已被修改过
if grep -q "__cursor_patched__" "$file" 2>/dev/null; then
log_warn "⚠️ [警告] 文件已被修改但无原始备份,将使用当前版本作为基础"
fi
cp "$file" "$original_backup"
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$original_backup" 2>/dev/null || true
chmod 444 "$original_backup" 2>/dev/null || true
log_info "✅ [备份] 原始备份创建成功: $file_name"
else
# 从原始备份恢复,确保每次都是干净的注入
log_info "🔄 [恢复] 从原始备份恢复: $file_name"
cp "$original_backup" "$file"
fi
# 创建时间戳备份(记录每次修改前的状态)
local backup_file="$backup_dir/$file_name.backup_$(date +%Y%m%d_%H%M%S)"
if ! cp "$file" "$backup_file"; then
log_error "无法创建文件备份: $file"
file_modification_status+=("'$(basename "$file")': Backup Failed")
continue
fi
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$backup_file" 2>/dev/null || true
chmod 444 "$backup_file" 2>/dev/null || true
chmod u+w "$file" || {
log_error "无法修改文件权限(写): $file"
file_modification_status+=("'$(basename "$file")': Permission Error")
continue
}
local replaced=false
# ========== 方法A: someValue占位符替换稳定锚点 ==========
# 重要说明:
# 当前 Cursor 的 main.js 中占位符通常是以字符串字面量形式出现,例如:
# this.machineId="someValue.machineId"
# 如果直接把 someValue.machineId 替换成 "\"<真实值>\"",会形成 ""<真实值>"" 导致 JS 语法错误。
# 因此这里优先替换完整的字符串字面量(包含外层引号),再兜底替换不带引号的占位符。
if grep -q 'someValue\.machineId' "$file"; then
sed -i "s/\"someValue\.machineId\"/\"${machine_id}\"/g" "$file"
sed -i "s/'someValue\.machineId'/\"${machine_id}\"/g" "$file"
sed -i "s/someValue\.machineId/\"${machine_id}\"/g" "$file"
log_info " ✓ [方案A] 替换 someValue.machineId"
replaced=true
fi
if grep -q 'someValue\.macMachineId' "$file"; then
sed -i "s/\"someValue\.macMachineId\"/\"${mac_machine_id}\"/g" "$file"
sed -i "s/'someValue\.macMachineId'/\"${mac_machine_id}\"/g" "$file"
sed -i "s/someValue\.macMachineId/\"${mac_machine_id}\"/g" "$file"
log_info " ✓ [方案A] 替换 someValue.macMachineId"
replaced=true
fi
if grep -q 'someValue\.devDeviceId' "$file"; then
sed -i "s/\"someValue\.devDeviceId\"/\"${device_id}\"/g" "$file"
sed -i "s/'someValue\.devDeviceId'/\"${device_id}\"/g" "$file"
sed -i "s/someValue\.devDeviceId/\"${device_id}\"/g" "$file"
log_info " ✓ [方案A] 替换 someValue.devDeviceId"
replaced=true
fi
if grep -q 'someValue\.sqmId' "$file"; then
sed -i "s/\"someValue\.sqmId\"/\"${sqm_id}\"/g" "$file"
sed -i "s/'someValue\.sqmId'/\"${sqm_id}\"/g" "$file"
sed -i "s/someValue\.sqmId/\"${sqm_id}\"/g" "$file"
log_info " ✓ [方案A] 替换 someValue.sqmId"
replaced=true
fi
if grep -q 'someValue\.sessionId' "$file"; then
sed -i "s/\"someValue\.sessionId\"/\"${session_id}\"/g" "$file"
sed -i "s/'someValue\.sessionId'/\"${session_id}\"/g" "$file"
sed -i "s/someValue\.sessionId/\"${session_id}\"/g" "$file"
log_info " ✓ [方案A] 替换 someValue.sessionId"
replaced=true
fi
if grep -q 'someValue\.firstSessionDate' "$file"; then
sed -i "s/\"someValue\.firstSessionDate\"/\"${first_session_date}\"/g" "$file"
sed -i "s/'someValue\.firstSessionDate'/\"${first_session_date}\"/g" "$file"
sed -i "s/someValue\.firstSessionDate/\"${first_session_date}\"/g" "$file"
log_info " ✓ [方案A] 替换 someValue.firstSessionDate"
replaced=true
fi
# ========== 方法B: b6 定点重写(机器码源函数,仅 main.js ==========
local b6_patched=false
if [ "$(basename "$file")" = "main.js" ]; then
if command -v python3 >/dev/null 2>&1; then
local b6_result
b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY'
if True:
import re, sys
def diag(msg):
print(f"[方案B][诊断] {msg}", file=sys.stderr)
path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3]
with open(path, "r", encoding="utf-8") as f:
data = f.read()
# ✅ 1+3 融合:限定 out-build/vs/base/node/id.js 模块内做特征匹配 + 花括号配对定位函数边界
marker = "out-build/vs/base/node/id.js"
marker_index = data.find(marker)
if marker_index < 0:
print("NOT_FOUND")
diag(f"未找到模块标记: {marker}")
raise SystemExit(0)
window_end = min(len(data), marker_index + 200000)
window = data[marker_index:window_end]
def find_matching_brace(text, open_index, max_scan=20000):
limit = min(len(text), open_index + max_scan)
depth = 1
in_single = in_double = in_template = False
in_line_comment = in_block_comment = False
escape = False
i = open_index + 1
while i < limit:
ch = text[i]
nxt = text[i + 1] if i + 1 < limit else ""
if in_line_comment:
if ch == "\n":
in_line_comment = False
i += 1
continue
if in_block_comment:
if ch == "*" and nxt == "/":
in_block_comment = False
i += 2
continue
i += 1
continue
if in_single:
if escape:
escape = False
elif ch == "\\\\":
escape = True
elif ch == "'":
in_single = False
i += 1
continue
if in_double:
if escape:
escape = False
elif ch == "\\\\":
escape = True
elif ch == '"':
in_double = False
i += 1
continue
if in_template:
if escape:
escape = False
elif ch == "\\\\":
escape = True
elif ch == "`":
in_template = False
i += 1
continue
if ch == "/" and nxt == "/":
in_line_comment = True
i += 2
continue
if ch == "/" and nxt == "*":
in_block_comment = True
i += 2
continue
if ch == "'":
in_single = True
i += 1
continue
if ch == '"':
in_double = True
i += 1
continue
if ch == "`":
in_template = True
i += 1
continue
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return i
i += 1
return None
hash_re = re.compile(r'createHash\\([\"\\']sha256[\"\\']\\)')
sig_re = re.compile(r'^async function (\\w+)\\((\\w+)\\)')
hash_matches = list(hash_re.finditer(window))
diag(f"marker_index={marker_index} window_len={len(window)} sha256_createHash={len(hash_matches)}")
for idx, hm in enumerate(hash_matches, start=1):
hash_pos = hm.start()
func_start = window.rfind("async function", 0, hash_pos)
if func_start < 0:
if idx <= 3:
diag(f"候选#{idx}: 未找到 async function 起点")
continue
open_brace = window.find("{", func_start)
if open_brace < 0:
if idx <= 3:
diag(f"候选#{idx}: 未找到函数起始花括号")
continue
end_brace = find_matching_brace(window, open_brace, max_scan=20000)
if end_brace is None:
if idx <= 3:
diag(f"候选#{idx}: 花括号配对失败(扫描上限内未闭合)")
continue
func_text = window[func_start:end_brace + 1]
if len(func_text) > 8000:
if idx <= 3:
diag(f"候选#{idx}: 函数体过长 len={len(func_text)},已跳过")
continue
sm = sig_re.match(func_text)
if not sm:
if idx <= 3:
diag(f"候选#{idx}: 未解析到函数签名async function name(param)")
continue
name, param = sm.group(1), sm.group(2)
# 特征校验sha256 + hex digest + return param ? raw : hash
has_digest = re.search(r'\\.digest\\([\"\\']hex[\"\\']\\)', func_text) is not None
has_return = re.search(r'return\\s+' + re.escape(param) + r'\\?\\w+:\\w+\\}', func_text) is not None
if idx <= 3:
diag(f"候选#{idx}: {name}({param}) len={len(func_text)} digest={has_digest} return={has_return}")
if not has_digest:
continue
if not has_return:
continue
replacement = f'async function {name}({param}){{return {param}?"{machine_guid}":"{machine_id}";}}'
abs_start = marker_index + func_start
abs_end = marker_index + end_brace
new_data = data[:abs_start] + replacement + data[abs_end + 1:]
with open(path, "w", encoding="utf-8") as f:
f.write(new_data)
diag(f"命中并重写: {name}({param}) len={len(func_text)}")
print("PATCHED")
break
else:
diag("未找到满足特征的候选函数")
print("NOT_FOUND")
PY
)
if [ "$b6_result" = "PATCHED" ]; then
log_info " ✓ [方案B] 已重写 b6 特征函数"
b6_patched=true
else
log_warn "⚠️ [方案B] 未定位到 b6 特征函数"
fi
else
log_warn "⚠️ [方案B] 未检测到 python3跳过 b6 定点重写"
fi
fi
# ========== 方法C: Loader Stub 注入 ==========
local inject_code='// ========== Cursor Hook Loader 开始 ==========
;(async function(){/*__cursor_patched__*/
"use strict";
if(globalThis.__cursor_hook_loaded__)return;
globalThis.__cursor_hook_loaded__=true;
try{
// 兼容 ESM/CJS避免使用 import.meta仅 ESM 支持),统一用动态 import 加载 Hook
var fsMod=await import("fs");
var pathMod=await import("path");
var osMod=await import("os");
var urlMod=await import("url");
var fs=fsMod&&(fsMod.default||fsMod);
var path=pathMod&&(pathMod.default||pathMod);
var os=osMod&&(osMod.default||osMod);
var url=urlMod&&(urlMod.default||urlMod);
if(fs&&path&&os&&url&&typeof url.pathToFileURL==="function"){
var hookPath=path.join(os.homedir(), ".cursor_hook.js");
if(typeof fs.existsSync==="function"&&fs.existsSync(hookPath)){
await import(url.pathToFileURL(hookPath).href);
}
}
}catch(e){
// 失败静默,避免影响启动
}
})();
// ========== Cursor Hook Loader 结束 ==========
'
# 在版权声明后注入代码
local temp_file=$(mktemp)
if grep -q '\*/' "$file"; then
awk -v inject="$inject_code" '
/\*\// && !injected {
print
print ""
print inject
injected = 1
next
}
{ print }
' "$file" > "$temp_file"
log_info " ✓ [方案C] Loader Stub 已注入(版权声明后)"
else
echo "$inject_code" > "$temp_file"
cat "$file" >> "$temp_file"
log_info " ✓ [方案C] Loader Stub 已注入(文件开头)"
fi
if mv "$temp_file" "$file"; then
local summary="Hook加载器"
if [ "$replaced" = true ]; then
summary="someValue替换 + $summary"
fi
if [ "$b6_patched" = true ]; then
summary="b6定点重写 + $summary"
fi
log_info "✅ [成功] 增强版方案修改成功($summary"
((modified_count++))
file_modification_status+=("'$(basename "$file")': Success")
chmod u-w,go-w "$file" 2>/dev/null || true
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$file" 2>/dev/null || true
else
log_error "Hook注入失败 (无法移动临时文件)"
rm -f "$temp_file"
file_modification_status+=("'$(basename "$file")': Inject Failed")
cp "$original_backup" "$file" 2>/dev/null || true
fi
done
log_info "📊 [统计] JS 文件处理状态汇总:"
for status in "${file_modification_status[@]}"; do
log_info " - $status"
done
if [ "$modified_count" -eq 0 ]; then
log_error "❌ [失败] 未能成功修改任何JS文件。"
return 1
fi
log_info "🎉 [完成] 成功修改 $modified_count 个JS文件"
log_info "💡 [说明] 使用增强版三重方案:"
log_info " • 方案A: someValue占位符替换稳定锚点跨版本兼容"
log_info " • 方案B: b6 定点重写(机器码源函数)"
log_info " • 方案C: Loader Stub + 外置 Hookcursor_hook.js"
log_info "📁 [配置] ID 配置文件: $ids_config_path"
return 0
}
# 禁用自动更新
disable_auto_update() {
log_info "正在尝试禁用 Cursor 自动更新..."
# 查找可能的更新配置文件
local update_configs=()
# 用户配置目录下的
if [ -d "$CURSOR_CONFIG_DIR" ]; then
update_configs+=("$CURSOR_CONFIG_DIR/update-config.json")
update_configs+=("$CURSOR_CONFIG_DIR/settings.json") # 有些设置可能在这里
fi
# 安装目录下的 (如果资源目录确定)
if [ -n "$CURSOR_RESOURCES" ] && [ -d "$CURSOR_RESOURCES" ]; then
update_configs+=("$CURSOR_RESOURCES/resources/app-update.yml")
update_configs+=("$CURSOR_RESOURCES/app-update.yml") # 可能的位置
fi
# 标准安装目录下的
if [ -d "$INSTALL_DIR" ]; then
update_configs+=("$INSTALL_DIR/resources/app-update.yml")
update_configs+=("$INSTALL_DIR/app-update.yml")
fi
# $HOME/.local/share
update_configs+=("$HOME/.local/share/cursor/update-config.json")
local disabled_count=0
# 处理 JSON 配置文件
local json_config_pattern='update-config.json|settings.json'
for config in "${update_configs[@]}"; do
if [[ "$config" =~ $json_config_pattern ]] && [ -f "$config" ]; then
log_info "找到可能的更新配置文件: $config"
# 备份
cp "$config" "${config}.bak_$(date +%Y%m%d%H%M%S)" 2>/dev/null
# 尝试修改 JSON (如果存在且是 settings.json)
if [[ "$config" == *settings.json ]]; then
# 尝试添加或修改 "update.mode": "none"
if grep -q '"update.mode"' "$config"; then
sed -i 's/"update.mode":[[:space:]]*"[^"]*"/"update.mode": "none"/' "$config" || log_warn "修改 settings.json 中的 update.mode 失败"
elif grep -q "}" "$config"; then # 尝试注入
sed -i '$ s/}/,\n "update.mode": "none"\n}/' "$config" || log_warn "注入 update.mode 到 settings.json 失败"
else
log_warn "无法修改 settings.json 以禁用更新(结构未知)"
fi
# 确保权限正确
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$config" || log_warn "设置所有权失败: $config"
chmod 644 "$config" || log_warn "设置权限失败: $config"
((disabled_count++))
log_info "已尝试在 '$config' 中设置 'update.mode' 为 'none'"
elif [[ "$config" == *update-config.json ]]; then
# 直接覆盖 update-config.json
echo '{"autoCheck": false, "autoDownload": false}' > "$config"
chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$config" || log_warn "设置所有权失败: $config"
chmod 644 "$config" || log_warn "设置权限失败: $config"
((disabled_count++))
log_info "已覆盖更新配置文件: $config"
fi
fi
done
# 处理 YAML 配置文件
local yml_config_pattern='app-update.yml'
for config in "${update_configs[@]}"; do
if [[ "$config" =~ $yml_config_pattern ]] && [ -f "$config" ]; then
log_info "找到可能的更新配置文件: $config"
# 备份
cp "$config" "${config}.bak_$(date +%Y%m%d%H%M%S)" 2>/dev/null
# 清空或修改内容 (简单起见,直接清空或写入禁用标记)
echo "# Automatic updates disabled by script $(date)" > "$config"
# echo "provider: generic" > "$config" # 或者尝试修改 provider
# echo "url: http://127.0.0.1" >> "$config"
chmod 444 "$config" # 设置为只读
((disabled_count++))
log_info "已修改/清空更新配置文件: $config"
fi
done
# 尝试查找updater可执行文件并禁用重命名或移除权限
local updater_paths=()
if [ -n "$CURSOR_RESOURCES" ] && [ -d "$CURSOR_RESOURCES" ]; then
updater_paths+=($(find "$CURSOR_RESOURCES" -name "updater" -type f -executable 2>/dev/null))
updater_paths+=($(find "$CURSOR_RESOURCES" -name "CursorUpdater" -type f -executable 2>/dev/null)) # macOS 风格?
fi
if [ -d "$INSTALL_DIR" ]; then
updater_paths+=($(find "$INSTALL_DIR" -name "updater" -type f -executable 2>/dev/null))
updater_paths+=($(find "$INSTALL_DIR" -name "CursorUpdater" -type f -executable 2>/dev/null))
fi
updater_paths+=("$HOME/.config/Cursor/updater") # 旧位置?
for updater in "${updater_paths[@]}"; do
if [ -f "$updater" ] && [ -x "$updater" ]; then
log_info "找到更新程序: $updater"
local bak_updater="${updater}.bak_$(date +%Y%m%d%H%M%S)"
if mv "$updater" "$bak_updater"; then
log_info "已重命名更新程序为: $bak_updater"
((disabled_count++))
else
log_warn "重命名更新程序失败: $updater,尝试移除执行权限..."
if chmod a-x "$updater"; then
log_info "已移除更新程序执行权限: $updater"
((disabled_count++))
else
log_error "无法禁用更新程序: $updater"
fi
fi
# elif [ -d "$updater" ]; then # 如果是目录,尝试禁用
# log_info "找到更新程序目录: $updater"
# touch "${updater}.disabled_by_script"
# log_info "已标记禁用更新程序目录: $updater"
# ((disabled_count++))
fi
done
if [ "$disabled_count" -eq 0 ]; then
log_warn "未能找到或禁用任何已知的自动更新机制。"
log_warn "如果 Cursor 仍然自动更新,可能需要手动查找并禁用相关文件或设置。"
else
log_info "成功禁用或尝试禁用了 $disabled_count 个自动更新相关的文件/程序。"
fi
return 0 # 即使没找到,也认为函数执行成功
}
# 新增:通用菜单选择函数
select_menu_option() {
local prompt="$1"
IFS='|' read -ra options <<< "$2"
local default_index=${3:-0}
local selected_index=$default_index
local key_input
local cursor_up=$'\e[A' # 更标准的 ANSI 码
local cursor_down=$'\e[B'
local enter_key=$'\n'
# 隐藏光标
tput civis
# 清除可能存在的旧菜单行 (假设菜单最多 N 行)
local num_options=${#options[@]}
for ((i=0; i<num_options+1; i++)); do echo -e "\033[K"; done # 清除行
tput cuu $((num_options + 1)) # 光标移回顶部
# 显示提示信息
echo -e "$prompt"
# 绘制菜单函数
draw_menu() {
# 光标移到菜单开始行下方一行
tput cud 1
for i in "${!options[@]}"; do
tput el # 清除当前行
if [ $i -eq $selected_index ]; then
echo -e " ${GREEN}${NC} ${options[$i]}"
else
echo -e " ${options[$i]}"
fi
done
# 将光标移回提示行下方
tput cuu "$num_options"
}
# 第一次显示菜单
draw_menu
# 循环处理键盘输入
while true; do
# 读取按键 (使用 -sn1 或 -sn3 取决于系统对箭头键的处理)
# -N 1 读取单个字符,可能需要多次读取箭头键
# -N 3 一次读取3个字符通常用于箭头键
read -rsn1 key_press_1 # 读取第一个字符
if [[ "$key_press_1" == $'\e' ]]; then # 如果是 ESC读取后续字符
read -rsn2 key_press_2 # 读取 '[' 和 A/B
key_input="$key_press_1$key_press_2"
elif [[ "$key_press_1" == "" ]]; then # 如果是 Enter
key_input=$enter_key
else
key_input="$key_press_1" # 其他按键
fi
# 检测按键
case "$key_input" in
# 上箭头键
"$cursor_up")
if [ $selected_index -gt 0 ]; then
((selected_index--))
draw_menu
fi
;;
# 下箭头键
"$cursor_down")
if [ $selected_index -lt $((${#options[@]}-1)) ]; then
((selected_index++))
draw_menu
fi
;;
# Enter键
"$enter_key")
# 清除菜单区域
tput cud 1 # 下移一行开始清除
for i in "${!options[@]}"; do tput el; tput cud 1; done
tput cuu $((num_options + 1)) # 移回提示行
tput el # 清除提示行本身
echo -e "$prompt ${GREEN}${options[$selected_index]}${NC}" # 显示最终选择
# 恢复光标
tput cnorm
# 返回选择的索引
return $selected_index
;;
*)
# 忽略其他按键
;;
esac
done
}
# 新增 Cursor 初始化清理函数
cursor_initialize_cleanup() {
log_info "正在执行 Cursor 初始化清理..."
# CURSOR_CONFIG_DIR 在脚本全局已定义: $HOME/.config/Cursor
local USER_CONFIG_BASE_PATH="$CURSOR_CONFIG_DIR/User"
log_debug "用户配置基础路径: $USER_CONFIG_BASE_PATH"
local files_to_delete=(
"$USER_CONFIG_BASE_PATH/globalStorage/state.vscdb"
"$USER_CONFIG_BASE_PATH/globalStorage/state.vscdb.backup"
)
local folder_to_clean_contents="$USER_CONFIG_BASE_PATH/History"
local folder_to_delete_completely="$USER_CONFIG_BASE_PATH/workspaceStorage"
# 删除指定文件
for file_path in "${files_to_delete[@]}"; do
log_debug "检查文件: $file_path"
if [ -f "$file_path" ]; then
if rm -f "$file_path"; then
log_info "已删除文件: $file_path"
else
log_error "删除文件 $file_path 失败"
fi
else
log_warn "文件不存在,跳过删除: $file_path"
fi
done
# 清空指定文件夹内容
log_debug "检查待清空文件夹: $folder_to_clean_contents"
if [ -d "$folder_to_clean_contents" ]; then
if find "$folder_to_clean_contents" -mindepth 1 -delete; then
log_info "已清空文件夹内容: $folder_to_clean_contents"
else
if [ -z "$(ls -A "$folder_to_clean_contents")" ]; then
log_info "文件夹 $folder_to_clean_contents 现在为空。"
else
log_error "清空文件夹 $folder_to_clean_contents 内容失败 (部分或全部)。请检查权限或手动删除。"
fi
fi
else
log_warn "文件夹不存在,跳过清空: $folder_to_clean_contents"
fi
# 删除指定文件夹及其内容
log_debug "检查待删除文件夹: $folder_to_delete_completely"
if [ -d "$folder_to_delete_completely" ]; then
if rm -rf "$folder_to_delete_completely"; then
log_info "已删除文件夹: $folder_to_delete_completely"
else
log_error "删除文件夹 $folder_to_delete_completely 失败"
fi
else
log_warn "文件夹不存在,跳过删除: $folder_to_delete_completely"
fi
log_info "Cursor 初始化清理完成。"
}
# 主函数
main() {
# 初始化日志文件
initialize_log
log_info "脚本启动..."
log_info "运行用户: $CURRENT_USER (脚本以 EUID=$EUID 运行)"
# 检查权限 (必须在脚本早期)
check_permissions # 需要 root 权限进行安装和修改系统文件
# 记录系统信息
log_info "系统信息: $(uname -a)"
log_cmd_output "lsb_release -a 2>/dev/null || cat /etc/*release 2>/dev/null || cat /etc/issue" "系统版本信息"
clear
# 显示 Logo
echo -e "
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
"
echo -e "${BLUE}=====================================================${NC}"
echo -e "${GREEN} Cursor Linux 启动与修改工具(免费) ${NC}"
echo -e "${YELLOW} 关注公众号【煎饼果子卷AI】 ${NC}"
echo -e "${YELLOW} 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) ${NC}"
echo -e "${BLUE}=====================================================${NC}"
echo
echo -e "${YELLOW}⚡ [小小广告] Cursor官网正规成品号Pro¥65 | Pro+¥265 | Ultra¥888 独享账号| ¥688 Team绝版次数号1000次+20刀额度 | 全部7天质保 | WeChatJavaRookie666 ${NC}"
echo
echo -e "${YELLOW}[提示]${NC} 本工具旨在修改 Cursor 以解决可能的启动问题或设备限制。"
echo -e "${YELLOW}[提示]${NC} 它将优先修改 JS 文件并可选择重置设备ID和禁用自动更新。"
echo -e "${YELLOW}[提示]${NC} 如果未找到 Cursor将尝试从 '$APPIMAGE_SEARCH_DIR' 目录安装。"
echo
# 查找 Cursor 路径
if ! find_cursor_path; then
log_warn "系统中未找到现有的 Cursor 安装。"
set +e
select_menu_option "是否尝试从 '$APPIMAGE_SEARCH_DIR' 目录中的 AppImage 文件安装 Cursor" "是,安装 Cursor|否,退出脚本" 0
install_choice=$?
set -e
if [ "$install_choice" -eq 0 ]; then
if ! install_cursor_appimage; then
log_error "Cursor 安装失败,请检查上面的日志。脚本将退出。"
exit 1
fi
# 安装成功后,重新查找路径
if ! find_cursor_path || ! find_cursor_resources; then
log_error "安装后仍然无法找到 Cursor 的可执行文件或资源目录。请检查 '$INSTALL_DIR' 和 '/usr/local/bin/cursor'。脚本退出。"
exit 1
fi
log_info "Cursor 安装成功,继续执行修改步骤..."
else
log_info "用户选择不安装 Cursor脚本退出。"
exit 0
fi
else
# 如果找到了 Cursor也要确保找到资源目录
if ! find_cursor_resources; then
log_error "找到了 Cursor 可执行文件 ($CURSOR_PATH),但未能定位资源目录。"
log_error "无法继续修改 JS 文件。请检查 Cursor 安装是否完整。脚本退出。"
exit 1
fi
log_info "发现已安装的 Cursor ($CURSOR_PATH),资源目录 ($CURSOR_RESOURCES)。"
fi
# 到这里Cursor 应该已安装并且路径已知
# 检查并关闭Cursor进程
if ! check_and_kill_cursor; then
# check_and_kill_cursor 内部会记录错误并退出,但以防万一
exit 1
fi
# 执行 Cursor 初始化清理
# cursor_initialize_cleanup
# 备份并处理配置文件 (机器码重置选项)
if ! generate_new_config; then
log_error "处理配置文件时出错,脚本中止。"
# 此处可能需要考虑是否回滚JS修改如果已执行目前不回滚。
exit 1
fi
# 修改JS文件
log_info "正在修改 Cursor JS 文件..."
if ! modify_cursor_js_files; then
log_error "JS 文件修改过程中发生错误。"
log_warn "配置文件可能已被修改,但 JS 文件修改失败。"
log_warn "如果重启后 Cursor 行为异常或仍有问题,请检查日志并考虑手动恢复备份或重新运行脚本。"
# 决定是否继续执行禁用更新?通常建议继续
# exit 1 # 或者选择退出
else
log_info "JS 文件修改成功!"
fi
# 禁用自动更新
if ! disable_auto_update; then
# disable_auto_update 内部会记录警告,不视为致命错误
log_warn "尝试禁用自动更新时遇到问题(详见日志),但脚本将继续。"
fi
log_info "所有修改步骤已完成!"
log_info "请启动 Cursor 以应用更改。"
# 显示最后的提示信息
echo
echo -e "${GREEN}=====================================================${NC}"
echo -e "${YELLOW} 请关注公众号【煎饼果子卷AI】获取更多技巧和交流 ${NC}"
echo -e "${YELLOW}⚡ [小小广告] Cursor官网正规成品号Pro¥65 | Pro+¥265 | Ultra¥888 独享账号| ¥688 Team绝版次数号1000次+20刀额度 | 全部7天质保 | WeChatJavaRookie666 ${NC}"
echo -e "${GREEN}=====================================================${NC}"
echo
# 记录脚本完成信息
log_info "脚本执行完成"
echo "========== Cursor ID 修改工具日志结束 $(date) ==========" >> "$LOG_FILE"
# 显示日志文件位置
echo
log_info "详细日志已保存到: $LOG_FILE"
echo "如遇问题请将此日志文件提供给开发者以协助排查"
echo
}
# 执行主函数
main
exit 0 # 确保最后返回成功状态码