commit 35324930ae7452c562b4005ca2a47649dc82e6fc Author: hesoyam Date: Thu May 14 15:54:05 2026 +0300 first commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..716e98d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM alpine:latest + +# Install required packages +RUN apk add --no-cache wget tar ca-certificates + +# Set working directory +WORKDIR /app + +# Download and extract sing-box +RUN wget https://github.com/shtorm-7/sing-box-extended/releases/download/v1.13.2-extended-1.6.2/sing-box-1.13.2-extended-1.6.2-linux-amd64.tar.gz && \ + tar -xzf sing-box-1.13.2-extended-1.6.2-linux-amd64.tar.gz && \ + mv sing-box-1.13.2-extended-1.6.2-linux-amd64/sing-box /usr/local/bin/sing-box && \ + chmod +x /usr/local/bin/sing-box && \ + rm -rf sing-box-1.13.2-extended-1.6.2-linux-amd64.tar.gz sing-box-1.13.2-extended-1.6.2-linux-amd64 + +# Copy configuration generator script +COPY generate-config.sh /app/generate-config.sh +RUN chmod +x /app/generate-config.sh + +# Copy warp configuration +COPY warp.conf /app/warp.conf + +# Expose SOCKS5 proxy port +EXPOSE 2080 + +# Generate config and run sing-box +CMD ["/bin/sh", "-c", "/app/generate-config.sh && sing-box run -c /app/config.json"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..2dd615f --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# Sing-Box WARP Project +> Author remark: +> вы спросите - нахуя такие сложности? А потому что нативный warp не поддерживает amneziaWG ключи для обфускации WireGuard. А ебаться в саму амнезию нет желания. К тому же sing-box сразу может поднять локальный сокс что упрощает просовывание этого варпа во всякого рода панели. + + +Проект для запуска sing-box с WARP конфигурацией. + +## Структура проекта + +- `Dockerfile` - образ с sing-box +- `docker-compose.yml` - оркестрация контейнера +- `generate-config.sh` - генератор конфига из WireGuard AWG3.0 формата +- `warp.conf` - WARP конфигурация в формате WireGuard AWG3.0 + +## Как это работает + +1. Положите ваш WARP конфиг в формате AWG3.0 в файл `warp.conf` +2. При запуске контейнера `generate-config.sh` парсит конфиг и генерирует `config.json` +3. SOCKS5 прокси поднимается на порту 2080 + +## Формат warp.conf + +Вставьте WireGuard конфигурацию в файл `warp.conf`: + +```AWG3.0 +[Interface] +PrivateKey = {privkey} +Address = 172.16.0.2, 2606:4700:110:890e:48c0:b073:6ebe:d347 +DNS = 1.1.1.1, 1.0.0.1, 2606:4700:4700::1111, 2606:4700:4700::1001 +MTU = 1280 +S1 = 0 +S2 = 0 +Jc = 4 +Jmin = 40 +Jmax = 70 +H1 = 1 +H2 = 2 +H3 = 3 +H4 = 4 +I1 = {string} + +[Peer] +PublicKey = {pubkey} +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = engage.cloudflareclient.com:2408 +PersistentKeepalive = 25 +``` + +**Параметры:** +- `PrivateKey` - приватный ключ WireGuard +- `Address` - локальный IP адрес (IPv4) +- `MTU` - размер MTU (по умолчанию 1280) +- `S1-S4` - reserved байты для обфускации (используются первые 3) +- `Jc, Jmin, Jmax` - параметры Amnezia для junk пакетов +- `H1-H4` - magic headers для обфускации +- `PublicKey` - публичный ключ сервера +- `Endpoint` - адрес и порт сервера + +## Запуск Docker-Compose + +```bash +# Собрать и запустить +docker compose up -d + +# Просмотр логов +docker compose logs -f + +# Остановить +docker compose down -v +``` + +## (Alternative) Установка как systemd service без docker! +Скрипт попросит вставить WireGuard конфигурацию при установке +https://warp-generator.github.io/ AWG 3.0 + +``` +curl -fsSL https://raw.githubusercontent.com/jahlib/sing-warp-socks5/refs/heads/master/quick-install.sh | sudo bash +``` + +## Использование + +SOCKS5 прокси доступен на `localhost:2080` без авторизации. + +Пример использования: +```bash +curl --proxy socks5://localhost:2080 ip-api.com +``` + +## Обновление конфигурации + +После изменения `config.json`: +```bash +docker-compose restart +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..70733e6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.8' + +services: + sing-box: + build: . + container_name: sing-box-warp + restart: unless-stopped + ports: + - "2080:2080" + cap_add: + - NET_ADMIN + sysctls: + - net.ipv4.conf.all.src_valid_mark=1 + - net.ipv6.conf.all.disable_ipv6=0 + volumes: + - ./warp.conf:/app/warp.conf:ro + - warp-cache:/app/.cache + networks: + - sing-box-network + +volumes: + warp-cache: + +networks: + sing-box-network: + driver: bridge diff --git a/generate-config.sh b/generate-config.sh new file mode 100644 index 0000000..ad39ae4 --- /dev/null +++ b/generate-config.sh @@ -0,0 +1,308 @@ +#!/bin/sh + +WARP_CONF="/app/warp.conf" +OUTPUT_CONFIG="/app/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 +} + +# Extract parameter from URL +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" <> 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 <