Skip to content

Instantly share code, notes, and snippets.

@masroore
Created July 18, 2026 07:35
Show Gist options
  • Select an option

  • Save masroore/6087e25764b8e2b4da7f1efa92abae52 to your computer and use it in GitHub Desktop.

Select an option

Save masroore/6087e25764b8e2b4da7f1efa92abae52 to your computer and use it in GitHub Desktop.
ckey.run - jetbrains
#!/bin/bash
#set -e
# ============ 平台检测 =============
detect_platform() {
case "$(uname -s)" in
Darwin)
OS="macOS"
SHA_TOOL="shasum"
OPEN_CMD="open"
DATE_PARSER="mac"
FILE_VMOPTIONS=".vmoptions"
;;
Linux)
OS="Linux"
SHA_TOOL="sha1sum"
OPEN_CMD="xdg-open"
DATE_PARSER="linux"
FILE_VMOPTIONS="64.vmoptions"
;;
*)
OS="Unknown"
SHA_TOOL="sha1sum"
OPEN_CMD="xdg-open"
DATE_PARSER="linux"
FILE_VMOPTIONS=".vmoptions"
;;
esac
}
# 自动检测平台
detect_platform
# ============ 语言检测 =============
detect_language() {
local lang=${LANG:-}
if [[ $lang == zh* ]]; then
LANGUAGE="zh"
else
LANGUAGE="en"
fi
}
detect_language
# ============ 国际化支持 =============
# 初始化国际化字符串
init_i18n_strings() {
# processing_env_vars
processing_env_vars_zh="处理 {0} 环境变量"
processing_env_vars_en="Processing {0} environment variables"
# checking_env
checking_env_zh="检查 [{0}]: {1} = '{2}'"
checking_env_en="Checking [{0}]: {1} = '{2}'"
# deleting_env
deleting_env_zh="删除 [{0}]: {1}"
deleting_env_en="Deleting [{0}]: {1}"
# welcome_msg
welcome_msg_zh="欢迎使用 JetBrains 激活工具 | CodeKey Run"
welcome_msg_en="Welcome to JetBrains Activation Tool | CodeKey Run"
# script_date
script_date_zh="脚本日期:2026-1-28"
script_date_en="Script Date: 2026-1-28"
# warning_msg
warning_msg_zh="注意,执行脚本默认会将所有产品全部激活一遍,无论之前是否激活过!!!"
warning_msg_en="Warning: This script will forcibly re-activate all products!!!"
# close_products
close_products_zh="请确保激活的软件处于关闭状态,请按回车继续..."
close_products_en="Please make sure all JetBrains software is closed, press Enter to continue..."
# processing
processing_zh="处理中,请耐心等待..."
processing_en="Processing, please wait patiently..."
# processing_configs
processing_configs_zh="开始处理配置..."
processing_configs_en="Starting to process configurations..."
# not_found_dir
not_found_dir_zh="未找到 {0} 目录!"
not_found_dir_en="Directory not found: {0}!"
# processing_product
processing_product_zh="处理: {0}"
processing_product_en="Processing: {0}"
# not_found_home
not_found_home_zh="未找到 .home 文件: {0}"
not_found_home_en=".home file not found: {0}"
# path_not_exist
path_not_exist_zh="路径不存在: {0}"
path_not_exist_en="Path does not exist: {0}"
# not_found_bin
not_found_bin_zh="未找到 bin 目录: {0}"
not_found_bin_en="bin directory not found: {0}"
# config_exists_cleaning
config_exists_cleaning_zh="{0} 配置文件已存在,正在清理..."
config_exists_cleaning_en="{0} configuration file already exists, cleaning..."
# key_exists_cleaning
key_exists_cleaning_zh="key已存在,正在清理..."
key_exists_cleaning_en="Key already exists, cleaning..."
# activation_success
activation_success_zh="{0} 激活成功!"
activation_success_en="{0} activated successfully!"
# manual_activation_required
manual_activation_required_zh="{0} 需要手动输入激活码!"
manual_activation_required_en="{0} requires manual activation code entry!"
# download_failed
download_failed_zh="下载失败: {0}"
download_failed_en="Download failed: {0}"
# request_failed
request_failed_zh="{0} 请求失败: {1}"
request_failed_en="{0} request failed: {1}"
# file_in_use
file_in_use_zh="文件被占用,请先关闭所有 JetBrains IDE 后再试!"
file_in_use_en="File is in use, please close all JetBrains IDEs and try again!"
# processing_completed
processing_completed_zh="所有项处理结束,如需要激活码,请前往网站获取!"
processing_completed_en="All items processed. If you need an activation code, please visit the website!"
# format_error
format_error_zh="格式错误:请使用 yyyy-MM-dd 格式"
format_error_en="Format error: Please use yyyy-MM-dd format"
# invalid_date
invalid_date_zh="非法日期:{0}"
invalid_date_en="Invalid date: {0}"
# reading_config
reading_config_zh="读取配置文件:{0}, 寻找键:{1}"
reading_config_en="Reading config file: {0}, looking for key: {1}"
# found_key
found_key_zh="找到键 '{0}',值为 '{1}'"
found_key_en="Found key '{0}' with value '{1}'"
# failed_read_config
failed_read_config_zh="读取配置文件失败:{0}"
failed_read_config_en="Failed to read config file: {0}"
# cleaning_vmoptions
cleaning_vmoptions_zh="清理 VMOptions: {0}"
cleaning_vmoptions_en="Cleaning VMOptions: {0}"
# updating_vmoptions
updating_vmoptions_zh="更新 VMOptions: {0}"
updating_vmoptions_en="Updating VMOptions: {0}"
# processing_disabled_plugins
processing_disabled_plugins_zh="已处理文件: {0} 中com.intellij.modules.ultimate项"
processing_disabled_plugins_en="Processed file: {0} com.intellij.modules.ultimate item"
# file_not_exist_skip
file_not_exist_skip_zh="文件不存在,跳过: {0}"
file_not_exist_skip_en="File does not exist, skipping: {0}"
# error_processing_plugins
error_processing_plugins_zh="处理禁用插件文件时出错: {0}"
error_processing_plugins_en="Error processing disabled plugins file: {0}"
# processing_config
processing_config_zh="处理配置: {0}, {1}, {2}"
processing_config_en="Processing config: {0}, {1}, {2}"
# requesting_key
requesting_key_zh="请求key: {0},请求body: {1},保存地址: {2}"
requesting_key_en="Requesting key: {0}, request body: {1}, save path: {2}"
# writing_key
writing_key_zh="写入key,激活中: {0}"
writing_key_en="Writing key, activating: {0}"
# source_address
source_address_zh="源ja-netfilter项目地址: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"
source_address_en="Source ja-netfilter address: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"
# source_privacy
source_privacy_zh="源 privacy.jar 地址: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"
source_privacy_en="Source privacy.jar address: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"
# suggest_check_sha1
suggest_check_sha1_zh="如需检查下载的.jar是否被篡改请核对sha1的值是否与源项目文件一致"
suggest_check_sha1_en="It is recommended to verify the SHA1 value to ensure integrity"
# configuring_ja_netfilter
configuring_ja_netfilter_zh="配置 ja-netfilter..."
configuring_ja_netfilter_en="Configuring ja-netfilter..."
# custom_license_name
custom_license_name_zh="自定义授权名称 (回车默认 ckey.run)"
custom_license_name_en="Custom license name (Press Enter to use the default ckey.run)"
# custom_expiry_date
custom_expiry_date_zh="自定义授权日期 (回车默认 2099-12-31, 格式 yyyy-MM-dd)"
custom_expiry_date_en="Custom expiration date (Press Enter to use the default 2099-12-31, format yyyy-MM-dd)"
# default_license_name
default_license_name="ckey.run"
# default_expiry_date
default_expiry_date="2099-12-31"
# file_path_empty
file_path_empty_zh="文件路径为空,跳过处理"
file_path_empty_en="File path is empty, skipping processing"
# found_home_file
found_home_file_zh="找到.home文件: {0}"
found_home_file_en="Found .home file: {0}"
# read_home_content
read_home_content_zh="读取.home文件内容: {0}"
read_home_content_en="Read .home file content: {0}"
# found_bin_dir
found_bin_dir_zh="找到bin目录: {0}"
found_bin_dir_en="Found bin directory: {0}"
# sha1_info
sha1_info="{0} :sha1: {1}"
# request_fail
request_fail_zh="请求失败: {0}"
request_fail_en="Request failed: {0}"
# all_deps_installed
all_deps_installed_zh="所有依赖已安装。"
all_deps_installed_en="All dependencies are installed."
# missing_deps
missing_deps_zh="缺少以下依赖:{0},正在尝试自动安装..."
missing_deps_en="Missing dependencies: {0}, trying to install automatically..."
# homebrew_not_found
homebrew_not_found_zh="检测到 macOS但未安装 Homebrew"
homebrew_not_found_en="Detected macOS but Homebrew is not installed"
# install_homebrew_prompt
install_homebrew_prompt_zh="是否要自动安装 Homebrew?(y/n) "
install_homebrew_prompt_en="Do you want to install Homebrew automatically? (y/n) "
# installing_homebrew
installing_homebrew_zh="正在安装 Homebrew..."
installing_homebrew_en="Installing Homebrew..."
# homebrew_post_install
homebrew_post_install_zh="Homebrew 安装后仍不可用,请手动重启终端后重试"
homebrew_post_install_en="Homebrew is still not available after installation, please restart the terminal and try again"
# homebrew_required
homebrew_required_zh="必须安装 Homebrew 才能继续!"
homebrew_required_en="Homebrew must be installed to continue!"
# installing_deps_with_brew
installing_deps_with_brew_zh="使用 Homebrew 安装依赖"
installing_deps_with_brew_en="Installing dependencies with Homebrew"
# unrecognized_linux_distro
unrecognized_linux_distro_zh="无法识别的 Linux 发行版,请手动安装依赖:{0}"
unrecognized_linux_distro_en="Unrecognized Linux distribution, please manually install dependencies: {0}"
# unsupported_os
unsupported_os_zh="不支持的操作系统"
unsupported_os_en="Unsupported operating system"
# install_failed
install_failed_zh="安装失败:{0}"
install_failed_en="Installation failed: {0}"
# all_deps_installed_success
all_deps_installed_success_zh="所有依赖已成功安装!"
all_deps_installed_success_en="All dependencies have been successfully installed!"
# env_cleanup_start
env_cleanup_start_zh="开始清理 JetBrains 相关环境变量"
env_cleanup_start_en="Start cleaning JetBrains related environment variables"
# no_env_files_found
no_env_files_found_zh="未找到任何环境变量文件,跳过"
no_env_files_found_en="No environment variable files found, skipping"
# file_not_writable
file_not_writable_zh="文件 {0} 不可写,跳过修改"
file_not_writable_en="File {0} is not writable, skipping modification"
# backup_env_file
backup_env_file_zh="备份环境变量文件: {0}, {1}, {2}"
backup_env_file_en="Backup environment variable file: {0}, {1}, {2}"
# removing_env_var
removing_env_var_zh="删除环境变量: {0},{1}"
removing_env_var_en="Removing environment variable: {0},{1}"
# other_tools_env_cleanup
other_tools_env_cleanup_zh="清理三方工具环境变量完成"
other_tools_env_cleanup_en="Cleanup of third-party tool environment variables completed"
# enter_license_name
enter_license_name_zh="自定义授权名称 (回车默认 ckey.run): "
enter_license_name_en="Custom license name (Enter for default ckey.run): "
# enter_expiry_date
enter_expiry_date_zh="自定义授权日期 (回车默认 {0}, 格式 yyyy-MM-dd): "
enter_expiry_date_en="Custom expiration date (Enter for default {0}, format yyyy-MM-dd): "
# entered_expiry_date
entered_expiry_date_zh="输入的授权日期: {0}"
entered_expiry_date_en="Entered expiration date: {0}"
# invalid_date_format
invalid_date_format_zh="日期格式不合法,请输入正确的 yyyy-MM-dd 格式(例如:2099-12-31)"
invalid_date_format_en="Invalid date format, please enter the correct yyyy-MM-dd format (e.g. 2099-12-31)"
# illegal_path_detected
illegal_path_detected_zh="检测到非法路径: {0},请检查配置。"
illegal_path_detected_en="Illegal path detected: {0}, please check configuration."
# creating_work_dirs
creating_work_dirs_zh="创建工作目录: {0}"
creating_work_dirs_en="Creating work directories: {0}"
# download_progress
download_progress_zh="正在下载: {0} -> {1}"
download_progress_en="Downloading: {0} -> {1}"
# sha1_verification_skipped
sha1_verification_skipped_zh="未找到 {0} 工具,跳过 SHA-1 校验"
sha1_verification_skipped_en="{0} tool not found, skipping SHA-1 verification"
# cleaning_vm_file
cleaning_vm_file_zh="清理vm: {0}"
cleaning_vm_file_en="Cleaning vm: {0}"
# cleaning_vm_file_not_exist
cleaning_vm_file_not_exist_zh="清理vm: 文件不存在,跳过清理: {0}"
cleaning_vm_file_not_exist_en="Cleaning vm: File does not exist, skipping cleanup: {0}"
# generating_vm_file
generating_vm_file_zh="生成vm: {0}"
generating_vm_file_en="Generating vm: {0}"
# generating_vm_file_failed
generating_vm_file_failed_zh="生成vm: 创建失败: {0}"
generating_vm_file_failed_en="Generating vm: Failed to create: {0}"
# processing_disabled_plugins_file
processing_disabled_plugins_file_zh="已处理文件 {0} 中com.intellij.modules.ultimate 项"
processing_disabled_plugins_file_en="Processed com.intellij.modules.ultimate item in file {0}"
# url_license_params
url_license_params="URL_LICENSE:{0},params:{1},save_path:{2}"
# home_path
home_path_zh=".home路径: {0}"
home_path_en=".home path: {0}"
# home_content
home_content_zh=".home内容: {0}"
home_content_en=".home content: {0}"
# bin_dir_not_exist
bin_dir_not_exist_zh="{0} 的 bin 目录不存在,请确认是否正确安装!"
bin_dir_not_exist_en="The bin directory of {0} does not exist, please confirm if it is installed correctly!"
# vmoptions_not_found_creating
vmoptions_not_found_creating_zh="未找到{0} 的.vmoptions文件,将创建一个默认的"
vmoptions_not_found_creating_en=".vmoptions file for {0} not found, will create a default one"
# config_dir
config_dir_zh="config目录:{0}"
config_dir_en="config directory: {0}"
# dir_not_found
dir_not_found_zh="未找到{0}目录"
dir_not_found_en="Directory {0} not found"
}
# 获取国际化字符串
get_i18n_string() {
local key="$1"
local var_name="${key}_${LANGUAGE}"
# 使用间接引用获取变量值
if [[ -n "${!var_name}" ]]; then
echo "${!var_name}"
else
# 如果找不到对应语言的字符串,返回键名本身
echo "${!key}"
fi
}
# 格式化字符串(最多支持3个参数)
format_string() {
local template="$1"
local arg1="$2"
local arg2="$3"
local arg3="$4"
# 替换 {0}, {1}, {2} 占位符
local result="$template"
result="${result//\{0\}/$arg1}"
result="${result//\{1\}/$arg2}"
result="${result//\{2\}/$arg3}"
echo "$result"
}
# 初始化国际化字符串字典
init_i18n_strings
# ============ 配置 =============
DEBUG=false
ENABLE_COLOR=true
URL_BASE="https://ckey.run"
#URL_BASE="http://192.168.31.254:10768"
URL_DOWNLOAD="${URL_BASE}/ja-netfilter"
URL_LICENSE="${URL_BASE}/generateLicense/file"
# 获取原始用户和家目录
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_run"
dir_config="${dir_work}/config"
dir_plugins="${dir_work}/plugins"
dir_backups="${dir_work}/backups"
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":"rustrover","productCode":"RR,PSI,PCWP"}
]'
# ============ 工具函数 =============
# ============ 日期验证 =============
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
if [ ${#missing[@]} -eq 0 ]; then
info "all_deps_installed"
return
fi
local missing_str=$(IFS=", "; echo "${missing[*]}")
warning "missing_deps" "$missing_str"
# 先检测系统类型
case "$(uname -s)" in
Darwin)
# 检测 Homebrew 是否存在
if ! command -v brew &>/dev/null; then
warning "homebrew_not_found"
local prompt_msg=$(get_i18n_string "install_homebrew_prompt")
read -p "$prompt_msg" install_brew
if [[ "$install_brew" =~ [yY] ]]; then
info "installing_homebrew"
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 自动配置环境变量
if [ -x "/opt/homebrew/bin/brew" ]; then # Apple Silicon
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc
source ~/.zshrc
elif [ -x "/usr/local/bin/brew" ]; then # Intel
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zshrc
source ~/.zshrc
fi
# 再次验证
if ! command -v brew &>/dev/null; then
error "homebrew_post_install"
exit 1
fi
else
error "homebrew_required"
exit 1
fi
fi
# 安装依赖
info "installing_deps_with_brew"
brew install "${missing[@]}"
;;
Linux)
# 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 "unrecognized_linux_distro" "$missing_str"
exit 1
fi
;;
*)
error "unsupported_os"
exit 1
;;
esac
# 验证安装结果
for dep in "${missing[@]}"; do
if ! command -v "$dep" &>/dev/null; then
error "install_failed" "$dep"
exit 1
fi
done
success "all_deps_installed_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"
}
# ============ 日志函数 =============
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() {
local msg=$(get_i18n_string "$1")
if [[ $# -gt 1 ]]; then
msg=$(format_string "$msg" "$2" "$3" "$4")
fi
log "DEBUG" "$msg"
}
info() {
local msg=$(get_i18n_string "$1")
if [[ $# -gt 1 ]]; then
msg=$(format_string "$msg" "$2" "$3" "$4")
fi
log "INFO" "$msg"
}
warning() {
local msg=$(get_i18n_string "$1")
if [[ $# -gt 1 ]]; then
msg=$(format_string "$msg" "$2" "$3" "$4")
fi
log "WARNING" "$msg"
}
error() {
local msg=$(get_i18n_string "$1")
if [[ $# -gt 1 ]]; then
msg=$(format_string "$msg" "$2" "$3" "$4")
fi
log "ERROR" "$msg"
}
success() {
local msg=$(get_i18n_string "$1")
if [[ $# -gt 1 ]]; then
msg=$(format_string "$msg" "$2" "$3" "$4")
fi
log "SUCCESS" "$msg"
}
# ============ ASCII Art =============
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 "other_tools_env_cleanup"
}
remove_env_item_vars() {
local shell_files=(
"${USER_HOME}/.bash_profile"
"${USER_HOME}/.bashrc"
"${USER_HOME}/.zshrc"
"${USER_HOME}/.profile"
)
# 解析产品
local index=0
local product_count=$(echo "$PRODUCTS" | jq length)
# 先过滤出实际存在的文件
local existing_files=()
for file in "${shell_files[@]}"; do
[ -f "$file" ] && existing_files+=("$file")
done
# 如果没有存在的文件则直接返回
[ ${#existing_files[@]} -eq 0 ] && {
debug "no_env_files_found"
return
}
# 环境变量备份目录
local dir_date_backup="$dir_backups/$(date +%s)"
for file in "${existing_files[@]}"; do
# 判断文件中是否包含指定环境变量
if [ ! -w "$file" ]; then
warning "file_not_writable" "$file" >&2
continue
fi
# 备份环境变量文件到dir_backups/时间
if [ ! -d "$dir_date_backup" ]; then
mkdir -p "$dir_date_backup"
fi
cp "$file" "${dir_date_backup}/_$(basename ${file})"
debug "backup_env_file" "$file" "$dir_date_backup" "_$(basename ${file})"
# 检测环境变量配置文件
local index=0
while [ $index -lt $product_count ]; do
IFS='|' read -r name code <<< "$(parse_product_from_json "$index")"
if [ -z "$name" ]; then
break
fi
local upper_key="$(echo "${name}" | tr '[:lower:]' '[:upper:]')_VM_OPTIONS"
# 判断file里面是否包含upper_key
if grep -q "^${upper_key}" "$file"; then
sed -i -E "/${upper_key}/d" "$file"
debug "removing_env_var" "$file" "$upper_key"
fi
((index++))
done
source "$file"
done
}
remove_env_vars() {
info "env_cleanup_start"
remove_env_item_vars
# 删除其它激活工具残留
remove_env_other
}
# ============ 用户输入授权信息 =============
validate_date_format() {
local input="$1"
# 第一步:检查是否符合 yyyy-MM-dd 格式
if [[ ! "$input" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
warning "format_error"
return 1
fi
# 第二步:直接返回原值(无需调用 date 验证真实性)
echo "$input"
return 0
}
read_license_info() {
local enter_license_name_msg=$(get_i18n_string "enter_license_name")
read -p "$enter_license_name_msg" license_name
license_name=${license_name:-ckey.run}
local default_expiry="2099-12-31"
local expiry_input
local valid=false
while [ "$valid" == "false" ]; do
local enter_expiry_date_msg=$(get_i18n_string "enter_expiry_date")
read -p "$(format_string "$enter_expiry_date_msg" "$default_expiry") " expiry_input
expiry_input=${expiry_input:-$default_expiry}
debug "entered_expiry_date" "$expiry_input"
if expiry=$(validate_date_format "$expiry_input"); then
expiry="$expiry"
valid=true
else
warning "invalid_date_format"
fi
done
LICENSE_JSON=$(cat <<EOF
{
"assigneeName": "",
"expiryDate": "$expiry",
"licenseName": "$license_name",
"productCode": ""
}
EOF
)
}
# ============ 创建工作目录 =============
do_create_work_dir() {
if [[ "${dir_work}" == "/" || -z "${dir_work}" ]]; then
error "illegal_path_detected" "${dir_work}"
exit 1
fi
if [ -d "${dir_work}" ]; then
rm -rf "${dir_plugins}" "${dir_config}" "${file_netfilter_jar}" || {
error "file_in_use"
exit 1
}
fi
mkdir -p "${dir_config}" "${dir_plugins}" "${dir_backups}" || {
error "dir_not_found" "${dir_config}"
exit 1
}
debug "creating_work_dirs" "${dir_work}"
}
# ============ 下载文件 =============
download_one_file() {
local url="$1"
local file_save_path="$2"
debug "download_progress" "${url}" "${file_save_path}"
curl -s -o "${file_save_path}" "${url}"
if [ $? -ne 0 ]; then
error "download_failed" "${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 "sha1_verification_skipped" "$SHA_TOOL"
return
fi
debug "sha1_info" "$url" "$sha1_hash \n"
fi
}
progress_bar() {
# 如果处于DEBUG模式,则不显示进度条
if [ "$DEBUG" = true ]; then
return
fi
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+="]"
local configuring_msg=$(get_i18n_string "configuring_ja_netfilter")
printf "\r%s %d/%d %s %d%%" "$configuring_msg" "$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/env.conf|${dir_config}/env.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/env.jar|${dir_plugins}/env.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 "source_address"
debug "source_privacy"
debug "suggest_check_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 "cleaning_vm_file_not_exist" "$file"
return 0
fi
local temp_lines=()
local keywords=(
"-javaagent"
"--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED"
"--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED"
)
while IFS= read -r line; do
local matched=false
for keyword in "${keywords[@]}"; do
[[ "$line" == *"$keyword"* ]] && matched=true && break
done
[[ "$matched" == false ]] && temp_lines+=("$line")
done < "$file"
printf "%s\n" "${temp_lines[@]}" > "$file"
debug "cleaning_vm_file" "$file"
}
append_vmoptions() {
local file="$1"
if [ ! -f "$file" ]; then
touch "$file" || {
error "generating_vm_file_failed" "$file"
return
}
fi
cat >> "$file" <<EOF
-javaagent:${file_netfilter_jar}
EOF
debug "generating_vm_file" "$file"
}
# 读取文件,如果文件中有com.intellij.modules.ultimate,则清空这行
process_disabled_plugins() {
local file_disabled_plugins="$1"
# 参数验证
if [ -z "$file_disabled_plugins" ]; then
debug "file_path_empty"
return
fi
# 检查文件是否存在
if [ ! -f "$file_disabled_plugins" ]; then
debug "file_not_exist_skip" "$file_disabled_plugins"
return
fi
# 直接在原文件中将匹配的行替换为空(即删除该行)
# 兼容 macOS 和 Linux 上的 sed 命令
if [[ "$OS" == "macOS" ]]; then
sed -i '' '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
else
sed -i '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
fi
debug "processing_disabled_plugins_file" "$file_disabled_plugins"
}
# ============ 生成激活码 =============
generate_license() {
local obj_product_name="$1"
local obj_product_code="$2"
local dir_product_name="$3"
local file_license="${dir_config_jb}/${dir_product_name}/${obj_product_name}.key"
local file_disabled_plugins="${dir_config_jb}/${dir_product_name}/disabled_plugins.txt"
process_disabled_plugins "$file_disabled_plugins"
[ -f "$file_license" ] && rm -f "$file_license"
local json_body=$(jq --arg code "$obj_product_code" '.productCode = $code' <<< "$LICENSE_JSON")
debug "url_license_params" "$URL_LICENSE" "$json_body" "$file_license"
curl -s -X POST "$URL_LICENSE" \
-H "Content-Type: application/json" \
-d "$json_body" \
-o "$file_license" > /dev/null
if [ $? -eq 0 ]; then
success "activation_success" "$dir_product_name"
else
warning "manual_activation_required" "$dir_product_name"
fi
}
# ============ 处理单个 Jetbrains 产品 =============
handle_jetbrains_dir() {
local dir="$1"
local dir_product_name=$(basename "$dir")
local obj_product_name=""
local obj_product_code=""
for ((i = 0; i < $(echo "$PRODUCTS" | jq length); i++)); do
IFS='|' read -r name code <<< "$(parse_product_from_json "$i")"
local lowercase_dir=$(echo "${dir_product_name}" | tr '[:upper:]' '[:lower:]')
if [[ "$lowercase_dir" == *"$name"* ]]; then
obj_product_name="$name"
obj_product_code="$code"
break
fi
done
[ -z "$obj_product_name" ] && return
info "processing_product" "$dir_product_name"
local file_home="${dir}/.home"
[ -f "$file_home" ] || {
warning "not_found_home" "$file_home"
return
}
debug "home_path" "$file_home"
local install_path=$(cat "$file_home")
[ -d "$install_path" ] || {
warning "path_not_exist" "$install_path"
return
}
debug "home_content" "$install_path"
local dir_bin="${install_path}/bin"
[ -d "$dir_bin" ] || {
warning "bin_dir_not_exist" "$dir_product_name"
return
}
local dir_config_product="${dir_config_jb}/${dir_product_name}"
# 先查找所有 .vmoptions 文件
files=("${dir_config_product}"/*${FILE_VMOPTIONS})
# 判断是否真的找到了文件
if [[ -f "${files[0]}" ]]; then
for file_vmoption in "${files[@]}"; do
clean_vmoptions "$file_vmoption"
append_vmoptions "$file_vmoption"
done
else
debug "vmoptions_not_found_creating" "$dir_product_name"
append_vmoptions "${dir_config_product}/${obj_product_name}${FILE_VMOPTIONS}"
fi
# 判断${dir_config_product}/jetbrains_client.vmoptions是否存在,如果不存在则创建一个默认的
local file_jetbrains_client="${dir_config_product}/jetbrains_client.vmoptions"
if [ ! -f "${file_jetbrains_client}" ]; then
append_vmoptions "${file_jetbrains_client}"
else
clean_vmoptions "${file_jetbrains_client}"
append_vmoptions "${file_jetbrains_client}"
fi
generate_license "$obj_product_name" "$obj_product_code" "$dir_product_name"
}
# ============ 主流程 =============
main() {
clear
show_ascii_jb
info "welcome_msg"
warning "script_date"
error "warning_msg"
warning "close_products"
read -r
read_license_info
info "processing"
check_and_install_deps
if [ ! -d "${dir_config_jb}" ]; then
error "dir_not_found" "${dir_config_jb}"
exit 1
fi
debug "config_dir" "${dir_config_jb}"
do_create_work_dir
remove_env_vars
do_download_resources
for dir in "${dir_cache_jb}"/*; do
[ -d "$dir" ] && handle_jetbrains_dir "$dir"
done
info "processing_completed"
sleep 1
$OPEN_CMD "$URL_BASE" &>/dev/null
}
main "$@"
# 删除自己
rm -f "${BASH_SOURCE[0]}"
@masroore

Copy link
Copy Markdown
Author
#!/bin/bash
#set -e

# ============ 平台检测 =============
detect_platform() {
    case "$(uname -s)" in
        Darwin)
            OS="macOS"
            SHA_TOOL="shasum"
            OPEN_CMD="open"
            DATE_PARSER="mac"
            FILE_VMOPTIONS=".vmoptions"
            ;;
        Linux)
            OS="Linux"
            SHA_TOOL="sha1sum"
            OPEN_CMD="xdg-open"
            DATE_PARSER="linux"
            FILE_VMOPTIONS="64.vmoptions"
            ;;
        *)
            OS="Unknown"
            SHA_TOOL="sha1sum"
            OPEN_CMD="xdg-open"
            DATE_PARSER="linux"
            FILE_VMOPTIONS=".vmoptions"
            ;;
    esac
}
# 自动检测平台
detect_platform

# ============ 语言检测 =============
detect_language() {
    local lang=${LANG:-}
    if [[ $lang == zh* ]]; then
        LANGUAGE="zh"
    else
        LANGUAGE="en"
    fi
}
detect_language
# ============ 国际化支持 =============
# 初始化国际化字符串
init_i18n_strings() {
    # processing_env_vars
    processing_env_vars_zh="处理 {0} 环境变量"
    processing_env_vars_en="Processing {0} environment variables"

    # checking_env
    checking_env_zh="检查 [{0}]: {1} = '{2}'"
    checking_env_en="Checking [{0}]: {1} = '{2}'"

    # deleting_env
    deleting_env_zh="删除 [{0}]: {1}"
    deleting_env_en="Deleting [{0}]: {1}"

    # welcome_msg
    welcome_msg_zh="欢迎使用 JetBrains 激活工具 | CodeKey Run"
    welcome_msg_en="Welcome to JetBrains Activation Tool | CodeKey Run"

    # script_date
    script_date_zh="脚本日期:2026-1-28"
    script_date_en="Script Date: 2026-1-28"

    # warning_msg
    warning_msg_zh="注意,执行脚本默认会将所有产品全部激活一遍,无论之前是否激活过!!!"
    warning_msg_en="Warning: This script will forcibly re-activate all products!!!"

    # close_products
    close_products_zh="请确保激活的软件处于关闭状态,请按回车继续..."
    close_products_en="Please make sure all JetBrains software is closed, press Enter to continue..."

    # processing
    processing_zh="处理中,请耐心等待..."
    processing_en="Processing, please wait patiently..."

    # processing_configs
    processing_configs_zh="开始处理配置..."
    processing_configs_en="Starting to process configurations..."

    # not_found_dir
    not_found_dir_zh="未找到 {0} 目录!"
    not_found_dir_en="Directory not found: {0}!"

    # processing_product
    processing_product_zh="处理: {0}"
    processing_product_en="Processing: {0}"

    # not_found_home
    not_found_home_zh="未找到 .home 文件: {0}"
    not_found_home_en=".home file not found: {0}"

    # path_not_exist
    path_not_exist_zh="路径不存在: {0}"
    path_not_exist_en="Path does not exist: {0}"

    # not_found_bin
    not_found_bin_zh="未找到 bin 目录: {0}"
    not_found_bin_en="bin directory not found: {0}"

    # config_exists_cleaning
    config_exists_cleaning_zh="{0} 配置文件已存在,正在清理..."
    config_exists_cleaning_en="{0} configuration file already exists, cleaning..."

    # key_exists_cleaning
    key_exists_cleaning_zh="key已存在,正在清理..."
    key_exists_cleaning_en="Key already exists, cleaning..."

    # activation_success
    activation_success_zh="{0} 激活成功!"
    activation_success_en="{0} activated successfully!"

    # manual_activation_required
    manual_activation_required_zh="{0} 需要手动输入激活码!"
    manual_activation_required_en="{0} requires manual activation code entry!"

    # download_failed
    download_failed_zh="下载失败: {0}"
    download_failed_en="Download failed: {0}"

    # request_failed
    request_failed_zh="{0} 请求失败: {1}"
    request_failed_en="{0} request failed: {1}"

    # file_in_use
    file_in_use_zh="文件被占用,请先关闭所有 JetBrains IDE 后再试!"
    file_in_use_en="File is in use, please close all JetBrains IDEs and try again!"

    # processing_completed
    processing_completed_zh="所有项处理结束,如需要激活码,请前往网站获取!"
    processing_completed_en="All items processed. If you need an activation code, please visit the website!"

    # format_error
    format_error_zh="格式错误:请使用 yyyy-MM-dd 格式"
    format_error_en="Format error: Please use yyyy-MM-dd format"

    # invalid_date
    invalid_date_zh="非法日期:{0}"
    invalid_date_en="Invalid date: {0}"

    # reading_config
    reading_config_zh="读取配置文件:{0}, 寻找键:{1}"
    reading_config_en="Reading config file: {0}, looking for key: {1}"

    # found_key
    found_key_zh="找到键 '{0}',值为 '{1}'"
    found_key_en="Found key '{0}' with value '{1}'"

    # failed_read_config
    failed_read_config_zh="读取配置文件失败:{0}"
    failed_read_config_en="Failed to read config file: {0}"

    # cleaning_vmoptions
    cleaning_vmoptions_zh="清理 VMOptions: {0}"
    cleaning_vmoptions_en="Cleaning VMOptions: {0}"

    # updating_vmoptions
    updating_vmoptions_zh="更新 VMOptions: {0}"
    updating_vmoptions_en="Updating VMOptions: {0}"

    # processing_disabled_plugins
    processing_disabled_plugins_zh="已处理文件: {0} 中com.intellij.modules.ultimate项"
    processing_disabled_plugins_en="Processed file: {0} com.intellij.modules.ultimate item"

    # file_not_exist_skip
    file_not_exist_skip_zh="文件不存在,跳过: {0}"
    file_not_exist_skip_en="File does not exist, skipping: {0}"

    # error_processing_plugins
    error_processing_plugins_zh="处理禁用插件文件时出错: {0}"
    error_processing_plugins_en="Error processing disabled plugins file: {0}"

    # processing_config
    processing_config_zh="处理配置: {0}, {1}, {2}"
    processing_config_en="Processing config: {0}, {1}, {2}"

    # requesting_key
    requesting_key_zh="请求key: {0},请求body: {1},保存地址: {2}"
    requesting_key_en="Requesting key: {0}, request body: {1}, save path: {2}"

    # writing_key
    writing_key_zh="写入key,激活中: {0}"
    writing_key_en="Writing key, activating: {0}"

    # source_address
    source_address_zh="源ja-netfilter项目地址: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"
    source_address_en="Source ja-netfilter address: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"

    # source_privacy
    source_privacy_zh="源 privacy.jar 地址: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"
    source_privacy_en="Source privacy.jar address: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"

    # suggest_check_sha1
    suggest_check_sha1_zh="如需检查下载的.jar是否被篡改请核对sha1的值是否与源项目文件一致"
    suggest_check_sha1_en="It is recommended to verify the SHA1 value to ensure integrity"

    # configuring_ja_netfilter
    configuring_ja_netfilter_zh="配置 ja-netfilter..."
    configuring_ja_netfilter_en="Configuring ja-netfilter..."

    # custom_license_name
    custom_license_name_zh="自定义授权名称 (回车默认 ckey.run)"
    custom_license_name_en="Custom license name (Press Enter to use the default ckey.run)"

    # custom_expiry_date
    custom_expiry_date_zh="自定义授权日期 (回车默认 2099-12-31, 格式 yyyy-MM-dd)"
    custom_expiry_date_en="Custom expiration date (Press Enter to use the default 2099-12-31, format yyyy-MM-dd)"

    # default_license_name
    default_license_name="ckey.run"

    # default_expiry_date
    default_expiry_date="2099-12-31"

    # file_path_empty
    file_path_empty_zh="文件路径为空,跳过处理"
    file_path_empty_en="File path is empty, skipping processing"

    # found_home_file
    found_home_file_zh="找到.home文件: {0}"
    found_home_file_en="Found .home file: {0}"

    # read_home_content
    read_home_content_zh="读取.home文件内容: {0}"
    read_home_content_en="Read .home file content: {0}"

    # found_bin_dir
    found_bin_dir_zh="找到bin目录: {0}"
    found_bin_dir_en="Found bin directory: {0}"

    # sha1_info
    sha1_info="{0} :sha1: {1}"

    # request_fail
    request_fail_zh="请求失败: {0}"
    request_fail_en="Request failed: {0}"

    # all_deps_installed
    all_deps_installed_zh="所有依赖已安装。"
    all_deps_installed_en="All dependencies are installed."

    # missing_deps
    missing_deps_zh="缺少以下依赖:{0},正在尝试自动安装..."
    missing_deps_en="Missing dependencies: {0}, trying to install automatically..."

    # homebrew_not_found
    homebrew_not_found_zh="检测到 macOS但未安装 Homebrew"
    homebrew_not_found_en="Detected macOS but Homebrew is not installed"

    # install_homebrew_prompt
    install_homebrew_prompt_zh="是否要自动安装 Homebrew?(y/n) "
    install_homebrew_prompt_en="Do you want to install Homebrew automatically? (y/n) "

    # installing_homebrew
    installing_homebrew_zh="正在安装 Homebrew..."
    installing_homebrew_en="Installing Homebrew..."

    # homebrew_post_install
    homebrew_post_install_zh="Homebrew 安装后仍不可用,请手动重启终端后重试"
    homebrew_post_install_en="Homebrew is still not available after installation, please restart the terminal and try again"

    # homebrew_required
    homebrew_required_zh="必须安装 Homebrew 才能继续!"
    homebrew_required_en="Homebrew must be installed to continue!"

    # installing_deps_with_brew
    installing_deps_with_brew_zh="使用 Homebrew 安装依赖"
    installing_deps_with_brew_en="Installing dependencies with Homebrew"

    # unrecognized_linux_distro
    unrecognized_linux_distro_zh="无法识别的 Linux 发行版,请手动安装依赖:{0}"
    unrecognized_linux_distro_en="Unrecognized Linux distribution, please manually install dependencies: {0}"

    # unsupported_os
    unsupported_os_zh="不支持的操作系统"
    unsupported_os_en="Unsupported operating system"

    # install_failed
    install_failed_zh="安装失败:{0}"
    install_failed_en="Installation failed: {0}"

    # all_deps_installed_success
    all_deps_installed_success_zh="所有依赖已成功安装!"
    all_deps_installed_success_en="All dependencies have been successfully installed!"

    # env_cleanup_start
    env_cleanup_start_zh="开始清理 JetBrains 相关环境变量"
    env_cleanup_start_en="Start cleaning JetBrains related environment variables"

    # no_env_files_found
    no_env_files_found_zh="未找到任何环境变量文件,跳过"
    no_env_files_found_en="No environment variable files found, skipping"

    # file_not_writable
    file_not_writable_zh="文件 {0} 不可写,跳过修改"
    file_not_writable_en="File {0} is not writable, skipping modification"

    # backup_env_file
    backup_env_file_zh="备份环境变量文件: {0}, {1}, {2}"
    backup_env_file_en="Backup environment variable file: {0}, {1}, {2}"

    # removing_env_var
    removing_env_var_zh="删除环境变量: {0},{1}"
    removing_env_var_en="Removing environment variable: {0},{1}"

    # other_tools_env_cleanup
    other_tools_env_cleanup_zh="清理三方工具环境变量完成"
    other_tools_env_cleanup_en="Cleanup of third-party tool environment variables completed"

    # enter_license_name
    enter_license_name_zh="自定义授权名称 (回车默认 ckey.run): "
    enter_license_name_en="Custom license name (Enter for default ckey.run): "

    # enter_expiry_date
    enter_expiry_date_zh="自定义授权日期 (回车默认 {0}, 格式 yyyy-MM-dd): "
    enter_expiry_date_en="Custom expiration date (Enter for default {0}, format yyyy-MM-dd): "

    # entered_expiry_date
    entered_expiry_date_zh="输入的授权日期: {0}"
    entered_expiry_date_en="Entered expiration date: {0}"

    # invalid_date_format
    invalid_date_format_zh="日期格式不合法,请输入正确的 yyyy-MM-dd 格式(例如:2099-12-31)"
    invalid_date_format_en="Invalid date format, please enter the correct yyyy-MM-dd format (e.g. 2099-12-31)"

    # illegal_path_detected
    illegal_path_detected_zh="检测到非法路径: {0},请检查配置。"
    illegal_path_detected_en="Illegal path detected: {0}, please check configuration."

    # creating_work_dirs
    creating_work_dirs_zh="创建工作目录: {0}"
    creating_work_dirs_en="Creating work directories: {0}"

    # download_progress
    download_progress_zh="正在下载: {0} -> {1}"
    download_progress_en="Downloading: {0} -> {1}"

    # sha1_verification_skipped
    sha1_verification_skipped_zh="未找到 {0} 工具,跳过 SHA-1 校验"
    sha1_verification_skipped_en="{0} tool not found, skipping SHA-1 verification"

    # cleaning_vm_file
    cleaning_vm_file_zh="清理vm: {0}"
    cleaning_vm_file_en="Cleaning vm: {0}"

    # cleaning_vm_file_not_exist
    cleaning_vm_file_not_exist_zh="清理vm: 文件不存在,跳过清理: {0}"
    cleaning_vm_file_not_exist_en="Cleaning vm: File does not exist, skipping cleanup: {0}"

    # generating_vm_file
    generating_vm_file_zh="生成vm: {0}"
    generating_vm_file_en="Generating vm: {0}"

    # generating_vm_file_failed
    generating_vm_file_failed_zh="生成vm: 创建失败: {0}"
    generating_vm_file_failed_en="Generating vm: Failed to create: {0}"

    # processing_disabled_plugins_file
    processing_disabled_plugins_file_zh="已处理文件 {0} 中com.intellij.modules.ultimate 项"
    processing_disabled_plugins_file_en="Processed com.intellij.modules.ultimate item in file {0}"

    # url_license_params
    url_license_params="URL_LICENSE:{0},params:{1},save_path:{2}"

    # home_path
    home_path_zh=".home路径: {0}"
    home_path_en=".home path: {0}"

    # home_content
    home_content_zh=".home内容: {0}"
    home_content_en=".home content: {0}"

    # bin_dir_not_exist
    bin_dir_not_exist_zh="{0} 的 bin 目录不存在,请确认是否正确安装!"
    bin_dir_not_exist_en="The bin directory of {0} does not exist, please confirm if it is installed correctly!"

    # vmoptions_not_found_creating
    vmoptions_not_found_creating_zh="未找到{0} 的.vmoptions文件,将创建一个默认的"
    vmoptions_not_found_creating_en=".vmoptions file for {0} not found, will create a default one"

    # config_dir
    config_dir_zh="config目录:{0}"
    config_dir_en="config directory: {0}"

    # dir_not_found
    dir_not_found_zh="未找到{0}目录"
    dir_not_found_en="Directory {0} not found"
}

# 获取国际化字符串
get_i18n_string() {
    local key="$1"
    local var_name="${key}_${LANGUAGE}"

    # 使用间接引用获取变量值
    if [[ -n "${!var_name}" ]]; then
        echo "${!var_name}"
    else
        # 如果找不到对应语言的字符串,返回键名本身
        echo "${!key}"
    fi
}

# 格式化字符串(最多支持3个参数)
format_string() {
    local template="$1"
    local arg1="$2"
    local arg2="$3"
    local arg3="$4"

    # 替换 {0}, {1}, {2} 占位符
    local result="$template"
    result="${result//\{0\}/$arg1}"
    result="${result//\{1\}/$arg2}"
    result="${result//\{2\}/$arg3}"

    echo "$result"
}

# 初始化国际化字符串字典
init_i18n_strings

# ============ 配置 =============
DEBUG=false
ENABLE_COLOR=true

URL_BASE="https://ckey.run"
#URL_BASE="http://192.168.31.254:10768"
URL_DOWNLOAD="${URL_BASE}/ja-netfilter"
URL_LICENSE="${URL_BASE}/generateLicense/file"

# 获取原始用户和家目录
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_run"
dir_config="${dir_work}/config"
dir_plugins="${dir_work}/plugins"
dir_backups="${dir_work}/backups"
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":"rustrover","productCode":"RR,PSI,PCWMP"}
]'

# 预解析产品到并行数组(避免循环内重复调用 jq)
PRODUCT_NAMES=()
PRODUCT_CODES=()
while IFS='|' read -r _name _code; do
    [[ -n "$_name" ]] && PRODUCT_NAMES+=("$_name") && PRODUCT_CODES+=("$_code")
done < <(echo "$PRODUCTS" | jq -r '.[] | "\(.name)|\(.productCode)"')
PRODUCT_COUNT=${#PRODUCT_NAMES[@]}

# ============ 工具函数 =============

# ============ 日期验证 =============
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

    if [ ${#missing[@]} -eq 0 ]; then
        info "all_deps_installed"
        return
    fi

    local missing_str=$(IFS=", "; echo "${missing[*]}")
    warning "missing_deps" "$missing_str"

    # 先检测系统类型
    case "$(uname -s)" in
        Darwin)
          # 检测 Homebrew 是否存在
          if ! command -v brew &>/dev/null; then
              warning "homebrew_not_found"
              local prompt_msg=$(get_i18n_string "install_homebrew_prompt")
              read -p "$prompt_msg" install_brew
              if [[ "$install_brew" =~ [yY] ]]; then
                  info "installing_homebrew"
                  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

                  # 自动配置环境变量
                  if [ -x "/opt/homebrew/bin/brew" ]; then  # Apple Silicon
                      echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc
                      source ~/.zshrc
                  elif [ -x "/usr/local/bin/brew" ]; then  # Intel
                      echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zshrc
                      source ~/.zshrc
                  fi

                  # 再次验证
                  if ! command -v brew &>/dev/null; then
                      error "homebrew_post_install"
                      exit 1
                  fi
              else
                  error "homebrew_required"
                  exit 1
              fi
          fi

          # 安装依赖
          info "installing_deps_with_brew"
          brew install "${missing[@]}"
          ;;
        Linux)
            # 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 "unrecognized_linux_distro" "$missing_str"
                exit 1
            fi
            ;;
        *)
            error "unsupported_os"
            exit 1
            ;;
    esac

    # 验证安装结果
    for dep in "${missing[@]}"; do
        if ! command -v "$dep" &>/dev/null; then
            error "install_failed" "$dep"
            exit 1
        fi
    done

    success "all_deps_installed_success"
}

# ============ 解析产品 =============
# 通过预解析的 PRODUCT_NAMES[] / PRODUCT_CODES[] 并行数组访问
parse_product_from_json() {
    local index="$1"
    echo "${PRODUCT_NAMES[$index]}|${PRODUCT_CODES[$index]}"
}

# ============ 日志函数 =============
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"   "$(_format_log "$@")"; }
info()    { log "INFO"    "$(_format_log "$@")"; }
warning() { log "WARNING" "$(_format_log "$@")"; }
error()   { log "ERROR"   "$(_format_log "$@")"; }
success() { log "SUCCESS" "$(_format_log "$@")"; }

# 内部:取 i18n 字符串并应用占位符
_format_log() {
    local msg
    msg=$(get_i18n_string "$1")
    if [[ $# -gt 1 ]]; then
        msg=$(format_string "$msg" "$2" "$3" "$4")
    fi
    printf '%s' "$msg"
}

# ============ ASCII Art =============
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(){
  local OS_NAME="$OS"
  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 "other_tools_env_cleanup"
}

remove_env_item_vars() {
    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 "no_env_files_found"
        return
    }

    # 环境变量备份目录
    local dir_date_backup="$dir_backups/$(date +%s)"
    for file in "${existing_files[@]}"; do
      # 判断文件中是否包含指定环境变量
        if [ ! -w "$file" ]; then
            warning "file_not_writable" "$file" >&2
            continue
        fi

        # 备份环境变量文件到dir_backups/时间
        if [ ! -d "$dir_date_backup" ]; then
            mkdir -p "$dir_date_backup"
        fi

        cp "$file" "${dir_date_backup}/_$(basename ${file})"
        debug "backup_env_file" "$file" "$dir_date_backup" "_$(basename ${file})"

        # 检测环境变量配置文件
        local index=0
        while [ $index -lt $PRODUCT_COUNT ]; do
            local name="${PRODUCT_NAMES[$index]}"
            local code="${PRODUCT_CODES[$index]}"

            if [ -z "$name" ]; then
                break
            fi

            local upper_key="$(echo "${name}" | tr '[:lower:]' '[:upper:]')_VM_OPTIONS"
            # 判断file里面是否包含upper_key
            if grep -q "^${upper_key}" "$file"; then
                sed -i -E "/${upper_key}/d" "$file"
                debug "removing_env_var" "$file" "$upper_key"
            fi
            ((index++))
        done
        source "$file"
    done
}

remove_env_vars() {
    info "env_cleanup_start"
    remove_env_item_vars
    # 删除其它激活工具残留
    remove_env_other
}

# ============ 用户输入授权信息 =============
validate_date_format() {
    local input="$1"

    # 第一步:检查是否符合 yyyy-MM-dd 格式
    if [[ ! "$input" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
        warning "format_error"
        return 1
    fi

    # 第二步:直接返回原值(无需调用 date 验证真实性)
    echo "$input"
    return 0
}

read_license_info() {
    local enter_license_name_msg=$(get_i18n_string "enter_license_name")
    read -p "$enter_license_name_msg" license_name
    license_name=${license_name:-ckey.run}

    local default_expiry="2099-12-31"
    local expiry_input
    local valid=false

    while [ "$valid" == "false" ]; do
        local enter_expiry_date_msg=$(get_i18n_string "enter_expiry_date")
        read -p "$(format_string "$enter_expiry_date_msg" "$default_expiry") " expiry_input
        expiry_input=${expiry_input:-$default_expiry}
        debug "entered_expiry_date" "$expiry_input"
        if expiry=$(validate_date_format "$expiry_input"); then
            valid=true
        else
            warning "invalid_date_format"
        fi
    done

    LICENSE_JSON=$(cat <<EOF
{
  "assigneeName": "",
  "expiryDate": "$expiry",
  "licenseName": "$license_name",
  "productCode": ""
}
EOF
)
}

# ============ 创建工作目录 =============
do_create_work_dir() {
    if [[ "${dir_work}" == "/" || -z "${dir_work}" ]]; then
        error "illegal_path_detected" "${dir_work}"
        exit 1
    fi

    if [ -d "${dir_work}" ]; then
        rm -rf "${dir_plugins}" "${dir_config}" "${file_netfilter_jar}" || {
            error "file_in_use"
            exit 1
        }
    fi
    mkdir -p "${dir_config}" "${dir_plugins}" "${dir_backups}" || {
         error  "dir_not_found" "${dir_config}"
         exit 1
     }
     debug "creating_work_dirs" "${dir_work}"
}

# ============ 下载文件 =============
download_one_file() {
    local url="$1"
    local file_save_path="$2"
    debug "download_progress" "${url}" "${file_save_path}"
    curl -s -o "${file_save_path}" "${url}"

    if [ $? -ne 0 ]; then
        error "download_failed" "${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 "sha1_verification_skipped" "$SHA_TOOL"
            return
        fi
        debug "sha1_info" "$url" "$sha1_hash"
    fi
}

progress_bar() {
  # 如果处于DEBUG模式,则不显示进度条
    if [ "$DEBUG" = true ]; then
      return
    fi
    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+="]"
    local configuring_msg=$(get_i18n_string "configuring_ja_netfilter")
    printf "\r%s %d/%d %s %d%%" "$configuring_msg" "$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/env.conf|${dir_config}/env.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/env.jar|${dir_plugins}/env.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 "source_address"
    debug "source_privacy"
    debug "suggest_check_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 "cleaning_vm_file_not_exist" "$file"
        return 0
    fi

    local temp_lines=()
    local keywords=(
        "-javaagent"
        "--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED"
        "--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED"
    )

    while IFS= read -r line; do
        local matched=false
        for keyword in "${keywords[@]}"; do
            [[ "$line" == *"$keyword"* ]] && matched=true && break
        done
        [[ "$matched" == false ]] && temp_lines+=("$line")
    done < "$file"

    if [ ${#temp_lines[@]} -gt 0 ]; then
        printf "%s\n" "${temp_lines[@]}" > "$file"
    else
        : > "$file"
    fi
    debug "cleaning_vm_file" "$file"
}

append_vmoptions() {
    local file="$1"
    if [ ! -f "$file" ]; then
        touch "$file" || {
            error "generating_vm_file_failed" "$file"
            return
        }
    fi

    cat >> "$file" <<EOF
-javaagent:${file_netfilter_jar}
EOF
    debug "generating_vm_file" "$file"
}


# 读取文件,如果文件中有com.intellij.modules.ultimate,则清空这行
process_disabled_plugins() {
    local file_disabled_plugins="$1"
    # 参数验证
    if [ -z "$file_disabled_plugins" ]; then
        debug "file_path_empty"
        return
    fi

    # 检查文件是否存在
    if [ ! -f "$file_disabled_plugins" ]; then
        debug "file_not_exist_skip" "$file_disabled_plugins"
        return
    fi

    # 直接在原文件中将匹配的行替换为空(即删除该行)
    # 兼容 macOS 和 Linux 上的 sed 命令
    if [[ "$OS" == "macOS" ]]; then
        sed -i '' '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
    else
        sed -i '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
    fi
    debug "processing_disabled_plugins_file" "$file_disabled_plugins"
}

# ============ 生成激活码 =============
generate_license() {
    local obj_product_name="$1"
    local obj_product_code="$2"
    local dir_product_name="$3"
    local file_license="${dir_config_jb}/${dir_product_name}/${obj_product_name}.key"
    local file_disabled_plugins="${dir_config_jb}/${dir_product_name}/disabled_plugins.txt"

    process_disabled_plugins "$file_disabled_plugins"

    [ -f "$file_license" ] && rm -f "$file_license"

    local json_body=$(jq --arg code "$obj_product_code" '.productCode = $code' <<< "$LICENSE_JSON")
    debug "url_license_params" "$URL_LICENSE" "$json_body" "$file_license"
    curl -s -X POST "$URL_LICENSE" \
        -H "Content-Type: application/json" \
        -d "$json_body" \
        -o "$file_license" > /dev/null

    if [ $? -eq 0 ]; then
        success "activation_success" "$dir_product_name"
    else
        warning "manual_activation_required" "$dir_product_name"
    fi
}

# ============ 处理单个 Jetbrains 产品 =============
handle_jetbrains_dir() {
    local dir="$1"
    local dir_product_name=$(basename "$dir")
    local obj_product_name=""
    local obj_product_code=""

    for ((i = 0; i < PRODUCT_COUNT; i++)); do
        local name="${PRODUCT_NAMES[$i]}"
        local code="${PRODUCT_CODES[$i]}"
        local lowercase_dir=$(echo "${dir_product_name}" | tr '[:upper:]' '[:lower:]')
        if [[ "$lowercase_dir" == *"$name"* ]]; then
            obj_product_name="$name"
            obj_product_code="$code"
            break
        fi
    done

    [ -z "$obj_product_name" ] && return

    info "processing_product" "$dir_product_name"

    local file_home="${dir}/.home"
    [ -f "$file_home" ] || {
        warning "not_found_home" "$file_home"
        return
    }

    debug "home_path" "$file_home"

    local install_path=$(cat "$file_home")
    [ -d "$install_path" ] || {
        warning "path_not_exist" "$install_path"
        return
    }

    debug "home_content" "$install_path"

    local dir_bin="${install_path}/bin"
    [ -d "$dir_bin" ] || {
        warning "bin_dir_not_exist" "$dir_product_name"
        return
    }

    local dir_config_product="${dir_config_jb}/${dir_product_name}"

    # 先查找所有 .vmoptions 文件
    local files=("${dir_config_product}"/*${FILE_VMOPTIONS})

    # 判断是否真的找到了文件
    if [[ -f "${files[0]}" ]]; then
      for file_vmoption in "${files[@]}"; do
        clean_vmoptions "$file_vmoption"
        append_vmoptions "$file_vmoption"
      done
    else
      debug "vmoptions_not_found_creating" "$dir_product_name"
      append_vmoptions "${dir_config_product}/${obj_product_name}${FILE_VMOPTIONS}"
    fi

    # 判断${dir_config_product}/jetbrains_client.vmoptions是否存在,如果不存在则创建一个默认的
    local file_jetbrains_client="${dir_config_product}/jetbrains_client.vmoptions"
    if [ ! -f "${file_jetbrains_client}" ]; then
        append_vmoptions "${file_jetbrains_client}"
    else
        clean_vmoptions "${file_jetbrains_client}"
        append_vmoptions "${file_jetbrains_client}"
    fi

    generate_license "$obj_product_name" "$obj_product_code" "$dir_product_name"
}

# ============ 主流程 =============
main() {
    clear
    show_ascii_jb

    info "welcome_msg"

    warning "script_date"

    warning "warning_msg"

    warning "close_products"
    read -r

    read_license_info

    info "processing"

    check_and_install_deps

    if [ ! -d "${dir_config_jb}" ]; then
        error "dir_not_found" "${dir_config_jb}"
        exit 1
    fi

    debug "config_dir" "${dir_config_jb}"

    do_create_work_dir

    remove_env_vars

    do_download_resources

    if [ ! -d "${dir_cache_jb}" ]; then
        warning "dir_not_found" "${dir_cache_jb}"
    else
        for dir in "${dir_cache_jb}"/*; do
            [ -d "$dir" ] && handle_jetbrains_dir "$dir"
        done
    fi

    info "processing_completed"
    sleep 1
    $OPEN_CMD "$URL_BASE" &>/dev/null
}

main "$@"
# 删除自己
rm -f "${BASH_SOURCE[0]}"

@masroore

Copy link
Copy Markdown
Author
#!/bin/bash
#set -e

# ============ 平台检测 =============
detect_platform() {
    case "$(uname -s)" in
        Darwin)
            OS="macOS"
            SHA_TOOL="shasum"
            OPEN_CMD="open"
            DATE_PARSER="mac"
            FILE_VMOPTIONS=".vmoptions"
            ;;
        Linux)
            OS="Linux"
            SHA_TOOL="sha1sum"
            OPEN_CMD="xdg-open"
            DATE_PARSER="linux"
            FILE_VMOPTIONS="64.vmoptions"
            ;;
        *)
            OS="Unknown"
            SHA_TOOL="sha1sum"
            OPEN_CMD="xdg-open"
            DATE_PARSER="linux"
            FILE_VMOPTIONS=".vmoptions"
            ;;
    esac
}
# 自动检测平台
detect_platform

# ============ 语言检测 =============
detect_language() {
    local lang=${LANG:-}
    if [[ $lang == zh* ]]; then
        LANGUAGE="zh"
    else
        LANGUAGE="en"
    fi
}
detect_language
# ============ 国际化支持 =============
# 初始化国际化字符串
init_i18n_strings() {
    # processing_env_vars
    processing_env_vars_zh="处理 {0} 环境变量"
    processing_env_vars_en="Processing {0} environment variables"

    # checking_env
    checking_env_zh="检查 [{0}]: {1} = '{2}'"
    checking_env_en="Checking [{0}]: {1} = '{2}'"

    # deleting_env
    deleting_env_zh="删除 [{0}]: {1}"
    deleting_env_en="Deleting [{0}]: {1}"

    # welcome_msg
    welcome_msg_zh="欢迎使用 JetBrains 激活工具 | CodeKey Run"
    welcome_msg_en="Welcome to JetBrains Activation Tool | CodeKey Run"

    # script_date
    script_date_zh="脚本日期:2026-1-28"
    script_date_en="Script Date: 2026-1-28"

    # warning_msg
    warning_msg_zh="注意,执行脚本默认会将所有产品全部激活一遍,无论之前是否激活过!!!"
    warning_msg_en="Warning: This script will forcibly re-activate all products!!!"

    # close_products
    close_products_zh="请确保激活的软件处于关闭状态,请按回车继续..."
    close_products_en="Please make sure all JetBrains software is closed, press Enter to continue..."

    # processing
    processing_zh="处理中,请耐心等待..."
    processing_en="Processing, please wait patiently..."

    # processing_configs
    processing_configs_zh="开始处理配置..."
    processing_configs_en="Starting to process configurations..."

    # not_found_dir
    not_found_dir_zh="未找到 {0} 目录!"
    not_found_dir_en="Directory not found: {0}!"

    # processing_product
    processing_product_zh="处理: {0}"
    processing_product_en="Processing: {0}"

    # not_found_home
    not_found_home_zh="未找到 .home 文件: {0}"
    not_found_home_en=".home file not found: {0}"

    # path_not_exist
    path_not_exist_zh="路径不存在: {0}"
    path_not_exist_en="Path does not exist: {0}"

    # not_found_bin
    not_found_bin_zh="未找到 bin 目录: {0}"
    not_found_bin_en="bin directory not found: {0}"

    # config_exists_cleaning
    config_exists_cleaning_zh="{0} 配置文件已存在,正在清理..."
    config_exists_cleaning_en="{0} configuration file already exists, cleaning..."

    # key_exists_cleaning
    key_exists_cleaning_zh="key已存在,正在清理..."
    key_exists_cleaning_en="Key already exists, cleaning..."

    # activation_success
    activation_success_zh="{0} 激活成功!"
    activation_success_en="{0} activated successfully!"

    # manual_activation_required
    manual_activation_required_zh="{0} 需要手动输入激活码!"
    manual_activation_required_en="{0} requires manual activation code entry!"

    # download_failed
    download_failed_zh="下载失败: {0}"
    download_failed_en="Download failed: {0}"

    # request_failed
    request_failed_zh="{0} 请求失败: {1}"
    request_failed_en="{0} request failed: {1}"

    # file_in_use
    file_in_use_zh="文件被占用,请先关闭所有 JetBrains IDE 后再试!"
    file_in_use_en="File is in use, please close all JetBrains IDEs and try again!"

    # processing_completed
    processing_completed_zh="所有项处理结束,如需要激活码,请前往网站获取!"
    processing_completed_en="All items processed. If you need an activation code, please visit the website!"

    # format_error
    format_error_zh="格式错误:请使用 yyyy-MM-dd 格式"
    format_error_en="Format error: Please use yyyy-MM-dd format"

    # invalid_date
    invalid_date_zh="非法日期:{0}"
    invalid_date_en="Invalid date: {0}"

    # reading_config
    reading_config_zh="读取配置文件:{0}, 寻找键:{1}"
    reading_config_en="Reading config file: {0}, looking for key: {1}"

    # found_key
    found_key_zh="找到键 '{0}',值为 '{1}'"
    found_key_en="Found key '{0}' with value '{1}'"

    # failed_read_config
    failed_read_config_zh="读取配置文件失败:{0}"
    failed_read_config_en="Failed to read config file: {0}"

    # cleaning_vmoptions
    cleaning_vmoptions_zh="清理 VMOptions: {0}"
    cleaning_vmoptions_en="Cleaning VMOptions: {0}"

    # updating_vmoptions
    updating_vmoptions_zh="更新 VMOptions: {0}"
    updating_vmoptions_en="Updating VMOptions: {0}"

    # processing_disabled_plugins
    processing_disabled_plugins_zh="已处理文件: {0} 中com.intellij.modules.ultimate项"
    processing_disabled_plugins_en="Processed file: {0} com.intellij.modules.ultimate item"

    # file_not_exist_skip
    file_not_exist_skip_zh="文件不存在,跳过: {0}"
    file_not_exist_skip_en="File does not exist, skipping: {0}"

    # error_processing_plugins
    error_processing_plugins_zh="处理禁用插件文件时出错: {0}"
    error_processing_plugins_en="Error processing disabled plugins file: {0}"

    # processing_config
    processing_config_zh="处理配置: {0}, {1}, {2}"
    processing_config_en="Processing config: {0}, {1}, {2}"

    # requesting_key
    requesting_key_zh="请求key: {0},请求body: {1},保存地址: {2}"
    requesting_key_en="Requesting key: {0}, request body: {1}, save path: {2}"

    # writing_key
    writing_key_zh="写入key,激活中: {0}"
    writing_key_en="Writing key, activating: {0}"

    # source_address
    source_address_zh="源ja-netfilter项目地址: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"
    source_address_en="Source ja-netfilter address: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"

    # source_privacy
    source_privacy_zh="源 privacy.jar 地址: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"
    source_privacy_en="Source privacy.jar address: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"

    # suggest_check_sha1
    suggest_check_sha1_zh="如需检查下载的.jar是否被篡改请核对sha1的值是否与源项目文件一致"
    suggest_check_sha1_en="It is recommended to verify the SHA1 value to ensure integrity"

    # configuring_ja_netfilter
    configuring_ja_netfilter_zh="配置 ja-netfilter..."
    configuring_ja_netfilter_en="Configuring ja-netfilter..."

    # custom_license_name
    custom_license_name_zh="自定义授权名称 (回车默认 ckey.run)"
    custom_license_name_en="Custom license name (Press Enter to use the default ckey.run)"

    # custom_expiry_date
    custom_expiry_date_zh="自定义授权日期 (回车默认 2099-12-31, 格式 yyyy-MM-dd)"
    custom_expiry_date_en="Custom expiration date (Press Enter to use the default 2099-12-31, format yyyy-MM-dd)"

    # default_license_name
    default_license_name="ckey.run"

    # default_expiry_date
    default_expiry_date="2099-12-31"

    # file_path_empty
    file_path_empty_zh="文件路径为空,跳过处理"
    file_path_empty_en="File path is empty, skipping processing"

    # found_home_file
    found_home_file_zh="找到.home文件: {0}"
    found_home_file_en="Found .home file: {0}"

    # read_home_content
    read_home_content_zh="读取.home文件内容: {0}"
    read_home_content_en="Read .home file content: {0}"

    # found_bin_dir
    found_bin_dir_zh="找到bin目录: {0}"
    found_bin_dir_en="Found bin directory: {0}"

    # sha1_info
    sha1_info="{0} :sha1: {1}"

    # request_fail
    request_fail_zh="请求失败: {0}"
    request_fail_en="Request failed: {0}"

    # all_deps_installed
    all_deps_installed_zh="所有依赖已安装。"
    all_deps_installed_en="All dependencies are installed."

    # missing_deps
    missing_deps_zh="缺少以下依赖:{0},正在尝试自动安装..."
    missing_deps_en="Missing dependencies: {0}, trying to install automatically..."

    # homebrew_not_found
    homebrew_not_found_zh="检测到 macOS但未安装 Homebrew"
    homebrew_not_found_en="Detected macOS but Homebrew is not installed"

    # install_homebrew_prompt
    install_homebrew_prompt_zh="是否要自动安装 Homebrew?(y/n) "
    install_homebrew_prompt_en="Do you want to install Homebrew automatically? (y/n) "

    # installing_homebrew
    installing_homebrew_zh="正在安装 Homebrew..."
    installing_homebrew_en="Installing Homebrew..."

    # homebrew_post_install
    homebrew_post_install_zh="Homebrew 安装后仍不可用,请手动重启终端后重试"
    homebrew_post_install_en="Homebrew is still not available after installation, please restart the terminal and try again"

    # homebrew_required
    homebrew_required_zh="必须安装 Homebrew 才能继续!"
    homebrew_required_en="Homebrew must be installed to continue!"

    # installing_deps_with_brew
    installing_deps_with_brew_zh="使用 Homebrew 安装依赖"
    installing_deps_with_brew_en="Installing dependencies with Homebrew"

    # unrecognized_linux_distro
    unrecognized_linux_distro_zh="无法识别的 Linux 发行版,请手动安装依赖:{0}"
    unrecognized_linux_distro_en="Unrecognized Linux distribution, please manually install dependencies: {0}"

    # unsupported_os
    unsupported_os_zh="不支持的操作系统"
    unsupported_os_en="Unsupported operating system"

    # install_failed
    install_failed_zh="安装失败:{0}"
    install_failed_en="Installation failed: {0}"

    # all_deps_installed_success
    all_deps_installed_success_zh="所有依赖已成功安装!"
    all_deps_installed_success_en="All dependencies have been successfully installed!"

    # env_cleanup_start
    env_cleanup_start_zh="开始清理 JetBrains 相关环境变量"
    env_cleanup_start_en="Start cleaning JetBrains related environment variables"

    # no_env_files_found
    no_env_files_found_zh="未找到任何环境变量文件,跳过"
    no_env_files_found_en="No environment variable files found, skipping"

    # file_not_writable
    file_not_writable_zh="文件 {0} 不可写,跳过修改"
    file_not_writable_en="File {0} is not writable, skipping modification"

    # backup_env_file
    backup_env_file_zh="备份环境变量文件: {0}, {1}, {2}"
    backup_env_file_en="Backup environment variable file: {0}, {1}, {2}"

    # removing_env_var
    removing_env_var_zh="删除环境变量: {0},{1}"
    removing_env_var_en="Removing environment variable: {0},{1}"

    # other_tools_env_cleanup
    other_tools_env_cleanup_zh="清理三方工具环境变量完成"
    other_tools_env_cleanup_en="Cleanup of third-party tool environment variables completed"

    # enter_license_name
    enter_license_name_zh="自定义授权名称 (回车默认 ckey.run): "
    enter_license_name_en="Custom license name (Enter for default ckey.run): "

    # enter_expiry_date
    enter_expiry_date_zh="自定义授权日期 (回车默认 {0}, 格式 yyyy-MM-dd): "
    enter_expiry_date_en="Custom expiration date (Enter for default {0}, format yyyy-MM-dd): "

    # entered_expiry_date
    entered_expiry_date_zh="输入的授权日期: {0}"
    entered_expiry_date_en="Entered expiration date: {0}"

    # invalid_date_format
    invalid_date_format_zh="日期格式不合法,请输入正确的 yyyy-MM-dd 格式(例如:2099-12-31)"
    invalid_date_format_en="Invalid date format, please enter the correct yyyy-MM-dd format (e.g. 2099-12-31)"

    # illegal_path_detected
    illegal_path_detected_zh="检测到非法路径: {0},请检查配置。"
    illegal_path_detected_en="Illegal path detected: {0}, please check configuration."

    # creating_work_dirs
    creating_work_dirs_zh="创建工作目录: {0}"
    creating_work_dirs_en="Creating work directories: {0}"

    # download_progress
    download_progress_zh="正在下载: {0} -> {1}"
    download_progress_en="Downloading: {0} -> {1}"

    # sha1_verification_skipped
    sha1_verification_skipped_zh="未找到 {0} 工具,跳过 SHA-1 校验"
    sha1_verification_skipped_en="{0} tool not found, skipping SHA-1 verification"

    # cleaning_vm_file
    cleaning_vm_file_zh="清理vm: {0}"
    cleaning_vm_file_en="Cleaning vm: {0}"

    # cleaning_vm_file_not_exist
    cleaning_vm_file_not_exist_zh="清理vm: 文件不存在,跳过清理: {0}"
    cleaning_vm_file_not_exist_en="Cleaning vm: File does not exist, skipping cleanup: {0}"

    # generating_vm_file
    generating_vm_file_zh="生成vm: {0}"
    generating_vm_file_en="Generating vm: {0}"

    # generating_vm_file_failed
    generating_vm_file_failed_zh="生成vm: 创建失败: {0}"
    generating_vm_file_failed_en="Generating vm: Failed to create: {0}"

    # processing_disabled_plugins_file
    processing_disabled_plugins_file_zh="已处理文件 {0} 中com.intellij.modules.ultimate 项"
    processing_disabled_plugins_file_en="Processed com.intellij.modules.ultimate item in file {0}"

    # url_license_params
    url_license_params="URL_LICENSE:{0},params:{1},save_path:{2}"

    # home_path
    home_path_zh=".home路径: {0}"
    home_path_en=".home path: {0}"

    # home_content
    home_content_zh=".home内容: {0}"
    home_content_en=".home content: {0}"

    # bin_dir_not_exist
    bin_dir_not_exist_zh="{0} 的 bin 目录不存在,请确认是否正确安装!"
    bin_dir_not_exist_en="The bin directory of {0} does not exist, please confirm if it is installed correctly!"

    # vmoptions_not_found_creating
    vmoptions_not_found_creating_zh="未找到{0} 的.vmoptions文件,将创建一个默认的"
    vmoptions_not_found_creating_en=".vmoptions file for {0} not found, will create a default one"

    # config_dir
    config_dir_zh="config目录:{0}"
    config_dir_en="config directory: {0}"

    # dir_not_found
    dir_not_found_zh="未找到{0}目录"
    dir_not_found_en="Directory {0} not found"
}

# 获取国际化字符串
get_i18n_string() {
    local key="$1"
    local var_name="${key}_${LANGUAGE}"

    # 使用间接引用获取变量值
    if [[ -n "${!var_name}" ]]; then
        echo "${!var_name}"
    else
        # 如果找不到对应语言的字符串,返回键名本身
        echo "${!key}"
    fi
}

# 格式化字符串(最多支持3个参数)
format_string() {
    local template="$1"
    local arg1="$2"
    local arg2="$3"
    local arg3="$4"

    # 替换 {0}, {1}, {2} 占位符
    local result="$template"
    result="${result//\{0\}/$arg1}"
    result="${result//\{1\}/$arg2}"
    result="${result//\{2\}/$arg3}"

    echo "$result"
}

# 初始化国际化字符串字典
init_i18n_strings

# ============ 配置 =============
DEBUG=false
ENABLE_COLOR=true

URL_BASE="https://ckey.run"
#URL_BASE="http://192.168.31.254:10768"
URL_DOWNLOAD="${URL_BASE}/ja-netfilter"
URL_LICENSE="${URL_BASE}/generateLicense/file"

# 获取原始用户和家目录
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_run"
dir_config="${dir_work}/config"
dir_plugins="${dir_work}/plugins"
dir_backups="${dir_work}/backups"
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":"rustrover","productCode":"RR,PSI,PCWMP"}
]'

# 预解析产品到并行数组(避免循环内重复调用 jq)
PRODUCT_NAMES=()
PRODUCT_CODES=()
while IFS='|' read -r _name _code; do
    [[ -n "$_name" ]] && PRODUCT_NAMES+=("$_name") && PRODUCT_CODES+=("$_code")
done < <(echo "$PRODUCTS" | jq -r '.[] | "\(.name)|\(.productCode)"')
PRODUCT_COUNT=${#PRODUCT_NAMES[@]}

# ============ 工具函数 =============

# ============ 日期验证 =============
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

    if [ ${#missing[@]} -eq 0 ]; then
        info "all_deps_installed"
        return
    fi

    local missing_str=$(IFS=", "; echo "${missing[*]}")
    warning "missing_deps" "$missing_str"

    # 先检测系统类型
    case "$(uname -s)" in
        Darwin)
          # 检测 Homebrew 是否存在
          if ! command -v brew &>/dev/null; then
              warning "homebrew_not_found"
              local prompt_msg=$(get_i18n_string "install_homebrew_prompt")
              read -p "$prompt_msg" install_brew
              if [[ "$install_brew" =~ [yY] ]]; then
                  info "installing_homebrew"
                  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

                  # 自动配置环境变量
                  # 不要 source ~/.zshrc(zsh-only 语法会把当前 bash 弄炸)
                  # 当前会话直接 export PATH,新 shell 由写入的 shellenv 行处理
                  if [ -x "/opt/homebrew/bin/brew" ]; then  # Apple Silicon
                      echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc
                      export PATH="/opt/homebrew/bin:$PATH"
                  elif [ -x "/usr/local/bin/brew" ]; then  # Intel
                      echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zshrc
                      export PATH="/usr/local/bin:$PATH"
                  fi

                  # 再次验证
                  if ! command -v brew &>/dev/null; then
                      error "homebrew_post_install"
                      exit 1
                  fi
              else
                  error "homebrew_required"
                  exit 1
              fi
          fi

          # 安装依赖
          info "installing_deps_with_brew"
          brew install "${missing[@]}"
          ;;
        Linux)
            # 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 "unrecognized_linux_distro" "$missing_str"
                exit 1
            fi
            ;;
        *)
            error "unsupported_os"
            exit 1
            ;;
    esac

    # 验证安装结果
    for dep in "${missing[@]}"; do
        if ! command -v "$dep" &>/dev/null; then
            error "install_failed" "$dep"
            exit 1
        fi
    done

    success "all_deps_installed_success"
}

# ============ 解析产品 =============
# 通过预解析的 PRODUCT_NAMES[] / PRODUCT_CODES[] 并行数组访问
parse_product_from_json() {
    local index="$1"
    echo "${PRODUCT_NAMES[$index]}|${PRODUCT_CODES[$index]}"
}

# ============ 日志函数 =============
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"   "$(_format_log "$@")"; }
info()    { log "INFO"    "$(_format_log "$@")"; }
warning() { log "WARNING" "$(_format_log "$@")"; }
error()   { log "ERROR"   "$(_format_log "$@")"; }
success() { log "SUCCESS" "$(_format_log "$@")"; }

# 内部:取 i18n 字符串并应用占位符
_format_log() {
    local msg
    msg=$(get_i18n_string "$1")
    if [[ $# -gt 1 ]]; then
        msg=$(format_string "$msg" "$2" "$3" "$4")
    fi
    printf '%s' "$msg"
}

# ============ ASCII Art =============
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(){
  local OS_NAME="$OS"
  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 "other_tools_env_cleanup"
}

remove_env_item_vars() {
    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 "no_env_files_found"
        return
    }

    # 环境变量备份目录
    local dir_date_backup="$dir_backups/$(date +%s)"
    for file in "${existing_files[@]}"; do
      # 判断文件中是否包含指定环境变量
        if [ ! -w "$file" ]; then
            warning "file_not_writable" "$file" >&2
            continue
        fi

        # 备份环境变量文件到dir_backups/时间
        if [ ! -d "$dir_date_backup" ]; then
            mkdir -p "$dir_date_backup"
        fi

        cp "$file" "${dir_date_backup}/_$(basename ${file})"
        debug "backup_env_file" "$file" "$dir_date_backup" "_$(basename ${file})"

        # 检测环境变量配置文件
        local index=0
        while [ $index -lt $PRODUCT_COUNT ]; do
            local name="${PRODUCT_NAMES[$index]}"
            local code="${PRODUCT_CODES[$index]}"

            if [ -z "$name" ]; then
                break
            fi

            local upper_key="$(echo "${name}" | tr '[:lower:]' '[:upper:]')_VM_OPTIONS"
            # 判断file里面是否包含upper_key
            if grep -q "^${upper_key}" "$file"; then
                sed -i -E "/${upper_key}/d" "$file"
                debug "removing_env_var" "$file" "$upper_key"
            fi
            ((index++))
        done
        # 不再 source rc 文件:
        #   1) 我们是 bash,无法 source .zshrc(zsh-only 语法会炸)
        #   2) sed 已就地清理,下一次交互式 shell 会读到干净版本
    done
}

remove_env_vars() {
    info "env_cleanup_start"
    remove_env_item_vars
    # 删除其它激活工具残留
    remove_env_other
}

# ============ 用户输入授权信息 =============
validate_date_format() {
    local input="$1"

    # 第一步:检查是否符合 yyyy-MM-dd 格式
    if [[ ! "$input" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
        warning "format_error"
        return 1
    fi

    # 第二步:直接返回原值(无需调用 date 验证真实性)
    echo "$input"
    return 0
}

read_license_info() {
    local enter_license_name_msg=$(get_i18n_string "enter_license_name")
    read -p "$enter_license_name_msg" license_name
    license_name=${license_name:-ckey.run}

    local default_expiry="2099-12-31"
    local expiry_input
    local valid=false

    while [ "$valid" == "false" ]; do
        local enter_expiry_date_msg=$(get_i18n_string "enter_expiry_date")
        read -p "$(format_string "$enter_expiry_date_msg" "$default_expiry") " expiry_input
        expiry_input=${expiry_input:-$default_expiry}
        debug "entered_expiry_date" "$expiry_input"
        if expiry=$(validate_date_format "$expiry_input"); then
            valid=true
        else
            warning "invalid_date_format"
        fi
    done

    LICENSE_JSON=$(cat <<EOF
{
  "assigneeName": "",
  "expiryDate": "$expiry",
  "licenseName": "$license_name",
  "productCode": ""
}
EOF
)
}

# ============ 创建工作目录 =============
do_create_work_dir() {
    if [[ "${dir_work}" == "/" || -z "${dir_work}" ]]; then
        error "illegal_path_detected" "${dir_work}"
        exit 1
    fi

    if [ -d "${dir_work}" ]; then
        rm -rf "${dir_plugins}" "${dir_config}" "${file_netfilter_jar}" || {
            error "file_in_use"
            exit 1
        }
    fi
    mkdir -p "${dir_config}" "${dir_plugins}" "${dir_backups}" || {
         error  "dir_not_found" "${dir_config}"
         exit 1
     }
     debug "creating_work_dirs" "${dir_work}"
}

# ============ 下载文件 =============
download_one_file() {
    local url="$1"
    local file_save_path="$2"
    debug "download_progress" "${url}" "${file_save_path}"
    curl -s -o "${file_save_path}" "${url}"

    if [ $? -ne 0 ]; then
        error "download_failed" "${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 "sha1_verification_skipped" "$SHA_TOOL"
            return
        fi
        debug "sha1_info" "$url" "$sha1_hash"
    fi
}

progress_bar() {
  # 如果处于DEBUG模式,则不显示进度条
    if [ "$DEBUG" = true ]; then
      return
    fi
    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+="]"
    local configuring_msg=$(get_i18n_string "configuring_ja_netfilter")
    printf "\r%s %d/%d %s %d%%" "$configuring_msg" "$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/env.conf|${dir_config}/env.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/env.jar|${dir_plugins}/env.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 "source_address"
    debug "source_privacy"
    debug "suggest_check_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 "cleaning_vm_file_not_exist" "$file"
        return 0
    fi

    local temp_lines=()
    local keywords=(
        "-javaagent"
        "--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED"
        "--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED"
    )

    while IFS= read -r line; do
        local matched=false
        for keyword in "${keywords[@]}"; do
            [[ "$line" == *"$keyword"* ]] && matched=true && break
        done
        [[ "$matched" == false ]] && temp_lines+=("$line")
    done < "$file"

    if [ ${#temp_lines[@]} -gt 0 ]; then
        printf "%s\n" "${temp_lines[@]}" > "$file"
    else
        : > "$file"
    fi
    debug "cleaning_vm_file" "$file"
}

append_vmoptions() {
    local file="$1"
    if [ ! -f "$file" ]; then
        touch "$file" || {
            error "generating_vm_file_failed" "$file"
            return
        }
    fi

    cat >> "$file" <<EOF
-javaagent:${file_netfilter_jar}
EOF
    debug "generating_vm_file" "$file"
}


# 读取文件,如果文件中有com.intellij.modules.ultimate,则清空这行
process_disabled_plugins() {
    local file_disabled_plugins="$1"
    # 参数验证
    if [ -z "$file_disabled_plugins" ]; then
        debug "file_path_empty"
        return
    fi

    # 检查文件是否存在
    if [ ! -f "$file_disabled_plugins" ]; then
        debug "file_not_exist_skip" "$file_disabled_plugins"
        return
    fi

    # 直接在原文件中将匹配的行替换为空(即删除该行)
    # 兼容 macOS 和 Linux 上的 sed 命令
    if [[ "$OS" == "macOS" ]]; then
        sed -i '' '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
    else
        sed -i '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
    fi
    debug "processing_disabled_plugins_file" "$file_disabled_plugins"
}

# ============ 生成激活码 =============
generate_license() {
    local obj_product_name="$1"
    local obj_product_code="$2"
    local dir_product_name="$3"
    local file_license="${dir_config_jb}/${dir_product_name}/${obj_product_name}.key"
    local file_disabled_plugins="${dir_config_jb}/${dir_product_name}/disabled_plugins.txt"

    process_disabled_plugins "$file_disabled_plugins"

    [ -f "$file_license" ] && rm -f "$file_license"

    local json_body=$(jq --arg code "$obj_product_code" '.productCode = $code' <<< "$LICENSE_JSON")
    debug "url_license_params" "$URL_LICENSE" "$json_body" "$file_license"
    curl -s -X POST "$URL_LICENSE" \
        -H "Content-Type: application/json" \
        -d "$json_body" \
        -o "$file_license" > /dev/null

    if [ $? -eq 0 ]; then
        success "activation_success" "$dir_product_name"
    else
        warning "manual_activation_required" "$dir_product_name"
    fi
}

# ============ 处理单个 Jetbrains 产品 =============
handle_jetbrains_dir() {
    local dir="$1"
    local dir_product_name=$(basename "$dir")
    local obj_product_name=""
    local obj_product_code=""

    for ((i = 0; i < PRODUCT_COUNT; i++)); do
        local name="${PRODUCT_NAMES[$i]}"
        local code="${PRODUCT_CODES[$i]}"
        local lowercase_dir=$(echo "${dir_product_name}" | tr '[:upper:]' '[:lower:]')
        if [[ "$lowercase_dir" == *"$name"* ]]; then
            obj_product_name="$name"
            obj_product_code="$code"
            break
        fi
    done

    [ -z "$obj_product_name" ] && return

    info "processing_product" "$dir_product_name"

    local file_home="${dir}/.home"
    [ -f "$file_home" ] || {
        warning "not_found_home" "$file_home"
        return
    }

    debug "home_path" "$file_home"

    local install_path=$(cat "$file_home")
    [ -d "$install_path" ] || {
        warning "path_not_exist" "$install_path"
        return
    }

    debug "home_content" "$install_path"

    local dir_bin="${install_path}/bin"
    [ -d "$dir_bin" ] || {
        warning "bin_dir_not_exist" "$dir_product_name"
        return
    }

    local dir_config_product="${dir_config_jb}/${dir_product_name}"

    # 先查找所有 .vmoptions 文件
    local files=("${dir_config_product}"/*${FILE_VMOPTIONS})

    # 判断是否真的找到了文件
    if [[ -f "${files[0]}" ]]; then
      for file_vmoption in "${files[@]}"; do
        clean_vmoptions "$file_vmoption"
        append_vmoptions "$file_vmoption"
      done
    else
      debug "vmoptions_not_found_creating" "$dir_product_name"
      append_vmoptions "${dir_config_product}/${obj_product_name}${FILE_VMOPTIONS}"
    fi

    # 判断${dir_config_product}/jetbrains_client.vmoptions是否存在,如果不存在则创建一个默认的
    local file_jetbrains_client="${dir_config_product}/jetbrains_client.vmoptions"
    if [ ! -f "${file_jetbrains_client}" ]; then
        append_vmoptions "${file_jetbrains_client}"
    else
        clean_vmoptions "${file_jetbrains_client}"
        append_vmoptions "${file_jetbrains_client}"
    fi

    generate_license "$obj_product_name" "$obj_product_code" "$dir_product_name"
}

# ============ 主流程 =============
main() {
    clear
    show_ascii_jb

    info "welcome_msg"

    warning "script_date"

    warning "warning_msg"

    warning "close_products"
    read -r

    read_license_info

    info "processing"

    check_and_install_deps

    if [ ! -d "${dir_config_jb}" ]; then
        error "dir_not_found" "${dir_config_jb}"
        exit 1
    fi

    debug "config_dir" "${dir_config_jb}"

    do_create_work_dir

    remove_env_vars

    do_download_resources

    if [ ! -d "${dir_cache_jb}" ]; then
        warning "dir_not_found" "${dir_cache_jb}"
    else
        for dir in "${dir_cache_jb}"/*; do
            [ -d "$dir" ] && handle_jetbrains_dir "$dir"
        done
    fi

    info "processing_completed"
    sleep 1
    $OPEN_CMD "$URL_BASE" &>/dev/null
}

main "$@"
# 删除自己
# rm -f "${BASH_SOURCE[0]}"

@masroore

Copy link
Copy Markdown
Author

ENGLISH version

#!/bin/bash
#set -e

# ============ Platform Detection =============
detect_platform() {
    case "$(uname -s)" in
        Darwin)
            OS="macOS"
            SHA_TOOL="shasum"
            OPEN_CMD="open"
            DATE_PARSER="mac"
            FILE_VMOPTIONS=".vmoptions"
            ;;
        Linux)
            OS="Linux"
            SHA_TOOL="sha1sum"
            OPEN_CMD="xdg-open"
            DATE_PARSER="linux"
            FILE_VMOPTIONS="64.vmoptions"
            ;;
        *)
            OS="Unknown"
            SHA_TOOL="sha1sum"
            OPEN_CMD="xdg-open"
            DATE_PARSER="linux"
            FILE_VMOPTIONS=".vmoptions"
            ;;
    esac
}
# Auto-detect platform
detect_platform

# ============ Language Detection =============
detect_language() {
    local lang=${LANG:-}
    if [[ $lang == zh* ]]; then
        LANGUAGE="zh"
    else
        LANGUAGE="en"
    fi
}
detect_language
# ============ Internationalization =============
# Initialize i18n strings
init_i18n_strings() {
    # processing_env_vars
    processing_env_vars_zh="处理 {0} 环境变量"
    processing_env_vars_en="Processing {0} environment variables"

    # checking_env
    checking_env_zh="检查 [{0}]: {1} = '{2}'"
    checking_env_en="Checking [{0}]: {1} = '{2}'"

    # deleting_env
    deleting_env_zh="删除 [{0}]: {1}"
    deleting_env_en="Deleting [{0}]: {1}"

    # welcome_msg
    welcome_msg_zh="欢迎使用 JetBrains 激活工具 | CodeKey Run"
    welcome_msg_en="Welcome to JetBrains Activation Tool | CodeKey Run"

    # script_date
    script_date_zh="脚本日期:2026-1-28"
    script_date_en="Script Date: 2026-1-28"

    # warning_msg
    warning_msg_zh="注意,执行脚本默认会将所有产品全部激活一遍,无论之前是否激活过!!!"
    warning_msg_en="Warning: This script will forcibly re-activate all products!!!"

    # close_products
    close_products_zh="请确保激活的软件处于关闭状态,请按回车继续..."
    close_products_en="Please make sure all JetBrains software is closed, press Enter to continue..."

    # processing
    processing_zh="处理中,请耐心等待..."
    processing_en="Processing, please wait patiently..."

    # processing_configs
    processing_configs_zh="开始处理配置..."
    processing_configs_en="Starting to process configurations..."

    # not_found_dir
    not_found_dir_zh="未找到 {0} 目录!"
    not_found_dir_en="Directory not found: {0}!"

    # processing_product
    processing_product_zh="处理: {0}"
    processing_product_en="Processing: {0}"

    # not_found_home
    not_found_home_zh="未找到 .home 文件: {0}"
    not_found_home_en=".home file not found: {0}"

    # path_not_exist
    path_not_exist_zh="路径不存在: {0}"
    path_not_exist_en="Path does not exist: {0}"

    # not_found_bin
    not_found_bin_zh="未找到 bin 目录: {0}"
    not_found_bin_en="bin directory not found: {0}"

    # config_exists_cleaning
    config_exists_cleaning_zh="{0} 配置文件已存在,正在清理..."
    config_exists_cleaning_en="{0} configuration file already exists, cleaning..."

    # key_exists_cleaning
    key_exists_cleaning_zh="key已存在,正在清理..."
    key_exists_cleaning_en="Key already exists, cleaning..."

    # activation_success
    activation_success_zh="{0} 激活成功!"
    activation_success_en="{0} activated successfully!"

    # manual_activation_required
    manual_activation_required_zh="{0} 需要手动输入激活码!"
    manual_activation_required_en="{0} requires manual activation code entry!"

    # download_failed
    download_failed_zh="下载失败: {0}"
    download_failed_en="Download failed: {0}"

    # request_failed
    request_failed_zh="{0} 请求失败: {1}"
    request_failed_en="{0} request failed: {1}"

    # file_in_use
    file_in_use_zh="文件被占用,请先关闭所有 JetBrains IDE 后再试!"
    file_in_use_en="File is in use, please close all JetBrains IDEs and try again!"

    # processing_completed
    processing_completed_zh="所有项处理结束,如需要激活码,请前往网站获取!"
    processing_completed_en="All items processed. If you need an activation code, please visit the website!"

    # format_error
    format_error_zh="格式错误:请使用 yyyy-MM-dd 格式"
    format_error_en="Format error: Please use yyyy-MM-dd format"

    # invalid_date
    invalid_date_zh="非法日期:{0}"
    invalid_date_en="Invalid date: {0}"

    # reading_config
    reading_config_zh="读取配置文件:{0}, 寻找键:{1}"
    reading_config_en="Reading config file: {0}, looking for key: {1}"

    # found_key
    found_key_zh="找到键 '{0}',值为 '{1}'"
    found_key_en="Found key '{0}' with value '{1}'"

    # failed_read_config
    failed_read_config_zh="读取配置文件失败:{0}"
    failed_read_config_en="Failed to read config file: {0}"

    # cleaning_vmoptions
    cleaning_vmoptions_zh="清理 VMOptions: {0}"
    cleaning_vmoptions_en="Cleaning VMOptions: {0}"

    # updating_vmoptions
    updating_vmoptions_zh="更新 VMOptions: {0}"
    updating_vmoptions_en="Updating VMOptions: {0}"

    # processing_disabled_plugins
    processing_disabled_plugins_zh="已处理文件: {0} 中com.intellij.modules.ultimate项"
    processing_disabled_plugins_en="Processed file: {0} com.intellij.modules.ultimate item"

    # file_not_exist_skip
    file_not_exist_skip_zh="文件不存在,跳过: {0}"
    file_not_exist_skip_en="File does not exist, skipping: {0}"

    # error_processing_plugins
    error_processing_plugins_zh="处理禁用插件文件时出错: {0}"
    error_processing_plugins_en="Error processing disabled plugins file: {0}"

    # processing_config
    processing_config_zh="处理配置: {0}, {1}, {2}"
    processing_config_en="Processing config: {0}, {1}, {2}"

    # requesting_key
    requesting_key_zh="请求key: {0},请求body: {1},保存地址: {2}"
    requesting_key_en="Requesting key: {0}, request body: {1}, save path: {2}"

    # writing_key
    writing_key_zh="写入key,激活中: {0}"
    writing_key_en="Writing key, activating: {0}"

    # source_address
    source_address_zh="源ja-netfilter项目地址: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"
    source_address_en="Source ja-netfilter address: https://gitee.com/ja-netfilter/ja-netfilter/releases/tag/2025.3.0"

    # source_privacy
    source_privacy_zh="源 privacy.jar 地址: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"
    source_privacy_en="Source privacy.jar address: https://gitea.998043.xyz/novice/plugin-privacy/releases/tag/release"

    # suggest_check_sha1
    suggest_check_sha1_zh="如需检查下载的.jar是否被篡改请核对sha1的值是否与源项目文件一致"
    suggest_check_sha1_en="It is recommended to verify the SHA1 value to ensure integrity"

    # configuring_ja_netfilter
    configuring_ja_netfilter_zh="配置 ja-netfilter..."
    configuring_ja_netfilter_en="Configuring ja-netfilter..."

    # custom_license_name
    custom_license_name_zh="自定义授权名称 (回车默认 ckey.run)"
    custom_license_name_en="Custom license name (Press Enter to use the default ckey.run)"

    # custom_expiry_date
    custom_expiry_date_zh="自定义授权日期 (回车默认 2099-12-31, 格式 yyyy-MM-dd)"
    custom_expiry_date_en="Custom expiration date (Press Enter to use the default 2099-12-31, format yyyy-MM-dd)"

    # default_license_name
    default_license_name="ckey.run"

    # default_expiry_date
    default_expiry_date="2099-12-31"

    # file_path_empty
    file_path_empty_zh="文件路径为空,跳过处理"
    file_path_empty_en="File path is empty, skipping processing"

    # found_home_file
    found_home_file_zh="找到.home文件: {0}"
    found_home_file_en="Found .home file: {0}"

    # read_home_content
    read_home_content_zh="读取.home文件内容: {0}"
    read_home_content_en="Read .home file content: {0}"

    # found_bin_dir
    found_bin_dir_zh="找到bin目录: {0}"
    found_bin_dir_en="Found bin directory: {0}"

    # sha1_info
    sha1_info="{0} :sha1: {1}"

    # request_fail
    request_fail_zh="请求失败: {0}"
    request_fail_en="Request failed: {0}"

    # all_deps_installed
    all_deps_installed_zh="所有依赖已安装。"
    all_deps_installed_en="All dependencies are installed."

    # missing_deps
    missing_deps_zh="缺少以下依赖:{0},正在尝试自动安装..."
    missing_deps_en="Missing dependencies: {0}, trying to install automatically..."

    # homebrew_not_found
    homebrew_not_found_zh="检测到 macOS但未安装 Homebrew"
    homebrew_not_found_en="Detected macOS but Homebrew is not installed"

    # install_homebrew_prompt
    install_homebrew_prompt_zh="是否要自动安装 Homebrew?(y/n) "
    install_homebrew_prompt_en="Do you want to install Homebrew automatically? (y/n) "

    # installing_homebrew
    installing_homebrew_zh="正在安装 Homebrew..."
    installing_homebrew_en="Installing Homebrew..."

    # homebrew_post_install
    homebrew_post_install_zh="Homebrew 安装后仍不可用,请手动重启终端后重试"
    homebrew_post_install_en="Homebrew is still not available after installation, please restart the terminal and try again"

    # homebrew_required
    homebrew_required_zh="必须安装 Homebrew 才能继续!"
    homebrew_required_en="Homebrew must be installed to continue!"

    # installing_deps_with_brew
    installing_deps_with_brew_zh="使用 Homebrew 安装依赖"
    installing_deps_with_brew_en="Installing dependencies with Homebrew"

    # unrecognized_linux_distro
    unrecognized_linux_distro_zh="无法识别的 Linux 发行版,请手动安装依赖:{0}"
    unrecognized_linux_distro_en="Unrecognized Linux distribution, please manually install dependencies: {0}"

    # unsupported_os
    unsupported_os_zh="不支持的操作系统"
    unsupported_os_en="Unsupported operating system"

    # install_failed
    install_failed_zh="安装失败:{0}"
    install_failed_en="Installation failed: {0}"

    # all_deps_installed_success
    all_deps_installed_success_zh="所有依赖已成功安装!"
    all_deps_installed_success_en="All dependencies have been successfully installed!"

    # env_cleanup_start
    env_cleanup_start_zh="开始清理 JetBrains 相关环境变量"
    env_cleanup_start_en="Start cleaning JetBrains related environment variables"

    # no_env_files_found
    no_env_files_found_zh="未找到任何环境变量文件,跳过"
    no_env_files_found_en="No environment variable files found, skipping"

    # file_not_writable
    file_not_writable_zh="文件 {0} 不可写,跳过修改"
    file_not_writable_en="File {0} is not writable, skipping modification"

    # backup_env_file
    backup_env_file_zh="备份环境变量文件: {0}, {1}, {2}"
    backup_env_file_en="Backup environment variable file: {0}, {1}, {2}"

    # removing_env_var
    removing_env_var_zh="删除环境变量: {0},{1}"
    removing_env_var_en="Removing environment variable: {0},{1}"

    # other_tools_env_cleanup
    other_tools_env_cleanup_zh="清理三方工具环境变量完成"
    other_tools_env_cleanup_en="Cleanup of third-party tool environment variables completed"

    # enter_license_name
    enter_license_name_zh="自定义授权名称 (回车默认 ckey.run): "
    enter_license_name_en="Custom license name (Enter for default ckey.run): "

    # enter_expiry_date
    enter_expiry_date_zh="自定义授权日期 (回车默认 {0}, 格式 yyyy-MM-dd): "
    enter_expiry_date_en="Custom expiration date (Enter for default {0}, format yyyy-MM-dd): "

    # entered_expiry_date
    entered_expiry_date_zh="输入的授权日期: {0}"
    entered_expiry_date_en="Entered expiration date: {0}"

    # invalid_date_format
    invalid_date_format_zh="日期格式不合法,请输入正确的 yyyy-MM-dd 格式(例如:2099-12-31)"
    invalid_date_format_en="Invalid date format, please enter the correct yyyy-MM-dd format (e.g. 2099-12-31)"

    # illegal_path_detected
    illegal_path_detected_zh="检测到非法路径: {0},请检查配置。"
    illegal_path_detected_en="Illegal path detected: {0}, please check configuration."

    # creating_work_dirs
    creating_work_dirs_zh="创建工作目录: {0}"
    creating_work_dirs_en="Creating work directories: {0}"

    # download_progress
    download_progress_zh="正在下载: {0} -> {1}"
    download_progress_en="Downloading: {0} -> {1}"

    # sha1_verification_skipped
    sha1_verification_skipped_zh="未找到 {0} 工具,跳过 SHA-1 校验"
    sha1_verification_skipped_en="{0} tool not found, skipping SHA-1 verification"

    # cleaning_vm_file
    cleaning_vm_file_zh="清理vm: {0}"
    cleaning_vm_file_en="Cleaning vm: {0}"

    # cleaning_vm_file_not_exist
    cleaning_vm_file_not_exist_zh="清理vm: 文件不存在,跳过清理: {0}"
    cleaning_vm_file_not_exist_en="Cleaning vm: File does not exist, skipping cleanup: {0}"

    # generating_vm_file
    generating_vm_file_zh="生成vm: {0}"
    generating_vm_file_en="Generating vm: {0}"

    # generating_vm_file_failed
    generating_vm_file_failed_zh="生成vm: 创建失败: {0}"
    generating_vm_file_failed_en="Generating vm: Failed to create: {0}"

    # processing_disabled_plugins_file
    processing_disabled_plugins_file_zh="已处理文件 {0} 中com.intellij.modules.ultimate 项"
    processing_disabled_plugins_file_en="Processed com.intellij.modules.ultimate item in file {0}"

    # url_license_params
    url_license_params="URL_LICENSE:{0},params:{1},save_path:{2}"

    # home_path
    home_path_zh=".home路径: {0}"
    home_path_en=".home path: {0}"

    # home_content
    home_content_zh=".home内容: {0}"
    home_content_en=".home content: {0}"

    # bin_dir_not_exist
    bin_dir_not_exist_zh="{0} 的 bin 目录不存在,请确认是否正确安装!"
    bin_dir_not_exist_en="The bin directory of {0} does not exist, please confirm if it is installed correctly!"

    # vmoptions_not_found_creating
    vmoptions_not_found_creating_zh="未找到{0} 的.vmoptions文件,将创建一个默认的"
    vmoptions_not_found_creating_en=".vmoptions file for {0} not found, will create a default one"

    # config_dir
    config_dir_zh="config目录:{0}"
    config_dir_en="config directory: {0}"

    # dir_not_found
    dir_not_found_zh="未找到{0}目录"
    dir_not_found_en="Directory {0} not found"
}

# Get i18n string
get_i18n_string() {
    local key="$1"
    local var_name="${key}_${LANGUAGE}"

    # Use indirection to fetch variable value
    if [[ -n "${!var_name}" ]]; then
        echo "${!var_name}"
    else
        # If no string found for current language, return the key itself
        echo "${!key}"
    fi
}

# Format string (up to 3 args)
format_string() {
    local template="$1"
    local arg1="$2"
    local arg2="$3"
    local arg3="$4"

    # Replace {0}, {1}, {2} placeholders
    local result="$template"
    result="${result//\{0\}/$arg1}"
    result="${result//\{1\}/$arg2}"
    result="${result//\{2\}/$arg3}"

    echo "$result"
}

# Initialize i18n string dictionary
init_i18n_strings

# ============ Configuration =============
DEBUG=false
ENABLE_COLOR=true

URL_BASE="https://ckey.run"
#URL_BASE="http://192.168.31.254:10768"
URL_DOWNLOAD="${URL_BASE}/ja-netfilter"
URL_LICENSE="${URL_BASE}/generateLicense/file"

# Get original user and home directory
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 user path correction
if [[ "$OS" == "macOS" ]]; then
    USER_HOME="/Users/${ORIGINAL_USER}"
fi

# Working paths
dir_work="${USER_HOME}/.jb_run"
dir_config="${dir_work}/config"
dir_plugins="${dir_work}/plugins"
dir_backups="${dir_work}/backups"
file_netfilter_jar="${dir_work}/ja-netfilter.jar"

# JetBrains directories
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

# Log color setup
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

# Product list
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":"rustrover","productCode":"RR,PSI,PCWMP"}
]'

# Pre-parse products into parallel arrays (avoids repeated jq calls in loops)
PRODUCT_NAMES=()
PRODUCT_CODES=()
while IFS='|' read -r _name _code; do
    [[ -n "$_name" ]] && PRODUCT_NAMES+=("$_name") && PRODUCT_CODES+=("$_code")
done < <(echo "$PRODUCTS" | jq -r '.[] | "\(.name)|\(.productCode)"')
PRODUCT_COUNT=${#PRODUCT_NAMES[@]}

# ============ Utility Functions =============

# ============ Date Validation =============
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

    if [ ${#missing[@]} -eq 0 ]; then
        info "all_deps_installed"
        return
    fi

    local missing_str=$(IFS=", "; echo "${missing[*]}")
    warning "missing_deps" "$missing_str"

    # Detect system type first
    case "$(uname -s)" in
        Darwin)
          # Check if Homebrew exists
          if ! command -v brew &>/dev/null; then
              warning "homebrew_not_found"
              local prompt_msg=$(get_i18n_string "install_homebrew_prompt")
              read -p "$prompt_msg" install_brew
              if [[ "$install_brew" =~ [yY] ]]; then
                  info "installing_homebrew"
                  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

                  # Auto-configure environment
                  # Don't source ~/.zshrc (zsh-only syntax would break this bash session)
                  # Export PATH directly in this session; new shells pick up via the written shellenv line
                  if [ -x "/opt/homebrew/bin/brew" ]; then  # Apple Silicon
                      echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc
                      export PATH="/opt/homebrew/bin:$PATH"
                  elif [ -x "/usr/local/bin/brew" ]; then  # Intel
                      echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zshrc
                      export PATH="/usr/local/bin:$PATH"
                  fi

                  # Re-verify
                  if ! command -v brew &>/dev/null; then
                      error "homebrew_post_install"
                      exit 1
                  fi
              else
                  error "homebrew_required"
                  exit 1
              fi
          fi

          # Install dependencies
          info "installing_deps_with_brew"
          brew install "${missing[@]}"
          ;;
        Linux)
            # Linux (original logic)
            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 "unrecognized_linux_distro" "$missing_str"
                exit 1
            fi
            ;;
        *)
            error "unsupported_os"
            exit 1
            ;;
    esac

    # Verify installation result
    for dep in "${missing[@]}"; do
        if ! command -v "$dep" &>/dev/null; then
            error "install_failed" "$dep"
            exit 1
        fi
    done

    success "all_deps_installed_success"
}

# ============ Product Parsing =============
# Access via pre-parsed PRODUCT_NAMES[] / PRODUCT_CODES[] parallel arrays
parse_product_from_json() {
    local index="$1"
    echo "${PRODUCT_NAMES[$index]}|${PRODUCT_CODES[$index]}"
}

# ============ Logging =============
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"   "$(_format_log "$@")"; }
info()    { log "INFO"    "$(_format_log "$@")"; }
warning() { log "WARNING" "$(_format_log "$@")"; }
error()   { log "ERROR"   "$(_format_log "$@")"; }
success() { log "SUCCESS" "$(_format_log "$@")"; }

# Internal: fetch i18n string and apply placeholders
_format_log() {
    local msg
    msg=$(get_i18n_string "$1")
    if [[ $# -gt 1 ]]; then
        msg=$(format_string "$msg" "$2" "$3" "$4")
    fi
    printf '%s' "$msg"
}

# ============ ASCII Art =============
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
}

# ============ Clean Environment Variables =============
# Clean up residual state from other activation tools
remove_env_other(){
  local OS_NAME="$OS"
  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 "other_tools_env_cleanup"
}

remove_env_item_vars() {
    local shell_files=(
        "${USER_HOME}/.bash_profile"
        "${USER_HOME}/.bashrc"
        "${USER_HOME}/.zshrc"
        "${USER_HOME}/.profile"
    )

    # Filter to files that actually exist
    local existing_files=()
    for file in "${shell_files[@]}"; do
        [ -f "$file" ] && existing_files+=("$file")
    done

    # If no files exist, return immediately
    [ ${#existing_files[@]} -eq 0 ] && {
        debug "no_env_files_found"
        return
    }

    # Environment variable backup directory
    local dir_date_backup="$dir_backups/$(date +%s)"
    for file in "${existing_files[@]}"; do
      # Check if the file contains the target env var
        if [ ! -w "$file" ]; then
            warning "file_not_writable" "$file" >&2
            continue
        fi

        # Backup env file to dir_backups/<timestamp>
        if [ ! -d "$dir_date_backup" ]; then
            mkdir -p "$dir_date_backup"
        fi

        cp "$file" "${dir_date_backup}/_$(basename ${file})"
        debug "backup_env_file" "$file" "$dir_date_backup" "_$(basename ${file})"

        # Check env config files
        local index=0
        while [ $index -lt $PRODUCT_COUNT ]; do
            local name="${PRODUCT_NAMES[$index]}"
            local code="${PRODUCT_CODES[$index]}"

            if [ -z "$name" ]; then
                break
            fi

            local upper_key="$(echo "${name}" | tr '[:lower:]' '[:upper:]')_VM_OPTIONS"
            # Check if the file contains upper_key
            if grep -q "^${upper_key}" "$file"; then
                sed -i -E "/${upper_key}/d" "$file"
                debug "removing_env_var" "$file" "$upper_key"
            fi
            ((index++))
        done
        # No longer sourcing rc files:
        #   1) We're in bash, can't source .zshrc (zsh-only syntax breaks)
        #   2) sed already cleaned in-place; next interactive shell reads the clean version
    done
}

remove_env_vars() {
    info "env_cleanup_start"
    remove_env_item_vars
    # Remove residual from other activation tools
    remove_env_other
}

# ============ User License Input =============
validate_date_format() {
    local input="$1"

    # Step 1: validate yyyy-MM-dd format
    if [[ ! "$input" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
        warning "format_error"
        return 1
    fi

    # Step 2: return input as-is (no need to invoke date for validation)
    echo "$input"
    return 0
}

read_license_info() {
    local enter_license_name_msg=$(get_i18n_string "enter_license_name")
    read -p "$enter_license_name_msg" license_name
    license_name=${license_name:-ckey.run}

    local default_expiry="2099-12-31"
    local expiry_input
    local valid=false

    while [ "$valid" == "false" ]; do
        local enter_expiry_date_msg=$(get_i18n_string "enter_expiry_date")
        read -p "$(format_string "$enter_expiry_date_msg" "$default_expiry") " expiry_input
        expiry_input=${expiry_input:-$default_expiry}
        debug "entered_expiry_date" "$expiry_input"
        if expiry=$(validate_date_format "$expiry_input"); then
            valid=true
        else
            warning "invalid_date_format"
        fi
    done

    LICENSE_JSON=$(cat <<EOF
{
  "assigneeName": "",
  "expiryDate": "$expiry",
  "licenseName": "$license_name",
  "productCode": ""
}
EOF
)
}

# ============ Create Work Directories =============
do_create_work_dir() {
    if [[ "${dir_work}" == "/" || -z "${dir_work}" ]]; then
        error "illegal_path_detected" "${dir_work}"
        exit 1
    fi

    if [ -d "${dir_work}" ]; then
        rm -rf "${dir_plugins}" "${dir_config}" "${file_netfilter_jar}" || {
            error "file_in_use"
            exit 1
        }
    fi
    mkdir -p "${dir_config}" "${dir_plugins}" "${dir_backups}" || {
         error  "dir_not_found" "${dir_config}"
         exit 1
     }
     debug "creating_work_dirs" "${dir_work}"
}

# ============ File Download =============
download_one_file() {
    local url="$1"
    local file_save_path="$2"
    debug "download_progress" "${url}" "${file_save_path}"
    curl -s -o "${file_save_path}" "${url}"

    if [ $? -ne 0 ]; then
        error "download_failed" "${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 "sha1_verification_skipped" "$SHA_TOOL"
            return
        fi
        debug "sha1_info" "$url" "$sha1_hash"
    fi
}

progress_bar() {
  # Skip progress bar when DEBUG mode is on
    if [ "$DEBUG" = true ]; then
      return
    fi
    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+="]"
    local configuring_msg=$(get_i18n_string "configuring_ja_netfilter")
    printf "\r%s %d/%d %s %d%%" "$configuring_msg" "$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/env.conf|${dir_config}/env.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/env.jar|${dir_plugins}/env.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 "source_address"
    debug "source_privacy"
    debug "suggest_check_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"
}

# ============ Clean and Update .vmoptions Files =============
clean_vmoptions() {
    local file="$1"
    if [ ! -f "$file" ]; then
        debug "cleaning_vm_file_not_exist" "$file"
        return 0
    fi

    local temp_lines=()
    local keywords=(
        "-javaagent"
        "--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED"
        "--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED"
    )

    while IFS= read -r line; do
        local matched=false
        for keyword in "${keywords[@]}"; do
            [[ "$line" == *"$keyword"* ]] && matched=true && break
        done
        [[ "$matched" == false ]] && temp_lines+=("$line")
    done < "$file"

    if [ ${#temp_lines[@]} -gt 0 ]; then
        printf "%s\n" "${temp_lines[@]}" > "$file"
    else
        : > "$file"
    fi
    debug "cleaning_vm_file" "$file"
}

append_vmoptions() {
    local file="$1"
    if [ ! -f "$file" ]; then
        touch "$file" || {
            error "generating_vm_file_failed" "$file"
            return
        }
    fi

    cat >> "$file" <<EOF
-javaagent:${file_netfilter_jar}
EOF
    debug "generating_vm_file" "$file"
}


# Read file; if a com.intellij.modules.ultimate line exists, clear it
process_disabled_plugins() {
    local file_disabled_plugins="$1"
    # Parameter validation
    if [ -z "$file_disabled_plugins" ]; then
        debug "file_path_empty"
        return
    fi

    # Check if file exists
    if [ ! -f "$file_disabled_plugins" ]; then
        debug "file_not_exist_skip" "$file_disabled_plugins"
        return
    fi

    # Replace matching lines with empty in-place (i.e. delete them)
    # sed syntax compatibility for macOS and Linux
    if [[ "$OS" == "macOS" ]]; then
        sed -i '' '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
    else
        sed -i '/^com.intellij.modules.ultimate$/d' "$file_disabled_plugins"
    fi
    debug "processing_disabled_plugins_file" "$file_disabled_plugins"
}

# ============ Generate License =============
generate_license() {
    local obj_product_name="$1"
    local obj_product_code="$2"
    local dir_product_name="$3"
    local file_license="${dir_config_jb}/${dir_product_name}/${obj_product_name}.key"
    local file_disabled_plugins="${dir_config_jb}/${dir_product_name}/disabled_plugins.txt"

    process_disabled_plugins "$file_disabled_plugins"

    [ -f "$file_license" ] && rm -f "$file_license"

    local json_body=$(jq --arg code "$obj_product_code" '.productCode = $code' <<< "$LICENSE_JSON")
    debug "url_license_params" "$URL_LICENSE" "$json_body" "$file_license"
    curl -s -X POST "$URL_LICENSE" \
        -H "Content-Type: application/json" \
        -d "$json_body" \
        -o "$file_license" > /dev/null

    if [ $? -eq 0 ]; then
        success "activation_success" "$dir_product_name"
    else
        warning "manual_activation_required" "$dir_product_name"
    fi
}

# ============ Handle Single JetBrains Product =============
handle_jetbrains_dir() {
    local dir="$1"
    local dir_product_name=$(basename "$dir")
    local obj_product_name=""
    local obj_product_code=""

    for ((i = 0; i < PRODUCT_COUNT; i++)); do
        local name="${PRODUCT_NAMES[$i]}"
        local code="${PRODUCT_CODES[$i]}"
        local lowercase_dir=$(echo "${dir_product_name}" | tr '[:upper:]' '[:lower:]')
        if [[ "$lowercase_dir" == *"$name"* ]]; then
            obj_product_name="$name"
            obj_product_code="$code"
            break
        fi
    done

    [ -z "$obj_product_name" ] && return

    info "processing_product" "$dir_product_name"

    local file_home="${dir}/.home"
    [ -f "$file_home" ] || {
        warning "not_found_home" "$file_home"
        return
    }

    debug "home_path" "$file_home"

    local install_path=$(cat "$file_home")
    [ -d "$install_path" ] || {
        warning "path_not_exist" "$install_path"
        return
    }

    debug "home_content" "$install_path"

    local dir_bin="${install_path}/bin"
    [ -d "$dir_bin" ] || {
        warning "bin_dir_not_exist" "$dir_product_name"
        return
    }

    local dir_config_product="${dir_config_jb}/${dir_product_name}"

    # First, find all .vmoptions files
    local files=("${dir_config_product}"/*${FILE_VMOPTIONS})

    # Check whether any files were actually found
    if [[ -f "${files[0]}" ]]; then
      for file_vmoption in "${files[@]}"; do
        clean_vmoptions "$file_vmoption"
        append_vmoptions "$file_vmoption"
      done
    else
      debug "vmoptions_not_found_creating" "$dir_product_name"
      append_vmoptions "${dir_config_product}/${obj_product_name}${FILE_VMOPTIONS}"
    fi

    # If ${dir_config_product}/jetbrains_client.vmoptions does not exist, create a default one
    local file_jetbrains_client="${dir_config_product}/jetbrains_client.vmoptions"
    if [ ! -f "${file_jetbrains_client}" ]; then
        append_vmoptions "${file_jetbrains_client}"
    else
        clean_vmoptions "${file_jetbrains_client}"
        append_vmoptions "${file_jetbrains_client}"
    fi

    generate_license "$obj_product_name" "$obj_product_code" "$dir_product_name"
}

# ============ Main Flow =============
main() {
    clear
    show_ascii_jb

    info "welcome_msg"

    warning "script_date"

    warning "warning_msg"

    warning "close_products"
    read -r

    read_license_info

    info "processing"

    check_and_install_deps

    if [ ! -d "${dir_config_jb}" ]; then
        error "dir_not_found" "${dir_config_jb}"
        exit 1
    fi

    debug "config_dir" "${dir_config_jb}"

    do_create_work_dir

    remove_env_vars

    do_download_resources

    if [ ! -d "${dir_cache_jb}" ]; then
        warning "dir_not_found" "${dir_cache_jb}"
    else
        for dir in "${dir_cache_jb}"/*; do
            [ -d "$dir" ] && handle_jetbrains_dir "$dir"
        done
    fi

    info "processing_completed"
    sleep 1
    $OPEN_CMD "$URL_BASE" &>/dev/null
}

main "$@"
# Delete self
rm -f "${BASH_SOURCE[0]}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment