#!/bin/bash set -e INSTALL_DIR="/opt/sing-box-warp" CONFIG_DIR="/etc/sing-box-warp" CACHE_DIR="/var/cache/sing-box-warp" SING_BOX_VERSION="1.13.2-extended-1.6.2" SING_BOX_URL="https://ghettoloader.duckdns.org/hesoyam/sing-box-extended-mirror/releases/download/v${SING_BOX_VERSION}/sing-box-${SING_BOX_VERSION}-linux-amd64.tar.gz" echo "=== Sing-Box WARP Quick Installer ===" echo "" if [ "$EUID" -ne 0 ]; then echo "Please run as root (use sudo)" exit 1 fi echo "Creating directories..." mkdir -p "$INSTALL_DIR" mkdir -p "$CONFIG_DIR" mkdir -p "$CACHE_DIR" echo "" echo "=== WARP Configuration ===" echo "goto ->> https://warp-generator.github.io/ generater for AWG 3.0" echo "Paste your WARP config (wg://... or [Interface]/[Peer] INI)." echo "Finish input with Ctrl-D." WARP_INPUT=$(cat < /dev/tty) if [ -z "$WARP_INPUT" ]; then echo "Error: warp.conf input is empty!" exit 1 fi printf "%s\n" "$WARP_INPUT" > "$CONFIG_DIR/warp.conf" echo "" echo "Configuration saved to $CONFIG_DIR/warp.conf" echo "" echo "Downloading sing-box..." NEED_DOWNLOAD=1 if command -v sing-box >/dev/null 2>&1; then INSTALLED_VERSION=$(sing-box version 2>/dev/null | head -n 1 || true) if echo "$INSTALLED_VERSION" | grep -q "$SING_BOX_VERSION"; then NEED_DOWNLOAD=0 echo "sing-box already installed ($INSTALLED_VERSION), skipping download." else echo "sing-box is installed ($INSTALLED_VERSION) but version mismatch, downloading $SING_BOX_VERSION..." fi fi if [ "$NEED_DOWNLOAD" -eq 1 ]; then cd /tmp TARBALL="sing-box-${SING_BOX_VERSION}-linux-amd64.tar.gz" rm -f "$TARBALL" echo "Downloading $TARBALL ..." if ! wget -q --show-progress --timeout=20 --tries=3 --waitretry=5 --retry-connrefused --continue -O "$TARBALL" "$SING_BOX_URL"; then echo "wget failed, trying curl..." curl -fL --connect-timeout 20 --retry 3 --retry-delay 5 -o "$TARBALL" "$SING_BOX_URL" fi tar -xzf "$TARBALL" mv "sing-box-${SING_BOX_VERSION}-linux-amd64/sing-box" /usr/local/bin/sing-box chmod +x /usr/local/bin/sing-box rm -rf "$TARBALL" "sing-box-${SING_BOX_VERSION}-linux-amd64" fi echo "Creating generate-config.sh..." cat > "$INSTALL_DIR/generate-config.sh" <<"'GENERATE_CONFIG_EOF'" #!/bin/bash WARP_CONF="${WARP_CONF:-/etc/sing-box-warp/warp.conf}" OUTPUT_CONFIG="${OUTPUT_CONFIG:-/opt/sing-box-warp/config.json}" urldecode() { echo "$1" | sed 's/%3[dD]/=/g; s/%2[bB]/+/g; s/%2[fF]/\//g; s/%2[cC]/,/g' } trim() { echo "$1" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' } normalize_cidr() { local addr local suffix addr=$(trim "$1") suffix="$2" if [ -z "$addr" ]; then echo "" return fi case "$addr" in */*) echo "$addr" ;; *) echo "${addr}/${suffix}" ;; esac } get_param() { local url="$1" local param="$2" echo "$url" | sed -n "s/.*[?&]${param}=\([^&#]*\).*/\1/p" } parse_from_wg_url() { WG_URL=$(grep "^wg://" "$WARP_CONF" | head -1) if [ -z "$WG_URL" ]; then echo "Error: No wg:// URL found in $WARP_CONF" exit 1 fi SERVER=$(echo "$WG_URL" | sed 's|wg://\([^:]*\):.*|\1|') PORT=$(echo "$WG_URL" | sed 's|wg://[^:]*:\([0-9]*\)?.*|\1|') PRIVATE_KEY=$(urldecode "$(get_param "$WG_URL" "private_key")") PUBLIC_KEY=$(urldecode "$(get_param "$WG_URL" "peer_public_key")") MTU=$(get_param "$WG_URL" "mtu") LOCAL_ADDRESS=$(urldecode "$(get_param "$WG_URL" "local_address")") Jc=$(get_param "$WG_URL" "junk_packet_count") Jmin=$(get_param "$WG_URL" "junk_packet_min_size") Jmax=$(get_param "$WG_URL" "junk_packet_max_size") H1=$(get_param "$WG_URL" "init_packet_magic_header") H2=$(get_param "$WG_URL" "response_packet_magic_header") H3=$(get_param "$WG_URL" "underload_packet_magic_header") H4=$(get_param "$WG_URL" "transport_packet_magic_header") IPV4=$(echo "$LOCAL_ADDRESS" | tr ',-' '\n' | sed -n '1p') IPV6=$(echo "$LOCAL_ADDRESS" | tr ',-' '\n' | sed -n '2p') IPV4=$(normalize_cidr "$IPV4" 32) IPV6=$(normalize_cidr "$IPV6" 128) if [ -z "$IPV4" ]; then echo "Error: local_address (IPv4) is empty" exit 1 fi ADDRESS_JSON=$(printf '"%s"' "$IPV4") MTU=${MTU:-1280} Jc=${Jc:-4} Jmin=${Jmin:-40} Jmax=${Jmax:-70} H1=${H1:-1} H2=${H2:-2} H3=${H3:-3} H4=${H4:-4} ALLOWED_IPS="0.0.0.0/0" TAG="wireguard-out" LOG_LEVEL="debug" } parse_from_ini() { local section="" local key="" local value="" while IFS= read -r line || [ -n "$line" ]; do line=$(trim "$line") [ -z "$line" ] && continue case "$line" in \#*|\;*) continue ;; esac case "$line" in "[Interface]") section="Interface"; continue ;; "[Peer]") section="Peer"; continue ;; esac key=$(trim "$(echo "$line" | cut -d'=' -f1)") value=$(trim "$(echo "$line" | cut -d'=' -f2-)") [ -z "$key" ] && continue if [ "$section" = "Interface" ]; then case "$key" in PrivateKey) PRIVATE_KEY="$value" ;; Address) IPV4=$(trim "$(echo "$value" | cut -d',' -f1)") IPV6=$(trim "$(echo "$value" | cut -d',' -f2)") ;; MTU) MTU="$value" ;; S1) S1="$value" ;; S2) S2="$value" ;; S3) S3="$value" ;; Jc) Jc="$value" ;; Jmin) Jmin="$value" ;; Jmax) Jmax="$value" ;; H1) H1="$value" ;; H2) H2="$value" ;; H3) H3="$value" ;; H4) H4="$value" ;; I1) I1="$value" ;; I2) I2="$value" ;; esac elif [ "$section" = "Peer" ]; then case "$key" in PublicKey) PUBLIC_KEY="$value" ;; AllowedIPs) if echo "$value" | grep -q "0.0.0.0/0"; then ALLOWED_IPS="0.0.0.0/0" else ALLOWED_IPS=$(trim "$(echo "$value" | cut -d',' -f1)") fi ;; Endpoint) SERVER=$(echo "$value" | cut -d':' -f1) PORT=$(echo "$value" | cut -d':' -f2) ;; esac fi done < "$WARP_CONF" IPV4=$(normalize_cidr "$IPV4" 32) IPV6=$(normalize_cidr "$IPV6" 128) if [ -z "$IPV4" ]; then echo "Error: Address (IPv4) is empty" exit 1 fi ADDRESS_JSON=$(printf '"%s"' "$IPV4") MTU=${MTU:-1280} S1=${S1:-0} S2=${S2:-0} S3=${S3:-0} Jc=${Jc:-4} Jmin=${Jmin:-40} Jmax=${Jmax:-70} H1=${H1:-1} H2=${H2:-2} H3=${H3:-3} H4=${H4:-4} ALLOWED_IPS=${ALLOWED_IPS:-0.0.0.0/0} if [ -z "$SERVER" ] || [ -z "$PORT" ]; then echo "Error: Endpoint is empty" exit 1 fi TAG="wireguard-out" LOG_LEVEL="error" } write_config() { if [ -n "$I1" ] || [ -n "$I2" ]; then H4_COMMA="," else H4_COMMA="" fi if [ -n "$I1" ] && [ -n "$I2" ]; then I1_LINE=$(printf ' "i1": "%s",\n' "$I1") I2_LINE=$(printf ' "i2": "%s"\n' "$I2") elif [ -n "$I1" ]; then I1_LINE=$(printf ' "i1": "%s"\n' "$I1") I2_LINE="" elif [ -n "$I2" ]; then I1_LINE="" I2_LINE=$(printf ' "i2": "%s"\n' "$I2") else I1_LINE="" I2_LINE="" fi cat > "$OUTPUT_CONFIG" < /etc/systemd/system/sing-box-warp.service <<"'SERVICE_EOF'" [Unit] Description=Sing-Box WARP SOCKS5 Proxy After=network-online.target Wants=network-online.target [Service] Type=simple User=root WorkingDirectory=/opt/sing-box-warp Environment="HOME=/var/cache/sing-box-warp" Environment="WARP_CONF=/etc/sing-box-warp/warp.conf" Environment="OUTPUT_CONFIG=/opt/sing-box-warp/config.json" ExecStartPre=/opt/sing-box-warp/generate-config.sh ExecStart=/usr/local/bin/sing-box run -c /opt/sing-box-warp/config.json Restart=on-failure RestartSec=5s StandardOutput=journal StandardError=journal NoNewPrivileges=false PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/opt/sing-box-warp /var/cache/sing-box-warp AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target 'SERVICE_EOF' echo "Configuring sysctl parameters..." cat > /etc/sysctl.d/99-sing-box-warp.conf <