Files
BFR/box/scripts/box.tool
2026-08-13 11:56:37 +03:00

1344 lines
45 KiB
Bash
Executable File

#!/system/bin/sh
# Validate settings.ini
if ! /system/bin/sh -n /data/adb/box/settings.ini 2>"/data/adb/box/run/settings_err.log"; then
echo "Err: settings.ini contains a syntax error" | tee -a "/data/adb/box/run/settings_err.log"
exit 1
fi
scripts_dir="${0%/*}"
source /data/adb/box/settings.ini
# user agent
user_agent="box_for_root"
# whether use ghproxy to accelerate github download
url_ghproxy="https://ghfast.top"
use_ghproxy="false"
# to enable/disable download the stable mihomo kernel
mihomo_stable="enable"
singbox_stable="enable"
rev1="busybox wget --no-check-certificate -qO-"
if which curl >/dev/null; then
rev1="curl --progress-bar --insecure -sL"
fi
# Updating files from URLs
upfile() {
file="$1"
update_url="$2"
file_bak="${file}.bak"
if [ -f "${file}" ]; then
mv "${file}" "${file_bak}" || return 1
fi
# Use ghproxy
if [ "${use_ghproxy}" == true ] && [[ "${update_url}" == @(https://github.com/*|https://raw.githubusercontent.com/*|https://gist.github.com/*|https://gist.githubusercontent.com/*) ]]; then
update_url="${url_ghproxy}/${update_url}"
fi
# request
if which curl >/dev/null; then
# curl="$(which curl || echo /data/adb/box/bin/curl)"
request="curl --progress-bar"
request+=" -L"
request+=" --insecure"
request+=" --user-agent ${user_agent}"
request+=" --connect-timeout 30 --max-time 180"
request+=" -o ${file}"
request+=" ${update_url}"
echo "${yellow}${request}${normal}"
else
request="busybox wget"
request+=" --no-check-certificate"
request+=" --user-agent ${user_agent}"
request+=" -O ${file}"
request+=" ${update_url}"
echo "${yellow}${request}${normal}"
fi
${request} >&2 || {
if [ -f "${file_bak}" ]; then
mv "${file_bak}" "${file}" || true
fi
request_url=$(echo "${request}" | sed -E 's/(token=[^&]{6})[^&]*/\1*****/')
log Error "Download ${request_url} failed"
return 1
}
return 0
}
# Restart the binary, after stopping and running again
restart_box() {
"${scripts_dir}/box.service" restart
# PIDS=("clash" "xray" "sing-box" "v2fly" "hysteria")
PIDS=(${bin_name})
PID=""
i=0
while [ -z "$PID" ] && [ "$i" -lt "${#PIDS[@]}" ]; do
PID=$(busybox pidof "${PIDS[$i]}")
i=$((i+1))
done
if [ -n "$PID" ]; then
log Debug "${bin_name} Restart complete [$(date +"%F %R")]"
else
log Error "Failed to restart ${bin_name}."
${scripts_dir}/box.iptables disable >/dev/null 2>&1
fi
}
# Check Configuration
check() {
# su -c /data/adb/box/scripts/box.tool rconf
log Info "Checking configuration for <${bin_name}>..."
case "${bin_name}" in
sing-box)
if ${bin_path} check -D "${box_dir}/${bin_name}" -c "${sing_config}" >/dev/null; then
log Info "${sing_config} passed"
else
log Error "Configuration check failed for sing-box"
return 1
fi
;;
clash)
if ${bin_path} -t -d "${box_dir}/clash" -f "${clash_config}" 2>/dev/null; then
log Info "${clash_config} passed"
else
log Error "Configuration check failed for clash"
return 1
fi
;;
xray)
export XRAY_LOCATION_ASSET="${box_dir}/xray"
if ${bin_path} -test -confdir "${box_dir}/${bin_name}" 2>/dev/null; then
log Info "Xray configuration passed"
else
log Error "Configuration check failed for xray"
return 1
fi
;;
v2fly)
export V2RAY_LOCATION_ASSET="${box_dir}/v2fly"
if ${bin_path} test -d "${box_dir}/${bin_name}" >/dev/null; then
log Info "V2Fly configuration passed"
else
log Error "Configuration check failed for v2fly"
return 1
fi
;;
hysteria)
log Info "No configuration check implemented for hysteria, skipping."
;;
*)
log Error "<${bin_name}> unknown binary."
exit 1
;;
esac
}
# reload base config
reload() {
curl_command="curl"
# Pastikan curl tersedia
if ! command -v curl >/dev/null; then
if [ ! -x "${bin_dir}/curl" ]; then
log Debug "${bin_dir}/curl not found, downloading..."
upcurl || { log Error "Failed to install curl"; return 1; }
fi
curl_command="${bin_dir}/curl"
fi
# Cek config sebelum reload
if ! check; then
log Error "Configuration check failed, aborting reload."
return 1
fi
case "${bin_name}" in
clash|sing-box)
if [ "${bin_name}" = "clash" ] && [ "${xclash_option}" = "mihomo" ]; then
endpoint="http://${ip_port}/configs?force=true"
else
endpoint="http://${ip_port}/configs"
fi
if ${curl_command} -sS -X PUT \
-H "Authorization: Bearer ${secret}" \
"${endpoint}" \
-d '{"path": "", "payload": ""}'; then
log Info "${bin_name} configuration reloaded successfully."
else
log Error "${bin_name} configuration reload failed!"
return 1
fi
;;
xray|v2fly|hysteria)
if [ -f "${box_pid}" ]; then
pid="$(<"${box_pid}")"
if kill -0 "$pid" 2>/dev/null; then
log Info "Restarting ${bin_name} (PID: $pid)"
restart_box
else
log Error "${bin_name} process not running!"
return 1
fi
else
log Error "PID file for ${bin_name} not found!"
return 1
fi
;;
*)
log Warning "${bin_name} does not support API reload."
return 1
;;
esac
}
# Get latest curl
upcurl() {
local arch
case $(uname -m) in
"aarch64") arch="aarch64" ;;
"armv7l"|"armv8l") arch="armv7" ;;
"i686") arch="i686" ;;
"x86_64") arch="amd64" ;;
*)
log Warning "Unsupported architecture: $(uname -m)"
return 1
;;
esac
log Info "Detected architecture: $(uname -m) -> ${arch}"
# Backup existing curl if present
log Info "Ensuring backup directory: ${bin_dir}/backup"
mkdir -p "${bin_dir}/backup"
if [ -f "${bin_dir}/curl" ]; then
log Info "Backing up existing curl to ${bin_dir}/backup/curl.bak"
cp "${bin_dir}/curl" "${bin_dir}/backup/curl.bak" >/dev/null 2>&1
else
log Debug "No existing curl binary found, skipping backup."
fi
# Fetch latest version from GitHub
log Info "Fetching latest static-curl version..."
local latest_version=$($rev1 "https://api.github.com/repos/stunnel/static-curl/releases" \
| grep "tag_name" | busybox grep -oE "[0-9.]*" | head -1)
if [ -z "$latest_version" ]; then
log Error "Failed to retrieve latest static-curl version."
return 1
fi
log Info "Latest curl version: ${latest_version}"
# Download
local download_link="https://github.com/stunnel/static-curl/releases/download/${latest_version}/curl-linux-${arch}-glibc-${latest_version}.tar.xz"
log Info "Downloading from: ${download_link}"
if ! upfile "${bin_dir}/curl.tar.xz" "${download_link}"; then
log Error "Failed to download curl binary."
return 1
fi
# Extract
log Info "Extracting ${bin_dir}/curl.tar.xz..."
if busybox tar -xJf "${bin_dir}/curl.tar.xz" -C "${bin_dir}" >/dev/null; then
log Info "Extraction successful."
else
log Error "Failed to extract ${bin_dir}/curl.tar.xz"
if cp "${bin_dir}/backup/curl.bak" "${bin_dir}/curl" >/dev/null 2>&1; then
log Info "Restored curl from backup."
else
log Error "Failed to restore curl from backup."
return 1
fi
fi
# Permissions
log Info "Setting ownership and permissions..."
chown "${box_user_group}" "${bin_dir}/curl"
chmod 0700 "${bin_dir}/curl"
# Cleanup
log Info "Removing archive: ${bin_dir}/curl.tar.xz"
rm -f "${bin_dir}/curl.tar.xz"
rm -f "${bin_dir}/SHA256SUMS"
log Info "Curl update process completed."
}
# Get latest yq
upyq() {
local arch platform
case $(uname -m) in
"aarch64") arch="arm64"; platform="android" ;;
"armv7l"|"armv8l") arch="arm"; platform="android" ;;
"i686") arch="386"; platform="android" ;;
"x86_64") arch="amd64"; platform="android" ;;
*)
log Warning "Unsupported architecture: $(uname -m)"
return 1
;;
esac
log Info "Detected architecture: $(uname -m) -> platform=${platform}, arch=${arch}"
# Backup existing yq if exists
log Info "Ensuring backup directory: ${bin_dir}/backup"
mkdir -p "${bin_dir}/backup"
if [ -f "${box_dir}/bin/yq" ]; then
log Info "Backing up existing yq to ${bin_dir}/backup/yq.bak"
cp "${box_dir}/bin/yq" "${bin_dir}/backup/yq.bak" >/dev/null 2>&1
else
log Debug "No existing yq binary found, skipping backup."
fi
# Download link
local download_link="https://github.com/taamarin/yq/releases/download/prerelease/yq_${platform}_${arch}"
log Info "Downloading yq from: ${download_link}"
if ! upfile "${box_dir}/bin/yq" "${download_link}"; then
log Error "Failed to download yq binary."
if cp "${bin_dir}/backup/yq.bak" "${box_dir}/bin/yq" >/dev/null 2>&1; then
log Info "Restored yq from backup."
else
log Error "Failed to restore yq from backup."
fi
return 1
fi
# Permissions
log Info "Setting ownership and permissions for yq"
chown "${box_user_group}" "${box_dir}/bin/yq"
chmod 0700 "${box_dir}/bin/yq"
rm -f "${bin_dir}/yq.bak"
log Info "yq update process completed."
}
# Check and update geoip and geosite
upgeox() {
# su -c /data/adb/box/scripts/box.tool geox
geodata_mode=$(busybox awk '!/^ *#/ && /geodata-mode:*./{print $2}' "${clash_config}")
[ -z "${geodata_mode}" ] && geodata_mode=false
case "${bin_name}" in
clash)
if [[ "${xclash_option}" == "premium" || "${geodata_mode}" == "false" ]]; then
geoip_file="${box_dir}/clash/Country.mmdb"
geoip_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/country-lite.mmdb"
else
geoip_file="${box_dir}/clash/GeoIP.dat"
geoip_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/geoip-lite.dat"
fi
geosite_file="${box_dir}/clash/GeoSite.dat"
geosite_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/geosite.dat"
;;
sing-box)
geoip_file="${box_dir}/sing-box/geoip.db"
geoip_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/geoip-lite.db"
geosite_file="${box_dir}/sing-box/geosite.db"
geosite_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/geosite.db"
;;
*)
geoip_file="${box_dir}/${bin_name}/geoip.dat"
geoip_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/geoip-lite.dat"
geosite_file="${box_dir}/${bin_name}/geosite.dat"
geosite_url="https://github.com/MetaCubeX/meta-rules-dat/raw/release/geosite.dat"
;;
esac
if [ "${update_geo}" = "true" ]; then
log Info "${bin_name} daily updates GeoX → $(date)"
log Info "Downloading GeoIP file from: ${geoip_url}"
if upfile "${geoip_file}" "${geoip_url}"; then
log Info "GeoIP file updated: ${geoip_file}"
else
log Error "Failed to download GeoIP file."
return 1
fi
log Info "Downloading GeoSite file from: ${geosite_url}"
if upfile "${geosite_file}" "${geosite_url}"; then
log Info "GeoSite file updated: ${geosite_file}"
else
log Error "Failed to download GeoSite file."
return 1
fi
log Info "Cleaning old backup files..."
find "${box_dir}/${bin_name}" -maxdepth 1 -type f \( -name "*.db.bak" -o -name "*.dat.bak" -o -name "*.mmdb.bak" \) -delete >/dev/null
log Info "${bin_name} GeoX update completed → $(date)"
return 0
else
log Info "Geo update disabled, skipping."
return 1
fi
}
# Check and update subscription
upsubs() {
yq="yq"
if ! command -v yq &>/dev/null; then
if [ ! -e "${box_dir}/bin/yq" ]; then
log Debug "yq file not found, start to download from github"
${scripts_dir}/box.tool upyq
fi
yq="${box_dir}/bin/yq"
fi
case "${bin_name}" in
"clash")
# Clash Subscription
if [ -n "${subscription_url_clash[*]}" ]; then
if [ "${update_subscription}" = "true" ]; then
log Info "${bin_name} daily updates subscription → $(date)"
# renew = true → just take the first index
if [ "${renew}" = "true" ]; then
urls=("${subscription_url_clash[0]}")
cfgs=("${name_provide_clash_config[0]}")
else
# make sure the number of URLs and configs are the same
if [ "${#subscription_url_clash[@]}" -ne "${#name_provide_clash_config[@]}" ]; then
log Error "Mismatch: subscription_url_clash (${#subscription_url_clash[@]}) != name_provide_clash_config (${#name_provide_clash_config[@]})"
return 1
fi
urls=("${subscription_url_clash[@]}")
cfgs=("${name_provide_clash_config[@]}")
fi
for i in "${!urls[@]}"; do
sub_url="${urls[$i]}"
clash_provide_config="${clash_provide_path}/${cfgs[$i]}"
enhanced=false
update_file_name="${clash_config}"
if [ "${renew}" != "true" ]; then
enhanced=true
update_file_name="${update_file_name}.subscription"
fi
# token_url="${sub_url:0:25}*****"
token_url=$(echo "$sub_url" | sed -E 's/(token=[^&]{6})[^&]*/\1*****/')
log Debug "Downloading ${token_url}${update_file_name}"
if upfile "${update_file_name}" "${sub_url}"; then
log Info "${update_file_name} saved"
if [ "${enhanced}" = "true" ]; then
mkdir -p "$(dirname "${clash_provide_config}")"
if ${yq} 'has("proxies")' "${update_file_name}" | grep -q "true"; then
${yq} '.proxies' "${update_file_name}" >/dev/null 2>&1
${yq} '.proxies' "${update_file_name}" > "${clash_provide_config}"
${yq} -i '{"proxies": .}' "${clash_provide_config}"
if [ "${custom_rules_subs}" = "true" ]; then
if ${yq} '.rules' "${update_file_name}" >/dev/null; then
${yq} '.rules' "${update_file_name}" > "${clash_provide_rules}"
${yq} -i '{"rules": .}' "${clash_provide_rules}"
${yq} -i 'del(.rules)' "${clash_config}"
cat "${clash_provide_rules}" >> "${clash_config}"
fi
fi
log Info "subscription success"
log Info "Update subscription $(date +"%F %R")"
[ -f "${update_file_name}.bak" ] && rm "${update_file_name}.bak"
elif ${yq} '.. | select(tag == "!!str")' "${update_file_name}" | grep -qE "vless://|vmess://|ss://|hysteria2://|hysteria://|trojan://|tuic://|wireguard://|socks5://|http://|snell://|mieru://|anytls://"; then
mv "${update_file_name}" "${clash_provide_config}"
elif grep -qE '^[A-Za-z0-9+/=[:space:]]+$' "$update_file_name"; then
if busybox base64 -d "$update_file_name" >/dev/null 2>&1; then
log Debug "File is valid Base64"
mv "${update_file_name}" "${clash_provide_config}"
else
log Error "File is not valid Base64"
return 1
fi
else
log Error "${update_file_name} Unknown file format: cannot detect proxies, subscription URLs, or valid Base64"
return 1
fi
else
if [ -f "${box_pid}" ]; then
kill -0 "$(<"${box_pid}" 2>/dev/null)" && \
$scripts_dir/box.service restart 2>/dev/null
fi
log Info "${bin_name} subscription update completed → $(date)"
exit 1
fi
else
log Error "update $bin_name subscription failed → ${token_url}"
return 1
fi
done
log Info "All subscriptions updated → $(date)"
return 0
else
log Warning "update subscription: $update_subscription"
return 1
fi
else
log Warning "${bin_name} subscription url is empty..."
return 0
fi
;;
"sing-box")
# subscription sing-box
update_file_name="${sing_config}"
if [ -n "${subscription_url_singbox}" ]; then
if [ "${update_subscription}" = "true" ]; then
log Info "${bin_name} daily updates subscription → $(date)"
log Debug "Downloading ${update_file_name}"
if upfile "${update_file_name}" "${subscription_url_singbox}"; then
log Info "${update_file_name} saved"
if [ -f "${box_pid}" ]; then
kill -0 "$(<"${box_pid}" 2>/dev/null)" && \
$scripts_dir/box.service restart 2>/dev/null
fi
log Info "${bin_name} subscription update completed → $(date)"
exit 1
else
log Error "update subscription failed"
return 1
fi
else
log Warning "update subscription: $update_subscription"
return 1
fi
else
log Warning "${bin_name} subscription url is empty..."
return 1
fi
;;
"xray"|"v2fly"|"hysteria")
log Warning "${bin_name} does not support subscriptions.."
return 1
;;
*)
log Error "<${bin_name}> unknown binary."
return 1
;;
esac
}
upkernel() {
# su -c /data/adb/box/scripts/box.tool upkernel
mkdir -p "${bin_dir}/backup"
if [ -f "${bin_dir}/${bin_name}" ]; then
cp "${bin_dir}/${bin_name}" "${bin_dir}/backup/${bin_name}.bak" >/dev/null 2>&1
fi
case $(uname -m) in
"aarch64") if [ "${bin_name}" = "clash" ]; then arch="arm64-v8"; else arch="arm64"; fi; platform="android" ;;
"armv7l"|"armv8l") arch="armv7"; platform="linux" ;;
"i686") arch="386"; platform="linux" ;;
"x86_64") arch="amd64"; platform="linux" ;;
*) log Warning "Unsupported architecture: $(uname -m)" >/dev/null; exit 1 ;;
esac
# Do anything else below
file_kernel="${bin_name}-${arch}"
case "${bin_name}" in
"sing-box")
api_url="https://api.github.com/repos/SagerNet/sing-box/releases"
url_down="https://github.com/SagerNet/sing-box/releases"
if [ "${singbox_stable}" = "disable" ]; then
log Info "Fetching ${bin_name} pre-release version info..."
latest_version=$($rev1 "${api_url}" \
| grep "tag_name" | busybox grep -oE "v[0-9].*" | head -1 | cut -d'"' -f1)
else
log Info "Fetching ${bin_name} latest stable version info..."
latest_version=$($rev1 "${api_url}/latest" \
| grep "tag_name" | busybox grep -oE "v[0-9.]*" | head -1)
fi
if [ -z "$latest_version" ]; then
log Error "Failed to get latest version of sing-box."
return 1
fi
log Info "Latest ${bin_name} version: ${latest_version}"
download_link="${url_down}/download/${latest_version}/sing-box-${latest_version#v}-${platform}-${arch}.tar.gz"
log Info "Downloading from: ${download_link}"
if upfile "${box_dir}/${file_kernel}.tar.gz" "${download_link}"; then
log Info "Download completed: ${box_dir}/${file_kernel}.tar.gz"
log Info "Installing ${bin_name}..."
xkernel
else
log Error "Failed to download ${bin_name} binary."
return 1
fi
;;
"clash")
# If mihomo flag is active, download mihomo, otherwise give a warning.
if [ "${xclash_option}" = "mihomo" ]; then
download_link="https://github.com/MetaCubeX/mihomo/releases"
log Info "Updating Clash (mihomo) kernel from ${download_link}"
if [ "${mihomo_stable}" = "enable" ]; then
log Info "Fetching latest stable version..."
latest_version=$($rev1 "https://api.github.com/repos/MetaCubeX/mihomo/releases" \
| grep "tag_name" | busybox grep -oE "v[0-9.]*" | head -1)
if [ -z "${latest_version}" ]; then
log Error "Failed to retrieve latest stable version."
return 1
fi
tag="$latest_version"
log Info "Latest stable version: ${tag}"
else
log Info "Fetching latest alpha (Prerelease) version..."
if [ "$use_ghproxy" == true ]; then
log Info "Using GHProxy mirror: ${url_ghproxy}"
download_link="${url_ghproxy}/${download_link}"
fi
tag="Prerelease-Alpha"
latest_version=$($rev1 "${download_link}/expanded_assets/${tag}" \
| busybox grep -oE "alpha-[0-9a-z]+" | head -1)
if [ -z "${latest_version}" ]; then
log Error "Failed to retrieve latest alpha version."
return 1
fi
log Info "Latest alpha version: ${latest_version}"
fi
# Set file names according to platform and architecture
filename="mihomo-${platform}-${arch}-${latest_version}"
log Info "Preparing to download: ${filename}.gz"
# Download file
full_url="${download_link}/download/${tag}/${filename}.gz"
log Info "Downloading from: ${full_url}"
if upfile "${box_dir}/${file_kernel}.gz" "${full_url}"; then
log Info "Download completed successfully: ${box_dir}/${file_kernel}.gz"
log Info "Installing kernel..."
xkernel
else
log Error "Failed to download mihomo binary."
return 1
fi
else
log Warning "clash.${xclash_option} repository has been deleted."
fi
;;
"xray"|"v2fly")
# Specify the binary name
if [ "${bin_name}" = "xray" ]; then
bin='Xray'
repo_path="XTLS/Xray-core/releases"
else
bin='v2ray'
repo_path="v2fly/v2ray-core/releases"
fi
api_url="https://api.github.com/repos/${repo_path}"
log Info "Updating ${bin_name} from ${repo_path}"
log Info "Fetching latest release info from ${api_url}..."
# Get the latest version
latest_version=$($rev1 "${api_url}" | grep "tag_name" | busybox grep -oE "v[0-9.]*" | head -1)
if [ -z "${latest_version}" ]; then
log Error "Failed to retrieve latest version for ${bin_name}."
exit 1
fi
log Info "Latest ${bin_name} version: ${latest_version}"
# Specify files according to architecture
case $(uname -m) in
"i386") download_file="${bin}-linux-32.zip" ;;
"x86_64") download_file="${bin}-linux-64.zip" ;;
"armv7l"|"armv8l") download_file="${bin}-linux-arm32-v7a.zip" ;;
"aarch64") download_file="${bin}-android-arm64-v8a.zip" ;;
*)
log Error "Unsupported architecture: $(uname -m)"
exit 1
;;
esac
log Info "Detected architecture: $(uname -m) -> file: ${download_file}"
# Set link download
download_link="https://github.com/${repo_path}"
full_url="${download_link}/download/${latest_version}/${download_file}"
log Info "Downloading from: ${full_url}"
# Download & execute xkernel
if upfile "${box_dir}/${file_kernel}.zip" "${full_url}"; then
log Info "Download completed: ${box_dir}/${file_kernel}.zip"
log Info "Extracting and installing..."
xkernel
else
log Error "Failed to download ${bin_name} binary."
return 1
fi
;;
"hysteria")
local arch
case $(uname -m) in
"aarch64") arch="arm64" ;;
"armv7l" | "armv8l") arch="armv7" ;;
"i686") arch="386" ;;
"x86_64") arch="amd64" ;;
*)
log Warning "Unsupported architecture: $(uname -m)"
return 1
;;
esac
log Info "Detected architecture: $(uname -m) -> ${arch}"
# Create backup directory if it doesn't exist
log Info "Ensuring backup directory exists: ${bin_dir}/backup"
mkdir -p "${bin_dir}/backup"
# Backup existing Hysteria binary if it exists
if [ -f "${bin_dir}/hysteria" ]; then
log Info "Backing up existing Hysteria binary to ${bin_dir}/backup/hysteria.bak"
cp "${bin_dir}/hysteria" "${bin_dir}/backup/hysteria.bak" >/dev/null 2>&1
else
log Debug "No existing Hysteria binary found, skipping backup."
fi
# Fetch the latest version of Hysteria from GitHub releases
log Info "Fetching latest Hysteria version from GitHub..."
local latest_version=$($rev1 "https://api.github.com/repos/apernet/hysteria/releases" \
| grep "tag_name" | grep -oE "[0-9.].*" | head -1 | sed 's/,//g' | cut -d '"' -f 1)
if [ -z "${latest_version}" ]; then
log Error "Failed to retrieve latest Hysteria version."
return 1
fi
log Info "Latest Hysteria version: v${latest_version}"
local download_link="https://github.com/apernet/hysteria/releases/download/app%2Fv${latest_version}/hysteria-android-${arch}"
log Info "Downloading Hysteria from: ${download_link}"
if upfile "${bin_dir}/hysteria" "${download_link}"; then
log Info "Hysteria binary downloaded successfully to ${bin_dir}/hysteria"
log Info "Reloading kernel..."
xkernel
else
log Error "Failed to download Hysteria binary."
return 1
fi
;;
*)
log Error "<${bin_name}> unknown binary."
exit 1
;;
esac
}
# Check and update kernel
xkernel() {
case "${bin_name}" in
"clash")
gunzip_command="gunzip"
if ! command -v gunzip >/dev/null; then
gunzip_command="busybox gunzip"
log Info "Using busybox gunzip"
else
log Info "Using system gunzip"
fi
mkdir -p "${bin_dir}/xclash" && \
log Info "Creating directory: ${bin_dir}/xclash"
log Info "Extracting kernel: ${box_dir}/${file_kernel}.gz"
if ${gunzip_command} "${box_dir}/${file_kernel}.gz" >/dev/null; then
log Info "Extraction successful: ${box_dir}/${file_kernel}"
log Info "Moving kernel to ${bin_dir}/xclash/${xclash_option}"
if mv "${box_dir}/${file_kernel}" "${bin_dir}/xclash/${xclash_option}"; then
log Info "Kernel moved successfully."
log Info "Creating symlink: ${bin_dir}/${bin_name} -> ${bin_dir}/xclash/${xclash_option}"
ln -sf "${bin_dir}/xclash/${xclash_option}" "${bin_dir}/${bin_name}"
if [ -f "${box_pid}" ]; then
log Info "Restarting ${bin_name} service..."
restart_box
else
log Debug "${bin_name} does not need to be restarted."
fi
else
log Error "Failed to move the extracted kernel."
fi
else
log Error "Failed to extract kernel: ${box_dir}/${file_kernel}.gz"
fi
;;
"sing-box")
tar_command="tar"
if ! command -v tar >/dev/null; then
tar_command="busybox tar"
log Info "Using busybox tar"
else
log Info "Using system tar"
fi
log Info "Extracting kernel archive: ${box_dir}/${file_kernel}.tar.gz"
if ${tar_command} -xf "${box_dir}/${file_kernel}.tar.gz" -C "${bin_dir}" >/dev/null; then
log Info "Extraction successful."
src_dir="${bin_dir}/sing-box-${latest_version#v}-${platform}-${arch}"
log Info "Moving binary from ${src_dir}/sing-box to ${bin_dir}/${bin_name}"
if mv "${src_dir}/sing-box" "${bin_dir}/${bin_name}"; then
log Info "Kernel binary moved successfully."
if [ -f "${box_pid}" ]; then
log Info "Removing cache.db for clean restart..."
rm -rf /data/adb/box/sing-box/cache.db
log Info "Restarting ${bin_name}..."
restart_box
else
log Debug "${bin_name} does not need to be restarted."
fi
else
log Error "Failed to move binary from ${src_dir}."
fi
else
log Error "Failed to extract ${box_dir}/${file_kernel}.tar.gz."
fi
if [ -d "${bin_dir}/sing-box-${latest_version#v}-${platform}-${arch}" ]; then
log Info "Cleaning up extracted directory: ${src_dir}"
rm -r "${src_dir}"
fi
;;
"v2fly"|"xray")
bin="xray"
if [ "${bin_name}" != "xray" ]; then
bin="v2ray"
fi
log Info "Selected binary to extract: ${bin}"
unzip_command="unzip"
if ! command -v unzip >/dev/null; then
unzip_command="busybox unzip"
log Info "Using busybox unzip"
else
log Info "Using system unzip"
fi
log Info "Creating temporary update directory: ${bin_dir}/update"
mkdir -p "${bin_dir}/update"
log Info "Extracting ${bin} from ${box_dir}/${file_kernel}.zip..."
if ${unzip_command} -o "${box_dir}/${file_kernel}.zip" "${bin}" -d "${bin_dir}/update" >/dev/null; then
log Info "Extraction successful."
log Info "Moving ${bin} binary to ${bin_dir}/${bin_name}"
if mv "${bin_dir}/update/${bin}" "${bin_dir}/${bin_name}"; then
log Info "Kernel binary moved successfully."
if [ -f "${box_pid}" ]; then
log Info "Restarting ${bin_name}..."
restart_box
else
log Debug "${bin_name} does not need to be restarted."
fi
else
log Error "Failed to move the kernel binary from ${bin_dir}/update/${bin}."
fi
else
log Error "Failed to extract ${box_dir}/${file_kernel}.zip."
fi
log Info "Cleaning up temporary directory: ${bin_dir}/update"
rm -rf "${bin_dir}/update"
;;
"hysteria")
if [ -f "${box_pid}" ]; then
restart_box
else
log Debug "${bin_name} does not need to be restarted."
fi
;;
*)
log Error "<${bin_name}> unknown binary."
exit 1
;;
esac
find "${box_dir}" -maxdepth 1 -type f -name "${file_kernel}.*" -delete >/dev/null
chown ${box_user_group} ${bin_path}
chmod 6755 ${bin_path}
}
# Check and update yacd
upxui() {
# su -c /data/adb/box/scripts/box.tool upxui
xdashboard="${bin_name}/dashboard"
if [[ "${bin_name}" == @(clash|sing-box) ]]; then
file_dashboard="${box_dir}/${xdashboard}.zip"
url="https://github.com/Zephyruso/zashboard/archive/gh-pages.zip"
if [ "$use_ghproxy" == true ]; then
url="${url_ghproxy}/${url}"
fi
dir_name="zashboard-gh-pages"
log Debug "Download ${url}"
if which curl >/dev/null; then
rev2="curl -L --progress-bar --insecure ${url} -o"
else
rev2="busybox wget --no-check-certificate ${url} -O"
fi
if $rev2 "${file_dashboard}" >/dev/null; then
log Info "Dashboard file downloaded: ${file_dashboard}"
if [ ! -d "${box_dir}/${xdashboard}" ]; then
log Info "Dashboard folder not exist, creating it"
mkdir "${box_dir}/${xdashboard}"
else
log Info "Dashboard folder exists, cleaning old files"
rm -rf "${box_dir}/${xdashboard}/"*
fi
if command -v unzip >/dev/null; then
unzip_command="unzip"
log Info "Using system unzip"
else
unzip_command="busybox unzip"
log Info "Using busybox unzip"
fi
log Info "Extracting dashboard from ${file_dashboard}..."
"${unzip_command}" -o "${file_dashboard}" "${dir_name}/*" -d "${box_dir}/${xdashboard}" >/dev/null
log Info "Moving extracted files to ${box_dir}/${xdashboard}/"
mv -f "${box_dir}/${xdashboard}/$dir_name"/* "${box_dir}/${xdashboard}/"
log Info "Cleaning up temporary files"
rm -f "${file_dashboard}"
rm -rf "${box_dir}/${xdashboard}/${dir_name}"
log Info "Dashboard update completed successfully"
else
log Error "Failed to download dashboard"
return 1
fi
return 0
else
log Debug "${bin_name} does not support dashboards"
return 1
fi
}
cgroup_blkio() {
local pid_file="$1"
local fallback_weight="${2:-900}" # default weight jika pakai 'box'
if [ -z "$pid_file" ] || [ ! -f "$pid_file" ]; then
log Warning "PID file missing or invalid: $pid_file"
return 1
fi
local PID=$(<"$pid_file" 2>/dev/null)
if [ -z "$PID" ] || ! kill -0 "$PID" >/dev/null; then
log Warning "Invalid or dead PID: $PID"
return 1
fi
# Temukan blkio path
if [ -z "$blkio_path" ]; then
blkio_path=$(mount | busybox awk '/blkio/ {print $3}' | head -1)
if [ -z "$blkio_path" ] || [ ! -d "$blkio_path" ]; then
log Warning "blkio path not found"
return 1
fi
fi
# Pilih target group: foreground jika ada, jika tidak buat box
local target
if [ -d "${blkio_path}/foreground" ]; then
target="${blkio_path}/foreground"
log Info "Using existing blkio group: foreground"
else
target="${blkio_path}/box"
mkdir -p "$target"
echo "$fallback_weight" > "${target}/blkio.weight"
log Info "Created blkio group: box with weight $fallback_weight"
fi
echo "$PID" > "${target}/cgroup.procs" \
&& log Info "Assigned PID $PID to $target"
return 0
}
cgroup_memcg() {
local pid_file="$1"
local raw_limit="$2"
if [ -z "$pid_file" ] || [ ! -f "$pid_file" ]; then
log Warning "PID file missing or invalid: $pid_file"
return 1
fi
if [ -z "$raw_limit" ]; then
log Warning "memcg limit not specified"
return 1
fi
local limit
case "$raw_limit" in
*[Mm])
limit=$(( ${raw_limit%[Mm]} * 1024 * 1024 ))
;;
*[Gg])
limit=$(( ${raw_limit%[Gg]} * 1024 * 1024 * 1024 ))
;;
*[Kk])
limit=$(( ${raw_limit%[Kk]} * 1024 ))
;;
*[0-9])
limit=$raw_limit # assume raw bytes
;;
*)
log Warning "Invalid memcg limit format: $raw_limit"
return 1
;;
esac
local PID
PID=$(<"$pid_file" 2>/dev/null)
if [ -z "$PID" ] || ! kill -0 "$PID" >/dev/null; then
log Warning "Invalid or dead PID: $PID"
return 1
fi
# Deteksi memcg_path jika belum diset
if [ -z "$memcg_path" ]; then
memcg_path=$(mount | grep cgroup | busybox awk '/memory/{print $3}' | head -1)
if [ -z "$memcg_path" ] || [ ! -d "$memcg_path" ]; then
log Warning "memcg path could not be determined"
return 1
fi
fi
# Gunakan bin_name jika tersedia, default ke 'app'
local name="${bin_name:-app}"
local target="${memcg_path}/${name}"
mkdir -p "$target"
echo "$limit" > "${target}/memory.limit_in_bytes" \
&& log Info "Set memory limit for $name: ${limit} bytes"
echo "$PID" > "${target}/cgroup.procs" \
&& log Info "Assigned PID $PID to ${target}"
return 0
}
cgroup_cpuset() {
local pid_file="${1}"
local cores="${2}"
if [ -z "${pid_file}" ] || [ ! -f "${pid_file}" ]; then
log Warning "Missing or invalid PID file: ${pid_file}"
return 1
fi
local PID
PID=$(<"${pid_file}" 2>/dev/null)
if [ -z "$PID" ] || ! kill -0 "$PID" >/dev/null; then
log Warning "PID $PID from ${pid_file} is not valid or not running"
return 1
fi
# Deteksi jumlah core jika cores belum ditentukan
if [ -z "${cores}" ]; then
local total_core
total_core=$(nproc --all 2>/dev/null)
if [ -z "$total_core" ] || [ "$total_core" -le 0 ]; then
log Warning "Failed to detect CPU cores"
return 1
fi
cores="0-$((total_core - 1))"
fi
# Deteksi cpuset_path
if [ -z "${cpuset_path}" ]; then
cpuset_path=$(mount | grep cgroup | busybox awk '/cpuset/{print $3}' | head -1)
if [ -z "${cpuset_path}" ] || [ ! -d "${cpuset_path}" ]; then
log Warning "cpuset path not found"
return 1
fi
fi
local cpuset_target="${cpuset_path}/foreground"
if [ ! -d "${cpuset_target}" ]; then
cpuset_target="${cpuset_path}/top-app"
elif [ ! -d "${cpuset_target}" ]; then
cpuset_target="${cpuset_path}/apps"
[ ! -d "${cpuset_target}" ] && log Warning "cpuset target not found" && return 1
fi
echo "${cores}" > "${cpuset_target}/cpus"
echo "0" > "${cpuset_target}/mems"
echo "${PID}" > "${cpuset_target}/cgroup.procs" \
&& log Info "Assigned PID $PID to ${cpuset_target} with CPU cores [$cores]"
return 0
}
ip_port=$(if [ "${bin_name}" = "clash" ]; then busybox awk '/external-controller:/ {print $2}' "${clash_config}"; else find /data/adb/box/sing-box/ -type f -name 'config.json' -exec busybox awk -F'[:,]' '/external_controller/ {print $2":"$3}' {} \; | sed 's/^[ \t]*//;s/"//g'; fi;)
secret=""
webroot() {
path_webroot="/data/adb/modules/box_for_root/webroot/index.html"
touch -n > $path_webroot
if [[ "${bin_name}" = @(clash|sing-box) ]]; then
echo -e '
<!DOCTYPE html>
<script>
document.location = 'http://127.0.0.1:9090/ui/'
</script>
</html>
' > $path_webroot
sed -i "s#document\.location =.*#document.location = 'http://$ip_port/ui/'#" $path_webroot
else
echo -e '
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Unsupported Dashboard</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
}
h1 {
color: red;
}
</style>
</head>
<body>
<h1>Unsupported Dashboard</h1>
<p>Sorry, xray/v2ray does not support the necessary Dashboard features.</p>
</body>
</html>' > $path_webroot
fi
}
bond0() {
# Menonaktifkan mode low latency untuk TCP
sysctl -w net.ipv4.tcp_low_latency=0 >/dev/null 2>&1
log Debug "tcp low latency: 0"
# Mengatur panjang antrian transmisi (txqueuelen) menjadi 3000 untuk semua interface wireless (wlan*)
for dev in /sys/class/net/wlan*; do ip link set dev $(basename $dev) txqueuelen 3000; done
log Debug "wlan* txqueuelen: 3000"
# Mengatur panjang antrian transmisi (txqueuelen) menjadi 1000 untuk semua interface rmnet_data*
for txqueuelen in /sys/class/net/rmnet_data*; do txqueuelen_name=$(basename $txqueuelen); ip link set dev $txqueuelen_name txqueuelen 1000; done
log Debug "rmnet_data* txqueuelen: 1000"
# Mengatur MTU (Maximum Transmission Unit) menjadi 1500 untuk semua interface rmnet_data*
for mtu in /sys/class/net/rmnet_data*; do mtu_name=$(basename $mtu); ip link set dev $mtu_name mtu 1500; done
log Debug "rmnet_data* mtu: 1500"
}
bond1() {
# Mengaktifkan mode low latency untuk TCP
sysctl -w net.ipv4.tcp_low_latency=1 >/dev/null 2>&1
log Debug "tcp low latency: 1"
# Mengatur panjang antrian transmisi (txqueuelen) menjadi 4000 untuk semua interface wireless (wlan*)
for dev in /sys/class/net/wlan*; do ip link set dev $(basename $dev) txqueuelen 4000; done
log Debug "wlan* txqueuelen: 4000"
# Mengatur panjang antrian transmisi (txqueuelen) menjadi 2000 untuk semua interface rmnet_data*
for txqueuelen in /sys/class/net/rmnet_data*; do txqueuelen_name=$(basename $txqueuelen); ip link set dev $txqueuelen_name txqueuelen 2000; done
log Debug "rmnet_data* txqueuelen: 2000"
# Mengatur MTU (Maximum Transmission Unit) menjadi 9000 untuk semua interface rmnet_data*
for mtu in /sys/class/net/rmnet_data*; do mtu_name=$(basename $mtu); ip link set dev $mtu_name mtu 9000; done
log Debug "rmnet_data* mtu: 9000"
}
upsidercar() {
# su -c /data/adb/box/scripts/box.tool upsidercar [olcrtc|trusttunnel|all]
target="${1:-all}"
mkdir -p "${bin_dir}" "${box_dir}/sidecar"
case $(uname -m) in
aarch64) tt_arch="linux-aarch64" ;;
armv7l|armv8l) tt_arch="linux-armv7" ;;
x86_64) tt_arch="linux-x86_64" ;;
i686) tt_arch="linux-x86_64" ;;
*) log Error "Unsupported arch: $(uname -m)"; return 1 ;;
esac
if [ "${target}" = "trusttunnel" ] || [ "${target}" = "all" ]; then
api="https://api.github.com/repos/TrustTunnel/TrustTunnelClient/releases/latest"
tag=$($rev1 "${api}" | grep '"tag_name"' | head -1 | busybox grep -oE 'v[0-9.]+')
ver="${tag#v}"
asset="trusttunnel_client-${tag}-${tt_arch}.tar.gz"
url="https://github.com/TrustTunnel/TrustTunnelClient/releases/download/${tag}/${asset}"
log Info "Downloading TrustTunnel client ${tag} (${tt_arch})"
if upfile "${box_dir}/${asset}" "${url}"; then
tar -xzf "${box_dir}/${asset}" -C "${box_dir}" 2>/dev/null || tar -xzf "${box_dir}/${asset}" -C "${bin_dir}" 2>/dev/null
find "${box_dir}" "${bin_dir}" -maxdepth 3 -name 'trusttunnel_client' -type f 2>/dev/null | while read -r f; do
cp "${f}" "${bin_dir}/trusttunnel_client"
chmod 755 "${bin_dir}/trusttunnel_client"
done
rm -f "${box_dir}/${asset}"
[ -x "${bin_dir}/trusttunnel_client" ] && log Info "Installed ${bin_dir}/trusttunnel_client" || log Warning "Extract trusttunnel_client manually to ${bin_dir}/"
else
log Error "TrustTunnel download failed"
fi
fi
if [ "${target}" = "olcrtc" ] || [ "${target}" = "all" ]; then
if [ -x "${bin_dir}/olcrtc" ]; then
log Info "olcrtc already present: ${bin_dir}/olcrtc"
"${bin_dir}/olcrtc" 2>&1 | head -1 || true
else
log Warning "olcrtc has no official Android release bundle."
log Warning "On PC build linux/arm64: tools/build_olcrtc_arm64.ps1"
log Warning "Then: adb push olcrtc-linux-arm64 /data/local/tmp/olcrtc && su -c 'cp /data/local/tmp/olcrtc ${bin_dir}/olcrtc && chmod 755 ${bin_dir}/olcrtc'"
log Warning "Config: ${box_dir}/sidecar/olcrtc.example.yaml -> olcrtc.yaml (socks.port 10801 for dual mode)"
fi
fi
}
upwgobfs() {
# su -c /data/adb/box/scripts/box.tool upwgobfs
mkdir -p "${bin_dir}" "${box_dir}/sidecar"
case $(uname -m) in
aarch64) asset="wg-obfuscator-v1.5-linux-arm64.tar.gz" ;;
armv7l|armv8l) asset="wg-obfuscator-v1.5-linux-armv7-hf.tar.gz" ;;
x86_64) asset="wg-obfuscator-v1.5-linux-x64.tar.gz" ;;
*) log Error "Unsupported arch for wg-obfuscator: $(uname -m)"; return 1 ;;
esac
url="https://github.com/ClusterM/wg-obfuscator/releases/download/v1.5/${asset}"
log Info "Downloading ${asset}"
if upfile "${box_dir}/${asset}" "${url}"; then
tar -xzf "${box_dir}/${asset}" -C "${box_dir}" 2>/dev/null
find "${box_dir}" -maxdepth 3 -name 'wg-obfuscator' -type f 2>/dev/null | while read -r f; do
cp -f "${f}" "${bin_dir}/wg-obfuscator"
chmod 755 "${bin_dir}/wg-obfuscator"
done
rm -f "${box_dir}/${asset}"
if [ -x "${bin_dir}/wg-obfuscator" ]; then
log Info "Installed ${bin_dir}/wg-obfuscator"
"${bin_dir}/wg-obfuscator" -V 2>&1 | head -2 || true
else
log Error "wg-obfuscator binary not found after extract"
fi
if [ ! -f "${box_dir}/sidecar/wg-obfuscator.conf" ] && [ -f "${box_dir}/sidecar/wg-obfuscator.example.conf" ]; then
cp -f "${box_dir}/sidecar/wg-obfuscator.example.conf" "${box_dir}/sidecar/wg-obfuscator.conf"
log Warning "Created sidecar/wg-obfuscator.conf - set key= and target="
fi
else
log Error "Download failed: ${url}"
return 1
fi
}
case "$1" in
check)
check
;;
memcg|cpuset|blkio)
case "$1" in
memcg)
memcg_path=""
cgroup_memcg "${box_pid}" ${memcg_limit}
;;
cpuset)
cpuset_path=""
cgroup_cpuset "${box_pid}" ${allow_cpu}
;;
blkio)
blkio_path=""
cgroup_blkio "${box_pid}" "${weight}"
;;
esac
;;
bond0|bond1)
$1
;;
upsidercar)
upsidercar
;;
upwgobfs)
upwgobfs
;;
geosub)
upgeox
upsubs
if [ -f "${box_pid}" ]; then
kill -0 "$(<"${box_pid}" 2>/dev/null)" && reload
fi
;;
geox|subs)
if [ "$1" = "geox" ]; then
upgeox
else
upsubs
[ "${bin_name}" != "clash" ] && exit 1
fi
if [ -f "${box_pid}" ]; then
kill -0 "$(<"${box_pid}" 2>/dev/null)" && reload
fi
;;
upkernel)
upkernel
;;
upxui)
upxui
;;
upyq|upcurl)
$1
;;
reload)
reload
;;
webroot)
webroot
;;
all)
update_geo="true"
for bin_name in "$2"; do
upkernel
upgeox
upsubs
upxui
done
;;
help|-h|--help|"")
echo "Usage: $0 <command>"
echo
echo "Commands:"
echo " check - Check Configuration"
echo " memcg - Apply memory cgroup limit to process"
echo " cpuset - Apply CPU core affinity to process"
echo " blkio - Apply I/O weight limit to process"
echo " bond0|bond1 - Run bonding configuration functions"
echo " geosub - Update both subscription and GeoX files, then reload if running"
echo " geox - Update GeoX database, then reload if running"
echo " subs - Update subscription (only for clash), then reload if running"
echo " upkernel - Update kernel-related components"
echo " upsidercar - Install sidecar binaries (olcrtc / trusttunnel)"
echo " upwgobfs - Install ClusterM wg-obfuscator (WireGuard UDP obfuscation)"
echo " upxui - Update XUI panel"
echo " upyq - Update yq binary"
echo " upcurl - Update curl binary"
echo " reload - Reload running service configuration"
echo " webroot - Update/rebuild webroot files"
echo " all - Run all update commands in sequence"
echo
echo "Example:"
echo " $0 check"
;;
*)
echo "${red}$0 $1 not found${normal}"
echo "Run '$0 help' for usage."
;;
esac