#!/bin/bash # 批量搭建ss2022入站到批量sk5出站代理,链式代理 # 读取sk5文件实现批量导入出站 # 作者sky22333 red='\e[31m' yellow='\e[33m' green='\e[32m' none='\e[0m' config_file="/usr/local/etc/xray/config.json" default_config=' { "inbounds": [ { "port": 9999, "protocol": "shadowsocks", "settings": { "method": "2022-blake3-aes-256-gcm", "password": "75ENbpfSCyzUdZnLRjVGexaQxVPdCLw5T4RXbTGRQ/Q=", "network": "tcp,udp" }, "tag": "inbound0" } ], "outbounds": [ { "protocol": "socks", "settings": { "servers": [ { "address": "127.0.0.2", "port": 2222, "users": [ { "user": "admin123", "pass": "admin333" } ] } ] }, "tag": "outbound0" } ], "routing": { "rules": [ { "type": "field", "inboundTag": ["inbound0"], "outboundTag": "outbound0" } ] } } ' check_and_install_curl() { if ! type curl &>/dev/null; then echo -e "${yellow}正在安装curl...${none}" apt update && apt install -yq curl fi } check_and_install_jq() { if ! type jq &>/dev/null; then echo -e "${yellow}正在安装jq...${none}" apt update && apt install -yq jq fi } check_and_install_openssl() { if ! type openssl &>/dev/null; then echo -e "${yellow}正在安装 openssl...${none}" apt update && apt install -yq openssl fi } check_and_install_xray() { if ! type xray &>/dev/null; then echo -e "${yellow}正在安装 xray...${none}" sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.13 fi } check_existing_inbound_config() { if grep -q '"tag":' "$config_file"; then return 0 else return 1 fi } create_default_config() { if ! check_existing_inbound_config; then echo "$default_config" > "$config_file" echo -e "${green}已创建默认配置文件。${none}" else echo -e "${yellow}入站配置已存在,跳过创建默认配置文件。${none}" fi } get_local_ip() { local ip=$(curl -s http://ipinfo.io/ip) if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "$ip" else echo "无法自动获取公网IP地址,请手动输入。" read -p "请输入您的公网IP地址: " manual_ip if [[ $manual_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "$manual_ip" else echo "输入的IP地址格式不正确,请重新运行脚本并输入有效的公网IP地址。" exit 1 fi fi } get_ss_filename() { local timestamp=$(date +"%Y%m%d-%H点%M分%S秒") echo "/home/${timestamp}-ss.txt" } generate_ss_link() { local server=$1 local port=$2 local method=$3 local password=$4 local ps=$5 local password_urlencoded=$(echo -n "$password" | xxd -p | tr -d '\n' | sed 's/\(..\)/%\1/g') local base64_part=$(echo -n "${method}:${password}" | base64 -w 0) echo "ss://${base64_part}@${server}:${port}#${ps}" } save_multiple_ss_links() { local local_ip=$1 shift local ss_file=$(get_ss_filename) > "$ss_file" while [[ $# -gt 0 ]]; do local port=$1 local password=$2 local method=$3 local index=$4 shift 4 local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file") if [[ -z "$sk5_ip" ]]; then sk5_ip="未知IP" fi local ss_link=$(generate_ss_link "$local_ip" "$port" "$method" "$password" "$sk5_ip") echo "$ss_link" >> "$ss_file" done echo -e "${green}已将操作的所有节点保存至 $ss_file${none}" } save_multiple_ss_links_with_ps() { local local_ip=$1 shift local ss_file=$(get_ss_filename) > "$ss_file" while [[ $# -gt 0 ]]; do local port=$1 local password=$2 local method=$3 local index=$4 local sk5_ip=$5 shift 5 local ss_link=$(generate_ss_link "$local_ip" "$port" "$method" "$password" "$sk5_ip") echo "$ss_link" >> "$ss_file" done echo -e "${green}已将操作的所有节点保存至 $ss_file${none}" } save_all_ss_links() { local local_ip=$(get_local_ip) local ss_file=$(get_ss_filename) > "$ss_file" local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file") local length=$(jq '. | length' <<< "$config") for ((i = 0; i < length; i++)); do local port=$(jq -r ".[$i].port" <<< "$config") local method=$(jq -r ".[$i].settings.method" <<< "$config") local password=$(jq -r ".[$i].settings.password" <<< "$config") local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file") if [[ -z "$sk5_ip" ]]; then sk5_ip="未知IP" fi # 生成SS链接 local ss_link=$(generate_ss_link "$local_ip" "$port" "$method" "$password" "$sk5_ip") # 写入文件 echo "$ss_link" >> "$ss_file" done echo -e "${green}已将全部Shadowsocks节点保存至 $ss_file${none}" } show_inbound_configs() { local local_ip=$(get_local_ip) local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file") local outbounds=$(jq '.outbounds' "$config_file") echo -e "${green}入站节点配置:${none}" local length=$(jq '. | length' <<< "$config") for ((i = 0; i < length; i++)); do local port=$(jq -r ".[$i].port" <<< "$config") local method=$(jq -r ".[$i].settings.method" <<< "$config") local password=$(jq -r ".[$i].settings.password" <<< "$config") local node_address="$local_ip" local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file") if [[ -z "$sk5_ip" ]]; then sk5_ip="未知IP" fi local ss_link=$(generate_ss_link "$node_address" "$port" "$method" "$password" "$sk5_ip") echo -e "${yellow}节点: $(($i + 1))${none} - 端口: ${port}, Shadowsocks 链接: ${ss_link}" # 构造出站配置的标签 local outbound_tag="outbound$port" # 根据构造的标签查找对应的出站配置 local outbound_config=$(jq --arg tag "$outbound_tag" '.[] | select(.tag == $tag) | .settings.servers[] | {address, port, user: .users[0].user, pass: .users[0].pass}' <<< "$outbounds") if [[ ! -z $outbound_config ]]; then echo -e "${green}出站配置:${none} 地址: $(jq -r '.address' <<< "$outbound_config"), 端口: $(jq -r '.port' <<< "$outbound_config"), 用户名: $(jq -r '.user' <<< "$outbound_config"), 密码: $(jq -r '.pass' <<< "$outbound_config")" else echo -e "${red}未找到对应的出站配置。${none}" fi done save_all_ss_links } add_new_nodes() { local sk5_file="/home/sk5.txt" # 检查sk5.txt文件是否存在 if [ ! -f "$sk5_file" ]; then echo -e "${red}错误!${none} $sk5_file 文件不存在。" return fi # 读取sk5.txt文件中的代理信息 local sk5_proxies=() while IFS= read -r line || [[ -n "$line" ]]; do # 忽略空行 if [[ -z "$line" ]]; then continue fi sk5_proxies+=("$line") done < "$sk5_file" local proxy_count=${#sk5_proxies[@]} if [ $proxy_count -eq 0 ]; then echo -e "${red}错误!${none} 未在 $sk5_file 中找到有效的代理配置。" return fi echo -e "${green}从 $sk5_file 读取到 $proxy_count 个代理配置。${none}" read -p "是否要导入全部配置?(y/n): " confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then read -p "请输入要导入的代理数量 (最大 $proxy_count): " num_to_import if ! [[ $num_to_import =~ ^[0-9]+$ ]] || [ $num_to_import -le 0 ] || [ $num_to_import -gt $proxy_count ]; then echo -e "${red}错误!${none} 输入数量无效。" return fi else num_to_import=$proxy_count fi local max_port=$(jq '[.inbounds[].port] | max // 10000' "$config_file") local start_port=$((max_port+1)) local local_ip=$(get_local_ip) local nodes_to_save=() for ((i=0; i "$config_file.tmp" && mv "$config_file.tmp" "$config_file" # 添加出站配置 jq --arg tag "$new_outbound_tag" --arg addr "$outbound_addr" --argjson port "$outbound_port" --arg user "$outbound_user" --arg pass "$outbound_pass" ' .outbounds += [{ protocol: "socks", settings: { servers: [{ address: $addr, port: $port | tonumber, users: [{ user: $user, pass: $pass }] }] }, tag: $tag }]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file" # 添加路由规则 jq --arg inTag "$new_tag" --arg outTag "$new_outbound_tag" ' .routing.rules += [{ type: "field", inboundTag: [$inTag], outboundTag: $outTag }] ' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file" # 保存节点信息以便后续生成SS链接 nodes_to_save+=("$new_port" "$new_password" "$method" "$((i+1))") done # 保存所有新添加的节点到一个文件 save_multiple_ss_links "$local_ip" "${nodes_to_save[@]}" echo -e "${green}已成功添加 $num_to_import 个节点。${none}" sudo systemctl restart xray echo -e "${green}Xray 服务已重新启动。${none}" } # 根据xiugai.txt文件修改SOCKS5出站代理 modify_socks5_outbound() { local modify_file="/home/xiugai.txt" # 检查xiugai.txt文件是否存在 if [ ! -f "$modify_file" ]; then echo -e "${red}错误!${none} $modify_file 文件不存在。" return fi # 读取xiugai.txt文件中的代理信息 local modify_proxies=() while IFS= read -r line || [[ -n "$line" ]]; do # 忽略空行 if [[ -z "$line" ]]; then continue fi modify_proxies+=("$line") done < "$modify_file" # 检查是否读取到代理 local proxy_count=${#modify_proxies[@]} if [ $proxy_count -eq 0 ]; then echo -e "${red}错误!${none} 未在 $modify_file 中找到有效的代理配置。" return fi echo -e "${green}从 $modify_file 读取到 $proxy_count 个代理配置。${none}" local local_ip=$(get_local_ip) local nodes_to_save=() # 处理每个要修改的代理 for proxy in "${modify_proxies[@]}"; do IFS=':' read -r old_ip new_port new_user new_pass <<< "$proxy" if [[ -z "$old_ip" || -z "$new_port" || -z "$new_user" || -z "$new_pass" ]]; then echo -e "${red}警告:${none} 代理格式无效,终止脚本运行: $proxy" exit 1 fi # 查找匹配的出站节点 local outbound_config=$(jq --arg ip "$old_ip" '.outbounds[] | select(.protocol == "socks" and .settings.servers[0].address == $ip) | {tag: .tag, address: .settings.servers[0].address, port: .settings.servers[0].port, user: .settings.servers[0].users[0].user, pass: .settings.servers[0].users[0].pass}' "$config_file") if [[ -z "$outbound_config" ]]; then echo -e "${red}警告:${none} 未找到IP地址为 $old_ip 的SOCKS5出站节点,终止脚本运行" exit 1 fi local tag=$(echo "$outbound_config" | jq -r '.tag') local old_port=$(echo "$outbound_config" | jq -r '.port') local old_user=$(echo "$outbound_config" | jq -r '.user') local old_pass=$(echo "$outbound_config" | jq -r '.pass') echo -e "${yellow}找到匹配的出站节点:${none} 标签=$tag, 旧IP=$old_ip, 旧端口=$old_port, 旧用户名=$old_user, 旧密码=$old_pass" echo -e "${green}将更新为:${none} 新IP=$old_ip, 新端口=$new_port, 新用户名=$new_user, 新密码=$new_pass" # 更新SOCKS5出站配置 local temp_file=$(mktemp) jq --arg tag "$tag" \ --arg ip "$old_ip" \ --arg port "$new_port" \ --arg user "$new_user" \ --arg pass "$new_pass" \ '(.outbounds[] | select(.tag == $tag) | .settings.servers[0].address) = $ip | (.outbounds[] | select(.tag == $tag) | .settings.servers[0].port) = ($port | tonumber) | (.outbounds[] | select(.tag == $tag) | .settings.servers[0].users[0].user) = $user | (.outbounds[] | select(.tag == $tag) | .settings.servers[0].users[0].pass) = $pass' \ "$config_file" > "$temp_file" if [ $? -eq 0 ] && [ -s "$temp_file" ]; then mv "$temp_file" "$config_file" echo -e "${green}成功修改SOCKS5出站节点配置!${none}" # 查找对应的入站配置并保存节点信息 local inbound_port=${tag//outbound/} local inbound_config=$(jq --arg port "$inbound_port" '.inbounds[] | select(.port == ($port | tonumber))' "$config_file") if [[ -n "$inbound_config" ]]; then local method=$(echo "$inbound_config" | jq -r '.settings.method') local password=$(echo "$inbound_config" | jq -r '.settings.password') local index=$(jq --arg port "$inbound_port" '.inbounds | map(select(.port != 9999)) | map(.port == ($port | tonumber)) | index(true)' "$config_file") # 包含实际的SOCKS5 IP地址作为PS字段 nodes_to_save+=("$inbound_port" "$password" "$method" "$((index+1))" "$old_ip") fi else echo -e "${red}更新配置失败!${none}" rm -f "$temp_file" continue fi done if [[ ${#nodes_to_save[@]} -gt 0 ]]; then save_multiple_ss_links_with_ps "$local_ip" "${nodes_to_save[@]}" fi sudo chmod 755 /usr/local/etc/xray/config.json sudo systemctl restart xray echo -e "${green}Xray 服务已重新启动。${none}" } # 根据xiugai.txt文件删除节点 delete_nodes_by_ip() { local modify_file="/home/xiugai.txt" # 检查xiugai.txt文件是否存在 if [ ! -f "$modify_file" ]; then echo -e "${red}错误!${none} $modify_file 文件不存在。" return fi # 读取xiugai.txt文件中的代理信息 local modify_proxies=() while IFS= read -r line || [[ -n "$line" ]]; do # 忽略空行 if [[ -z "$line" ]]; then continue fi # 只提取IP部分 IFS=':' read -r ip _ <<< "$line" modify_proxies+=("$ip") done < "$modify_file" # 检查是否读取到IP local ip_count=${#modify_proxies[@]} if [ $ip_count -eq 0 ]; then echo -e "${red}错误!${none} 未在 $modify_file 中找到有效的IP地址。" return fi echo -e "${green}从 $modify_file 读取到 $ip_count 个IP地址。${none}" # 处理每个要删除的IP for ip in "${modify_proxies[@]}"; do # 查找匹配的出站节点 local outbound_config=$(jq --arg ip "$ip" '.outbounds[] | select(.protocol == "socks" and .settings.servers[0].address == $ip) | {tag: .tag, port: .settings.servers[0].port}' "$config_file") if [[ -z "$outbound_config" ]]; then echo -e "${red}警告:${none} 未找到IP地址为 $ip 的SOCKS5出站节点,终止脚本运行" exit 1 fi local outbound_tag=$(echo "$outbound_config" | jq -r '.tag') # 从outbound_tag中提取端口号(假设格式为"outbound端口号") local port=${outbound_tag#outbound} echo -e "${yellow}找到匹配的节点:${none} 出站标签=$outbound_tag, IP=$ip, 端口=$port" # 查找对应的入站配置 local inbound_config=$(jq --arg port "$port" '.inbounds[] | select(.port == ($port | tonumber))' "$config_file") if [[ -z "$inbound_config" ]]; then echo -e "${red}警告:${none} 未找到对应端口 $port 的入站配置,继续删除出站配置" else local inbound_tag=$(echo "$inbound_config" | jq -r '.tag') echo -e "${yellow}找到对应的入站配置:${none} 标签=$inbound_tag" # 删除入站配置 jq --arg port "$port" 'del(.inbounds[] | select(.port == ($port | tonumber)))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file" # 删除路由规则(使用实际的inbound_tag而不是构造的标签) jq --arg inTag "$inbound_tag" 'del(.routing.rules[] | select(.inboundTag[] == $inTag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file" fi # 删除出站配置 jq --arg tag "$outbound_tag" 'del(.outbounds[] | select(.tag == $tag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file" echo -e "${green}已成功删除IP地址为 $ip 的节点。${none}" done sudo systemctl restart xray echo -e "${green}Xray 服务已重新启动。${none}" } main_menu() { while true; do echo -e "\n${green}快速批量搭建二级代理脚本-管理菜单:${none}" echo "1. 查看所有节点" echo "2. 新增Shadowsocks入站sk5出站(从/home/sk5.txt文件导入)" echo "3. 删除节点(根据/home/xiugai.txt文件匹配)" echo "4. 修改SOCKS5出站节点(根据/home/xiugai.txt文件匹配)" echo "5. 退出" read -p "请输入选项: " choice case $choice in 1) show_inbound_configs ;; 2) add_new_nodes ;; 3) delete_nodes_by_ip ;; 4) modify_socks5_outbound ;; 5) break ;; *) echo -e "${red}无效的选项,请重新选择。${none}" ;; esac done } check_and_install_curl check_and_install_jq check_and_install_openssl check_and_install_xray create_default_config get_local_ip main_menu