export JB_AUTH_CODE=default_auth #!/bin/bash #set -e # ============ 平台检测 ============= detect_platform() { case "$(uname -s)" in Darwin) OS="macOS" SHA_TOOL="shasum" OPEN_CMD="open" FILE_VMOPTIONS=".vmoptions" ;; Linux) OS="Linux" SHA_TOOL="sha1sum" OPEN_CMD="xdg-open" FILE_VMOPTIONS="64.vmoptions" ;; *) OS="Unknown" SHA_TOOL="sha1sum" OPEN_CMD="xdg-open" FILE_VMOPTIONS=".vmoptions" ;; esac } detect_platform # ============ 配置 ============= DEBUG=false ENABLE_COLOR=true # 服务器配置 URL_BASE="https://jb.rucode.cn" URL_DOWNLOAD="${URL_BASE}/ja-netfilter" URL_LICENSE="${URL_BASE}/auth" # 从环境变量获取授权码 AUTH_CODE="default_auth" # 用户路径处理 if [ "$(id -u)" -eq 0 ] && [ -n "$SUDO_USER" ]; then ORIGINAL_USER="$SUDO_USER" USER_HOME="/home/${SUDO_USER}" else ORIGINAL_USER="$(whoami)" USER_HOME="${HOME}" fi # macOS 用户路径修正 if [[ "$OS" == "macOS" ]]; then USER_HOME="/Users/${ORIGINAL_USER}" fi # 工作路径配置 dir_work="${USER_HOME}/.jb_ide" dir_config="${dir_work}/config" dir_plugins="${dir_work}/plugins" file_netfilter_jar="${dir_work}/ja-netfilter.jar" # JetBrains 目录配置 if [[ "$OS" == "macOS" ]]; then dir_cache_jb="${USER_HOME}/Library/Caches/JetBrains" dir_config_jb="${USER_HOME}/Library/Application Support/JetBrains" else dir_cache_jb="${USER_HOME}/.cache/JetBrains" dir_config_jb="${USER_HOME}/.config/JetBrains" fi # 日志颜色设置 if $ENABLE_COLOR; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' GRAY='\033[38;5;240m' NC='\033[0m' else RED='' GREEN='' YELLOW='' GRAY='' NC='' fi # 产品列表配置 PRODUCTS='[ {"name":"idea","productCode":"II,PCWMP,PSI"}, {"name":"clion","productCode":"CL,PSI,PCWMP"}, {"name":"phpstorm","productCode":"PS,PCWMP,PSI"}, {"name":"goland","productCode":"GO,PSI,PCWMP"}, {"name":"pycharm","productCode":"PC,PSI,PCWMP"}, {"name":"webstorm","productCode":"WS,PCWMP,PSI"}, {"name":"rider","productCode":"RD,PDB,PSI,PCWMP"}, {"name":"datagrip","productCode":"DB,PSI,PDB"}, {"name":"rubymine","productCode":"RM,PCWMP,PSI"}, {"name":"appcode","productCode":"AC,PCWMP,PSI"}, {"name":"dataspell","productCode":"DS,PSI,PDB,PCWMP"}, {"name":"dotmemory","productCode":"DM"}, {"name":"rustrover","productCode":"RR,PSI,PCWP"} ]' # ============ 工具函数 ============= log() { local level="$1" local message="$2" local color="" case "$level" in "INFO") color="$NC" ;; "DEBUG") [[ "$DEBUG" == true ]] || return; color="$GRAY" ;; "WARNING") color="$YELLOW" ;; "ERROR") color="$RED" ;; "SUCCESS") color="$GREEN" ;; *) color="$NC" ;; esac echo -e "${color}[$(date '+%Y-%m-%d %H:%M:%S')][$level] ${message}${NC}" } debug() { log "DEBUG" "$1"; } info() { log "INFO" "$1"; } warning() { log "WARNING" "$1"; } error() { log "ERROR" "$1"; } success() { log "SUCCESS" "$1"; } # 显示ASCII Logo show_ascii_jb() { cat <<'EOF' JJJJJJ EEEEEEE TTTTTTTT BBBBBBB RRRRRR AAAAAA IIIIIIII NNNN NN SSSSSS JJ EE TT BB BB RR RR AA AA II NNNNN NN SS JJ EE TT BB BB RR RR AA AA II NN NNN NN SS JJ EEEEE TT BBBBBBB RRRRRR AAAAAA II NN NNNNN SSSSS JJ EE TT BB BB RR RR AA AA II NN NNNN SS JJ JJ EE TT BB BB RR RR AA AA II NN NNN SS JJJJ EEEEEEE TT BBBBBBB RR RR AA AA IIIIIIII NN NNN SSSSSS EOF } # 环境变量清理函数 remove_env_other(){ OS_NAME=$(uname -s) JB_PRODUCTS="idea clion phpstorm goland pycharm webstorm webide rider datagrip rubymine appcode dataspell gateway jetbrains_client jetbrainsclient" KDE_ENV_DIR="${USER_HOME}/.config/plasma-workspace/env" PROFILE_PATH="${USER_HOME}/.profile" ZSH_PROFILE_PATH="${USER_HOME}/.zshrc" PLIST_PATH="${USER_HOME}/Library/LaunchAgents/jetbrains.vmoptions.plist" if [ $OS_NAME = "Darwin" ]; then BASH_PROFILE_PATH="${USER_HOME}/.bash_profile" else BASH_PROFILE_PATH="${USER_HOME}/.bashrc" fi touch "${PROFILE_PATH}" touch "${BASH_PROFILE_PATH}" touch "${ZSH_PROFILE_PATH}" MY_VMOPTIONS_SHELL_NAME="jetbrains.vmoptions.sh" MY_VMOPTIONS_SHELL_FILE="${USER_HOME}/.${MY_VMOPTIONS_SHELL_NAME}" rm -rf "${MY_VMOPTIONS_SHELL_FILE}" if [ $OS_NAME = "Darwin" ]; then for PRD in $JB_PRODUCTS; do ENV_NAME=$(echo $PRD | tr '[a-z]' '[A-Z]')"_VM_OPTIONS" launchctl unsetenv "${ENV_NAME}" done rm -rf "${PLIST_PATH}" sed -i '' '/___MY_VMOPTIONS_SHELL_FILE="${HOME}\/\.jetbrains\.vmoptions\.sh"; if /d' "${PROFILE_PATH}" >/dev/null 2>&1 sed -i '' '/___MY_VMOPTIONS_SHELL_FILE="${HOME}\/\.jetbrains\.vmoptions\.sh"; if /d' "${BASH_PROFILE_PATH}" >/dev/null 2>&1 sed -i '' '/___MY_VMOPTIONS_SHELL_FILE="${HOME}\/\.jetbrains\.vmoptions\.sh"; if /d' "${ZSH_PROFILE_PATH}" >/dev/null 2>&1 else sed -i '/___MY_VMOPTIONS_SHELL_FILE="${HOME}\/\.jetbrains\.vmoptions\.sh"; if /d' "${PROFILE_PATH}" >/dev/null 2>&1 sed -i '/___MY_VMOPTIONS_SHELL_FILE="${HOME}\/\.jetbrains\.vmoptions\.sh"; if /d' "${BASH_PROFILE_PATH}" >/dev/null 2>&1 sed -i '/___MY_VMOPTIONS_SHELL_FILE="${HOME}\/\.jetbrains\.vmoptions\.sh"; if /d' "${ZSH_PROFILE_PATH}" >/dev/null 2>&1 rm -rf "${KDE_ENV_DIR}/${MY_VMOPTIONS_SHELL_NAME}" fi debug "清理三方工具环境变量完成" } remove_env_item_vars() { local env_key="$1" local shell_files=("${USER_HOME}/.bash_profile" "${USER_HOME}/.bashrc" "${USER_HOME}/.zshrc" "${USER_HOME}/.profile") local existing_files=() for file in "${shell_files[@]}"; do [ -f "$file" ] && existing_files+=("$file"); done [ ${#existing_files[@]} -eq 0 ] && { debug "未找到任何环境变量文件,跳过"; return; } for file in "${existing_files[@]}"; do if [ ! -w "$file" ]; then warning "文件 $file 不可写,跳过修改"; continue; fi debug "正在处理文件: $file,$env_key" local temp_file=$(mktemp "${file}.XXXXXX") grep -v -E "^(export[[:space:]]+)?${env_key}=" "$file" > "$temp_file" if ! cmp --silent "$file" "$temp_file"; then mv "$temp_file" "$file" && { info "已从 $file 中删除环境变量 [$env_key]"; } else rm -f "$temp_file" fi source "$file" done } remove_env_vars() { local index=0 local product_count=$(echo "$PRODUCTS" | jq length) info "开始清理 JetBrains 相关环境变量" while [ $index -lt $product_count ]; do IFS='|' read -r name code <<< "$(parse_product_from_json "$index")" [ -z "$name" ] && break local upper_key="$(echo "${name}" | tr '[:lower:]' '[:upper:]').VMOPTIONS" local lower_key="$(echo "${name}" | tr '[:upper:]' '[:lower:]').vmoptions" local upper_key2="$(echo "${name}" | tr '[:lower:]' '[:upper:]')_VM_OPTIONS" remove_env_item_vars $upper_key remove_env_item_vars $lower_key remove_env_item_vars $upper_key2 ((index++)) done remove_env_other } # 依赖检查与安装 check_and_install_deps() { local deps=("curl" "jq") local missing=() for dep in "${deps[@]}"; do if ! command -v "$dep" &>/dev/null; then missing+=("$dep"); fi done [ ${#missing[@]} -eq 0 ] && { info "所有依赖已安装。"; return; } warning "缺少以下依赖:${missing[*]},正在尝试自动安装..." case "$(uname -s)" in Darwin) if ! command -v brew &>/dev/null; then warning "检测到 macOS 但未安装 Homebrew" read -p "是否要自动安装 Homebrew?(y/n) " install_brew if [[ "$install_brew" =~ [yY] ]]; then info "正在安装 Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" if [ -x "/opt/homebrew/bin/brew" ]; then echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc source ~/.zshrc elif [ -x "/usr/local/bin/brew" ]; then echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zshrc source ~/.zshrc fi if ! command -v brew &>/dev/null; then error "Homebrew 安装后仍不可用,请手动重启终端后重试" exit 1 fi else error "必须安装 Homebrew 才能继续!" exit 1 fi fi brew install "${missing[@]}" ;; Linux) if command -v apt-get &>/dev/null; then sudo apt update && sudo apt install -y "${missing[@]}" elif command -v dnf &>/dev/null; then sudo dnf install -y "${missing[@]}" elif command -v yum &>/dev/null; then sudo yum install -y "${missing[@]}" elif command -v pacman &>/dev/null; then sudo pacman -Sy --noconfirm "${missing[@]}" else error "无法识别的 Linux 发行版,请手动安装依赖:${missing[*]}" exit 1 fi ;; *) error "不支持的操作系统" exit 1 ;; esac for dep in "${missing[@]}"; do if ! command -v "$dep" &>/dev/null; then error "安装失败:$dep" exit 1 fi done success "所有依赖已成功安装!" } # 产品解析工具 parse_product_from_json() { local index="$1" local name=$(echo "$PRODUCTS" | jq -r ".[$index].name") local code=$(echo "$PRODUCTS" | jq -r ".[$index].productCode") echo "$name|$code" } # ============ 核心授权处理 ============= get_auth_code() { if [ -n "$AUTH_CODE" ]; then debug "从环境变量获取授权码" return 0 fi error "授权码不正确或已过期" error "请使用正确的授权码重试" exit 1 } # ============ 工作目录创建 ============= do_create_work_dir() { if [[ "${dir_work}" == "/" || -z "${dir_work}" ]]; then error "检测到非法路径: ${dir_work},请检查配置。" exit 1 fi if [ -d "${dir_work}" ]; then rm -rf "${dir_work}" || { error "文件被占用,请先关闭所有Jetbrains IDE后再试!" exit 1 } fi mkdir -p "${dir_config}" "${dir_plugins}" || { error "创建工作目录失败: ${dir_config} 或 ${dir_plugins}" exit 1 } debug "创建工作目录: ${dir_work}" } # ============ 文件下载 ============= download_one_file() { local url="$1" local file_save_path="$2" debug "\r正在下载: ${url} -> ${file_save_path}" curl -s -o "${file_save_path}" "${url}" if [ $? -ne 0 ]; then error "\r下载失败: ${url}" exit 1 fi if [[ "$file_save_path" == *.jar ]]; then local sha1_hash if command -v $SHA_TOOL &>/dev/null; then sha1_hash=$($SHA_TOOL "$file_save_path" | awk '{print $1}') else warning "未找到 $SHA_TOOL 工具,跳过 SHA-1 校验" return fi debug "sha1: $sha1_hash" fi } progress_bar() { local current=$1 local total=$2 local bar_length=30 local percent=$((current * 100 / total)) local filled=$((percent * bar_length / 100)) local bar="[" bar+=$(printf '#%.0s' $(seq 1 $filled)) bar+=$(printf '.%.0s' $(seq 1 $((bar_length - filled)))) bar+="]" printf "\r配置 ja-netfilter... %d/%d %s %d%%" "$current" "$total" "$bar" "$percent" } do_download_resources() { local resources=( "${URL_DOWNLOAD}/ja-netfilter.jar|${file_netfilter_jar}" "${URL_DOWNLOAD}/config/dns.conf|${dir_config}/dns.conf" "${URL_DOWNLOAD}/config/native.conf|${dir_config}/native.conf" "${URL_DOWNLOAD}/config/power.conf|${dir_config}/power.conf" "${URL_DOWNLOAD}/config/url.conf|${dir_config}/url.conf" "${URL_DOWNLOAD}/plugins/dns.jar|${dir_plugins}/dns.jar" "${URL_DOWNLOAD}/plugins/native.jar|${dir_plugins}/native.jar" "${URL_DOWNLOAD}/plugins/power.jar|${dir_plugins}/power.jar" "${URL_DOWNLOAD}/plugins/url.jar|${dir_plugins}/url.jar" "${URL_DOWNLOAD}/plugins/hideme.jar|${dir_plugins}/hideme.jar" "${URL_DOWNLOAD}/plugins/privacy.jar|${dir_plugins}/privacy.jar" ) local total_files=${#resources[@]} local count=0 debug "源ja-netfilter项目地址: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2022.2.0" debug "如需检查下载的.jar是否被篡改请核对sha1的值是否与源项目文件一致" for item in "${resources[@]}"; do IFS='|' read -r url path <<< "$item" download_one_file "$url" "$path" ((count++)) progress_bar "$count" "$total_files" done echo -e "\n" } # ============ 清理并更新 .vmoptions 文件 ============= clean_vmoptions() { local file="$1" if [ ! -f "$file" ]; then debug "清理vm: 文件不存在,跳过清理: $file" return 0 fi local temp_file=$(mktemp) while IFS= read -r line; do line_trim=$(echo "$line" | xargs) if [[ -z "$line_trim" ]]; then echo "$line" >> "$temp_file" continue fi skip=0 [[ "$line" == "-javaagent:"* ]] && skip=1 [[ "$line" == "--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED"* ]] && skip=1 [[ "$line" == "--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED"* ]] && skip=1 if [[ $skip -eq 0 ]]; then echo "$line" >> "$temp_file" fi done < "$file" cat "$temp_file" > "$file" rm -f "$temp_file" debug "安全清理vm(保留用户所有配置): $file" } # ============ 写入配置(自动去重,安全写入) ============= append_vmoptions() { local file="$1" if [ ! -f "$file" ]; then touch "$file" || { error "生成vm: 创建失败: $file" return } fi # 先清理再追加(保证唯一、不重复、不冲突) clean_vmoptions "$file" cat >> "$file" </dev/null 2>&1 fi info "所有项处理结束" sleep 1 } main "$@" # 删除自己 rm -f "${BASH_SOURCE[0]}"