#!/usr/bin/env bash # 当前脚本版本号 VERSION='1.6.13 (2025.12.15)' # Github 反代加速代理,第一个为空相当于直连 GITHUB_PROXY=('' 'https://v6.gh-proxy.org/' 'https://gh-proxy.com/' 'https://hub.glowp.xyz/' 'https://proxy.vvvv.ee/' 'https://ghproxy.lvedong.eu.org/') # 各变量默认值 WS_PATH_DEFAULT='argox' WORK_DIR='/etc/argox' TEMP_DIR='/tmp/argox' TLS_SERVER='addons.mozilla.org' NGINX_PORT='8080' METRICS_PORT='3333' CDN_DOMAIN=("skk.moe" "ip.sb" "time.is" "cfip.xxxxxxxx.tk" "bestcf.top" "cdn.2020111.xyz" "xn--b6gac.eu.org" "cf.090227.xyz") SUBSCRIBE_TEMPLATE="https://raw.githubusercontent.com/fscarmen/client_template/main" DEFAULT_XRAY_VERSION='26.1.18' export DEBIAN_FRONTEND=noninteractive trap "rm -rf $TEMP_DIR; echo -e '\n' ;exit 1" INT QUIT TERM EXIT mkdir -p $TEMP_DIR E[0]="Language:\n 1. English (default) \n 2. 简体中文" C[0]="${E[0]}" E[1]="1. Argo tunnel creation via API --- Automatically completed: Create tunnel > DNS configuration > Origin settings. Thanks to [zmlu] for providing the method: https://raw.githubusercontent.com/zmlu/sba/main/tunnel.sh; 2. Quick Install Mode: Added a one-click installation feature that auto-fills all parameters, simplifying the deployment process. Chinese users can use -l or -L; English users can use -k or -K. Case-insensitive support makes operations more flexible." C[1]="1. Argo 隧道新增通过 API 创建 --- 自动完成:创建隧道 > DNS 配置 > 回源设置。感谢热心网友 [zmlu] 提供的方法: https://raw.githubusercontent.com/zmlu/sba/main/tunnel.sh; 2. 极速安装模式:新增一键安装功能,所有参数自动填充,简化部署流程。中文用户使用 -l 或 -L,英文用户使用 -k 或 -K,大小写均支持,操作更灵活" E[2]="Project to create Argo tunnels and Xray specifically for VPS, detailed:[https://github.com/fscarmen/argox]\n Features:\n\t • Allows the creation of Argo tunnels via Token, Json and ad hoc methods. User can easily obtain the json at https://fscarmen.cloudflare.now.cc .\n\t • Extremely fast installation method, saving users time.\n\t • Support system: Ubuntu, Debian, CentOS, Alpine and Arch Linux 3.\n\t • Support architecture: AMD,ARM and s390x\n" C[2]="本项目专为 VPS 添加 Argo 隧道及 Xray,详细说明: [https://github.com/fscarmen/argox]\n 脚本特点:\n\t • 允许通过 Token, Json 及 临时方式来创建 Argo 隧道,用户通过以下网站轻松获取 json: https://fscarmen.cloudflare.now.cc\n\t • 极速安装方式,大大节省用户时间\n\t • 智能判断操作系统: Ubuntu 、Debian 、CentOS 、Alpine 和 Arch Linux,请务必选择 LTS 系统\n\t • 支持硬件结构类型: AMD 和 ARM\n" E[3]="Input errors up to 5 times.The script is aborted." C[3]="输入错误达5次,脚本退出" E[4]="UUID should be 36 characters, please re-enter \(\${a} times remaining\)" C[4]="UUID 应为36位字符,请重新输入 \(剩余\${a}次\)" E[5]="The script supports Debian, Ubuntu, CentOS, Alpine or Arch systems only. Feedback: [https://github.com/fscarmen/argox/issues]" C[5]="本脚本只支持 Debian、Ubuntu、CentOS、Alpine 或 Arch 系统,问题反馈:[https://github.com/fscarmen/argox/issues]" E[6]="Curren operating system is \$SYS.\\\n The system lower than \$SYSTEM \${MAJOR[int]} is not supported. Feedback: [https://github.com/fscarmen/argox/issues]" C[6]="当前操作是 \$SYS\\\n 不支持 \$SYSTEM \${MAJOR[int]} 以下系统,问题反馈:[https://github.com/fscarmen/argox/issues]" E[7]="Install dependence-list:" C[7]="安装依赖列表:" E[8]="All dependencies already exist and do not need to be installed additionally." C[8]="所有依赖已存在,不需要额外安装" E[9]="To upgrade, press [y]. No upgrade by default:" C[9]="升级请按 [y],默认不升级:" E[10]="(3/8) Please enter Argo Domain (Default is temporary domain if left blank):" C[10]="(3/8) 请输入 Argo 域名 (如果没有,可以跳过以使用 Argo 临时域名):" E[11]="Please enter Argo Token, Argo Json or Cloudflare API\n\n [*] Token: Visit https://dash.cloudflare.com/ , Zero Trust > Networks > Connectors > Create a tunnel > Select Cloudflared\n\n [*] Json: Users can easily obtain it through the following website: https://fscarmen.cloudflare.now.cc\n\n [*] Cloudflare API: Visit https://dash.cloudflare.com/profile/api-tokens > Create Token > Create Custom Token > Add the following permissions:\n - Account > Cloudflare One Connectors: cloudflared > Edit\n - Zone > DNS > Edit\n\n - Account Resources: Include > Required Account\n - Zone Resources: Include > Specific zone > Argo Root Domain" C[11]="请输入 Argo Token, Argo Json 或者 Cloudflare API\n\n [*] Token: 访问 https://dash.cloudflare.com/ ,Zero Trust > 网络 > 连接器 > 创建隧道 > 选择 Cloudflared\n\n [*] Json: 用户通过以下网站轻松获取: https://fscarmen.cloudflare.now.cc\n\n [*] Cloudflare API: 访问 https://dash.cloudflare.com/profile/api-tokens > 创建令牌 > 创建自定义令牌 > 添加以下权限:\n - 帐户 > Cloudflare One连接器: Cloudflared > 编辑\n - 区域 > DNS > 编辑\n\n - 帐户资源: 包括 > 所需账户\n - 区域资源: 包括 > 特定区域 > 所需域名" E[12]="\(6/8\) Please enter Xray UUID \(Default is \$UUID_DEFAULT\):" C[12]="\(6/8\) 请输入 Xray UUID \(默认为 \$UUID_DEFAULT\):" E[13]="\(7/8\) Please enter Xray WS Path \(Default is \$WS_PATH_DEFAULT\):" C[13]="\(7/8\) 请输入 Xray WS 路径 \(默认为 \$WS_PATH_DEFAULT\):" E[14]="Xray WS Path only allow uppercase and lowercase letters, numeric characters, hyphens, underscores, dots and @, please re-enter \(\${a} times remaining\):" C[14]="Xray WS 路径只允许英文大小写、数字、连字符、下划线、点和@字符,请重新输入 \(剩余\${a}次\):" E[15]="ArgoX script has not been installed yet." C[15]="ArgoX 脚本还没有安装" E[16]="ArgoX is completely uninstalled." C[16]="ArgoX 已彻底卸载" E[17]="Version" C[17]="脚本版本" E[18]="New features" C[18]="功能新增" E[19]="System infomation" C[19]="系统信息" E[20]="Operating System" C[20]="当前操作系统" E[21]="Kernel" C[21]="内核" E[22]="Architecture" C[22]="处理器架构" E[23]="Virtualization" C[23]="虚拟化" E[24]="Choose:" C[24]="请选择:" E[25]="Curren architecture \$(uname -m) is not supported. Feedback: [https://github.com/fscarmen/argox/issues]" C[25]="当前架构 \$(uname -m) 暂不支持,问题反馈:[https://github.com/fscarmen/argox/issues]" E[26]="Not install" C[26]="未安装" E[27]="close" C[27]="关闭" E[28]="open" C[28]="开启" E[29]="View links (argox -n)" C[29]="查看节点信息 (argox -n)" E[30]="Change the Argo tunnel (argox -t)" C[30]="更换 Argo 隧道 (argox -t)" E[31]="Sync Argo and Xray to the latest version (argox -v)" C[31]="同步 Argo 和 Xray 至最新版本 (argox -v)" E[32]="Upgrade kernel, turn on BBR, change Linux system (argox -b)" C[32]="升级内核、安装BBR、DD脚本 (argox -b)" E[33]="Uninstall (argox -u)" C[33]="卸载 (argox -u)" E[34]="Install ArgoX script (argo + xray)" C[34]="安装 ArgoX 脚本 (argo + xray)" E[35]="Exit" C[35]="退出" E[36]="Please enter the correct number" C[36]="请输入正确数字" E[37]="successful" C[37]="成功" E[38]="failed" C[38]="失败" E[39]="ArgoX is not installed." C[39]="ArgoX 未安装" E[40]="Argo tunnel is: \$ARGO_TYPE\\\n The domain is: \$ARGO_DOMAIN" C[40]="Argo 隧道类型为: \$ARGO_TYPE\\\n 域名是: \$ARGO_DOMAIN" E[41]="Argo tunnel type:\n 1. Try\n 2. Token or Json" C[41]="Argo 隧道类型:\n 1. Try\n 2. Token 或者 Json" E[42]="\(5/8\) Please select or enter the preferred domain, the default is \${CDN_DOMAIN[0]}:" C[42]="\(5/8\) 请选择或者填入优选域名,默认为 \${CDN_DOMAIN[0]}:" E[43]="\$APP local verion: \$LOCAL.\\\t The newest verion: \$ONLINE" C[43]="\$APP 本地版本: \$LOCAL.\\\t 最新版本: \$ONLINE" E[44]="No upgrade required." C[44]="不需要升级" E[45]="Argo authentication message does not match the rules, neither Token nor Json, script exits. Feedback:[https://github.com/fscarmen/argox/issues]" C[45]="Argo 认证信息不符合规则,既不是 Token,也是不是 Json,脚本退出,问题反馈:[https://github.com/fscarmen/argox/issues]" E[46]="Connect" C[46]="连接" E[47]="The script must be run as root, you can enter sudo -i and then download and run again. Feedback:[https://github.com/fscarmen/argox/issues]" C[47]="必须以root方式运行脚本,可以输入 sudo -i 后重新下载运行,问题反馈:[https://github.com/fscarmen/argox/issues]" E[48]="Downloading the latest version \$APP failed, script exits. Feedback:[https://github.com/fscarmen/argox/issues]" C[48]="下载最新版本 \$APP 失败,脚本退出,问题反馈:[https://github.com/fscarmen/argox/issues]" E[49]="\(8/8\) Please enter the node name. \(Default is \${NODE_NAME_DEFAULT}\):" C[49]="\(8/8\) 请输入节点名称 \(默认为 \${NODE_NAME_DEFAULT}\):" E[50]="\${APP[@]} services are not enabled, node information cannot be output. Press [y] if you want to open." C[50]="\${APP[@]} 服务未开启,不能输出节点信息。如需打开请按 [y]: " E[51]="Install Sing-box multi-protocol scripts [https://github.com/fscarmen/sing-box]" C[51]="安装 Sing-box 协议全家桶脚本 [https://github.com/fscarmen/sing-box]" E[52]="Memory Usage" C[52]="内存占用" E[53]="The xray service is detected to be installed. Script exits." C[53]="检测到已安装 xray 服务,脚本退出!" E[54]="Warp / warp-go was detected to be running. Please enter the correct server IP:" C[54]="检测到 warp / warp-go 正在运行,请输入确认的服务器 IP:" E[55]="The script runs today: \$TODAY. Total: \$TOTAL" C[55]="脚本当天运行次数: \$TODAY,累计运行次数: \$TOTAL" E[56]="\(4/8\) Please enter the Reality port \(Default is \${REALITY_PORT_DEFAULT}\):" C[56]="\(4/8\) 请输入 Reality 的端口号 \(默认为 \${REALITY_PORT_DEFAULT}\):" E[57]="Install sba scripts (argo + sing-box) [https://github.com/fscarmen/sba]" C[57]="安装 sba 脚本 (argo + sing-box) [https://github.com/fscarmen/sba]" E[58]="No server ip, script exits. Feedback:[https://github.com/fscarmen/sing-box/issues]" C[58]="没有 server ip,脚本退出,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[59]="\(2/8\) Please enter VPS IP \(Default is: \${SERVER_IP_DEFAULT}\):" C[59]="\(2/8\) 请输入 VPS IP \(默认为: \${SERVER_IP_DEFAULT}\):" E[60]="Quicktunnel domain can be obtained from: http://\${SERVER_IP_1}:\${METRICS_PORT}/quicktunnel" C[60]="临时隧道域名可以从以下网站获取: http://\${SERVER_IP_1}:\${METRICS_PORT}/quicktunnel" E[61]="Ports are in used: \$REALITY_PORT" C[61]="正在使用中的端口: \$REALITY_PORT" E[62]="Create shortcut [ argox ] successfully." C[62]="创建快捷 [ argox ] 指令成功!" E[63]="The full template can be found at:\n https://t.me/ztvps/67\n https://github.com/chika0801/sing-box-examples/tree/main/Tun" C[63]="完整模板可参照:\n https://t.me/ztvps/67\n https://github.com/chika0801/sing-box-examples/tree/main/Tun" E[64]="subscribe" C[64]="订阅" E[65]="To uninstall Nginx press [y], it is not uninstalled by default:" C[65]="如要卸载 Nginx 请按 [y],默认不卸载:" E[66]="Adaptive Clash / V2rayN / NekoBox / ShadowRocket / SFI / SFA / SFM Clients" C[66]="自适应 Clash / V2rayN / NekoBox / ShadowRocket / SFI / SFA / SFM 客户端" E[67]="template" C[67]="模版" E[68]="(1/8) Output subscription QR code and https service, need to install nginx\n If not, please enter [n]. Default installation:" C[68]="(1/8) 输出订阅二维码和 https 服务,需要安装依赖 nginx\n 如不需要,请输入 [n],默认安装:" E[69]="Set SElinux: enforcing --> disabled" C[69]="设置 SElinux: enforcing --> disabled" E[70]="ArgoX is not installed and cannot change the CDN." C[70]="ArgoX 未安装,不能更换 CDN" E[71]="Current CDN is: \${CDN_NOW}" C[71]="当前 CDN 为: \${CDN_NOW}" E[72]="Please select or enter a new CDN (press Enter to keep the current one):" C[72]="请选择或输入新的 CDN (回车保持当前值):" E[73]="CDN has been changed from \${CDN_NOW} to \${CDN_NEW}" C[73]="CDN 已从 \${CDN_NOW} 更改为 \${CDN_NEW}" E[74]="Unable to access api.github.com. This may be due to IP restrictions (HTTP/1.1 403 Rate Limit Exceeded). Please try again later" C[74]="无法访问 api.github.com,可能是由于 IP 限制导致的(HTTP/1.1 403 Rate Limit Exceeded),请稍后重试" E[75]="Nekobox: Set UoT to 2 to enable UDP over TCP" C[75]="Nekobox: 把 UoT 设置为2,以开启 UDP over TCP" E[76]="Change preferred domain or IP (argox -d)" C[76]="更换优选域名或 IP (argox -d)" E[77]="Quick install mode (argox -k)" C[77]="极速安装模式 (argox -l)" E[78]="Using Cloudflare API to create Tunnel and handle DNS config..." C[78]="使用 Cloudflare API 创建 Tunnel 和处理 DNS 配置..." E[79]="Found existing tunnel with the same name. Tunnel ID: \$EXISTING_TUNNEL_ID. Status: \$EXISTING_TUNNEL_STATUS. Overwrite? [y/N] \(default y\):" C[79]="发现同名隧道已创建,隧道 ID: \$EXISTING_TUNNEL_ID,状态: \$EXISTING_TUNNEL_STATUS。是否覆盖? [y/N] \(默认为 y\):" E[80]="Continue with quick fast tunnel" C[80]="使用临时隧道继续" E[81]="Invalid access token. Please roll at https://dash.cloudflare.com/profile/api-tokens to re-generate." C[81]="Token 访问令牌无效。请在 https://dash.cloudflare.com/profile/api-tokens 轮转,以重新获取" E[82]="Network request URL structure is wrong. Missing Zone ID" C[82]="网络请求地址(URL)结构不对,缺少 Zone ID" E[83]="Token zone resource failed. The tunnel root domain and the authorized domain of the token are inconsistent. Please go to https://dash.cloudflare.com/profile/api-tokens to re-authorize." C[83]="Token 区域资源获取失败,隧道的根域名和 Token 授权的域名不一致,请到 https://dash.cloudflare.com/profile/api-tokens 检查" E[84]="API execution failed. Response: \$RESPONSE" C[84]="执行 API 失败,返回: \$RESPONSE" E[85]="API does not have enough permissions. Please check at https://dash.cloudflare.com/profile/api-tokens\n\n [*] Token: Visit https://dash.cloudflare.com/ , Zero Trust > Networks > Connectors > Create a tunnel > Select Cloudflared\n\n [*] Json: Users can easily obtain it through the following website: https://fscarmen.cloudflare.now.cc\n\n [*] Cloudflare API: Visit https://dash.cloudflare.com/profile/api-tokens > Create Token > Create Custom Token > Add the following permissions:\n - Account > Cloudflare One Connectors: cloudflared > Edit\n - Zone > DNS > Edit\n\n - Account Resources: Include > Required Account\n - Zone Resources: Include > Specific zone > Argo Root Domain" C[85]="API 没有足够权限,请在 https://dash.cloudflare.com/profile/api-tokens 检查 Token 权限配置\n\n [*] Token: 访问 https://dash.cloudflare.com/ ,Zero Trust > 网络 > 连接器 > 创建隧道 > 选择 Cloudflared\n\n [*] Json: 用户通过以下网站轻松获取: https://fscarmen.cloudflare.now.cc\n\n [*] Cloudflare API: 访问 https://dash.cloudflare.com/profile/api-tokens > 创建令牌 > 创建自定义令牌 > 添加以下权限:\n - 帐户 > Cloudflare One连接器: Cloudflared > 编辑\n - 区域 > DNS > 编辑\n\n - 帐户资源: 包括 > 所需账户\n - 区域资源: 包括 > 特定区域 > 所需域名" E[86]="Please enter [Token, Json, API] value:" C[86]="请输入 [Token, Json, API] 的值:" # 自定义字体彩色,read 函数 warning() { echo -e "\033[31m\033[01m$*\033[0m"; } # 红色 error() { echo -e "\033[31m\033[01m$*\033[0m" && exit 1; } # 红色 info() { echo -e "\033[32m\033[01m$*\033[0m"; } # 绿色 hint() { echo -e "\033[33m\033[01m$*\033[0m"; } # 黄色 reading() { read -rp "$(info "$1")" "$2"; } text() { grep -q '\$' <<< "${E[$*]}" && eval echo "\$(eval echo "\${${L}[$*]}")" || eval echo "\${${L}[$*]}"; } # 检测是否需要启用 Github CDN,如能直接连通,则不使用 check_cdn() { # GITHUB_PROXY 数组第一个元素为空,相当于直连 for PROXY_URL in "${GITHUB_PROXY[@]}"; do local PROXY_STATUS_CODE=$(wget --server-response --spider --quiet --timeout=3 --tries=1 ${PROXY_URL}https://api.github.com/repos/XTLS/Xray-core/releases/latest 2>&1 | awk '/HTTP\//{last_field = $2} END {print last_field}') [ "$PROXY_STATUS_CODE" = "200" ] && GH_PROXY="$PROXY_URL" && break done } # 检测是否解锁 chatGPT,以决定是否使用 warp 链式代理或者是 direct out,此处判断改编自 https://github.com/lmc999/RegionRestrictionCheck check_chatgpt() { local CHECK_STACK=$1 local UA_BROWSER="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" local UA_SEC_CH_UA='"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"' wget --help | grep -q '\-\-ciphers' && local IS_CIPHERS=is_ciphers # 首先检查API访问 local CHECK_RESULT1=$(wget --timeout=2 --tries=2 --retry-connrefused --waitretry=5 ${CHECK_STACK} -qO- --content-on-error --header='authority: api.openai.com' --header='accept: */*' --header='accept-language: en-US,en;q=0.9' --header='authorization: Bearer null' --header='content-type: application/json' --header='origin: https://platform.openai.com' --header='referer: https://platform.openai.com/' --header="sec-ch-ua: ${UA_SEC_CH_UA}" --header='sec-ch-ua-mobile: ?0' --header='sec-ch-ua-platform: "Windows"' --header='sec-fetch-dest: empty' --header='sec-fetch-mode: cors' --header='sec-fetch-site: same-site' --user-agent="${UA_BROWSER}" 'https://api.openai.com/compliance/cookie_requirements') grep -q "^$" <<< "$CHECK_RESULT1" && grep -qw is_ciphers <<< "$IS_CIPHERS" && local CHECK_RESULT1=$(wget --timeout=2 --tries=2 --retry-connrefused --waitretry=5 ${CHECK_STACK} --ciphers=DEFAULT@SECLEVEL=1 --no-check-certificate -qO- --content-on-error --header='authority: api.openai.com' --header='accept: */*' --header='accept-language: en-US,en;q=0.9' --header='authorization: Bearer null' --header='content-type: application/json' --header='origin: https://platform.openai.com' --header='referer: https://platform.openai.com/' --header="sec-ch-ua: ${UA_SEC_CH_UA}" --header='sec-ch-ua-mobile: ?0' --header='sec-ch-ua-platform: "Windows"' --header='sec-fetch-dest: empty' --header='sec-fetch-mode: cors' --header='sec-fetch-site: same-site' --user-agent="${UA_BROWSER}" 'https://api.openai.com/compliance/cookie_requirements') # 如果API检测失败或者检测到unsupported_country,直接返回ban if grep -q "^$" <<< "$CHECK_RESULT1" || grep -qi 'unsupported_country' <<< "$CHECK_RESULT1"; then echo "ban" return fi # API检测通过后,继续检查网页访问 local CHECK_RESULT2=$(wget --timeout=2 --tries=2 --retry-connrefused --waitretry=5 ${CHECK_STACK} -qO- --content-on-error --header='authority: ios.chat.openai.com' --header='accept: */*;q=0.8,application/signed-exchange;v=b3;q=0.7' --header='accept-language: en-US,en;q=0.9' --header="sec-ch-ua: ${UA_SEC_CH_UA}" --header='sec-ch-ua-mobile: ?0' --header='sec-ch-ua-platform: "Windows"' --header='sec-fetch-dest: document' --header='sec-fetch-mode: navigate' --header='sec-fetch-site: none' --header='sec-fetch-user: ?1' --header='upgrade-insecure-requests: 1' --user-agent="${UA_BROWSER}" https://ios.chat.openai.com/) [ -z "$CHECK_RESULT2" ] && grep -qw is_ciphers <<< "$IS_CIPHERS" && local CHECK_RESULT2=$(wget --timeout=2 --tries=2 --retry-connrefused --waitretry=5 ${CHECK_STACK} --ciphers=DEFAULT@SECLEVEL=1 --no-check-certificate -qO- --content-on-error --header='authority: ios.chat.openai.com' --header='accept: */*;q=0.8,application/signed-exchange;v=b3;q=0.7' --header='accept-language: en-US,en;q=0.9' --header="sec-ch-ua: ${UA_SEC_CH_UA}" --header='sec-ch-ua-mobile: ?0' --header='sec-ch-ua-platform: "Windows"' --header='sec-fetch-dest: document' --header='sec-fetch-mode: navigate' --header='sec-fetch-site: none' --header='sec-fetch-user: ?1' --header='upgrade-insecure-requests: 1' --user-agent="${UA_BROWSER}" https://ios.chat.openai.com/) # 检查第二个结果 if [ -z "$CHECK_RESULT2" ] || grep -qi 'VPN' <<< "$CHECK_RESULT2"; then echo "ban" else echo "unlock" fi } # 脚本当天及累计运行次数统计 statistics_of_run-times() { local UPDATE_OR_GET=$1 local SCRIPT=$2 if grep -q 'update' <<< "$UPDATE_OR_GET"; then { wget --no-check-certificate -qO- --timeout=3 "https://stat.cloudflare.now.cc/api/updateStats?script=${SCRIPT}" > $TEMP_DIR/statistics 2>/dev/null || true; }& elif grep -q 'get' <<< "$UPDATE_OR_GET"; then [ -s $TEMP_DIR/statistics ] && [[ $(cat $TEMP_DIR/statistics) =~ \"todayCount\":([0-9]+),\"totalCount\":([0-9]+) ]] && local TODAY="${BASH_REMATCH[1]}" && local TOTAL="${BASH_REMATCH[2]}" && rm -f $TEMP_DIR/statistics hint "\n*******************************************\n\n $(text 55) \n" fi } # 选择中英语言 select_language() { if [ -z "$L" ]; then case $(cat $WORK_DIR/language 2>&1) in E ) L=E ;; C ) L=C ;; * ) [ -z "$L" ] && L=E && ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && hint "\n $(text 0) \n" && reading " $(text 24) " LANGUAGE [ "$LANGUAGE" = 2 ] && L=C ;; esac fi } # 只允许 root 用户安装脚本 check_root() { [ "$(id -u)" != 0 ] && error "\n $(text 47) \n" } # 判断处理器架构 check_arch() { case $(uname -m) in aarch64|arm64 ) ARGO_ARCH=arm64; XRAY_ARCH=arm64-v8a; JQ_ARCH=arm64; QRENCODE_ARCH=arm64 ;; x86_64|amd64 ) ARGO_ARCH=amd64; XRAY_ARCH=64; JQ_ARCH=amd64; QRENCODE_ARCH=amd64 ;; armv7l ) ARGO_ARCH=arm; XRAY_ARCH=arm32-v7a; JQ_ARCH=armhf; QRENCODE_ARCH=arm ;; * ) error " $(text 25) " esac } # 查安装及运行状态,下标0: argo,下标1: xray,下标2:docker;状态码: 26 未安装, 27 已安装未运行, 28 运行中 check_install() { [ -s $WORK_DIR/nginx.conf ] && IS_NGINX=is_nginx || IS_NGINX=no_nginx STATUS[0]=$(text 26) # 检查 argo 服务 [ -s ${ARGO_DAEMON_FILE} ] && STATUS[0]=$(text 27) && cmd_systemctl status argo &>/dev/null && STATUS[0]=$(text 28) STATUS[1]=$(text 26) # xray systemd 文件存在的话,检测一下是否本脚本安装的,如果不是则提示并提出 if [ -s ${XRAY_DAEMON_FILE} ]; then ! grep -q "$WORK_DIR" ${XRAY_DAEMON_FILE} && error " $(text 53)\n $(grep "${DAEMON_RUN_PATTERN}" ${XRAY_DAEMON_FILE}) " STATUS[1]=$(text 27) && cmd_systemctl status xray &>/dev/null && STATUS[1]=$(text 28) fi # 下载所需文件 [[ ${STATUS[0]} = "$(text 26)" ]] && [ ! -s $WORK_DIR/cloudflared ] && { wget --no-check-certificate -qO $TEMP_DIR/cloudflared ${GH_PROXY}https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-$ARGO_ARCH >/dev/null 2>&1 && chmod +x $TEMP_DIR/cloudflared >/dev/null 2>&1; }& [[ ${STATUS[1]} = "$(text 26)" ]] && [ ! -s $WORK_DIR/xray ] && { wget --no-check-certificate -qO $TEMP_DIR/Xray.zip ${GH_PROXY}https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-$XRAY_ARCH.zip >/dev/null 2>&1; unzip -qo $TEMP_DIR/Xray.zip xray *.dat -d $TEMP_DIR >/dev/null 2>&1; }& { wget --no-check-certificate --continue -qO $TEMP_DIR/jq ${GH_PROXY}https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-$JQ_ARCH >/dev/null 2>&1 && chmod +x $TEMP_DIR/jq >/dev/null 2>&1; }& { wget --no-check-certificate --continue -qO $TEMP_DIR/qrencode ${GH_PROXY}https://github.com/fscarmen/client_template/raw/main/qrencode-go/qrencode-go-linux-$QRENCODE_ARCH >/dev/null 2>&1 && chmod +x $TEMP_DIR/qrencode >/dev/null 2>&1; }& } # 为了适配 alpine,定义 cmd_systemctl 的函数 cmd_systemctl() { nginx_run() { $(type -p nginx) -c $WORK_DIR/nginx.conf } nginx_stop() { local NGINX_PID=$(ps -eo pid,args | awk -v work_dir="$WORK_DIR" '$0~(work_dir"/nginx.conf"){print $1;exit}') ss -nltp | sed -n "/pid=$NGINX_PID,/ s/,/ /gp" | grep -oP 'pid=\K\S+' | sort -u | xargs kill -9 >/dev/null 2>&1 } [ -s $WORK_DIR/nginx.conf ] && local IS_NGINX=is_nginx || local IS_NGINX=no_nginx local ENABLE_DISABLE=$1 local APP=$2 if [ "$ENABLE_DISABLE" = 'enable' ]; then if [ "$SYSTEM" = 'Alpine' ]; then # 使用 openrc 启动服务 rc-service $APP start # 添加到开机启动 rc-update add $APP default elif [ "$IS_CENTOS" = 'CentOS7' ]; then systemctl daemon-reload systemctl enable --now $APP [[ "$APP" = 'xray' && "$IS_NGINX" = 'is_nginx' ]] && [ -s $WORK_DIR/nginx.conf ] && { nginx_run; firewall_configuration open; } else systemctl daemon-reload systemctl enable --now $APP fi elif [ "$ENABLE_DISABLE" = 'disable' ]; then if [ "$SYSTEM" = 'Alpine' ]; then rc-service $APP stop rc-update del $APP default elif [ "$IS_CENTOS" = 'CentOS7' ]; then systemctl disable --now $APP [[ "$APP" = 'xray' && "$IS_NGINX" = 'is_nginx' ]] && [ -s $WORK_DIR/nginx.conf ] && { nginx_stop; firewall_configuration close; } else systemctl disable --now $APP fi elif [ "$ENABLE_DISABLE" = 'status' ]; then if [ "$SYSTEM" = 'Alpine' ]; then rc-service $APP status else systemctl is-active $APP fi fi } check_system_info() { [ -s /etc/os-release ] && SYS="$(awk -F '"' 'tolower($0) ~ /pretty_name/{print $2}' /etc/os-release)" [[ -z "$SYS" && -x "$(type -p hostnamectl)" ]] && SYS="$(hostnamectl | awk -F ': ' 'tolower($0) ~ /operating system/{print $2}')" [[ -z "$SYS" && -x "$(type -p lsb_release)" ]] && SYS="$(lsb_release -sd)" [[ -z "$SYS" && -s /etc/lsb-release ]] && SYS="$(awk -F '"' 'tolower($0) ~ /distrib_description/{print $2}' /etc/lsb-release)" [[ -z "$SYS" && -s /etc/redhat-release ]] && SYS="$(cat /etc/redhat-release)" [[ -z "$SYS" && -s /etc/issue ]] && SYS="$(sed -E '/^$|^\\/d' /etc/issue | awk -F '\\' '{print $1}' | sed 's/[ ]*$//g')" REGEX=("debian" "ubuntu" "centos|red hat|kernel|alma|rocky" "arch linux" "alpine" "fedora") RELEASE=("Debian" "Ubuntu" "CentOS" "Arch" "Alpine" "Fedora") EXCLUDE=("---") MAJOR=("9" "16" "7" "" "" "37") PACKAGE_UPDATE=("apt -y update" "apt -y update" "yum -y update" "pacman -Sy" "apk update -f" "dnf -y update") PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -y install" "pacman -S --noconfirm" "apk add --no-cache" "dnf -y install") PACKAGE_UNINSTALL=("apt -y autoremove" "apt -y autoremove" "yum -y autoremove" "pacman -Rcnsu --noconfirm" "apk del -f" "dnf -y autoremove") for int in "${!REGEX[@]}"; do [[ "${SYS,,}" =~ ${REGEX[int]} ]] && SYSTEM="${RELEASE[int]}" && break done [ -z "$SYSTEM" ] && error " $(text 5) " # 针对各厂商的订制系统 if [ -z "$SYSTEM" ]; then [ -x "$(type -p yum)" ] && int=2 && SYSTEM='CentOS' || error " $(text 5) " fi # 先排除 EXCLUDE 里包括的特定系统,其他系统需要作大发行版本的比较 for ex in "${EXCLUDE[@]}"; do [[ ! "{$SYS,,}" =~ $ex ]]; done && [[ "$(echo "$SYS" | sed "s/[^0-9.]//g" | cut -d. -f1)" -lt "${MAJOR[int]}" ]] && error " $(text 6) " # 针对部分系统作特殊处理 ARGO_DAEMON_FILE='/etc/systemd/system/argo.service'; XRAY_DAEMON_FILE='/etc/systemd/system/xray.service'; DAEMON_RUN_PATTERN="ExecStart=" if [ "$SYSTEM" = 'CentOS' ]; then IS_CENTOS="CentOS$(echo "$SYS" | sed "s/[^0-9.]//g" | cut -d. -f1)" elif [ "$SYSTEM" = 'Alpine' ]; then ARGO_DAEMON_FILE='/etc/init.d/argo'; XRAY_DAEMON_FILE='/etc/init.d/xray'; DAEMON_RUN_PATTERN="command_args=" fi # 判断虚拟化 if [ -x "$(type -p systemd-detect-virt)" ]; then VIRT=$(systemd-detect-virt) elif grep -qa container= /proc/1/environ 2>/dev/null; then VIRT=$(tr '\0' '\n' /dev/null; then VIRT=$(grep -Eo '(lxc|docker|kubepods|containerd)' /proc/1/cgroup | sed -n 1p) elif [ -x "$(type -p hostnamectl)" ]; then VIRT=$(hostnamectl | awk '/Virtualization/{print $NF}') else [ -x "$(type -p virt-what)" ] && ${PACKAGE_INSTALL[int]} virt-what >/dev/null 2>&1 [ -x "$(type -p virt-what)" ] && VIRT=$(virt-what | sed -n 1p) || VIRT=unknown fi } # 检测 IPv4 IPv6 信息 check_system_ip() { [ "$L" = 'C' ] && local IS_CHINESE='?lang=zh-CN' local DEFAULT_LOCAL_INTERFACE4=$(ip -4 route show default | awk '/default/ {for (i=0; i/dev/null 2>&1 & if grep -qi 'cloudflare' <<< "$ASNORG4$ASNORG6"; then if grep -qi 'cloudflare' <<< "$ASNORG6" && [ -n "$WAN4" ] && ! grep -qi 'cloudflare' <<< "$ASNORG4"; then SERVER_IP_DEFAULT=$WAN4 elif grep -qi 'cloudflare' <<< "$ASNORG4" && [ -n "$WAN6" ] && ! grep -qi 'cloudflare' <<< "$ASNORG6"; then SERVER_IP_DEFAULT=$WAN6 else local a=6 until [ -n "$SERVER_IP" ]; do ((a--)) || true [ "$a" = 0 ] && error "\n $(text 3) \n" reading "\n $(text 54) " SERVER_IP done fi elif [ -n "$WAN4" ]; then SERVER_IP_DEFAULT=$WAN4 elif [ -n "$WAN6" ]; then SERVER_IP_DEFAULT=$WAN6 fi # 输入服务器 IP,默认为检测到的服务器 IP,如果全部为空,则提示并退出脚本 if [ ! -d $WORK_DIR ]; then ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && [ -z "$SERVER_IP" ] && reading "\n $(text 59) " SERVER_IP SERVER_IP=${SERVER_IP:-"$SERVER_IP_DEFAULT"} [ -z "$SERVER_IP" ] && error " $(text 58) " # 检测是否解锁 chatGPT if [ ! -d $WORK_DIR ]; then [[ "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && CHATGPT_STACK='-4' || CHATGPT_STACK='-6' if [ "$(check_chatgpt ${CHATGPT_STACK})" = 'unlock' ]; then CHAT_GPT_OUT_V4=direct && CHAT_GPT_OUT_V6=direct else CHAT_GPT_OUT_V4=warp-IPv4 && CHAT_GPT_OUT_V6=warp-IPv6 fi fi fi # 处理可能输入的错误,去掉开头和结尾的空格,去掉最后的 : [[ "$NONINTERACTIVE_INSTALL" != 'noninteractive_install' && -z "$ARGO_DOMAIN" ]] && reading "\n $(text 10) " ARGO_DOMAIN ARGO_DOMAIN=$(sed 's/[ ]*//g; s/:[ ]*//' <<< "$ARGO_DOMAIN") # 输入 ARGO_AUTH if ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && [[ -n "$ARGO_DOMAIN" && -z "$ARGO_AUTH" ]]; then local a=5 until [[ "$ARGO_AUTH" =~ TunnelSecret || "$ARGO_AUTH" =~ [A-Z0-9a-z=]{120,250}$ || "${#ARGO_AUTH}" = 40 ]]; do if [ "$a" = 0 ]; then error "\n $(text 3) \n" else [ "$a" != 5 ] && warning "\n $(text 45) \n" hint "\n $(text 11) \n " && reading "\n $(text 86) " ARGO_AUTH fi ((a--)) || true done fi # 根据输入的 ARGO_AUTH 变量,判断是 TunnelSecret 还是 Token 或者 API if [[ "$ARGO_AUTH" =~ TunnelSecret ]]; then ARGO_JSON=${ARGO_AUTH//[ ]/} elif [[ "$ARGO_AUTH" =~ [A-Z0-9a-z=]{120,250}$ ]]; then ARGO_TOKEN=$(awk '{print $NF}' <<< "$ARGO_AUTH") elif [[ "${#ARGO_AUTH}" = 40 ]]; then hint "\n $(text 78) \n " create_argo_tunnel "${ARGO_AUTH}" "${ARGO_DOMAIN}" "${NGINX_PORT}" if [[ ! "$ARGO_JSON" =~ TunnelSecret ]]; then hint "\n $(text 80) \n " unset ARGO_DOMAIN fi fi } # 定义 Xray 变量 xray_variable() { local a=6 until [ -n "$REALITY_PORT" ]; do ((a--)) || true [ "$a" = 0 ] && error "\n $(text 3) \n" REALITY_PORT_DEFAULT=$(shuf -i 1000-65535 -n 1) ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && reading "\n $(text 56) " REALITY_PORT REALITY_PORT=${REALITY_PORT:-"$REALITY_PORT_DEFAULT"} ss -nltup | grep -q ":$REALITY_PORT" && warning "\n $(text 61) \n" && unset REALITY_PORT done # 提供网上热心网友的anycast域名 if [ -z "$SERVER" ]; then if ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL"; then echo "" for c in "${!CDN_DOMAIN[@]}"; do hint " $[c+1]. ${CDN_DOMAIN[c]} " done reading "\n $(text 42) " CUSTOM_CDN fi case "$CUSTOM_CDN" in [1-${#CDN_DOMAIN[@]}] ) SERVER="${CDN_DOMAIN[$((CUSTOM_CDN-1))]}" ;; ?????* ) SERVER="$CUSTOM_CDN" ;; * ) SERVER="${CDN_DOMAIN[0]}" esac fi local a=6 until [[ "${UUID,,}" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$ ]]; do (( a-- )) || true [ "$a" = 0 ] && error "\n $(text 3) \n" UUID_DEFAULT=$(cat /proc/sys/kernel/random/uuid) ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && reading "\n $(text 12) " UUID UUID=${UUID:-"$UUID_DEFAULT"} [[ ! "${UUID,,}" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$ ]] && warning "\n $(text 4) " done ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && [ -z "$WS_PATH" ] && reading "\n $(text 13) " WS_PATH local a=5 until [[ -z "$WS_PATH" || "$WS_PATH" =~ ^[A-Za-z0-9_.@-]+$ ]]; do (( a-- )) || true [ "$a" = 0 ] && error " $(text 3) " || reading " $(text 14) " WS_PATH done WS_PATH=${WS_PATH:-"$WS_PATH_DEFAULT"} # 输入节点名,以系统的 hostname 作为默认 local EMOJI="${EMOJI4:-$EMOJI6}" local EMOJI="${EMOJI}${EMOJI:+ }" if [ -z "$NODE_NAME" ]; then if [ -x "$(type -p hostname)" ]; then local NODE_NAME_DEFAULT="${EMOJI}$(hostname)" elif [ -s /etc/hostname ]; then local NODE_NAME_DEFAULT="${EMOJI}$(cat /etc/hostname)" else local NODE_NAME_DEFAULT="ArgoX" fi ! grep -q 'noninteractive_install' <<< "$NONINTERACTIVE_INSTALL" && reading "\n $(text 49) " NODE_NAME grep -q '^$' <<< "$NODE_NAME" && NODE_NAME="$NODE_NAME_DEFAULT" || NODE_NAME="${EMOJI}${NODE_NAME}" fi } # 快速安装的所有预设值 fast_install_variables() { # 设置为非交互式安装 NONINTERACTIVE_INSTALL='noninteractive_install' # 快速安装模式下使用自动配置 REALITY_PORT=${REALITY_PORT:-$(shuf -i 1000-65535 -n 1)} local PORT_USED_COUNT=0 while ss -nltup | grep ":$REALITY_PORT" >/dev/null 2>&1; do REALITY_PORT=$(shuf -i 1000-65535 -n 1) ((PORT_USED_COUNT++)) [ $PORT_USED_COUNT -gt 5 ] && error "\n $(text 3) \n" done SERVER=${SERVER:-"${CDN_DOMAIN[0]}"} UUID=${UUID:-$(cat /proc/sys/kernel/random/uuid)} WS_PATH=${WS_PATH:-"$WS_PATH_DEFAULT"} # 输入节点名,以系统的 hostname 作为默认 check_system_ip local EMOJI="${EMOJI4:-$EMOJI6}" local EMOJI="${EMOJI}${EMOJI:+ }" if [ -x "$(type -p hostname)" ]; then local NODE_NAME_DEFAULT="${EMOJI}$(hostname)" elif [ -s /etc/hostname ]; then local NODE_NAME_DEFAULT="${EMOJI}$(cat /etc/hostname)" else local NODE_NAME_DEFAULT="${EMOJI}ArgoX" fi grep -q '^$' <<< "$NODE_NAME" && NODE_NAME="$NODE_NAME_DEFAULT" || NODE_NAME="${EMOJI}${NODE_NAME}" } check_dependencies() { # 如果是 Alpine,先升级 wget if [ "$SYSTEM" = 'Alpine' ]; then local CHECK_WGET=$(wget 2>&1 | sed -n 1p) grep -qi 'busybox' <<< "$CHECK_WGET" && ${PACKAGE_INSTALL[int]} wget >/dev/null 2>&1 # Alpine 系统只检查必要的依赖,不需要 systemctl 和 python3 local DEPS_CHECK=("bash" "rc-update") local DEPS_INSTALL=("bash" "openrc") for g in "${!DEPS_CHECK[@]}"; do [ ! -x "$(type -p ${DEPS_CHECK[g]})" ] && DEPS_ALPINE+=(${DEPS_INSTALL[g]}) done if [ "${#DEPS_ALPINE[@]}" -ge 1 ]; then info "\n $(text 7) $(sed "s/ /,&/g" <<< ${DEPS_ALPINE[@]}) \n" ${PACKAGE_UPDATE[int]} >/dev/null 2>&1 ${PACKAGE_INSTALL[int]} ${DEPS_ALPINE[@]} >/dev/null 2>&1 fi fi # 检测 Linux 系统的依赖,升级库并重新安装依赖 # 所有系统都需要的基本依赖 local DEPS_CHECK=("wget" "ss" "unzip" "bash") local DEPS_INSTALL=("wget" "iproute2" "unzip" "bash") # 非 Alpine 系统额外需要 systemctl [ "$SYSTEM" != 'Alpine' ] && DEPS_CHECK+=("systemctl") && DEPS_INSTALL+=("systemctl") for g in "${!DEPS_CHECK[@]}"; do [ ! -x "$(type -p ${DEPS_CHECK[g]})" ] && DEPS+=(${DEPS_INSTALL[g]}) done if [ "${#DEPS[@]}" -ge 1 ]; then info "\n $(text 7) $(sed "s/ /,&/g" <<< ${DEPS[@]}) \n" [ "$SYSTEM" != 'CentOS' ] && ${PACKAGE_UPDATE[int]} >/dev/null 2>&1 ${PACKAGE_INSTALL[int]} ${DEPS[@]} >/dev/null 2>&1 else info "\n $(text 8) \n" fi # 不需要 nginx 原来的服务 [[ "${DEPS[@]}" =~ 'nginx' ]] && cmd_systemctl disable nginx >/dev/null 2>&1 } # 检查并安装 nginx check_nginx() { if [ ! -x "$(type -p nginx)" ]; then info "\n $(text 7) nginx \n" ${PACKAGE_INSTALL[int]} nginx >/dev/null 2>&1 # 如果新安装的 Nginx ,先停掉服务 [ "$SYSTEM" != 'Alpine' ] && systemctl disable --now nginx >/dev/null 2>&1 fi } # 处理防火墙规则 firewall_configuration() { local LISTEN_PORT=$(awk -F [:,] '/"port"/{print $2; exit}' $WORK_DIR/inbound.json) if grep -q "open" <<< "$1"; then firewall-cmd --zone=public --add-port=${LISTEN_PORT}/tcp --permanent >/dev/null 2>&1 elif grep -q "close" <<< "$1"; then firewall-cmd --zone=public --remove-port=${LISTEN_PORT}/tcp --permanent >/dev/null 2>&1 fi firewall-cmd --reload >/dev/null 2>&1 if [[ -s /etc/selinux/config && -x "$(type -p getenforce)" && $(getenforce) = 'Enforcing' ]]; then hint "\n $(text 69) " setenforce 0 grep -qs '^SELINUX=disabled$' /etc/selinux/config || sed -i 's/^SELINUX=[epd].*/# &/; /SELINUX=[epd]/a\SELINUX=disabled' /etc/selinux/config fi } # Nginx 配置文件 json_nginx() { if [ -s $WORK_DIR/*inbound*.json ]; then JSON=$(cat $WORK_DIR/*inbound*.json) WS_PATH=$(expr "$JSON" : '.*path":"/\(.*\)-vl.*') SERVER_IP=${SERVER_IP:-"$(awk -F '"' '/"SERVER_IP"/{print $4}' <<< "$JSON")"} UUID=$(awk -F '"' '/"password"/{print $4; exit}' <<< "$JSON") fi [[ "$SERVER_IP" =~ : ]] && REVERSE_IP="[$SERVER_IP]" || REVERSE_IP="$SERVER_IP" cat > $WORK_DIR/nginx.conf << EOF user root; worker_processes auto; error_log /dev/null; pid /var/run/nginx.pid; events { worker_connections 1024; } http { map \$http_user_agent \$path { default /; # 默认路径 ~*v2rayN|Neko|Throne /base64; # 匹配 V2rayN / NekoBox / Throne 客户端 ~*clash /clash; # 匹配 Clash 客户端 ~*ShadowRocket /shadowrocket; # 匹配 ShadowRocket 客户端 ~*SFM|SFI|SFA /sing-box; # 匹配 Sing-box 官方客户端 # ~*Chrome|Firefox|Mozilla /; # 添加更多的分流规则 } include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' '\$status \$body_bytes_sent "\$http_referer" ' '"\$http_user_agent" "\$http_x_forwarded_for"'; access_log /dev/null; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; #include /etc/nginx/conf.d/*.conf; server { listen 127.0.0.1:3006 proxy_protocol; # xray fallbacks # 来自 /auto 的分流 location ~ ^/${UUID}/auto { default_type 'text/plain; charset=utf-8'; alias ${WORK_DIR}/subscribe/\$path; } location ~ ^/${UUID}/(.*) { autoindex on; proxy_set_header X-Real-IP \$proxy_protocol_addr; default_type 'text/plain; charset=utf-8'; alias ${WORK_DIR}/subscribe/\$1; } } } EOF } # Json 生成两个配置文件 json_argo() { [ ! -s $WORK_DIR/tunnel.json ] && echo $ARGO_JSON > $WORK_DIR/tunnel.json [ ! -s $WORK_DIR/tunnel.yml ] && cat > $WORK_DIR/tunnel.yml << EOF tunnel: $(cut -d\" -f12 <<< $ARGO_JSON) credentials-file: $WORK_DIR/tunnel.json ingress: - hostname: ${ARGO_DOMAIN} service: http://localhost:${NGINX_PORT} - service: http_status:404 EOF } # 创建 Argo Tunnel API create_argo_tunnel() { local CLOUDFLARE_API_TOKEN="$1" local ARGO_DOMAIN="$2" local SERVICE_PORT="$3" local TUNNEL_NAME=${ARGO_DOMAIN%%.*} local ROOT_DOMAIN=${ARGO_DOMAIN#*.} api_error() { local RESPONSE="$1" local CHECK_ZONE_ID="$2" if grep -q '"code":9109,' <<< "$RESPONSE"; then warning " $(text 81) " && sleep 2 && return 2 elif grep -q '"code":7003,' <<< "$RESPONSE"; then warning " $(text 82) " && sleep 2 && return 3 elif grep -q 'check_zone_id' <<< "$CHECK_ZONE_ID" && grep -q '"count":0,' <<< "$RESPONSE"; then warning " $(text 83) " && sleep 2 && return 4 elif grep -q '"code":10000,' <<< "$RESPONSE"; then warning " $(text 85) " && sleep 2 && return 1 elif grep -q '"success":true' <<< "$RESPONSE"; then return 0 else warning " $(text 84) " && sleep 2 && return 5 fi } # 步骤 1: 获取 Zone ID 和 Account ID local ZONE_RESPONSE=$(wget --no-check-certificate -qO- --content-on-error \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/zones?name=${ROOT_DOMAIN}") api_error "$ZONE_RESPONSE" 'check_zone_id' || return $? [[ "$ZONE_RESPONSE" =~ \"id\":\"([^\"]+)\".*\"account\":\{\"id\":\"([^\"]+)\" ]] && local ZONE_ID="${BASH_REMATCH[1]}" ACCOUNT_ID="${BASH_REMATCH[2]}" || \ return 5 # 步骤 2: 查询并处理现有 Tunnel local TUNNEL_LIST=$(wget --no-check-certificate -qO- --content-on-error \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cfd_tunnel?is_deleted=false") api_error "$TUNNEL_LIST" || return $? local TUNNEL_LIST_SPLIT=$(awk 'BEGIN{RS="";FS=""}{s=substr($0,index($0,"\"result\":[")+10);d=0;b="";for(i=1;i<=length(s);i++){c=substr(s,i,1);if(c=="{")d++;if(d>0)b=b c;if(c=="}"){d--;if(d==0){print b;b=""}}}}' <<< "$TUNNEL_LIST") # 检查是否存在同名 Tunnel while true; do unset TUNNEL_CHECK EXISTING_TUNNEL_ID EXISTING_TUNNEL_STATUS local TUNNEL_CHECK=$(grep '\"name\":\"'$TUNNEL_NAME'\"' <<< "$TUNNEL_LIST_SPLIT") if [[ "$TUNNEL_CHECK" =~ \"id\":\"([^\"]+)\".*\"status\":\"([^\"]+)\" ]]; then local EXISTING_TUNNEL_ID=${BASH_REMATCH[1]} EXISTING_TUNNEL_STATUS=${BASH_REMATCH[2]} # 处理状态显示的本地化 grep -qw 'C' <<< "$L" && EXISTING_TUNNEL_STATUS=$(sed 's/inactive/停用(未激活)/; s/down/离线/; s/healthy/连接中/; s/degraded/降级/ ' <<< "$EXISTING_TUNNEL_STATUS") reading "\n $(text 79) " OVERWRITE if grep -qw 'n' <<< "${OVERWRITE,,}"; then # 询问用户输入另一个域名前缀 unset ARGO_DOMAIN reading "\n $(text 10) " ARGO_DOMAIN # 用户直接回车,使用临时域名,退出当前流程 ! grep -q '\.' <<< "$ARGO_DOMAIN" && return 5 # 更新TUNNEL_NAME和ROOT_DOMAIN,循环会自动检查新名称 TUNNEL_NAME=${ARGO_DOMAIN%%.*} ROOT_DOMAIN=${ARGO_DOMAIN#*.} else # 用户选择覆盖,则跳出循环继续执行创建流程 break fi else # 如果新域名不存在,则跳出循环继续执行创建流程 unset TUNNEL_CHECK EXISTING_TUNNEL_ID EXISTING_TUNNEL_STATUS break fi done # 如果同名 Tunnel 不存在,则先创建 if grep -q '^$' <<< "$EXISTING_TUNNEL_ID"; then # 生成 Tunnel Secret (至少 32 字节的 base64 编码) local TUNNEL_SECRET=$(openssl rand -base64 32) # 创建新 Tunnel local CREATE_RESPONSE=$(wget --no-check-certificate -qO- --content-on-error \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ --post-data="{ \"name\": \"$TUNNEL_NAME\", \"config_src\": \"cloudflare\", \"tunnel_secret\": \"$TUNNEL_SECRET\" }" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cfd_tunnel") api_error "$CREATE_RESPONSE" || return $? [[ $CREATE_RESPONSE =~ \"id\":\"([^\"]+)\".*\"token\":\"([^\"]+)\" ]] && \ local TUNNEL_ID=${BASH_REMATCH[1]} TUNNEL_TOKEN=${BASH_REMATCH[2]} || \ return 5 else # 如果有同名 Tunnel (EXISTING_TUNNEL_ID 非空),则获取其 TOKEN local EXISTING_TUNNEL_TOKEN=$(wget -qO- --content-on-error \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cfd_tunnel/${EXISTING_TUNNEL_ID}/token") api_error "$EXISTING_TUNNEL_TOKEN" || return $? local TUNNEL_ID=$EXISTING_TUNNEL_ID \ TUNNEL_TOKEN=$(sed -n 's/.*"result":"\([^"]\+\)".*/\1/p' <<< "$EXISTING_TUNNEL_TOKEN") && \ TUNNEL_SECRET=$(base64 -d <<< "$TUNNEL_TOKEN" | sed 's/.*"s":"\([^"]\+\)".*/\1/') || \ return 5 fi # 步骤 3: 配置 Tunnel ingress 规则... 不管原来的规则,一率覆盖处理 local CONFIG_RESPONSE=$(wget --no-check-certificate -qO- --content-on-error \ --method=PUT \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ --body-data="{ \"config\": { \"ingress\": [ { \"service\": \"http://localhost:${SERVICE_PORT}\", \"hostname\": \"${ARGO_DOMAIN}\" }, { \"service\": \"http_status:404\" } ], \"warp-routing\": { \"enabled\": false } } }" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}/configurations") api_error "$CONFIG_RESPONSE" || return $? # 步骤 4: 管理 DNS 记录 local DNS_PAYLOAD="{ \"name\": \"${ARGO_DOMAIN}\", \"type\": \"CNAME\", \"content\": \"${TUNNEL_ID}.cfargotunnel.com\", \"proxied\": true, \"settings\": { \"flatten_cname\": false } }" local DNS_LIST=$(wget --no-check-certificate -qO- --content-on-error \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?type=CNAME&name=${ARGO_DOMAIN}") api_error "$DNS_LIST" || return $? # 如果已存在需要的 DNS 记录,就跳过 if [[ "$DNS_LIST" =~ \"id\":\"([^\"]+)\".*\"$ARGO_DOMAIN\".*\"content\":\"([^\"]+)\" ]]; then local EXISTING_DNS_ID="${BASH_REMATCH[1]}" EXISTED_DNS_CONTENT="${BASH_REMATCH[2]}" # DNS 记录与隧道 ID 不匹配的话,覆盖原来的 CNAME 记录 if ! grep -qw "$EXISTING_TUNNEL_ID" <<< "${EXISTED_DNS_CONTENT%%.*}"; then local DNS_RESPONSE=$(wget --no-check-certificate -qO- --content-on-error \ --method=PATCH \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ --body-data="$DNS_PAYLOAD" \ "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${EXISTING_DNS_ID}") api_error "$DNS_RESPONSE" || return $? fi else # 未找到现有 DNS 记录,使用 POST 创建 local DNS_RESPONSE=$(wget --no-check-certificate -qO- --content-on-error \ --method=POST \ --header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ --header="Content-Type: application/json" \ --body-data="$DNS_PAYLOAD" \ "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records") api_error "$DNS_RESPONSE" || return $? fi # 返回 Argo Tunnel Token 或者 Json ARGO_JSON="{\"AccountTag\":\"$ACCOUNT_ID\",\"TunnelSecret\":\"$TUNNEL_SECRET\",\"TunnelID\":\"$TUNNEL_ID\",\"Endpoint\":\"\"}" ### ARGO_TOKEN="$TUNNEL_TOKEN" } install_argox() { argo_variable xray_variable wait # 生成 reality 的公私钥 [[ -z "$REALITY_PRIVATE" || -z "$REALITY_PUBLIC" ]] && REALITY_KEYPAIR=$($TEMP_DIR/xray x25519) [ -z "$REALITY_PRIVATE" ] && REALITY_PRIVATE=$(awk '/Private/{print $NF}' <<< "$REALITY_KEYPAIR") [ -z "$REALITY_PUBLIC" ] && REALITY_PUBLIC=$(awk '/Public|Password/{print $NF}' <<< "$REALITY_KEYPAIR") [ ! -d /etc/systemd/system ] && mkdir -p /etc/systemd/system mkdir -p $WORK_DIR/subscribe && echo "$L" > $WORK_DIR/language [ -s "$VARIABLE_FILE" ] && cp $VARIABLE_FILE $WORK_DIR/ wait [[ ! -s $WORK_DIR/cloudflared && -x $TEMP_DIR/cloudflared ]] && mv $TEMP_DIR/cloudflared $WORK_DIR [[ ! -s $WORK_DIR/jq && -x $TEMP_DIR/jq ]] && mv $TEMP_DIR/jq $WORK_DIR [[ "$INSTALL_NGINX" != 'n' && ! -s $WORK_DIR/qrencode && -x $TEMP_DIR/qrencode ]] && mv $TEMP_DIR/qrencode $WORK_DIR if [[ -n "${ARGO_JSON}" && -n "${ARGO_DOMAIN}" ]]; then ARGO_RUNS="$WORK_DIR/cloudflared tunnel --edge-ip-version auto --config $WORK_DIR/tunnel.yml run" json_argo elif [[ -n "${ARGO_TOKEN}" && -n "${ARGO_DOMAIN}" ]]; then ARGO_RUNS="$WORK_DIR/cloudflared tunnel --edge-ip-version auto run --token ${ARGO_TOKEN}" else ARGO_RUNS="$WORK_DIR/cloudflared tunnel --edge-ip-version auto --no-autoupdate --metrics 0.0.0.0:${METRICS_PORT} --url http://localhost:${NGINX_PORT}" fi # Argo 生成守护进程文件 if [ "$SYSTEM" = 'Alpine' ]; then # 分离命令和参数 local COMMAND=${ARGO_RUNS%% --*} # 提取命令部分(包括 cloudflared tunnel) local ARGS=${ARGO_RUNS#$COMMAND } # 提取参数部分 # 为 Alpine 创建 OpenRC 服务文件 cat > ${ARGO_DAEMON_FILE} << EOF #!/sbin/openrc-run name="argo" description="Cloudflare Tunnel" command="${COMMAND}" command_args="${ARGS}" pidfile="/run/\${RC_SVCNAME}.pid" command_background="yes" output_log="${WORK_DIR}/argo.log" error_log="${WORK_DIR}/argo.log" depend() { need net after firewall } start_pre() { # 确保目录存在 mkdir -p ${WORK_DIR} /run # 清理残留 PID,防止 OpenRC 误判 rm -f "\$pidfile" # 如存在 nginx 配置,先启动 nginx if [ -s ${WORK_DIR}/nginx.conf ] && command -v /usr/sbin/nginx >/dev/null 2>&1; then /usr/sbin/nginx -c ${WORK_DIR}/nginx.conf fi } stop() { ebegin "Stopping \${RC_SVCNAME}" # 1. 优雅停止 cloudflared(优先 PID) start-stop-daemon --stop --quiet --pidfile "\$pidfile" --retry 5 # 2. PID 失效兜底 local CF_PIDS CF_PIDS="\$(ps -eo pid,args | awk '\$0~/\/etc\/argox\/cloudflared/{print \$1}')" if [ -n "\$CF_PIDS" ]; then einfo "Force killing cloudflared: \$CF_PIDS" kill -9 \$CF_PIDS 2>/dev/null fi # 3. 停止 nginx(仅限本配置) if [ -s ${WORK_DIR}/nginx.conf ] && command -v /usr/sbin/nginx >/dev/null 2>&1; then local NGINX_MASTER NGINX_MASTER="\$(ps -eo pid,args | awk '\$0~/nginx: master process .*\/etc\/argox\/nginx.conf/{print \$1}')" if [ -n "\$NGINX_MASTER" ]; then einfo "Stopping nginx master: \$NGINX_MASTER" kill -15 "\$NGINX_MASTER" 2>/dev/null sleep 1 kill -9 "\$NGINX_MASTER" 2>/dev/null fi fi # 4. 清理 PID 文件 rm -f "\$pidfile" eend 0 return 0 } EOF chmod +x ${ARGO_DAEMON_FILE} # 为 Xray 创建 OpenRC 服务文件 cat > ${XRAY_DAEMON_FILE} << EOF #!/sbin/openrc-run name="xray" description="Xray Service" command="${WORK_DIR}/xray" command_args="run -c ${WORK_DIR}/inbound.json -c ${WORK_DIR}/outbound.json" pidfile="/run/\${RC_SVCNAME}.pid" command_background="yes" output_log="${WORK_DIR}/xray.log" error_log="${WORK_DIR}/xray.log" depend() { need net after firewall } start_pre() { # 确保运行目录存在 mkdir -p ${WORK_DIR} /run chmod 755 ${WORK_DIR} # 清理残留 PID,避免 OpenRC 误判 rm -f "\$pidfile" } stop() { ebegin "Stopping \${RC_SVCNAME}" # 1. 优雅停止(优先) start-stop-daemon --stop --quiet --pidfile "\$pidfile" --retry 5 local RETVAL=\$? # 2. 兜底:PID 文件失效 / daemon 异常 if [ \$RETVAL -ne 0 ]; then einfo "Graceful stop failed, force killing xray processes" # BusyBox / Alpine 兼容写法 local XRAY_PIDS XRAY_PIDS="\$(ps -eo pid,args | awk -v work_dir="\$WORK_DIR" '\$0~(work_dir"/xray run"){print \$1;exit}')" if [ -n "\$XRAY_PIDS" ]; then for pid in \$XRAY_PIDS; do kill -9 "\$pid" 2>/dev/null done fi fi # 3. 清理 PID 文件 rm -f "\$pidfile" eend 0 } EOF chmod +x ${XRAY_DAEMON_FILE} else # 非 Alpine 系统使用 systemd local ARGO_SERVER="[Unit] Description=Cloudflare Tunnel After=network.target [Service] Type=simple NoNewPrivileges=yes TimeoutStartSec=0" [[ "$INSTALL_NGINX" != 'n' && "$IS_CENTOS" != 'CentOS7' ]] && ARGO_SERVER+=" ExecStartPre=$(type -p nginx) -c $WORK_DIR/nginx.conf" ARGO_SERVER+=" ExecStart=$ARGO_RUNS Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target" echo "$ARGO_SERVER" > ${ARGO_DAEMON_FILE} # 创建 Xray systemd 服务文件 cat > ${XRAY_DAEMON_FILE} << EOF [Unit] Description=Xray Service Documentation=https://github.com/XTLS/Xray-core After=network.target [Service] User=root ExecStart=$WORK_DIR/xray run -c $WORK_DIR/inbound.json -c $WORK_DIR/outbound.json Restart=on-failure RestartPreventExitStatus=23 [Install] WantedBy=multi-user.target EOF fi # 生成配置文件及守护进程文件 local i=1 [ ! -s $WORK_DIR/xray ] && wait && while [ "$i" -le 20 ]; do [[ -s $TEMP_DIR/xray && -s $TEMP_DIR/geoip.dat && -s $TEMP_DIR/geosite.dat ]] && mv $TEMP_DIR/xray $TEMP_DIR/geo*.dat $WORK_DIR && break; ((i++)); sleep 2; done [ "$i" -ge 20 ] && local APP=Xray && error "\n $(text 48) " cat > $WORK_DIR/inbound.json << EOF // "SERVER_IP": "${SERVER_IP}" // "REALITY_PUBLIC": "${REALITY_PUBLIC}" // "SERVER": "${SERVER}" { "log": { "access": "/dev/null", "error": "/dev/null", "loglevel": "none" }, "inbounds": [ { "tag": "${NODE_NAME} reality-vision", "protocol": "vless", "port": ${REALITY_PORT}, "settings": { "clients": [ { "id": "${UUID}", "flow": "xtls-rprx-vision" } ], "decryption": "none", "fallbacks": [ { "dest": "3001", "xver": 1 } ] }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "show": true, "dest": "${TLS_SERVER}:443", "xver": 0, "serverNames": [ "${TLS_SERVER}" ], "privateKey": "${REALITY_PRIVATE}", "publicKey": "${REALITY_PUBLIC}", "maxTimeDiff": 70000, "shortIds": [ "" ] } }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls" ] } }, { "port": 3001, "listen": "127.0.0.1", "protocol": "vless", "tag": "${NODE_NAME} reality-grpc", "settings": { "clients": [ { "id": "${UUID}", "flow": "" } ], "decryption": "none" }, "streamSettings": { "network": "grpc", "grpcSettings": { "serviceName": "grpc", "multiMode": true }, "sockopt": { "acceptProxyProtocol": true } }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls" ] } }, { "listen": "127.0.0.1", "port": ${NGINX_PORT}, "protocol": "vless", "settings": { "clients": [ { "id": "${UUID}", "flow": "xtls-rprx-vision" } ], "decryption": "none", "fallbacks": [ { "path": "/${WS_PATH}-vl", "dest": 3002 }, { "path": "/${WS_PATH}-vm", "dest": 3003 }, { "path": "/${WS_PATH}-tr", "dest": 3004 }, { "path": "/${WS_PATH}-sh", "dest": 3005 }, { "dest": 3006, "alpn": "", "xver": 1 } ] }, "streamSettings": { "network": "tcp" } }, { "port": 3002, "listen": "127.0.0.1", "protocol": "vless", "settings": { "clients": [ { "id": "${UUID}", "level": 0 } ], "decryption": "none" }, "streamSettings": { "network": "ws", "security": "none", "wsSettings": { "path": "/${WS_PATH}-vl" } }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls", "quic" ], "metadataOnly": false } }, { "port": 3003, "listen": "127.0.0.1", "protocol": "vmess", "settings": { "clients": [ { "id": "${UUID}", "alterId": 0 } ] }, "streamSettings": { "network": "ws", "wsSettings": { "path": "/${WS_PATH}-vm" } }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls", "quic" ], "metadataOnly": false } }, { "port": 3004, "listen": "127.0.0.1", "protocol": "trojan", "settings": { "clients": [ { "password": "${UUID}" } ] }, "streamSettings": { "network": "ws", "security": "none", "wsSettings": { "path": "/${WS_PATH}-tr" } }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls", "quic" ], "metadataOnly": false } }, { "port": 3005, "listen": "127.0.0.1", "protocol": "shadowsocks", "settings": { "clients": [ { "method": "chacha20-ietf-poly1305", "password": "${UUID}" } ], "decryption": "none" }, "streamSettings": { "network": "ws", "wsSettings": { "path": "/${WS_PATH}-sh" } }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls", "quic" ], "metadataOnly": false } } ], "dns": { "servers": [ "https+local://8.8.8.8/dns-query" ] } } EOF cat > $WORK_DIR/outbound.json << EOF { "outbounds": [ { "protocol": "freedom", "tag": "direct" }, { "protocol": "blackhole", "settings": { }, "tag": "block" }, { "protocol": "wireguard", "settings": { "secretKey": "YFYOAdbw1bKTHlNNi+aEjBM3BO7unuFC5rOkMRAz9XY=", "address": [ "172.16.0.2/32", "2606:4700:110:8a36:df92:102a:9602:fa18/128" ], "peers": [ { "publicKey": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", "allowedIPs": [ "0.0.0.0/0", "::/0" ], "endpoint": "engage.cloudflareclient.com:2408" } ], "reserved": [ 78, 135, 76 ], "mtu": 1280 }, "tag": "wireguard" }, { "protocol": "freedom", "settings": { "domainStrategy": "UseIPv4" }, "proxySettings": { "tag": "wireguard" }, "tag": "warp-IPv4" }, { "protocol": "freedom", "settings": { "domainStrategy": "UseIPv6" }, "proxySettings": { "tag": "wireguard" }, "tag": "warp-IPv6" } ], "routing": { "domainStrategy": "AsIs", "rules": [ { "type": "field", "domain": [ "api.openai.com" ], "outboundTag": "${CHAT_GPT_OUT_V4}" }, { "type": "field", "domain": [ "geosite:openai" ], "outboundTag": "${CHAT_GPT_OUT_V6}" } ] } } EOF # 生成 Nginx 配置文件 [ "$INSTALL_NGINX" != 'n' ] && json_nginx # 再次检测状态,运行 Argo 和 Xray check_install case "${STATUS[0]}" in "$(text 26)" ) warning "\n Argo $(text 28) $(text 38) \n" ;; "$(text 27)" ) cmd_systemctl enable argo cmd_systemctl status argo &>/dev/null && info "\n Argo $(text 28) $(text 37) \n" || warning "\n Argo $(text 28) $(text 38) \n" ;; "$(text 28)" ) info "\n Argo $(text 28) $(text 37) \n" esac case "${STATUS[1]}" in "$(text 26)" ) warning "\n Xray $(text 28) $(text 38) \n" ;; "$(text 27)" ) cmd_systemctl enable xray cmd_systemctl status xray &>/dev/null && info "\n Xray $(text 28) $(text 37) \n" || warning "\n Xray $(text 28) $(text 38) \n" ;; "$(text 28)" ) info "\n Xray $(text 28) $(text 37) \n" esac } # 创建快捷方式 create_shortcut() { cat > $WORK_DIR/ax.sh << EOF #!/usr/bin/env bash bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/fscarmen/argox/main/argox.sh) \$1 EOF chmod +x $WORK_DIR/ax.sh ln -sf $WORK_DIR/ax.sh /usr/bin/argox # 如果 /usr/bin 不在 PATH 中,添加到 ~/.bashrc if [[ ! ":$PATH:" == *":/usr/bin:"* ]]; then echo 'export PATH=$PATH:/usr/bin' >> ~/.bashrc source ~/.bashrc fi [ -s /usr/bin/argox ] && hint "\n $(text 62) " } export_list() { check_install # 没有开启 Argo 和 Xray 服务,将不输出节点信息 local APP [ "${STATUS[0]}" != "$(text 28)" ] && APP+=(Argo) [ "${STATUS[1]}" != "$(text 28)" ] && APP+=(Xray) if [ "${#APP[@]}" -gt 0 ]; then reading "\n $(text 50) " OPEN_APP if [ "${OPEN_APP,,}" = 'y' ]; then [ "${STATUS[0]}" != "$(text 28)" ] && cmd_systemctl enable argo [ "${STATUS[1]}" != "$(text 28)" ] && cmd_systemctl enable xray else exit fi fi if grep -qs "^${DAEMON_RUN_PATTERN}.*:${NGINX_PORT}" ${ARGO_DAEMON_FILE}; then local a=5 until [[ -n "$ARGO_DOMAIN" || "$a" = 0 ]]; do sleep 2 ARGO_DOMAIN=$(wget -qO- http://localhost:${METRICS_PORT}/quicktunnel | awk -F '"' '{print $4}') ((a--)) || true done else ARGO_DOMAIN=${ARGO_DOMAIN:-"$(grep -m1 '^vless.*host=.*' $WORK_DIR/list | sed "s@.*host=\(.*\)&.*@\1@g")"} fi JSON=$(cat $WORK_DIR/*inbound*.json) SERVER_IP=${SERVER_IP:-"$(awk -F '"' '/"SERVER_IP"/{print $4; exit}' <<< "$JSON")"} REALITY_PORT=${REALITY_PORT:-"$(sed -n '0,/"port":/ s/.*"port":[ ]*\([0-9]\+\),*/\1/gp' <<< "$JSON")"} REALITY_PUBLIC=${REALITY_PUBLIC:-"$(awk -F '"' '/"publicKey"/{print $4; exit}' <<< "$JSON")"} REALITY_PRIVATE=${REALITY_PRIVATE:-"$(awk -F '"' '/"privateKey"/{print $4; exit}' <<< "$JSON")"} TLS_SERVER=${TLS_SERVER:-"$(awk '/"serverNames"/ {flag=1; next} flag {gsub(/[" \t]/, ""); print; exit}' <<< "$JSON")"} SERVER=${SERVER:-"$(awk -F '"' '/"SERVER"/{print $4; exit}' <<< "$JSON")"} UUID=${UUID:-"$(awk -F '"' '/"password"/{print $4; exit}' <<< "$JSON")"} WS_PATH=${WS_PATH:-"$(expr "$JSON" : '.*path":[ ]*"/\(.*\)-vl.*')"} NODE_NAME=${NODE_NAME:-"$(sed -n 's/.*tag":[ ]*"\(.*\) reality-vision",/\1/gp' <<< "$JSON")"} SS_METHOD=${SS_METHOD:-"$(awk -F '"' '/"method"/{print $4; exit}' <<< "$JSON")"} # IPv6 时的 IP 处理 if [[ "$SERVER_IP" =~ : ]]; then SERVER_IP_1="[$SERVER_IP]" SERVER_IP_2="[[$SERVER_IP]]" else SERVER_IP_1="$SERVER_IP" SERVER_IP_2="$SERVER_IP" fi # 若为临时隧道,处理查询方法 grep -q 'metrics.*url' ${ARGO_DAEMON_FILE} && QUICK_TUNNEL_URL=$(text 60) # # 生成 vmess 文件 VMESS="{ \"v\": \"2\", \"ps\": \"${NODE_NAME}-Vm\", \"add\": \"${SERVER}\", \"port\": \"443\", \"id\": \"${UUID}\", \"aid\": \"0\", \"scy\": \"none\", \"net\": \"ws\", \"type\": \"none\", \"host\": \"${ARGO_DOMAIN}\", \"path\": \"/${WS_PATH}-vm?ed=2560\", \"tls\": \"tls\", \"sni\": \"${ARGO_DOMAIN}\", \"alpn\": \"\" }" # 生成各订阅文件 # 生成 Clash proxy providers 订阅文件 local CLASH_SUBSCRIBE="proxies: - {name: \"${NODE_NAME} reality-vision\", type: vless, server: ${SERVER_IP}, port: ${REALITY_PORT}, uuid: ${UUID}, network: tcp, udp: true, tls: true, servername: ${TLS_SERVER}, flow: xtls-rprx-vision, client-fingerprint: chrome, reality-opts: {public-key: ${REALITY_PUBLIC}, short-id: \"\"} } - {name: \"${NODE_NAME} reality-grpc\", type: vless, server: ${SERVER_IP}, port: ${REALITY_PORT}, uuid: ${UUID}, network: grpc, udp: true, tls: true, servername: ${TLS_SERVER}, flow: , client-fingerprint: chrome, reality-opts: {public-key: ${REALITY_PUBLIC}, short-id: \"\"}, grpc-opts: {grpc-service-name: \"grpc\"} } - {name: \"${NODE_NAME}-Vl\", type: vless, server: ${SERVER}, port: 443, uuid: ${UUID}, udp: true, tls: true, servername: ${ARGO_DOMAIN}, skip-cert-verify: false, network: ws, ws-opts: {path: \"/${WS_PATH}-vl\", headers: {Host: ${ARGO_DOMAIN}}, \"max_early_data\":2560, \"early_data_header_name\":\"Sec-WebSocket-Protocol\"} } - {name: \"${NODE_NAME}-Vm\", type: vmess, server: ${SERVER}, port: 443, uuid: ${UUID}, udp: true, alterId: 0, cipher: none, tls: true, servername: ${ARGO_DOMAIN}, skip-cert-verify: false, network: ws, ws-opts: {path: \"/${WS_PATH}-vm\", headers: {Host: ${ARGO_DOMAIN}}, \"max_early_data\":2560, \"early_data_header_name\":\"Sec-WebSocket-Protocol\"}} - {name: \"${NODE_NAME}-Tr\", type: trojan, server: ${SERVER}, port: 443, password: ${UUID}, udp: true, tls: true, servername: ${ARGO_DOMAIN}, sni: ${ARGO_DOMAIN}, skip-cert-verify: false, network: ws, ws-opts: {path: \"/${WS_PATH}-tr\", headers: {Host: ${ARGO_DOMAIN}}, \"max_early_data\":2560, \"early_data_header_name\":\"Sec-WebSocket-Protocol\" } } - {name: \"${NODE_NAME}-Sh\", type: ss, server: ${SERVER}, port: 443, cipher: ${SS_METHOD}, password: ${UUID}, udp: true, plugin: v2ray-plugin, plugin-opts: { mode: websocket, host: ${ARGO_DOMAIN}, path: \"/${WS_PATH}-sh\", tls: true, servername: ${ARGO_DOMAIN}, skip-cert-verify: false, mux: false } }" echo -n "${CLASH_SUBSCRIBE}" > $WORK_DIR/subscribe/proxies # 生成 clash 订阅配置文件 wget --no-check-certificate -qO- --tries=3 --timeout=2 ${SUBSCRIBE_TEMPLATE}/clash | sed "s#NODE_NAME#${NODE_NAME}#g; s#PROXY_PROVIDERS_URL#http://${ARGO_DOMAIN}/${UUID}/proxies#" > $WORK_DIR/subscribe/clash # 生成 Shadowrocket 订阅文件 local SHADOWROCKET_SUBSCRIBE="vless://$(echo -n "auto:${UUID}@${SERVER_IP_2}:${REALITY_PORT}" | base64 -w0)?remarks=${NODE_NAME// /%20}%20reality-vision&obfs=none&tls=1&peer=${TLS_SERVER}&xtls=2&pbk=${REALITY_PUBLIC} vless://$(echo -n "auto:${UUID}@${SERVER_IP_2}:${REALITY_PORT}" | base64 -w0)?remarks=${NODE_NAME// /%20}%20reality-grpc&path=grpc&obfs=grpc&tls=1&peer=${TLS_SERVER}&pbk=${REALITY_PUBLIC} vless://${UUID}@${SERVER}:443?encryption=none&security=tls&type=ws&host=${ARGO_DOMAIN}&path=/${WS_PATH}-vl?ed=2560&sni=${ARGO_DOMAIN}#${NODE_NAME// /%20}-Vl vmess://$(echo -n "none:${UUID}@${SERVER}:443" | base64 -w0)?remarks=${NODE_NAME// /%20}-Vm&obfsParam=${ARGO_DOMAIN}&path=/${WS_PATH}-vm?ed=2560&obfs=websocket&tls=1&peer=${ARGO_DOMAIN}&alterId=0 trojan://${UUID}@${SERVER}:443?peer=${ARGO_DOMAIN}&plugin=obfs-local;obfs=websocket;obfs-host=${ARGO_DOMAIN};obfs-uri=/${WS_PATH}-tr?ed=2560#${NODE_NAME// /%20}-Tr ss://$(echo -n "chacha20-ietf-poly1305:${UUID}@${SERVER}:443" | base64 -w0)?uot=2&v2ray-plugin=$(echo -n "{\"peer\":\"${ARGO_DOMAIN}\",\"mux\":false,\"path\":\"\\/${WS_PATH}-sh\",\"host\":\"${ARGO_DOMAIN}\",\"mode\":\"websocket\",\"tls\":true}" | base64 -w0)#${NODE_NAME}-Sh" echo -n "${SHADOWROCKET_SUBSCRIBE}" | base64 -w0 > $WORK_DIR/subscribe/shadowrocket # 生成 V2rayN / NekoBox 订阅文件 local V2RAYN_SUBSCRIBE="vless://${UUID}@${SERVER_IP_1}:${REALITY_PORT}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=tcp&headerType=none#${NODE_NAME}%20reality-vision vless://${UUID}@${SERVER_IP_1}:${REALITY_PORT}?security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=grpc&serviceName=grpc&encryption=none#${NODE_NAME// /%20}%20reality-grpc vless://${UUID}@${SERVER}:443?encryption=none&security=tls&sni=${ARGO_DOMAIN}&type=ws&host=${ARGO_DOMAIN}&path=%2F${WS_PATH}-vl%3Fed%3D2560#${NODE_NAME// /%20}-Vl vmess://$(echo -n "$VMESS" | base64 -w0) trojan://${UUID}@${SERVER}:443?security=tls&sni=${ARGO_DOMAIN}&type=ws&host=${ARGO_DOMAIN}&path=/${WS_PATH}-tr?ed%3D2560#${NODE_NAME// /%20}-Tr ss://$(echo -n "${SS_METHOD}:${UUID}" | base64 -w0)@${SERVER}:443?plugin=v2ray-plugin%3Bmode%3Dwebsocket%3Bhost%3D${ARGO_DOMAIN}%3Bpath%3D%2F${WS_PATH}-sh%3Btls#${NODE_NAME// /%20}-Sh ss://$(echo -n "${SS_METHOD}:${UUID}" | base64 -w0)@${SERVER}:443?plugin=v2ray-plugin;mode%3Dwebsocket;host%3D${ARGO_DOMAIN};path%3D/${WS_PATH}-sh;tls%3Dtrue;servername%3D${ARGO_DOMAIN};skip-cert-verify%3Dfalse;mux%3D0#${NODE_NAME// /%20}-Sh" echo -n "${V2RAYN_SUBSCRIBE}" | base64 -w0 > $WORK_DIR/subscribe/base64 # 生成 Sing-box 订阅文件 local OUTBOUND_REPLACE="{ \"type\":\"vless\", \"tag\":\"${NODE_NAME} reality-vision\", \"server\":\"${SERVER_IP}\", \"server_port\": ${REALITY_PORT}, \"uuid\":\"${UUID}\", \"flow\":\"xtls-rprx-vision\", \"packet_encoding\":\"xudp\", \"tls\":{ \"enabled\":true, \"server_name\":\"${TLS_SERVER}\", \"utls\":{ \"enabled\":true, \"fingerprint\":\"chrome\" }, \"reality\":{ \"enabled\":true, \"public_key\":\"${REALITY_PUBLIC}\", \"short_id\":\"\" } } }, { \"type\": \"vless\", \"tag\":\"${NODE_NAME} reality-grpc\", \"server\": \"${SERVER_IP}\", \"server_port\": ${REALITY_PORT}, \"uuid\": \"${UUID}\", \"packet_encoding\":\"xudp\", \"tls\": { \"enabled\": true, \"server_name\": \"${TLS_SERVER}\", \"utls\": { \"enabled\": true, \"fingerprint\": \"chrome\" }, \"reality\": { \"enabled\": true, \"public_key\": \"${REALITY_PUBLIC}\", \"short_id\": \"\" } }, \"transport\": { \"type\": \"grpc\", \"service_name\": \"grpc\" } }, { \"type\":\"vless\", \"tag\":\"${NODE_NAME}-Vl\", \"server\":\"${SERVER}\", \"server_port\":443, \"uuid\":\"${UUID}\", \"tls\": { \"enabled\":true, \"server_name\":\"${ARGO_DOMAIN}\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" } }, \"transport\": { \"type\":\"ws\", \"path\":\"/${WS_PATH}-vl\", \"headers\": { \"Host\": \"${ARGO_DOMAIN}\" }, \"max_early_data\":2560, \"early_data_header_name\":\"Sec-WebSocket-Protocol\" } }, { \"type\":\"vmess\", \"tag\":\"${NODE_NAME}-Vm\", \"server\":\"${SERVER}\", \"server_port\":443, \"uuid\":\"${UUID}\", \"tls\": { \"enabled\":true, \"server_name\":\"${ARGO_DOMAIN}\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" } }, \"transport\": { \"type\":\"ws\", \"path\":\"/${WS_PATH}-vm\", \"headers\": { \"Host\": \"${ARGO_DOMAIN}\" }, \"max_early_data\":2560, \"early_data_header_name\":\"Sec-WebSocket-Protocol\" } }, { \"type\":\"trojan\", \"tag\":\"${NODE_NAME}-Tr\", \"server\": \"${SERVER}\", \"server_port\": 443, \"password\": \"${UUID}\", \"tls\": { \"enabled\":true, \"server_name\":\"${ARGO_DOMAIN}\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" } }, \"transport\": { \"type\":\"ws\", \"path\":\"/${WS_PATH}-tr\", \"headers\": { \"Host\": \"${ARGO_DOMAIN}\" }, \"max_early_data\":2560, \"early_data_header_name\":\"Sec-WebSocket-Protocol\" } }, { \"type\": \"shadowsocks\", \"tag\": \"${NODE_NAME}-Sh\", \"server\": \"${SERVER}\", \"server_port\": 443, \"method\": \"chacha20-ietf-poly1305\", \"password\": \"${UUID}\", \"udp_over_tcp\": {\"enabled\": true,\"version\": 2}, \"plugin\": \"v2ray-plugin\", \"plugin_opts\": \"mode=websocket;host=${ARGO_DOMAIN};path=/${WS_PATH}-sh;tls=true;servername=${ARGO_DOMAIN};skip-cert-verify=false;mux=0\"}" local NODE_REPLACE="\"${NODE_NAME} reality-vision\", \"${NODE_NAME} reality-grpc\", \"${NODE_NAME}-Vl\", \"${NODE_NAME}-Vm\", \"${NODE_NAME}-Tr\", \"${NODE_NAME}-Sh\"" # 模板 local SING_BOX_JSON=$(wget --no-check-certificate -qO- --tries=3 --timeout=2 ${SUBSCRIBE_TEMPLATE}/sing-box) echo $SING_BOX_JSON | sed "s#\"\"#$OUTBOUND_REPLACE#; s#\"\"#$NODE_REPLACE#g" | $WORK_DIR/jq > $WORK_DIR/subscribe/sing-box # 生成二维码 url 文件 [ "$IS_NGINX" = 'is_nginx' ] && cat > $WORK_DIR/subscribe/qr << EOF $(text 66): $(text 67): https://${ARGO_DOMAIN}/${UUID}/auto $(text 67): $(text 64) QRcode: https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://${ARGO_DOMAIN}/${UUID}/auto $(text 67): $($WORK_DIR/qrencode "https://${ARGO_DOMAIN}/${UUID}/auto") EOF # 生成客户端配置文件 EXPORT_LIST_FILE="******************************************* ┌────────────────┐ ┌────────────────┐ │ │ │ │ │ $(warning "V2rayN") │ │ $(warning "NekoBox") │ │ │ │ │ └────────────────┘ └────────────────┘ ---------------------------- $(info "$(sed "G" <<< "${V2RAYN_SUBSCRIBE}") $(echo -e $(eval echo "\${$L[75]}"))") ******************************************* ┌────────────────┐ │ │ │ $(warning "Shadowrocket") │ │ │ └────────────────┘ ---------------------------- $(hint "$(sed "G" <<< "${SHADOWROCKET_SUBSCRIBE}")") ******************************************* ┌────────────────┐ │ │ │ $(warning "Clash Verge") │ │ │ └────────────────┘ ---------------------------- $(info "$(sed '1d;G' <<< "$CLASH_SUBSCRIBE")") ******************************************* ┌────────────────┐ │ │ │ $(warning "Sing-box") │ │ │ └────────────────┘ ---------------------------- $(hint "$(echo "{ \"outbounds\":[ ${OUTBOUND_REPLACE%,} ] }" | $WORK_DIR/jq) $(text 63)") " [ "$IS_NGINX" = 'is_nginx' ] && EXPORT_LIST_FILE+=" ******************************************* $(info "Index: https://${ARGO_DOMAIN}/${UUID}/ QR code: https://${ARGO_DOMAIN}/${UUID}/qr V2rayN / Nekoray $(text 66): https://${ARGO_DOMAIN}/${UUID}/base64") $(info "Clash $(text 66): https://${ARGO_DOMAIN}/${UUID}/clash sing-box $(text 66): https://${ARGO_DOMAIN}/${UUID}/sing-box Shadowrocket $(text 66): https://${ARGO_DOMAIN}/${UUID}/shadowrocket") ******************************************* $(hint " $(text 66): $(text 67): https://${ARGO_DOMAIN}/${UUID}/auto $(text 64) QRcode: $(text 67): https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://${ARGO_DOMAIN}/${UUID}/auto") $($WORK_DIR/qrencode https://${ARGO_DOMAIN}/${UUID}/auto) " EXPORT_LIST_FILE+=" $(info "\n******************************************* ${QUICK_TUNNEL_URL} ") " # 生成并显示节点信息 echo "$EXPORT_LIST_FILE" > $WORK_DIR/list cat $WORK_DIR/list # 显示脚本使用情况数据 statistics_of_run-times get } # 更换 Argo 隧道类型 change_argo() { check_install [[ ${STATUS[0]} = "$(text 26)" ]] && error " $(text 39) " # 统一处理 Argo 隧道类型检测 case $(grep "${DAEMON_RUN_PATTERN}" ${ARGO_DAEMON_FILE}) in *--config* ) ARGO_TYPE='Json'; ARGO_DOMAIN="$(grep -m1 '^vless.*&host=' $WORK_DIR/list | sed "s@.*host=\(.*\)&.*@\1@g")" ;; *--token* ) ARGO_TYPE='Token'; ARGO_DOMAIN="$(grep -m1 '^vless.*&host=' $WORK_DIR/list | sed "s@.*host=\(.*\)&.*@\1@g")" ;; * ) ARGO_TYPE='Try' ARGO_DOMAIN=$(wget -qO- http://localhost:${METRICS_PORT}/quicktunnel | awk -F '"' '{print $4}') esac hint "\n $(text 40) \n" unset ARGO_DOMAIN hint " $(text 41) \n" && reading " $(text 24) " CHANGE_TO case "$CHANGE_TO" in 1 ) cmd_systemctl disable argo [ -s $WORK_DIR/tunnel.json ] && rm -f $WORK_DIR/tunnel.{json,yml} if [ "$SYSTEM" = 'Alpine' ]; then # 修改 Alpine 的 OpenRC 服务文件 local ARGS="--edge-ip-version auto --no-autoupdate --metrics 0.0.0.0:${METRICS_PORT} --url http://localhost:${NGINX_PORT}" sed -i "s@^command_args=.*@command_args=\"$ARGS\"@g" ${ARGO_DAEMON_FILE} else # 修改 systemd 服务文件 sed -i "s@ExecStart=.*@ExecStart=$WORK_DIR/cloudflared tunnel --edge-ip-version auto --no-autoupdate --metrics 0.0.0.0:${METRICS_PORT} --url http://localhost:${NGINX_PORT}@g" ${ARGO_DAEMON_FILE} fi ;; 2 ) SERVER_IP=$(awk -F '"' '/"SERVER_IP"/{print $4}' $WORK_DIR/*inbound*.json) argo_variable cmd_systemctl disable argo if [ -n "$ARGO_TOKEN" ]; then [ -s $WORK_DIR/tunnel.json ] && rm -f $WORK_DIR/tunnel.{json,yml} if [ "$SYSTEM" = 'Alpine' ]; then # 修改 Alpine 的 OpenRC 服务文件 local ARGS="--edge-ip-version auto run --token ${ARGO_TOKEN}" sed -i "s@^command_args=.*@command_args=\"$ARGS\"@g" ${ARGO_DAEMON_FILE} else # 修改 systemd 服务文件 sed -i "s@ExecStart=.*@ExecStart=$WORK_DIR/cloudflared tunnel --edge-ip-version auto run --token ${ARGO_TOKEN}@g" ${ARGO_DAEMON_FILE} fi elif [ -n "$ARGO_JSON" ]; then [ -s $WORK_DIR/tunnel.json ] && rm -f $WORK_DIR/tunnel.{json,yml} json_argo if [ "$SYSTEM" = 'Alpine' ]; then # 修改 Alpine 的 OpenRC 服务文件 local ARGS="--edge-ip-version auto --config $WORK_DIR/tunnel.yml run" sed -i "s@^command_args=.*@command_args=\"$ARGS\"@g" ${ARGO_DAEMON_FILE} else # 修改 systemd 服务文件 sed -i "s@ExecStart=.*@ExecStart=$WORK_DIR/cloudflared tunnel --edge-ip-version auto --config $WORK_DIR/tunnel.yml run@g" ${ARGO_DAEMON_FILE} fi fi ;; * ) exit 0 esac [ "$IS_NGINX" = 'is_nginx' ] && json_nginx cmd_systemctl enable argo export_list } # 更换 cdn change_cdn() { [ ! -d "${WORK_DIR}" ] && error " $(text 70) " # 检测是否有使用 CDN,方法是查找是否有 ${WORK_DIR}/conf/ local CDN_NOW=$(awk -F '"' '/"SERVER"/{print $4; exit}' ${WORK_DIR}/inbound.json) # 提示当前使用的 CDN 并让用户选择或输入新的 CDN hint "\n $(text 71) \n" for ((c=0; c<${#CDN_DOMAIN[@]}; c++)); do hint " $[c+1]. ${CDN_DOMAIN[c]} " done reading "\n $(text 72) " CDN_CHOOSE # 如果用户直接回车,保持当前 CDN [ -z "$CDN_CHOOSE" ] && exit 0 # 如果用户直接回车,保持当前 CDN。否则则选择用户输入的 CDN if grep -q '.' <<< "$CDN_CHOOSE"; then [[ "$CDN_CHOOSE" =~ ^[1-9][0-9]*$ && "$CDN_CHOOSE" -le "${#CDN_DOMAIN[@]}" ]] && CDN_NEW=${CDN_DOMAIN[$((CDN_CHOOSE-1))]} || CDN_NEW=$CDN_CHOOSE # 使用 sed 更新所有文件中的 CDN 值 find ${WORK_DIR} -type f | xargs -P 50 sed -i "s/${CDN_NOW}/${CDN_NEW}/g" fi # 更新完成后提示并导出订阅列表 export_list grep -q '.' <<< "${CDN_NEW}" && info "\n $(text 73) \n" } # 卸载 ArgoX uninstall() { if [ -d $WORK_DIR ]; then cmd_systemctl disable argo cmd_systemctl disable xray [[ -s $WORK_DIR/nginx.conf && "$(ps -ef | grep -c '[n]ginx')" = 0 ]] && reading "\n $(text 65) " REMOVE_NGINX [ "${REMOVE_NGINX,,}" = 'y' ] && ${PACKAGE_UNINSTALL[int]} nginx >/dev/null 2>&1 # 根据系统类型删除不同的服务文件 [ "$SYSTEM" = 'Alpine' ] && rm -rf $WORK_DIR $TEMP_DIR /etc/init.d/{xray,argo} /usr/bin/argox || rm -rf $WORK_DIR $TEMP_DIR /etc/systemd/system/{xray,argo}.service /usr/bin/argox info "\n $(text 16) \n" else error "\n $(text 15) \n" fi } # Argo 与 Xray 的最新版本 version() { # Argo 版本 local ONLINE=$(wget --no-check-certificate -qO- "${GH_PROXY}https://api.github.com/repos/cloudflare/cloudflared/releases/latest" | grep "tag_name" | cut -d \" -f4) [ -z "$ONLINE" ] && error " $(text 74) " local LOCAL=$($WORK_DIR/cloudflared -v | awk '{for (i=0; i/dev/null && info " Argo $(text 28) $(text 37)" || error " Argo $(text 28) $(text 38) " else local APP=ARGO && error "\n $(text 48) " fi fi if [ "${UPDATE[1],,}" = 'y' ]; then wget --no-check-certificate -O $TEMP_DIR/Xray-linux-$XRAY_ARCH.zip ${GH_PROXY}https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-$XRAY_ARCH.zip if [ -s $TEMP_DIR/Xray-linux-$XRAY_ARCH.zip ]; then cmd_systemctl disable xray unzip -qo $TEMP_DIR/Xray-linux-$XRAY_ARCH.zip xray *.dat -d $WORK_DIR; rm -f $TEMP_DIR/Xray*.zip cmd_systemctl enable xray cmd_systemctl status xray &>/dev/null && info " Xray $(text 28) $(text 37)" || error " Xray $(text 28) $(text 38) " else local APP=Xray && error "\n $(text 48) " fi fi } # 判断当前 Argo-X 的运行状态,并对应的给菜单和动作赋值 menu_setting() { local PS_LIST=$(ps -eo pid,args | grep -E "$WORK_DIR.*([x]ray|[c]loudflared|[n]ginx)" | sed 's/^[ ]\+//g') if [[ "${STATUS[*]}" =~ $(text 27)|$(text 28) ]]; then if [ -s $WORK_DIR/cloudflared ]; then ARGO_VERSION=$($WORK_DIR/cloudflared -v | awk '{print $3}' | sed "s@^@Version: &@g") local ARGO_PID=$(awk '/xray run/{print $1}' <<< "$PS_LIST") local REALTIME_METRICS_PORT=$(ss -nltp | awk -v pid=${ARGO_PID} '$0 ~ "pid="pid"," {split($4, a, ":"); print a[length(a)]}') ss -nltp | grep -q "cloudflared.*pid=${ARGO_PID}," && ARGO_CHECKHEALTH="$(text 46): $(wget -qO- http://localhost:${REALTIME_METRICS_PORT}/healthcheck | sed "s/OK/$(text 37)/")" fi [ -s $WORK_DIR/xray ] && XRAY_VERSION=$($WORK_DIR/xray version | awk 'NR==1 {print $2}' | sed "s@^@Version: &@g") [ "$IS_NGINX" = 'is_nginx' ] && NGINX_VERSION=$(nginx -v 2>&1 | sed "s#.*/#Version: #") OPTION[1]="1 . $(text 29)" if [ ${STATUS[0]} = "$(text 28)" ]; then AEGO_MEMORY="$(text 52): $(awk '/VmRSS/{printf "%.1f\n", $2/1024}' /proc/$(awk '/\/etc\/argox\/cloudflared/{print $1}' <<< "$PS_LIST")/status) MB" [ "$IS_NGINX" = 'is_nginx' ] && NGINX_MEMORY="$(text 52): $(awk '/VmRSS/{printf "%.1f\n", $2/1024}' /proc/$(awk '/\/etc\/argox\/nginx/{print $1}' <<< "$PS_LIST")/status) MB" OPTION[2]="2 . $(text 27) Argo (argox -a)" else OPTION[2]="2 . $(text 28) Argo (argox -a)" fi [ ${STATUS[1]} = "$(text 28)" ] && XRAY_MEMORY="$(text 52): $(awk '/VmRSS/{printf "%.1f\n", $2/1024}' /proc/$(awk '/\/etc\/argox\/xray.*\/etc\/argox/{print $1}' <<< "$PS_LIST")/status) MB" && OPTION[3]="3 . $(text 27) Xray (argox -x)" || OPTION[3]="3 . $(text 28) Xray (argox -x)" OPTION[4]="4 . $(text 30)" OPTION[5]="5 . $(text 76)" OPTION[6]="6 . $(text 31)" OPTION[7]="7 . $(text 32)" OPTION[8]="8 . $(text 33)" OPTION[9]="9 . $(text 51)" OPTION[10]="10. $(text 57)" ACTION[1]() { export_list; exit 0; } [[ ${STATUS[0]} = "$(text 28)" ]] && ACTION[2]() { cmd_systemctl disable argo cmd_systemctl status argo &>/dev/null && error " Argo $(text 27) $(text 38) " || info "\n Argo $(text 27) $(text 37)" } || ACTION[2]() { cmd_systemctl enable argo sleep 2 cmd_systemctl status argo &>/dev/null && info "\n Argo $(text 28) $(text 37)" || error " Argo $(text 28) $(text 38) " grep -qs "^${DAEMON_RUN_PATTERN}.*${NGINX_PORT}$" ${ARGO_DAEMON_FILE} && export_list } [[ ${STATUS[1]} = "$(text 28)" ]] && ACTION[3]() { cmd_systemctl disable xray cmd_systemctl status xray &>/dev/null && error " Xray $(text 27) $(text 38) " || info "\n Xray $(text 27) $(text 37)" } || ACTION[3]() { cmd_systemctl enable xray sleep 2 cmd_systemctl status xray &>/dev/null && info "\n Xray $(text 28) $(text 37)" || error " Xray $(text 28) $(text 38) " } ACTION[4]() { change_argo; exit; } ACTION[5]() { change_cdn; exit; } ACTION[6]() { version; exit; } ACTION[7]() { bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh); exit; } ACTION[8]() { uninstall; exit; } ACTION[9]() { bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/fscarmen/sing-box/main/sing-box.sh) -$L; exit; } ACTION[10]() { bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/fscarmen/sba/main/sba.sh) -$L; exit; } else OPTION[1]="1. $(text 77)" OPTION[2]="2. $(text 34)" OPTION[3]="3. $(text 32)" OPTION[4]="4. $(text 51)" OPTION[5]="5. $(text 57)" ACTION[1]() { fast_install_variables; install_argox; export_list; create_shortcut; exit;} ACTION[2]() { install_argox; export_list; create_shortcut; exit; } ACTION[3]() { bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh); exit; } ACTION[4]() { bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/fscarmen/sing-box/main/sing-box.sh) -$L; exit; } ACTION[5]() { bash <(wget --no-check-certificate -qO- ${GH_PROXY}https://raw.githubusercontent.com/fscarmen/sba/main/sba.sh) -$L; exit; } fi [ "${#OPTION[@]}" -ge '10' ] && OPTION[0]="0 . $(text 35)" || OPTION[0]="0. $(text 35)" ACTION[0]() { exit; } } menu() { clear ### hint " $(text 2) " echo -e "======================================================================================================================\n" info " $(text 17):$VERSION\n $(text 18):$(text 1)\n $(text 19):\n\t $(text 20):$SYS\n\t $(text 21):$(uname -r)\n\t $(text 22):$ARGO_ARCH\n\t $(text 23):$VIRT " info "\t IPv4: $WAN4 $WARPSTATUS4 $COUNTRY4 $ASNORG4 " info "\t IPv6: $WAN6 $WARPSTATUS6 $COUNTRY6 $ASNORG6 " info "\t Argo: ${STATUS[0]}\t $ARGO_VERSION\t $AEGO_MEMORY\t $ARGO_CHECKHEALTH\n\t Xray: ${STATUS[1]}\t $XRAY_VERSION\t\t $XRAY_MEMORY " [ "$IS_NGINX" = 'is_nginx' ] && info "\t Nginx: ${STATUS[0]}\t $NGINX_VERSION\t $NGINX_MEMORY " echo -e "\n======================================================================================================================\n" for ((b=1;b<${#OPTION[*]};b++)); do hint " ${OPTION[b]} "; done hint " ${OPTION[0]} " reading "\n $(text 24) " CHOOSE # 输入必须是数字且少于等于最大可选项 if grep -qE "^[0-9]$" <<< "$CHOOSE" && [ "$CHOOSE" -lt "${#OPTION[*]}" ]; then ACTION[$CHOOSE] else warning " $(text 36) [0-$((${#OPTION[*]}-1))] " && sleep 1 && menu fi } check_cdn statistics_of_run-times update argox.sh 2>/dev/null # 传参 [[ "${*,,}" =~ '-e'|'-k' ]] && L=E [[ "${*,,}" =~ '-c'|'-b'|'-l' ]] && L=C while getopts ":AaXxTtDdUuNnVvBbF:f:KkLl" OPTNAME; do case "${OPTNAME,,}" in a ) select_language; check_system_info; check_install [ "${STATUS[0]}" = "$(text 28)" ] && { cmd_systemctl disable argo cmd_systemctl status argo &>/dev/null && error " Argo $(text 27) $(text 38) " || info "\n Argo $(text 27) $(text 37)" } || { cmd_systemctl enable argo sleep 2 if cmd_systemctl status argo &>/dev/null; then info "\n Argo $(text 28) $(text 37)" grep -qs "^${DAEMON_RUN_PATTERN}.*${NGINX_PORT}$" ${ARGO_DAEMON_FILE} && export_list else error " Argo $(text 28) $(text 38) " fi }; exit 0 ;; x ) select_language; check_system_info; check_install [ "${STATUS[1]}" = "$(text 28)" ] && { cmd_systemctl disable xray cmd_systemctl status xray &>/dev/null && error " Xray $(text 27) $(text 38) " || info "\n Xray $(text 27) $(text 37)" } || { cmd_systemctl enable xray sleep 2 cmd_systemctl status xray &>/dev/null && info "\n Xray $(text 28) $(text 37)" || error " Xray $(text 28) $(text 38) " }; exit 0 ;; t ) select_language; check_system_info; change_argo; exit 0 ;; d ) select_language; check_system_info; change_cdn; exit 0 ;; u ) select_language; check_system_info; uninstall; exit 0;; n ) select_language; check_system_info; export_list; exit 0 ;; v ) select_language; check_system_info; check_arch; version; exit 0;; b ) select_language; bash <(wget --no-check-certificate -qO- "${GH_PROXY}https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh"); exit ;; f ) NONINTERACTIVE_INSTALL='noninteractive_install'; VARIABLE_FILE=$OPTARG; . $VARIABLE_FILE ;; k|l ) fast_install_variables ;; esac done select_language check_root check_arch check_system_info check_dependencies [ "$NONINTERACTIVE_INSTALL" != 'noninteractive_install' ] && check_system_ip check_install menu_setting [ "$NONINTERACTIVE_INSTALL" = 'noninteractive_install' ] && ACTION[2] || menu