#!/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 ' ' > $path_webroot sed -i "s#document\.location =.*#document.location = 'http://$ip_port/ui/'#" $path_webroot else echo -e ' Unsupported Dashboard

Unsupported Dashboard

Sorry, xray/v2ray does not support the necessary Dashboard features.

' > $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 " 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