#!/bin/sh # B4 Universal Installer Script (POSIX Compliant) # Automatically detects system architecture and installs the appropriate b4 binary # Supports OpenWRT, MerlinWRT, and other Linux-based routers with only sh shell # # AUTO-GENERATED - Do not edit directly # Edit files in installer/ and run installer/build.sh # set -e # Entware paths first so wget-ssl/curl from /opt/bin are preferred over BusyBox export PATH="/opt/bin:/opt/sbin:$HOME/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:$PATH" # --- END header.sh --- # Configuration REPO_OWNER="DanielLavrushin" REPO_NAME="b4" # These will be set dynamically by set_system_paths() INSTALL_DIR="" CONFIG_DIR="" SERVICE_DIR="" SERVICE_NAME="" SYSTEM_TYPE="" BINARY_NAME="b4" CONFIG_FILE="" # Will be set after CONFIG_DIR is determined TEMP_DIR="/tmp/b4_install_$$" QUIET_MODE="0" WGET_INSECURE="" # Set to "--no-check-certificate" if CA certs are missing GEOSITE_SRC="" GEOSITE_DST="" # Proxy configuration for GitHub fallback PROXY_BASE_URL="https://proxy.lavrush.in/github" # geodat sources (pipe-delimited: number|name|url) GEODAT_SOURCES="1|Loyalsoldier source|https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download 2|RUNET Freedom source [recommended]|https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release 3|Nidelon source|https://github.com/Nidelon/ru-block-v2ray-rules/releases/latest/download 4|DustinWin source|https://github.com/DustinWin/ruleset_geodata/releases/download/mihomo 5|Chocolate4U source|https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release" # --- END config.sh --- # Colors for output (if terminal supports it) if [ -t 1 ]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' BOLD='\033[1m' NC='\033[0m' # No Color else RED='' GREEN='' YELLOW='' BLUE='' CYAN='' MAGENTA='' BOLD='' NC='' fi # --- END colors.sh --- # Helper functions print_info() { if [ "$QUIET_MODE" -eq 0 ]; then printf "${BLUE}[INFO ]${NC} %s\n" "$1" >&2 fi } print_success() { if [ "$QUIET_MODE" -eq 0 ]; then printf "${GREEN}[SUCCESS]${NC} %s\n" "$1" >&2 fi } print_error() { printf "${RED}[ERROR ]${NC} %s\n" "$1" >&2 } print_warning() { if [ "$QUIET_MODE" -eq 0 ]; then printf "${YELLOW}[WARNING]${NC} %s\n" "$1" >&2 fi } print_header() { if [ "$QUIET_MODE" -eq 0 ]; then printf "\n${MAGENTA}%s${NC}\n" "$1" >&2 fi } print_detail() { if [ "$QUIET_MODE" -eq 0 ]; then printf " ${CYAN}%-22s${NC}: %b\n" "$1" "$2" >&2 fi } # Check if command exists (works on routers without 'command' builtin) command_exists() { # Try which first (common on routers) if which "$1" >/dev/null 2>&1; then return 0 fi # Fallback: try to run with --help (most commands support this) if $1 --help >/dev/null 2>&1; then return 0 fi # Command not found return 1 } # Check if running as root (works on minimal routers) check_root() { # Method 1: Check $USER variable if [ "$USER" = "root" ]; then return 0 fi # Method 2: Try to write to /etc (only root can) if touch /etc/.root_test 2>/dev/null; then rm -f /etc/.root_test 2>/dev/null return 0 fi # Method 3: Check whoami if available if which whoami >/dev/null 2>&1 && [ "$(whoami 2>/dev/null)" = "root" ]; then return 0 fi # If we get here, probably not root print_error "This script must be run as root" print_info "Please switch to root user first" exit 1 } # Create necessary directories setup_directories() { print_info "Creating directories..." # Create install directory if it doesn't exist if [ ! -d "$INSTALL_DIR" ]; then if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then # Install dir creation failed - likely read-only filesystem print_warning "Cannot create $INSTALL_DIR (read-only filesystem?)" # Try Entware fallback if [ -d "/opt/sbin" ] && [ -w "/opt/sbin" ]; then print_warning "Falling back to /opt/sbin (Entware)" INSTALL_DIR="/opt/sbin" CONFIG_DIR="/opt/etc/b4" CONFIG_FILE="${CONFIG_DIR}/b4.json" SERVICE_DIR="/opt/etc/init.d" SERVICE_NAME="S99b4" # Try /tmp fallback (non-persistent but always writable) elif mkdir -p "/tmp/b4" 2>/dev/null; then print_warning "Falling back to /tmp/b4 (non-persistent, will not survive reboot)" INSTALL_DIR="/tmp/b4" else print_error "Failed to create install directory: $INSTALL_DIR" print_error "Filesystem is read-only. Try installing Entware first." exit 1 fi fi elif [ ! -w "$INSTALL_DIR" ]; then # Directory exists but is not writable print_warning "$INSTALL_DIR exists but is not writable" if [ -d "/opt/sbin" ] && [ -w "/opt/sbin" ]; then print_warning "Falling back to /opt/sbin (Entware)" INSTALL_DIR="/opt/sbin" CONFIG_DIR="/opt/etc/b4" CONFIG_FILE="${CONFIG_DIR}/b4.json" SERVICE_DIR="/opt/etc/init.d" SERVICE_NAME="S99b4" elif mkdir -p "/tmp/b4" 2>/dev/null; then print_warning "Falling back to /tmp/b4 (non-persistent, will not survive reboot)" INSTALL_DIR="/tmp/b4" else print_error "Failed to find writable install directory" exit 1 fi fi # Create config directory if [ ! -d "$CONFIG_DIR" ]; then if ! mkdir -p "$CONFIG_DIR" 2>/dev/null; then # Config dir creation failed - likely read-only filesystem # Try Entware fallback if [ -d "/opt/etc" ] && [ -w "/opt/etc" ]; then print_warning "Cannot write to $CONFIG_DIR (read-only?), falling back to /opt/etc/b4" CONFIG_DIR="/opt/etc/b4" CONFIG_FILE="${CONFIG_DIR}/b4.json" INSTALL_DIR="/opt/sbin" SERVICE_DIR="/opt/etc/init.d" SERVICE_NAME="S99b4" mkdir -p "$CONFIG_DIR" || { print_error "Failed to create config directory: $CONFIG_DIR" exit 1 } else print_error "Failed to create config directory: $CONFIG_DIR" print_error "Filesystem may be read-only. Try installing Entware first." exit 1 fi fi fi # Create temp directory rm -rf "$TEMP_DIR" 2>/dev/null || true mkdir -p "$TEMP_DIR" || { print_error "Failed to create temp directory" exit 1 } } # Clean up temporary files cleanup() { if [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR" fi } # Set up trap for cleanup trap cleanup EXIT INT TERM # Check if process is running (POSIX compliant, no pidof) is_process_running() { process_name="$1" # Match exact binary name, not the installer script if ps 2>/dev/null | grep -v grep | grep -v "b4install" | grep -q "^.*${process_name}$\|^.*${process_name}[[:space:]]"; then return 0 else return 1 fi } # Stop process (POSIX compliant) stop_process() { process_name="$1" if is_process_running "$process_name"; then print_info "Stopping existing $process_name process..." # Try pkill if available, otherwise use ps + kill if command_exists pkill; then pkill "^${process_name}$" 2>/dev/null || true else # BusyBox way: find and kill by name, exclude installer script ps | grep -v grep | grep -v "b4install" | grep "${process_name}$\|${process_name}[[:space:]]" | awk '{print $1}' | while read pid; do if [ -n "$pid" ]; then kill "$pid" 2>/dev/null || true fi done fi sleep 2 fi } # Convert GitHub URL to proxy URL convert_to_proxy_url() { url="$1" # Check if URL is from allowed GitHub domains case "$url" in https://raw.githubusercontent.com/DanielLavrushin/* | \ https://github.com/DanielLavrushin/* | \ https://objects.githubusercontent.com/DanielLavrushin/* | \ https://codeload.github.com/DanielLavrushin/* | \ https://gist.githubusercontent.com/DanielLavrushin/* | \ https://api.github.com/repos/DanielLavrushin/*) echo "${PROXY_BASE_URL}/${url}" ;; *) echo "$url" ;; esac } # Check if wget supports HTTPS wget_supports_https() { # Try HTTPS HEAD request against multiple endpoints # (github.com may be blocked in some regions) for test_url in "https://proxy.lavrush.in" "https://github.com" "https://cloudflare.com"; do if wget --spider -q --timeout=5 "$test_url" 2>/dev/null; then return 0 fi done return 1 } # Download file from URL with GitHub fallback fetch_file() { url="$1" output="$2" dl_err="" if ! command_exists curl && ! command_exists wget; then print_error "Neither curl nor wget found" return 1 fi # Try direct download - try both curl and wget (BusyBox curl may lack SSL) if command_exists curl; then dl_err=$(curl -sfL --max-time 10 -o "$output" "$url" 2>&1) && return 0 fi if command_exists wget; then dl_err=$(wget -q $WGET_INSECURE --timeout=10 -O "$output" "$url" 2>&1) && return 0 fi # If direct download failed, try proxy fallback proxy_url=$(convert_to_proxy_url "$url") if [ "$proxy_url" != "$url" ]; then print_warning "Direct download failed, trying proxy (proxy.lavrush.in)..." if command_exists curl; then dl_err=$(curl -sfL --max-time 15 -o "$output" "$proxy_url" 2>&1) && return 0 fi if command_exists wget; then dl_err=$(wget -q $WGET_INSECURE --timeout=15 -O "$output" "$proxy_url" 2>&1) && return 0 fi fi print_error "Failed to download: $url" [ -n "$dl_err" ] && print_error "Last error: $dl_err" return 1 } # Fetch URL content to stdout with GitHub fallback fetch_stdout() { url="$1" dl_err="" if ! command_exists curl && ! command_exists wget; then return 1 fi # Try direct download - try both curl and wget (BusyBox curl may lack SSL) if command_exists curl; then result=$(curl -sfL --max-time 10 "$url" 2>/tmp/b4_fetch_err) || true dl_err=$(cat /tmp/b4_fetch_err 2>/dev/null) rm -f /tmp/b4_fetch_err if [ -n "$result" ]; then echo "$result" return 0 fi fi if command_exists wget; then result=$(wget -qO- $WGET_INSECURE --timeout=10 "$url" 2>/tmp/b4_fetch_err) || true dl_err=$(cat /tmp/b4_fetch_err 2>/dev/null) rm -f /tmp/b4_fetch_err if [ -n "$result" ]; then echo "$result" return 0 fi fi # If direct download failed, try proxy fallback proxy_url=$(convert_to_proxy_url "$url") if [ "$proxy_url" != "$url" ]; then print_warning "Direct download failed, trying proxy (proxy.lavrush.in)..." if command_exists curl; then result=$(curl -sfL --max-time 15 "$proxy_url" 2>/tmp/b4_fetch_err) || true dl_err=$(cat /tmp/b4_fetch_err 2>/dev/null) rm -f /tmp/b4_fetch_err if [ -n "$result" ]; then echo "$result" return 0 fi fi if command_exists wget; then result=$(wget -qO- $WGET_INSECURE --timeout=15 "$proxy_url" 2>/tmp/b4_fetch_err) || true dl_err=$(cat /tmp/b4_fetch_err 2>/dev/null) rm -f /tmp/b4_fetch_err if [ -n "$result" ]; then echo "$result" return 0 fi fi fi [ -n "$dl_err" ] && print_error "Download error: $dl_err" return 1 } detect_pkg_manager() { if command_exists opkg; then PKG_MGR="opkg" PKG_UPDATE="opkg update" PKG_INSTALL="opkg install" elif command_exists apt-get; then PKG_MGR="apt" PKG_UPDATE="apt-get update" PKG_INSTALL="apt-get install -y" elif command_exists yum; then PKG_MGR="yum" PKG_UPDATE="" PKG_INSTALL="yum install -y" elif command_exists apk; then PKG_MGR="apk" PKG_UPDATE="apk update" PKG_INSTALL="apk add" fi } install_packages() { packages="$1" [ -z "$PKG_MGR" ] && detect_pkg_manager [ -z "$PKG_MGR" ] && { print_error "No package manager" return 1 } [ -n "$PKG_UPDATE" ] && $PKG_UPDATE >/dev/null 2>&1 || true for pkg in $packages; do actual_pkg="$pkg" case "$PKG_MGR" in opkg) [ "$pkg" = "nohup" ] && actual_pkg="coreutils-nohup" ;; apt | apk) [ "$pkg" = "nohup" ] && actual_pkg="coreutils" ;; esac print_info "Installing $actual_pkg..." $PKG_INSTALL $actual_pkg >/dev/null 2>&1 && print_success "Installed $actual_pkg" || print_warning "Failed: $actual_pkg" done } check_dependencies() { required_deps="tar" missing_required="" if ! command_exists curl && ! command_exists wget; then missing_required="${missing_required} wget" fi for dep in $required_deps; do if ! command_exists "$dep"; then missing_required="${missing_required} $dep" fi done if [ -n "$missing_required" ]; then print_error "Missing required dependencies:$missing_required" if [ "$QUIET_MODE" = "1" ]; then install_packages "$missing_required" || exit 1 else printf "${CYAN}Attempt to install? (Y/n): ${NC}" read answer /dev/null 2>&1; then return 0 fi done return 1 } # Ensure wget/curl can handle HTTPS (critical for GitHub downloads) ensure_https_support() { # If curl exists with SSL, we're fine if command_exists curl; then if curl_supports_https; then return 0 fi fi # Check if wget supports HTTPS if command_exists wget; then if wget_supports_https; then return 0 fi fi # Neither wget nor curl can do HTTPS print_warning "HTTPS support not available (required for GitHub downloads)" # Try to install wget-ssl and ca-certificates on Entware/OpenWrt if command_exists opkg; then print_info "Attempting to install wget-ssl and ca-certificates for HTTPS support..." if [ "$QUIET_MODE" = "1" ]; then opkg update >/dev/null 2>&1 || true opkg install ca-certificates >/dev/null 2>&1 || true opkg install wget-ssl >/dev/null 2>&1 || true else printf "${CYAN}Install wget-ssl and ca-certificates for HTTPS support? (Y/n): ${NC}" read answer /dev/null 2>&1 || true if opkg install ca-certificates >/dev/null 2>&1; then print_success "ca-certificates installed" else print_warning "Failed to install ca-certificates" fi if opkg install wget-ssl >/dev/null 2>&1; then print_success "wget-ssl installed" else print_warning "Failed to install wget-ssl" fi # Rehash PATH to pick up new binary hash -r 2>/dev/null || true ;; esac fi # Verify HTTPS now works if command_exists wget && wget_supports_https; then return 0 fi if command_exists curl && curl_supports_https; then return 0 fi fi # Last resort: try --no-check-certificate (missing CA certs but SSL works) if command_exists wget; then if wget --spider -q --timeout=5 --no-check-certificate "https://github.com" 2>/dev/null; then print_warning "HTTPS works only with --no-check-certificate (CA certificates missing)" print_info "Install CA certificates: opkg install ca-certificates" WGET_INSECURE="--no-check-certificate" return 0 fi fi print_warning "HTTPS may not work - downloads from GitHub might fail" print_info "On Entware/Keenetic: opkg install wget-ssl ca-certificates" print_info "On OpenWrt: opkg install wget-ssl ca-certificates" } # Map kernel module package name to the actual module name for lsmod check kmod_to_module() { case "$1" in kmod-nfnetlink-queue) echo "nfnetlink_queue" ;; kmod-ipt-nfqueue) echo "xt_NFQUEUE" ;; kmod-ipt-raw) echo "iptable_raw" ;; kmod-ipt-ipset) echo "ip_set" ;; kmod-nft-core | kmod-nft-base) echo "nf_tables" ;; kmod-nft-queue) echo "nft_queue" ;; kmod-nf-conntrack-netlink) echo "nf_conntrack_netlink" ;; iptables-mod-nfqueue) echo "xt_NFQUEUE" ;; *) echo "" ;; esac } # Check if a kernel module package is actually needed # Returns 0 (needed) or 1 (not needed / already loaded) kmod_pkg_needed() { pkg="$1" mod=$(kmod_to_module "$pkg") [ -z "$mod" ] && return 0 # Module already loaded - no need for the package lsmod 2>/dev/null | grep -q "^${mod}" && return 1 return 0 } check_recommended_packages() { case "$SYSTEM_TYPE" in openwrt) # Userspace packages always recommended recommended="jq wget-ssl coreutils-nohup" # Kernel module packages - only recommend if the module isn't already loaded kmod_packages="kmod-nfnetlink-queue kmod-ipt-nfqueue kmod-ipt-raw kmod-ipt-ipset kmod-nft-core kmod-nft-queue kmod-nf-conntrack-netlink iptables-mod-nfqueue" pkg_cmd="opkg" ;; entware | merlin) recommended="ca-certificates curl wget-ssl jq coreutils-nohup iptables" kmod_packages="" pkg_cmd="opkg" ;; padavan) # If Entware is available, use opkg if command_exists opkg; then recommended="ca-certificates curl wget-ssl jq coreutils-nohup iptables" kmod_packages="" pkg_cmd="opkg" else missing="" for cmd in jq nohup; do command_exists "$cmd" || missing="${missing} $cmd" done [ -z "$missing" ] && return 0 print_warning "Recommended but missing:$missing" print_warning "Install Entware to get a package manager: https://github.com/Entware/Entware/wiki" return 0 fi ;; systemd-linux | sysv-linux | generic-linux) missing="" for cmd in jq nohup iptables; do command_exists "$cmd" || missing="${missing} $cmd" done [ -z "$missing" ] && return 0 print_warning "Recommended but missing:$missing" [ "$QUIET_MODE" = "1" ] && return 0 detect_pkg_manager [ -z "$PKG_MGR" ] && { print_warning "No package manager found" return 0 } printf "${CYAN}Install recommended packages? (Y/n): ${NC}" read answer /dev/null | grep -q "^${pkg} " || missing="${missing} $pkg" done # Check kernel module packages: skip if module already loaded or package not in repo for pkg in $kmod_packages; do # Skip if already installed $pkg_cmd list-installed 2>/dev/null | grep -q "^${pkg} " && continue # Skip if the kernel module is already loaded (built-in or loaded by firmware) kmod_pkg_needed "$pkg" || continue # Skip if the package doesn't exist in the repo $pkg_cmd list "$pkg" 2>/dev/null | grep -q "^${pkg} " || continue missing="${missing} $pkg" done [ -z "$missing" ] && return 0 print_warning "Missing packages:$missing" if [ "$QUIET_MODE" = "1" ]; then $pkg_cmd update >/dev/null 2>&1 for pkg in $missing; do $pkg_cmd install "$pkg" >/dev/null 2>&1 || true done return 0 fi printf "${CYAN}Install missing packages? (Y/n): ${NC}" read answer /dev/null 2>&1 || true for pkg in $missing; do print_info "Installing $pkg..." $pkg_cmd install "$pkg" >/dev/null 2>&1 && print_success "Installed $pkg" || print_warning "Failed: $pkg" done ;; esac } # --- END utils.sh --- # Detect system type and set appropriate paths detect_system_type() { # Check for Entware # Some systems like Keenetic don't have entware_release file if [ -d "/opt/etc/init.d" ]; then # Has Entware init structure if [ -f "/opt/etc/entware_release" ] || [ -f "/opt/bin/opkg" ] || [ -d "/opt/lib/opkg" ]; then echo "entware" return fi fi # Check for Keenetic specifically if [ -f "/proc/device-tree/model" ] && grep -qi "keenetic" /proc/device-tree/model 2>/dev/null; then echo "entware" return fi # Fallback: if /opt/sbin exists and is writable but /etc is read-only, assume Entware-like if [ -d "/opt/sbin" ] && [ -w "/opt/sbin" ] && ! [ -w "/etc" ]; then echo "entware" return fi # Check for OpenWRT if [ -f "/etc/openwrt_release" ]; then echo "openwrt" return fi # Check for MerlinWRT if [ -f "/etc/merlinwrt_release" ] || [ -d "/jffs" ]; then echo "merlin" return fi # Check for Padavan firmware (has /etc/storage for persistent writable area and /etc_ro) if [ -d "/etc/storage" ] && [ -d "/etc_ro" ]; then echo "padavan" return fi # Check for standard systemd-based Linux if [ -d "/etc/systemd/system" ] && command_exists systemctl; then echo "systemd-linux" return fi # Check for standard init.d Linux if [ -d "/etc/init.d" ] && [ ! -f "/etc/openwrt_release" ]; then echo "sysv-linux" return fi # Default to generic Linux echo "generic-linux" } # Set paths based on system type set_system_paths() { SYSTEM_TYPE=$(detect_system_type) case "$SYSTEM_TYPE" in entware | merlin) INSTALL_DIR="/opt/sbin" CONFIG_DIR="/opt/etc/b4" SERVICE_DIR="/opt/etc/init.d" SERVICE_NAME="S99b4" ;; padavan) # Padavan: root filesystem is read-only (squashfs) # /etc/storage is the persistent writable JFFS partition if [ -d "/opt/sbin" ] && [ -w "/opt/sbin" ]; then # Entware is installed - use Entware paths INSTALL_DIR="/opt/sbin" CONFIG_DIR="/opt/etc/b4" SERVICE_DIR="/opt/etc/init.d" SERVICE_NAME="S99b4" else # No Entware - use /etc/storage (persistent) for config, # /tmp for binary (non-persistent, re-download on boot via startup script) INSTALL_DIR="/tmp/b4" CONFIG_DIR="/etc/storage/b4" SERVICE_DIR="/etc/storage" SERVICE_NAME="b4" print_warning "No Entware detected. Binary will be in /tmp (non-persistent)." print_warning "Consider installing Entware for persistent installation." fi ;; openwrt) # OpenWRT typically uses /usr/sbin or /usr/bin if [ -d "/usr/sbin" ]; then INSTALL_DIR="/usr/sbin" else INSTALL_DIR="/usr/bin" fi CONFIG_DIR="/etc/b4" SERVICE_DIR="/etc/init.d" SERVICE_NAME="b4" ;; systemd-linux) INSTALL_DIR="/usr/local/bin" CONFIG_DIR="/etc/b4" SERVICE_DIR="/etc/systemd/system" SERVICE_NAME="b4.service" ;; sysv-linux | generic-linux) INSTALL_DIR="/usr/local/bin" CONFIG_DIR="/etc/b4" SERVICE_DIR="/etc/init.d" SERVICE_NAME="b4" ;; *) # Fallback INSTALL_DIR="/usr/local/bin" CONFIG_DIR="/etc/b4" ;; esac CONFIG_FILE="${CONFIG_DIR}/b4.json" print_info "Detected system: $SYSTEM_TYPE" print_info "Using install directory: $INSTALL_DIR" print_info "Using config directory: $CONFIG_DIR" } # Detect system architecture and return appropriate binary variant detect_architecture() { arch=$(uname -m) arch_variant="" case "$arch" in x86_64 | amd64) arch_variant="amd64" ;; i386 | i486 | i586 | i686) arch_variant="386" ;; aarch64 | arm64) arch_variant="arm64" ;; armv7) arch_variant="armv7" ;; armv7* | armv7l | armv7-*) # Default to armv5 for compatibility, only use armv7 if certain arch_variant="armv5" # Only use armv7 if we have clear evidence of full support if [ -f /proc/cpuinfo ]; then # Need BOTH vfpv3+ AND proper architecture confirmation if grep -qE "(vfpv[3-9]|vfpv[0-9][0-9])" /proc/cpuinfo 2>/dev/null && grep -qE "CPU architecture:\s*7" /proc/cpuinfo 2>/dev/null; then arch_variant="armv7" print_info "Full ARMv7 support detected, using armv7 binary" else print_warning "armv7l detected but using armv5 for compatibility (safer for routers)" fi fi ;; armv6*) arch_variant="armv6" ;; armv5*) arch_variant="armv5" ;; arm*) # Generic ARM - try to detect version from CPU info if [ -f /proc/cpuinfo ]; then # Look for CPU architecture line first (most reliable) if grep -qE "CPU architecture:\s*7" /proc/cpuinfo; then arch_variant="armv7" elif grep -qE "CPU architecture:\s*6" /proc/cpuinfo; then arch_variant="armv6" elif grep -qE "CPU architecture:\s*5" /proc/cpuinfo; then arch_variant="armv5" # Fallback to searching for ARM version strings elif grep -qi "ARMv7" /proc/cpuinfo; then arch_variant="armv7" elif grep -qi "ARMv6" /proc/cpuinfo; then arch_variant="armv6" elif grep -qi "ARMv5" /proc/cpuinfo; then arch_variant="armv5" else # Default to armv5 for maximum compatibility arch_variant="armv5" fi else # No cpuinfo available, default to safest option arch_variant="armv5" fi ;; mips64) # Check MIPS endianness mips_le=false if uname -m | grep -qi "el"; then mips_le=true elif [ -f /sys/kernel/cpu_byteorder ] && grep -qi "little" /sys/kernel/cpu_byteorder 2>/dev/null; then mips_le=true elif [ -f /proc/cpuinfo ] && grep -qi "little.endian\|byteorder.*little" /proc/cpuinfo 2>/dev/null; then mips_le=true elif command -v opkg >/dev/null 2>&1 && opkg print-architecture 2>/dev/null | grep -qi "mipsel\|mips64el"; then mips_le=true elif [ "$(dd if=/bin/sh bs=1 skip=5 count=1 2>/dev/null)" = "$(printf '\1')" ]; then # ELF header byte 6 (index 5): 1=little-endian, 2=big-endian mips_le=true fi if [ "$mips_le" = true ]; then arch_variant="mips64le" else arch_variant="mips64" fi # Check for softfloat (no FPU) if [ -f /proc/cpuinfo ]; then if ! grep -qi "fpu" /proc/cpuinfo 2>/dev/null || grep -qi "nofpu\|no fpu" /proc/cpuinfo 2>/dev/null; then arch_variant="${arch_variant}_softfloat" print_warning "No FPU detected, using softfloat binary" fi fi ;; mips*) # 32-bit MIPS - determine endianness mips_le=false if uname -m | grep -qi "el"; then mips_le=true elif [ -f /sys/kernel/cpu_byteorder ] && grep -qi "little" /sys/kernel/cpu_byteorder 2>/dev/null; then mips_le=true elif [ -f /proc/cpuinfo ] && grep -qi "little.endian\|byteorder.*little" /proc/cpuinfo 2>/dev/null; then mips_le=true elif command -v opkg >/dev/null 2>&1 && opkg print-architecture 2>/dev/null | grep -qi "mipsel"; then mips_le=true elif [ "$(dd if=/bin/sh bs=1 skip=5 count=1 2>/dev/null)" = "$(printf '\1')" ]; then mips_le=true fi if [ "$mips_le" = true ]; then arch_variant="mipsle" else arch_variant="mips" fi # Check for softfloat (no FPU) if [ -f /proc/cpuinfo ]; then if ! grep -qi "fpu" /proc/cpuinfo 2>/dev/null || grep -qi "nofpu\|no fpu" /proc/cpuinfo 2>/dev/null; then arch_variant="${arch_variant}_softfloat" print_warning "No FPU detected, using softfloat binary" fi fi ;; ppc64le) arch_variant="ppc64le" ;; ppc64) arch_variant="ppc64" ;; riscv64) arch_variant="riscv64" ;; s390x) arch_variant="s390x" ;; loongarch64) arch_variant="loong64" ;; *) print_error "Unsupported architecture: $arch" exit 1 ;; esac # ONLY output the result to stdout echo "$arch_variant" } # --- END system.sh --- # This is the core installation part script for b4 Universal. # Get latest release version from GitHub - ONLY returns version string get_latest_version() { api_url="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" version=$(fetch_stdout "$api_url" | grep -o '"tag_name": *"[^"]*"' | head -1 | cut -d'"' -f4) if [ -z "$version" ]; then print_error "Failed to fetch latest version" exit 1 fi echo "$version" } # Verify checksum verify_checksum() { file="$1" checksum_url="$2" checksum_file="${file}.sha256" print_info "Downloading SHA256 checksum..." if ! fetch_file "$checksum_url" "$checksum_file"; then rm -f "$checksum_file" return 1 fi if [ ! -s "$checksum_file" ]; then rm -f "$checksum_file" return 1 fi expected_checksum=$(awk '{print $1}' "$checksum_file") if [ -z "$expected_checksum" ]; then print_warning "Could not parse checksum from file" rm -f "$checksum_file" return 1 fi if ! command_exists sha256sum; then print_warning "sha256sum not found, skipping verification" rm -f "$checksum_file" return 1 fi actual_checksum=$(sha256sum "$file" | awk '{print $1}') rm -f "$checksum_file" if [ "$expected_checksum" = "$actual_checksum" ]; then print_success "SHA256 checksum verified: $actual_checksum" return 0 else print_error "SHA256 checksum mismatch!" print_error "Expected: $expected_checksum" print_error "Got: $actual_checksum" return 2 fi } # Download file and verify checksums download_file() { url="$1" output="$2" version="$3" arch="$4" print_info "Downloading from: $url" if ! fetch_file "$url" "$output"; then print_error "Download failed" return 1 fi # Construct checksum URL file_name="${BINARY_NAME}-linux-${arch}.tar.gz" sha256_url="https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${version}/${file_name}.sha256" # Try to verify SHA256 checksum if verify_checksum "$output" "$sha256_url"; then return 0 elif [ $? -eq 2 ]; then print_error "Download verification failed!" return 0 else print_warning "No checksum file found - unable to verify download integrity" if command_exists sha256sum; then local_hash=$(sha256sum "$output" | awk '{print $1}') print_info "Local SHA256: $local_hash" fi fi return 0 } # --- END download.sh --- # Create systemd service file (for systems with systemd) create_systemd_service() { # Only create if systemd is actually available and functioning if ! [ -d "/etc/systemd/system" ] || ! command_exists systemctl; then return fi # Check if systemd is actually running (not just installed) if ! systemctl list-units >/dev/null 2>&1; then return fi cat >"/etc/systemd/system/b4.service" </dev/null | head -1) [ -n "$connbytes_mod_path" ] && insmod "$connbytes_mod_path" >/dev/null 2>&1 nfqueue_mod_path=$(find /lib/modules/$KERNEL -name "xt_NFQUEUE.ko*" 2>/dev/null | head -1) [ -n "$nfqueue_mod_path" ] && insmod "$nfqueue_mod_path" >/dev/null 2>&1 multiport_mod_path=$(find /lib/modules/$KERNEL -name "xt_multiport.ko*" 2>/dev/null | head -1) [ -n "$multiport_mod_path" ] && insmod "$multiport_mod_path" >/dev/null 2>&1 modprobe xt_connbytes >/dev/null 2>&1 || true modprobe xt_NFQUEUE >/dev/null 2>&1 || true modprobe xt_multiport >/dev/null 2>&1 || true } start() { echo "Starting b4..." if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then echo "b4 is already running" return 1 fi kernel_mod_load nohup $PROG --config $CONFIG_FILE > /var/log/b4.log 2>&1 & echo $! > "$PIDFILE" sleep 1 if kill -0 $(cat "$PIDFILE") 2>/dev/null; then echo "b4 started (PID: $(cat "$PIDFILE"))" else echo "Warning: b4 may have failed to start, check /var/log/b4.log" rm -f "$PIDFILE" return 1 fi } stop() { echo "Stopping b4..." if [ -f "$PIDFILE" ]; then kill $(cat "$PIDFILE") 2>/dev/null rm -f "$PIDFILE" echo "b4 stopped" else killall b4 2>/dev/null || true echo "b4 stopped" fi } status() { if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then echo "b4 is running (PID: $(cat "$PIDFILE"))" return 0 elif pgrep -x b4 >/dev/null 2>&1; then echo "b4 is running (no pidfile)" return 0 else echo "b4 is not running" return 1 fi } BODY } # Create OpenWRT/Entware init script create_sysv_service() { INIT_DIR="" if [ -d "/opt/etc/init.d" ] && [ -w "/opt/etc/init.d" ]; then INIT_DIR="/opt/etc/init.d" print_info "Detected Entware/MerlinWRT system" elif [ -d "/etc/init.d" ] && [ -w "/etc/init.d" ]; then INIT_DIR="/etc/init.d" elif [ -d "/opt/etc" ]; then mkdir -p /opt/etc/init.d 2>/dev/null && INIT_DIR="/opt/etc/init.d" fi if [ -z "$INIT_DIR" ]; then print_warning "Could not create init script - no writable init directory found" return fi print_info "Creating init script in $INIT_DIR..." if [ "$INIT_DIR" = "/etc/init.d" ]; then INIT_SCRIPT_NAME="b4" else INIT_SCRIPT_NAME="S99b4" fi INIT_FULL_PATH="${INIT_DIR}/${INIT_SCRIPT_NAME}" rm -f "${INIT_DIR}/S99b4" 2>/dev/null || true if [ -f "${INIT_DIR}/rc.func" ]; then # Merlin/Entware rc.func - completely different mechanism print_info "rc.func found in $INIT_DIR, using it for init script" cat >"${INIT_FULL_PATH}" <<'EOF' #!/bin/sh # B4 DPI Bypass Service Init Script ENABLED=yes PROCS=b4 ARGS="--config=CONFIG_PLACEHOLDER" PREARGS="nohup" DESC="$PROCS" PATH=/opt/sbin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin kernel_mod_load() { KERNEL=$(uname -r) connbytes_mod_path=$(find /lib/modules/$KERNEL -name "xt_connbytes.ko*" 2>/dev/null | head -1) [ -n "$connbytes_mod_path" ] && insmod "$connbytes_mod_path" >/dev/null 2>&1 nfqueue_mod_path=$(find /lib/modules/$KERNEL -name "xt_NFQUEUE.ko*" 2>/dev/null | head -1) [ -n "$nfqueue_mod_path" ] && insmod "$nfqueue_mod_path" >/dev/null 2>&1 multiport_mod_path=$(find /lib/modules/$KERNEL -name "xt_multiport.ko*" 2>/dev/null | head -1) [ -n "$multiport_mod_path" ] && insmod "$multiport_mod_path" >/dev/null 2>&1 modprobe xt_connbytes >/dev/null 2>&1 || true modprobe xt_NFQUEUE >/dev/null 2>&1 || true modprobe xt_multiport >/dev/null 2>&1 || true } [ "$1" = "start" ] || [ "$1" = "restart" ] && kernel_mod_load . /opt/etc/init.d/rc.func EOF elif [ -f "/etc/openwrt_release" ] && [ -f "/etc/rc.common" ]; then # OpenWRT - rc.common handles command dispatch print_info "Creating OpenWRT init script" { echo '#!/bin/sh /etc/rc.common' echo '# B4 DPI Bypass Service - OpenWRT' echo '' echo 'START=99' echo 'STOP=10' echo '' get_init_script_body } >"${INIT_FULL_PATH}" else # Standard SysV init - needs case block print_info "Creating standard init.d script" { echo '#!/bin/sh' echo '# B4 DPI Bypass Service Init Script' echo '' get_init_script_body cat <<'EOF' case "$1" in start) start ;; stop) stop ;; restart) stop; sleep 1; start ;; status) status ;; *) echo "Usage: $0 {start|stop|restart|status}"; exit 1 ;; esac EOF } >"${INIT_FULL_PATH}" fi sed "s|PROG_PLACEHOLDER|${INSTALL_DIR}/${BINARY_NAME}|g; s|CONFIG_PLACEHOLDER|${CONFIG_FILE}|g" \ "${INIT_FULL_PATH}" >"${INIT_FULL_PATH}.tmp" mv "${INIT_FULL_PATH}.tmp" "${INIT_FULL_PATH}" chmod +x "${INIT_FULL_PATH}" if [ -f "/etc/openwrt_release" ] && [ -f "/etc/rc.common" ]; then "${INIT_FULL_PATH}" enable 2>/dev/null && print_success "Service enabled for auto-start on boot" fi print_success "Init script created at ${INIT_FULL_PATH}" print_info " ${INIT_FULL_PATH} {start|stop|restart|status}" if [ -f "/etc/openwrt_release" ]; then print_info " ${INIT_FULL_PATH} enable # Start on boot" print_info " ${INIT_FULL_PATH} disable # Don't start on boot" fi } # --- END service.sh --- # Get geosite path from config using jq if available get_geodat_from_config() { if [ -f "$CONFIG_FILE" ] && command_exists jq; then sitedat_path=$(jq -r '.system.geo.sitedat_path // empty' "$CONFIG_FILE" 2>/dev/null) if [ -n "$sitedat_path" ] && [ "$sitedat_path" != "null" ]; then # Extract directory from path echo "$(dirname "$sitedat_path")" return 0 fi fi return 1 } # Display geosite source menu and get user choice select_geo_source() { echo "" >&2 echo "=======================================" >&2 echo " Select Geosite Data Source" >&2 echo "=======================================" >&2 echo "" >&2 # Display sources (using POSIX-compliant iteration) echo "$GEODAT_SOURCES" | while IFS='|' read -r num name url; do if [ -n "$num" ]; then printf " ${GREEN}%s${NC}) %s\n" "$num" "$name" >&2 fi done echo "" >&2 printf "${CYAN}Select source (1-5) or 'q' to skip: ${NC}" >&2 read choice "$CONFIG_FILE" print_success "Created new config file with geodat settings" return 0 fi # Create temporary file temp_file="${CONFIG_FILE}.tmp" # Merge into existing geo object instead of replacing if jq \ --arg sitedat_path "$sitedat_path" \ --arg sitedat_url "$sitedat_url" \ --arg ipdat_path "$ipdat_path" \ --arg ipdat_url "$ipdat_url" \ '.system.geo = (.system.geo // {}) + { sitedat_path: $sitedat_path, sitedat_url: $sitedat_url, ipdat_path: $ipdat_path, ipdat_url: $ipdat_url }' \ "$CONFIG_FILE" >"$temp_file" 2>/dev/null; then mv "$temp_file" "$CONFIG_FILE" || { print_error "Failed to update config file" rm -f "$temp_file" return 1 } print_success "Config updated:" print_success " Geosite: $sitedat_path" print_success " URL: $sitedat_url" print_success " GeoIP: $ipdat_path" print_success " URL: $ipdat_url" # Show what was actually written print_info "Verifying config..." if command_exists jq; then jq '.system.geo' "$CONFIG_FILE" 2>/dev/null || true fi return 0 else print_error "Failed to parse config with jq" rm -f "$temp_file" return 1 fi else print_warning "jq not found - cannot automatically update config" print_info "Please manually add to your config file:" print_info ' "system": {' print_info ' "geo": {' print_info " \"sitedat_path\": \"$sitedat_path\"," print_info " \"sitedat_url\": \"$sitedat_url\"," print_info " \"ipdat_path\": \"$ipdat_path\"," print_info " \"ipdat_url\": \"$ipdat_url\"" print_info ' }' print_info ' }' echo "" print_info "Or update paths in B4 Web UI: Settings -> Geodat Settings" return 0 fi } # Setup geosite data setup_geodat() { echo "" echo "=======================================" echo " GEO Data Setup" echo "=======================================" echo "" if [ -z "$GEOSITE_SRC" ] && [ -z "$GEOSITE_DST" ]; then # Skip prompts in quiet mode if [ "$QUIET_MODE" = "1" ]; then print_info "Geosite setup skipped (quiet mode)" return 0 fi printf "${CYAN}Do you want to download geosite.dat & geoip.dat files? (y/N): ${NC}" read answer /dev/null || true # remove legacy script rm -f "/etc/init.d/b4" 2>/dev/null || true # remove legacy script rm -f "/var/log/b4.log" 2>/dev/null || true # remove legacy log # Extract the binary print_info "Extracting archive..." cd "$TEMP_DIR" tar -xzf "$archive_path" || { print_error "Failed to extract archive" exit 1 } rm -f "$archive_path" # Check if binary exists if [ ! -f "${BINARY_NAME}" ]; then print_error "Binary not found in archive" exit 1 fi # Stop existing b4 if running stop_process "$BINARY_NAME" # Create timestamp in POSIX way timestamp=$(date '+%Y%m%d_%H%M%S') BACKUP_FILE="${INSTALL_DIR}/${BINARY_NAME}.backup.${timestamp}" # Backup existing binary if it exists if [ -f "${INSTALL_DIR}/${BINARY_NAME}" ]; then print_info "Backing up existing binary..." mv "${INSTALL_DIR}/${BINARY_NAME}" "$BACKUP_FILE" fi print_info "Installing b4 to ${INSTALL_DIR}..." mv "${BINARY_NAME}" "${INSTALL_DIR}/" 2>/dev/null || cp "${BINARY_NAME}" "${INSTALL_DIR}/" || { print_error "Failed to copy binary to install directory" exit 1 } # Set executable permissions chmod +x "${INSTALL_DIR}/${BINARY_NAME}" || { print_error "Failed to set executable permissions" exit 1 } # Verify installation if "${INSTALL_DIR}/${BINARY_NAME}" --version >/dev/null 2>&1; then [ -n "$BACKUP_FILE" ] && rm -f "$BACKUP_FILE" 2>/dev/null || true # Clean old backups rm -f "${INSTALL_DIR}/${BINARY_NAME}".backup.* 2>/dev/null || true print_success "b4 installed successfully!" else print_warning "Binary installed but version check failed" fi } detect_tls_certs() { if [ -f "/etc/uhttpd.crt" ] && [ -f "/etc/uhttpd.key" ]; then TLS_CERT="/etc/uhttpd.crt" TLS_KEY="/etc/uhttpd.key" return 0 fi if [ -f "/etc/cert.pem" ] && [ -f "/etc/key.pem" ]; then TLS_CERT="/etc/cert.pem" TLS_KEY="/etc/key.pem" return 0 fi return 1 } # Print web interface access information print_web_interface_info() { local web_port="7000" local protocol="http" if [ -f "$CONFIG_FILE" ] && command_exists jq; then web_port=$(jq -r '.system.web_server.port // 7000' "$CONFIG_FILE" 2>/dev/null) tls_cert=$(jq -r '.system.web_server.tls_cert // ""' "$CONFIG_FILE" 2>/dev/null) if [ -n "$tls_cert" ]; then protocol="https" fi fi echo "" echo "=======================================" echo " Web Interface Access" echo "=======================================" echo "" # Get LAN IP (br0 interface on routers) lan_ip="" if command_exists ip; then lan_ip=$(ip -4 addr show br0 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) fi if [ -z "$lan_ip" ] && command_exists ifconfig; then lan_ip=$(ifconfig br0 2>/dev/null | grep 'inet addr:' | awk '{print $2}' | cut -d':' -f2) fi if [ -z "$lan_ip" ]; then if command_exists ip; then lan_ip=$(ip -4 addr show 2>/dev/null | grep 'inet 192.168' | head -n1 | awk '{print $2}' | cut -d'/' -f1) elif command_exists ifconfig; then lan_ip=$(ifconfig 2>/dev/null | grep 'inet addr:192.168' | head -n1 | awk '{print $2}' | cut -d':' -f2) fi fi if [ -n "$lan_ip" ]; then print_info "Local network access (LAN):" # Print URL with detected protocol (http/https) printf " ${GREEN}%s://%s:%s${NC}\n" "$protocol" "$lan_ip" "$web_port" printf " (remember to start the service first)\n" fi echo "" } # Main installation process main_install() { # get args VERSION="" FORCE_ARCH="" for arg in "$@"; do case "$arg" in v* | V*) VERSION="$arg" print_info "Using specified version: $VERSION" ;; --arch=*) FORCE_ARCH="${arg#*=}" print_info "Using specified architecture: $FORCE_ARCH" ;; --quiet | -q) QUIET_MODE=1 ;; --geosite-src=*) GEOSITE_SRC="${arg#*=}" ;; --geosite-dst=*) GEOSITE_DST="${arg#*=}" ;; esac done # Detect system and set paths set_system_paths if [ "$QUIET_MODE" = "0" ]; then echo "" echo "=======================================" echo " B4 Universal Installer" echo "=======================================" echo "" fi # Check if running as root check_root # Check dependencies check_dependencies # Detect architecture if [ -n "$FORCE_ARCH" ]; then ARCH="$FORCE_ARCH" print_info "Raw architecture: $(uname -m)" print_success "Using forced architecture: $ARCH" else print_info "Detecting system architecture..." ARCH=$(detect_architecture) print_info "Raw architecture: $(uname -m)" print_success "Architecture detected: $ARCH" fi if [ -z "$VERSION" ]; then print_info "Fetching latest release information..." VERSION=$(get_latest_version) print_success "Latest version: $VERSION" fi # Setup directories setup_directories # Install b4 install_b4 "$ARCH" "$VERSION" # Create service files create_systemd_service if [ "$SYSTEMCTL_CREATED" != "1" ]; then create_sysv_service # Offer HTTPS setup if router certificates are detected if detect_tls_certs && [ "$QUIET_MODE" = "0" ] && [ -f "$CONFIG_FILE" ] && command_exists jq; then current_cert=$(jq -r '.system.web_server.tls_cert // ""' "$CONFIG_FILE" 2>/dev/null) if [ -z "$current_cert" ]; then echo "" print_info "Router SSL certificates detected: $TLS_CERT" printf "${CYAN}Enable HTTPS for the B4 web interface? (Y/n): ${NC}" read tls_answer Web Server." ;; *) jq --arg cert "$TLS_CERT" --arg key "$TLS_KEY" \ '.system.web_server.tls_cert = $cert | .system.web_server.tls_key = $key' \ "$CONFIG_FILE" >"${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE" print_success "HTTPS enabled with $TLS_CERT" ;; esac fi fi fi if [ "$QUIET_MODE" = "0" ]; then setup_geodat # Print installation summary echo "" print_info "Binary installed to: ${INSTALL_DIR}/${BINARY_NAME}" print_info "Configuration at: ${CONFIG_FILE}" echo "" print_info "To see all B4 options:" print_info " ${INSTALL_DIR}/${BINARY_NAME} --help" # Check PATH if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then print_warning "Note: $INSTALL_DIR is not in your PATH" print_info "You may want to add it to your PATH or create a symlink:" print_info " ln -s ${INSTALL_DIR}/${BINARY_NAME} /usr/bin/${BINARY_NAME}" fi print_web_interface_info echo "" print_success "Installation finished successfully!" echo "" printf "${CYAN}Start B4 service now? (Y/n): ${NC}" read answer /dev/null && print_success "Service started" elif [ -f "/opt/etc/init.d/S99b4" ]; then /opt/etc/init.d/S99b4 restart 2>/dev/null && print_success "Service started" elif [ -f "/etc/init.d/b4" ]; then /etc/init.d/b4 restart 2>/dev/null && print_success "Service started" fi ;; esac echo "" echo "=======================================" echo " Installation Complete!" echo "=======================================" fi } # --- END core.sh --- # B4 DPI Bypass Uninstaller Script remove_b4() { echo "" echo "=======================================" echo " B4 Uninstaller" echo "=======================================" echo "" # Detect system to get proper paths set_system_paths # Stop the service first print_info "Stopping b4 service if running..." # Check systemd service FIRST if [ -f "/etc/systemd/system/b4.service" ] && command_exists systemctl; then systemctl stop b4 2>/dev/null || true systemctl disable b4 2>/dev/null || true print_info "Stopped systemd service" fi # Check Entware init script if [ -f "/opt/etc/init.d/S99b4" ]; then /opt/etc/init.d/S99b4 stop 2>/dev/null || true print_info "Stopped Entware service" fi # Check standard init script if [ -f "/etc/init.d/b4" ]; then /etc/init.d/b4 stop 2>/dev/null || true print_info "Stopped init service" fi # Kill any remaining b4 processes kill_b4_processes # Remove binary from all possible locations POSSIBLE_DIRS="/opt/sbin /usr/local/bin /usr/bin /usr/sbin" for dir in $POSSIBLE_DIRS; do if [ -f "$dir/$BINARY_NAME" ]; then print_info "Removing binary: $dir/$BINARY_NAME" rm -f "$dir/$BINARY_NAME" print_success "Binary removed from $dir" # Remove any backup files rm -f "$dir/"${BINARY_NAME}.backup.* 2>/dev/null || true fi done # Remove service files if [ -f "/etc/systemd/system/b4.service" ]; then print_info "Removing systemd service..." rm -f "/etc/systemd/system/b4.service" if command_exists systemctl; then systemctl daemon-reload 2>/dev/null || true fi print_success "Systemd service removed" fi if [ -f "/opt/etc/init.d/S99b4" ]; then print_info "Removing Entware init script..." rm -f "/opt/etc/init.d/S99b4" print_success "Entware init script removed" fi if [ -f "/etc/init.d/b4" ]; then print_info "Removing init script..." rm -f "/etc/init.d/b4" print_success "Init script removed" fi # Remove symlinks if [ -L "/usr/bin/${BINARY_NAME}" ]; then print_info "Removing symlink: /usr/bin/${BINARY_NAME}" rm -f "/usr/bin/${BINARY_NAME}" fi # Ask about configuration ONCE printf "${CYAN}Remove configuration files as well? (y/N): ${NC}" read answer /dev/null || true rm -rf /var/log/b4 2>/dev/null || true rm -f /var/run/b4.pid 2>/dev/null || true # Remove temporary files created by b4 rm -rf /tmp/b4_* 2>/dev/null || true rm -f /tmp/b4install_update.sh 2>/dev/null || true rm -rf /tmp/b4 2>/dev/null || true echo "" print_success "B4 has been uninstalled successfully!" echo "" exit 0 } # Kill any remaining b4 processes kill_b4_processes() { # Collect PIDs first, avoid subshell issues pids=$(ps 2>/dev/null | grep -v grep | grep -v "b4install" | grep "b4$\|b4[[:space:]]" | awk '{print $1}' | tr '\n' ' ') if [ -n "$pids" ]; then print_info "Killing remaining b4 processes: $pids" # SIGTERM first for pid in $pids; do kill "$pid" 2>/dev/null || true done sleep 2 # SIGKILL stubborn processes for pid in $pids; do if kill -0 "$pid" 2>/dev/null; then print_warning "Force killing PID $pid" kill -9 "$pid" 2>/dev/null || true fi done sleep 1 fi } # --- END remove.sh --- # Perform update - stops service, updates, and restarts perform_update() { QUIET_MODE=1 # Force quiet mode during updates echo "" echo "=======================================" echo " B4 Update Process" echo "=======================================" echo "" # Find existing installation FOUND_BINARY="" for dir in /opt/sbin /usr/local/bin /usr/bin /usr/sbin; do if [ -f "$dir/$BINARY_NAME" ]; then FOUND_BINARY="$dir/$BINARY_NAME" INSTALL_DIR="$dir" break fi done if [ -z "$FOUND_BINARY" ]; then print_error "B4 binary not found. Please install first." exit 1 fi # Find existing config for dir in /opt/etc/b4 /etc/b4; do if [ -f "$dir/b4.json" ]; then CONFIG_DIR="$dir" CONFIG_FILE="$dir/b4.json" break fi done print_info "Found existing installation at: $INSTALL_DIR" print_info "Using config at: $CONFIG_DIR" # Detect service manager SERVICE_MANAGER="" RESTART_CMD="" if [ -f "/etc/systemd/system/b4.service" ] && command_exists systemctl; then SERVICE_MANAGER="systemd" RESTART_CMD="systemctl restart b4" elif [ -f "/opt/etc/init.d/S99b4" ]; then SERVICE_MANAGER="entware" RESTART_CMD="/opt/etc/init.d/S99b4 restart" elif [ -f "/etc/init.d/b4" ]; then SERVICE_MANAGER="init" RESTART_CMD="/etc/init.d/b4 restart" else print_error "No service manager detected. Cannot perform automatic update." exit 1 fi print_info "Detected service manager: $SERVICE_MANAGER" # Extract geosite settings from config if available GEOSITE_SRC="" GEOSITE_DST="" if [ -f "$CONFIG_FILE" ] && command_exists jq; then GEOSITE_SRC=$(jq -r '.system.geo.sitedat_url // empty' "$CONFIG_FILE" 2>/dev/null) sitedat_path=$(jq -r '.system.geo.sitedat_path // empty' "$CONFIG_FILE" 2>/dev/null) if [ -n "$sitedat_path" ] && [ "$sitedat_path" != "null" ]; then GEOSITE_DST=$(dirname "$sitedat_path") fi fi # Stop the service print_info "Stopping b4 service..." case "$SERVICE_MANAGER" in systemd) systemctl stop b4 2>/dev/null || true ;; entware) /opt/etc/init.d/S99b4 stop 2>/dev/null || true ;; init) /etc/init.d/b4 stop 2>/dev/null || true ;; esac sleep 2 print_success "Service stopped" # Perform the update (call main_install with quiet mode) print_info "Installing latest version..." main_install "$@" # Start the service print_info "Starting b4 service..." case "$SERVICE_MANAGER" in systemd) systemctl start b4 ;; entware) /opt/etc/init.d/S99b4 start ;; init) /etc/init.d/b4 start ;; esac sleep 2 # Verify service is running service_running=0 case "$SERVICE_MANAGER" in systemd) systemctl is-active --quiet b4 && service_running=1 ;; entware | init) ps | grep -v grep | grep -q "b4$\|b4[[:space:]]" && service_running=1 ;; esac if [ "$service_running" = "1" ]; then print_success "Service started successfully" echo "" echo "=======================================" echo " Update Complete!" echo "=======================================" echo "" else print_error "Service may not have started correctly" print_info "Check logs: journalctl -u b4 -f (systemd) or /var/log/b4.log" exit 1 fi } # --- END update.sh --- # Check kernel module status check_kernel_module() { module_name="$1" # Check if module is loaded if lsmod 2>/dev/null | grep -q "^$module_name"; then echo "loaded" return 0 fi # Skip filesystem check on routers - often hangs echo "unknown" return 0 } # Get service status get_service_status() { # Check if b4 process is running if ps 2>/dev/null | grep -v grep | grep -v "b4install" | grep -q "b4$\|b4[[:space:]]"; then echo "running" return 0 fi # Check systemd service if [ -f "/etc/systemd/system/b4.service" ] && command_exists systemctl; then if systemctl is-active --quiet b4 2>/dev/null; then echo "running (systemd)" return 0 else echo "stopped (systemd)" return 0 fi fi # Check Entware init if [ -f "/opt/etc/init.d/S99b4" ]; then echo "configured (entware)" return 0 fi # Check standard init if [ -f "/etc/init.d/b4" ]; then echo "configured (init.d)" return 0 fi echo "not installed" return 0 } # Get network interfaces info get_network_info() { primary_ip="" if command_exists ip; then primary_ip=$(ip -4 route get 1 2>/dev/null | awk '/src/{print $7}' | head -1 || true) elif command_exists ifconfig; then primary_ip=$(ifconfig 2>/dev/null | grep 'inet addr:' | grep -v '127.0.0.1' | head -1 | awk '{print $2}' | cut -d':' -f2 || true) fi echo "$primary_ip" } # Detect firewall backend detect_firewall_backend() { if command_exists nft; then out=$(nft list tables 2>/dev/null || true) if [ -n "$out" ]; then echo "nftables" return 0 fi fi # Check for iptables if command_exists iptables; then out=$(iptables --version 2>/dev/null || true) if echo "$out" | grep -q "nf_tables"; then echo "iptables-nft" else echo "iptables-legacy" fi return 0 fi echo "none" return 0 } # Check if iptables multiport module works check_multiport_support() { if ! command_exists iptables; then echo "no_iptables" return 0 fi # Try to add a test rule using multiport # Try with -w first (lock wait), fall back without it for older iptables if iptables -w -t filter -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || iptables -t filter -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null; then # Success - remove it immediately (try both variants) iptables -w -t filter -D INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || iptables -t filter -D INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null # Check if loaded as module or built-in if lsmod 2>/dev/null | grep -q "^xt_multiport"; then echo "works_module" else echo "works_builtin" fi return 0 fi echo "not_available" return 0 } # System information display function show_system_info() { set_system_paths echo "" echo "=======================================" echo " B4 System Information" echo "=======================================" print_header "System Information" # Map SYSTEM_TYPE (from detect_system_type) to display name os_version="" case "$SYSTEM_TYPE" in openwrt) os_type="OpenWRT" os_version=$(grep 'DISTRIB_RELEASE' /etc/openwrt_release 2>/dev/null | cut -d'=' -f2 | tr -d "'\"" || true) ;; merlin) os_type="MerlinWRT" os_version=$(cat /etc/merlinwrt_release 2>/dev/null || true) ;; entware) os_type="Entware" os_version=$(cat /opt/etc/entware_release 2>/dev/null || true) ;; padavan) os_type="Padavan" ;; systemd-linux | sysv-linux | generic-linux) if [ -f /etc/os-release ]; then os_type=$(grep '^NAME=' /etc/os-release | cut -d'=' -f2 | tr -d '"' || echo "Linux") os_version=$(grep '^VERSION=' /etc/os-release | cut -d'=' -f2 | tr -d '"' || true) else os_type="Linux" fi ;; *) os_type="Linux" ;; esac print_detail "Operating System" "$os_type ${os_version}" print_detail "Kernel Version" "$(uname -r)" print_detail "Architecture (raw)" "$(uname -m)" print_detail "Architecture (b4)" "$(detect_architecture)" print_detail "Hostname" "$(hostname 2>/dev/null || echo 'unknown')" # CPU Info if [ -f /proc/cpuinfo ]; then cpu_model=$(grep 'model name' /proc/cpuinfo 2>/dev/null | head -1 | cut -d':' -f2 | sed 's/^ *//' || true) if [ -z "$cpu_model" ]; then cpu_model=$(grep 'Processor' /proc/cpuinfo 2>/dev/null | head -1 | cut -d':' -f2 | sed 's/^ *//' || true) fi if [ -n "$cpu_model" ]; then print_detail "CPU Model" "$cpu_model" fi cpu_cores=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo "1") print_detail "CPU Cores" "$cpu_cores" fi # Memory Info if [ -f /proc/meminfo ]; then mem_total=$(grep '^MemTotal:' /proc/meminfo | awk '{printf "%.0f MB", $2/1024}') mem_available=$(grep '^MemAvailable:' /proc/meminfo | awk '{printf "%.0f MB", $2/1024}') if [ -n "$mem_available" ]; then print_detail "Memory" "$mem_total (Available: $mem_available)" else # Fallback for older kernels without MemAvailable mem_free=$(grep '^MemFree:' /proc/meminfo | awk '{printf "%.0f MB", $2/1024}') print_detail "Memory" "$mem_total (Free: $mem_free)" fi fi # B4 Installation Status print_header "B4 Status" # Check if b4 is installed if [ -f "${INSTALL_DIR}/${BINARY_NAME}" ]; then print_detail "Binary Location" "${INSTALL_DIR}/${BINARY_NAME}" # Get version if possible if "${INSTALL_DIR}/${BINARY_NAME}" --version >/dev/null 2>&1; then b4_version=$("${INSTALL_DIR}/${BINARY_NAME}" --version 2>&1 | head -1) print_detail "Installed Version" "$b4_version" else print_detail "Installed Version" "Unknown (binary present)" fi # Check service status service_status=$(get_service_status) if echo "$service_status" | grep -q "running"; then print_detail "Service Status" "${GREEN}$service_status${NC}" else print_detail "Service Status" "${YELLOW}$service_status${NC}" fi else print_detail "Installation Status" "${RED}Not installed${NC}" fi # Check for config file if [ -f "$CONFIG_FILE" ]; then print_detail "Config File" "$CONFIG_FILE" # Check config content if jq is available if command_exists jq; then queue_num=$(jq -r '.queue_start_num // 537' "$CONFIG_FILE" 2>/dev/null || echo "537") threads=$(jq -r '.threads // 4' "$CONFIG_FILE" 2>/dev/null || echo "4") web_port=$(jq -r '.web_server.port // 0' "$CONFIG_FILE" 2>/dev/null || echo "0") print_detail "Queue Number" "$queue_num" print_detail "Worker Threads" "$threads" if [ "$web_port" != "0" ]; then print_detail "Web UI Port" "$web_port" fi fi else print_detail "Config File" "${YELLOW}Not found${NC}" fi # Check for geosite data if [ -f "$CONFIG_FILE" ] && command_exists jq; then sitedat_path=$(jq -r '.system.geo.sitedat_path // empty' "$CONFIG_FILE" 2>/dev/null) if [ -n "$sitedat_path" ] && [ "$sitedat_path" != "null" ] && [ -f "$sitedat_path" ]; then geosite_size=$(du -h "$sitedat_path" 2>/dev/null | cut -f1) print_detail "geosite.dat" "$sitedat_path ($geosite_size)" fi ipdat_path=$(jq -r '.system.geo.ipdat_path // empty' "$CONFIG_FILE" 2>/dev/null) if [ -n "$ipdat_path" ] && [ "$ipdat_path" != "null" ] && [ -f "$ipdat_path" ]; then ipdat_size=$(du -h "$ipdat_path" 2>/dev/null | cut -f1) print_detail "geoip.dat" "$ipdat_path ($ipdat_size)" fi fi # Show process details if running if ps 2>/dev/null | grep -v grep | grep -v "b4install" | grep -q "b4$\|b4[[:space:]]"; then b4_pid=$(ps 2>/dev/null | grep -v grep | grep -v "b4install" | grep "b4$\|b4[[:space:]]" | awk '{print $1}' | head -1) if [ -n "$b4_pid" ] && [ -d "/proc/$b4_pid" ]; then # Memory usage if [ -f "/proc/$b4_pid/status" ]; then rss=$(grep '^VmRSS:' /proc/$b4_pid/status 2>/dev/null | awk '{printf "%.1f MB", $2/1024}') [ -n "$rss" ] && print_detail "Memory Usage" "$rss (PID: $b4_pid)" fi # Uptime if [ -f "/proc/$b4_pid/stat" ] && [ -f "/proc/uptime" ]; then start_ticks=$(awk '{print $22}' /proc/$b4_pid/stat 2>/dev/null) uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null) hz=$(getconf CLK_TCK 2>/dev/null || echo 100) if [ -n "$start_ticks" ] && [ -n "$uptime_sec" ]; then proc_uptime=$((uptime_sec - start_ticks / hz)) days=$((proc_uptime / 86400)) hours=$(((proc_uptime % 86400) / 3600)) mins=$(((proc_uptime % 3600) / 60)) if [ $days -gt 0 ]; then print_detail "Uptime" "${days}d ${hours}h ${mins}m" elif [ $hours -gt 0 ]; then print_detail "Uptime" "${hours}h ${mins}m" else print_detail "Uptime" "${mins}m" fi fi fi fi fi # Service Manager Detection print_header "Service Management" if [ -f "/etc/systemd/system/b4.service" ] && command_exists systemctl; then print_detail "Service Manager" "systemd" print_detail "Service File" "/etc/systemd/system/b4.service" elif [ -f "/opt/etc/init.d/S99b4" ]; then print_detail "Service Manager" "Entware init" print_detail "Service File" "/opt/etc/init.d/S99b4" elif [ -f "/etc/init.d/b4" ]; then print_detail "Service Manager" "SysV init" print_detail "Service File" "/etc/init.d/b4" else print_detail "Service Manager" "${YELLOW}None configured${NC}" fi # Firewall/Netfilter Status print_header "Firewall & Netfilter" firewall_backend=$(detect_firewall_backend) print_detail "Firewall Backend" "$firewall_backend" # Check for iptables if command_exists iptables; then iptables_version=$(iptables --version 2>&1 | head -1 | awk '{print $2}' | tr -d 'v') print_detail "iptables" "${GREEN}Available${NC} (v$iptables_version)" # Check for b4 rules in iptables if iptables -t mangle -L B4 -n 2>/dev/null | grep -q NFQUEUE; then print_detail "iptables Rules" "${GREEN}Active${NC}" fi else print_detail "iptables" "${YELLOW}Not found${NC}" fi # Check for nftables if command_exists nft; then nft_version=$(nft --version 2>&1 | awk '{print $2}' | tr -d 'v') print_detail "nftables" "${GREEN}Available${NC} (v$nft_version)" # Check for b4 rules in nftables if nft list table inet b4_mangle 2>/dev/null | grep -q queue; then print_detail "nftables Rules" "${GREEN}Active${NC}" fi else print_detail "nftables" "${YELLOW}Not found${NC}" fi # Check for ip6tables if command_exists ip6tables; then print_detail "ip6tables" "${GREEN}Available${NC}" else print_detail "ip6tables" "${YELLOW}Not found${NC}" fi # Check netfilter queue status if [ -f /proc/net/netfilter/nfnetlink_queue ]; then nfqueue_info=$(cat /proc/net/netfilter/nfnetlink_queue 2>/dev/null | grep -v "^#" | head -1 || true) if [ -n "$nfqueue_info" ]; then print_detail "NFQueue Status" "${GREEN}Available${NC}" else print_detail "NFQueue Status" "${YELLOW}Available (no queues)${NC}" fi else print_detail "NFQueue Status" "${RED}Not available${NC}" fi # Show queue stats if b4 is using queues if [ -f /proc/net/netfilter/nfnetlink_queue ]; then queue_stats=$(cat /proc/net/netfilter/nfnetlink_queue 2>/dev/null | grep -v "^#" | head -1) if [ -n "$queue_stats" ]; then queue_id=$(echo "$queue_stats" | awk '{print $1}') queue_dropped=$(echo "$queue_stats" | awk '{print $5}') print_detail "Queue ID" "$queue_id" if [ "$queue_dropped" != "0" ]; then print_detail "Queue Drops" "${YELLOW}$queue_dropped${NC}" fi fi fi print_header "Network Interfaces" # WAN interface (common names) # for iface in eth0 wan0 ppp0 vlan2; do # if [ -d "/sys/class/net/$iface" ]; then # ip_addr=$(ip -4 addr show "$iface" 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | head -1) # [ -n "$ip_addr" ] && print_detail "WAN ($iface)" "$ip_addr" # break # fi # done # LAN interface for iface in br0 br-lan eth1 lan0; do if [ -d "/sys/class/net/$iface" ]; then ip_addr=$(ip -4 addr show "$iface" 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | head -1) [ -n "$ip_addr" ] && print_detail "LAN ($iface)" "$ip_addr" break fi done # Kernel Modules print_header "Kernel Modules" # Netfilter modules modules="nf_conntrack xt_connbytes xt_NFQUEUE xt_multiport nf_tables nft_queue nft_ct" for mod in $modules; do status=$(check_kernel_module "$mod" || true) case "$status" in loaded) print_detail "$mod" "${GREEN}Loaded${NC}" ;; available) print_detail "$mod" "${CYAN}Available${NC}" ;; unknown) print_detail "$mod" "${YELLOW}Not found${NC}" ;; esac done # Check conntrack settings if [ -f /proc/sys/net/netfilter/nf_conntrack_checksum ]; then checksum=$(cat /proc/sys/net/netfilter/nf_conntrack_checksum 2>/dev/null || echo "1") if [ "$checksum" = "0" ]; then print_detail "conntrack_checksum" "${GREEN}Disabled (good)${NC}" else print_detail "conntrack_checksum" "${YELLOW}Enabled${NC}" fi fi if [ -f /proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal ]; then liberal=$(cat /proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal 2>/dev/null || echo "0") if [ "$liberal" = "1" ]; then print_detail "tcp_be_liberal" "${GREEN}Enabled (good)${NC}" else print_detail "tcp_be_liberal" "${YELLOW}Disabled${NC}" fi fi # Check multiport support (iptables only) if [ "$firewall_backend" != "nftables" ] && command_exists iptables; then multiport_status=$(check_multiport_support) case "$multiport_status" in works_module) print_detail "iptables multiport" "${GREEN}Available (module)${NC}" ;; works_builtin) print_detail "iptables multiport" "${GREEN}Available (built-in)${NC}" ;; not_available) print_detail "iptables multiport" "${YELLOW}Not available (fallback mode)${NC}" ;; esac fi # Dependencies Check print_header "Dependencies" deps="wget curl tar jq sha256sum nohup" for dep in $deps; do if command_exists "$dep"; then print_detail "$dep" "${GREEN}Available${NC}" else print_detail "$dep" "${YELLOW}Not found${NC}" fi done # Package Manager Detection print_header "Package Management" if command_exists opkg; then print_detail "Package Manager" "opkg (OpenWRT/Entware)" elif command_exists apt-get; then print_detail "Package Manager" "apt (Debian/Ubuntu)" elif command_exists yum; then print_detail "Package Manager" "yum (RedHat/CentOS)" elif command_exists apk; then print_detail "Package Manager" "apk (Alpine)" else print_detail "Package Manager" "${YELLOW}None detected${NC}" fi # Recommendations print_header "Recommendations" recommendations=0 # Check if running as root if [ "$USER" != "root" ] && ! (touch /etc/.root_test 2>/dev/null && rm -f /etc/.root_test 2>/dev/null); then printf " ${YELLOW}⚠${NC} Run this script as root for installation" recommendations=$((recommendations + 1)) fi # Check for missing critical dependencies if ! command_exists wget && ! command_exists curl; then printf " ${RED}✗${NC} Install wget or curl for downloading" recommendations=$((recommendations + 1)) fi if ! command_exists tar; then printf " ${RED}✗${NC} Install tar for extracting archives" recommendations=$((recommendations + 1)) fi # Check for missing kernel modules if [ "$(check_kernel_module nf_conntrack)" = "unknown" ]; then printf " ${RED}✗${NC} nf_conntrack module not found" recommendations=$((recommendations + 1)) fi if [ "$(check_kernel_module xt_connbytes)" = "unknown" ]; then printf " ${RED}✗${NC} xt_connbytes module not found" recommendations=$((recommendations + 1)) fi if [ "$(check_kernel_module xt_NFQUEUE)" = "unknown" ]; then printf " ${RED}✗${NC} xt_NFQUEUE module not found" recommendations=$((recommendations + 1)) fi if [ "$(check_kernel_module xt_multiport)" = "unknown" ]; then printf " ${RED}✗${NC} xt_multiport module not found" recommendations=$((recommendations + 1)) fi # Check firewall if [ "$firewall_backend" = "none" ]; then printf " ${RED}✗${NC} No firewall (iptables/nftables) detected" recommendations=$((recommendations + 1)) fi # Check if b4 is installed but not running if [ -f "${INSTALL_DIR}/${BINARY_NAME}" ]; then service_status=$(get_service_status) if ! echo "$service_status" | grep -q "running"; then printf " ${YELLOW}⚠${NC} B4 is installed but not running" if [ -f "/etc/systemd/system/b4.service" ]; then printf " Try: systemctl start b4" elif [ -f "/opt/etc/init.d/S99b4" ]; then printf " Try: /opt/etc/init.d/S99b4 start" elif [ -f "/etc/init.d/b4" ]; then printf " Try: /etc/init.d/b4 start" fi recommendations=$((recommendations + 1)) fi fi if [ $recommendations -eq 0 ]; then printf " ${GREEN}✓${NC} System appears ready for B4" fi echo "" } # --- END sysinfo.sh --- # Main function - parse arguments main() { # Check for remove flag first for arg in "$@"; do case "$arg" in --remove | --uninstall | -r) check_root remove_b4 exit 0 ;; --update | -u) check_root perform_update "$@" exit 0 ;; --info | -i | --sysinfo) show_system_info exit 0 ;; --help | -h) echo "Usage: $0 [OPTIONS] [VERSION]" echo "" echo "Options:" echo " --sysinfo, -i Show system information and b4 status" echo " --remove, -r Uninstall b4 from the system" echo " --update, -u Update b4 to latest version" echo " --arch=ARCH Force architecture (skip auto-detection)" echo " --help, -h Show this help message" echo " --quiet, -q Suppress output except for errors" echo " --geosite-src URL Specify geosite.dat source URL" echo " --geosite-dst DIR Specify directory to save geosite.dat" echo " VERSION Install specific version (e.g., v1.4.0)" echo "" echo "Architectures:" echo " amd64, 386, arm64, armv5, armv6, armv7," echo " mips, mipsle, mips_softfloat, mipsle_softfloat," echo " mips64, mips64le, loong64, ppc64, ppc64le, riscv64, s390x" echo "" echo "Examples:" echo " $0 Install latest version" echo " $0 v1.4.0 Install version 1.4.0" echo " $0 --arch=mipsle_softfloat Force architecture" echo " $0 --sysinfo Show system diagnostics" echo " $0 --update Update to latest version" echo " $0 --remove Uninstall b4" exit 0 ;; esac done # No remove/update flag found, proceed with installation main_install "$@" } # Run main function main "$@" # --- END main.sh ---