Build SingBox Configs #114
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build SingBox Configs | |
| on: | |
| schedule: | |
| - cron: "*/10 * * * *" | |
| timezone: "Europe/Moscow" | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| - name: Build configs | |
| run: | | |
| # НУЖНЫЕ ИСХОДНИКИ (4 источника) | |
| SOURCES=( | |
| "https://raw.githack.com/igareck/vpn-configs-for-russia/main/Vless-Reality-White-Lists-Rus-Mobile.txt" | |
| "https://raw.githack.com/igareck/vpn-configs-for-russia/main/WHITE-CIDR-RU-all.txt" | |
| "https://raw.githack.com/igareck/vpn-configs-for-russia/main/WHITE-CIDR-RU-checked.txt" | |
| "https://raw.githack.com/igareck/vpn-configs-for-russia/main/WHITE-SNI-RU-all.txt" | |
| ) | |
| # Функция создания конфига с правильным количеством ключей | |
| make_config_with_rules() { | |
| OUTPUT_FILE=$1 | |
| RUSSIAN_KEYS=$2 | |
| NON_RUSSIAN_KEYS=$3 | |
| CONFIG_NAME=$4 | |
| # Очищаем от пустых строк | |
| RUSSIAN_KEYS=$(echo "$RUSSIAN_KEYS" | grep -v '^$' | sed '/^[[:space:]]*$/d') | |
| NON_RUSSIAN_KEYS=$(echo "$NON_RUSSIAN_KEYS" | grep -v '^$' | sed '/^[[:space:]]*$/d') | |
| # Считаем количество русских ключей (безопасно) | |
| RU_COUNT=$(echo "$RUSSIAN_KEYS" | grep -c . 2>/dev/null | tr -d '\n\r\t ' | xargs) | |
| RU_COUNT=${RU_COUNT:-0} | |
| RU_COUNT=$((RU_COUNT + 0)) | |
| echo "Building $OUTPUT_FILE ($CONFIG_NAME)" | |
| echo " - Source RU keys: $RU_COUNT" | |
| # Определяем логику формирования набора ключей | |
| if [ "$RU_COUNT" -ge 10 ]; then | |
| # Если русских 10 или больше - берем 10 случайных русских | |
| FINAL=$(echo "$RUSSIAN_KEYS" | shuf | head -n 10) | |
| echo " - Strategy: >=10 RU keys, taking 10 random RU keys" | |
| elif [ "$RU_COUNT" -ge 6 ]; then | |
| # Если русских 6-9 - берем всех русских + добиваем нерусскими до 10 | |
| TARGET_TOTAL=10 | |
| NEED_NON_RU=$((TARGET_TOTAL - RU_COUNT)) | |
| FINAL="$RUSSIAN_KEYS" | |
| if [ "$NEED_NON_RU" -gt 0 ]; then | |
| NONRU_TO_ADD=$(echo "$NON_RUSSIAN_KEYS" | shuf | head -n "$NEED_NON_RU" 2>/dev/null || true) | |
| if [ -n "$NONRU_TO_ADD" ]; then | |
| FINAL=$(printf "%s\n%s\n" "$FINAL" "$NONRU_TO_ADD" | grep -v '^$') | |
| fi | |
| fi | |
| echo " - Strategy: $RU_COUNT RU keys (6-9), adding $NEED_NON_RU non-RU to reach 10 total" | |
| elif [ "$RU_COUNT" -ge 4 ]; then | |
| # Если русских 4-5 - берем всех русских + добиваем нерусскими до 7 | |
| TARGET_TOTAL=7 | |
| NEED_NON_RU=$((TARGET_TOTAL - RU_COUNT)) | |
| FINAL="$RUSSIAN_KEYS" | |
| if [ "$NEED_NON_RU" -gt 0 ]; then | |
| NONRU_TO_ADD=$(echo "$NON_RUSSIAN_KEYS" | shuf | head -n "$NEED_NON_RU" 2>/dev/null || true) | |
| if [ -n "$NONRU_TO_ADD" ]; then | |
| FINAL=$(printf "%s\n%s\n" "$FINAL" "$NONRU_TO_ADD" | grep -v '^$') | |
| fi | |
| fi | |
| echo " - Strategy: $RU_COUNT RU keys (4-5), adding $NEED_NON_RU non-RU to reach 7 total" | |
| elif [ "$RU_COUNT" -ge 1 ]; then | |
| # Если русских 1-3 - берем всех русских + добиваем нерусскими до 5 | |
| TARGET_TOTAL=5 | |
| NEED_NON_RU=$((TARGET_TOTAL - RU_COUNT)) | |
| FINAL="$RUSSIAN_KEYS" | |
| if [ "$NEED_NON_RU" -gt 0 ]; then | |
| NONRU_TO_ADD=$(echo "$NON_RUSSIAN_KEYS" | shuf | head -n "$NEED_NON_RU" 2>/dev/null || true) | |
| if [ -n "$NONRU_TO_ADD" ]; then | |
| FINAL=$(printf "%s\n%s\n" "$FINAL" "$NONRU_TO_ADD" | grep -v '^$') | |
| fi | |
| fi | |
| echo " - Strategy: $RU_COUNT RU keys (1-3), adding $NEED_NON_RU non-RU to reach 5 total" | |
| else | |
| # Если русских ключей НЕТ (RU_COUNT = 0) - берем 5 нерусских ключей | |
| TARGET_TOTAL=5 | |
| FINAL=$(echo "$NON_RUSSIAN_KEYS" | shuf | head -n "$TARGET_TOTAL" 2>/dev/null || true) | |
| echo " - Strategy: No RU keys, taking $TARGET_TOTAL non-RU keys" | |
| # Если нерусских меньше 5 - берем сколько есть | |
| NONRU_AVAILABLE=$(echo "$NON_RUSSIAN_KEYS" | grep -c . 2>/dev/null | tr -d '\n\r\t ' | xargs) | |
| NONRU_AVAILABLE=${NONRU_AVAILABLE:-0} | |
| NONRU_AVAILABLE=$((NONRU_AVAILABLE + 0)) | |
| if [ "$NONRU_AVAILABLE" -lt "$TARGET_TOTAL" ]; then | |
| echo " - Warning: Only $NONRU_AVAILABLE non-RU keys available (less than $TARGET_TOTAL)" | |
| fi | |
| fi | |
| # Убираем пустые строки и перемешиваем финальный список | |
| FINAL=$(echo "$FINAL" | grep -v '^$' | shuf) | |
| # Безопасное получение числовых значений | |
| FINAL_COUNT=$(echo "$FINAL" | grep -c . 2>/dev/null | tr -d '\n\r\t ' | xargs) | |
| FINAL_COUNT=${FINAL_COUNT:-0} | |
| FINAL_COUNT=$((FINAL_COUNT + 0)) | |
| RU_IN_FINAL=$(echo "$FINAL" | grep -Ei 'russia' | grep -c . 2>/dev/null | tr -d '\n\r\t ' | xargs) | |
| RU_IN_FINAL=${RU_IN_FINAL:-0} | |
| RU_IN_FINAL=$((RU_IN_FINAL + 0)) | |
| NONRU_IN_FINAL=$((FINAL_COUNT - RU_IN_FINAL)) | |
| echo " - Total nodes: $FINAL_COUNT" | |
| echo " - Russian nodes: $RU_IN_FINAL" | |
| echo " - Non-Russian nodes: $NONRU_IN_FINAL" | |
| # Отправляем запрос к API | |
| jq -n --arg input "$FINAL" '{ | |
| core: "singbox", | |
| input: $input, | |
| options: { | |
| addTun: true, | |
| addSocks: true, | |
| genClashSecret: false, | |
| useExtended: false | |
| } | |
| }' \ | |
| | curl -sS -X POST "https://api.web2core.workers.dev/" \ | |
| -H "Content-Type: application/json" \ | |
| --data-binary @- \ | |
| > "$OUTPUT_FILE" | |
| } | |
| # Создаем конфиги: по 2 конфига на каждый источник (основной и b) | |
| for i in "${!SOURCES[@]}"; do | |
| SOURCE_NUM=$((i + 1)) | |
| SOURCE_URL="${SOURCES[$i]}" | |
| echo "==========================================" | |
| echo "Processing source $SOURCE_NUM: $SOURCE_URL" | |
| echo "==========================================" | |
| # СКАЧИВАЕМ ТОЛЬКО ЭТОТ ИСТОЧНИК для основного конфига | |
| KEYS=$(curl -s "$SOURCE_URL" | tr -d '\r' | grep -E '^(vless|vmess|trojan)://' | grep -Evi 'raw' || true) | |
| UNIQUE_KEYS=$(echo "$KEYS" | grep -v '^$' | sort -u) | |
| TOTAL_KEYS=$(echo "$UNIQUE_KEYS" | grep -c . 2>/dev/null || echo "0") | |
| echo "Total unique keys in source: $TOTAL_KEYS" | |
| # Разделяем на RU и NON-RU для этого источника | |
| RUSSIA=$(echo "$UNIQUE_KEYS" | grep -Ei 'russia' | shuf || true) | |
| NON_RUSSIA=$(echo "$UNIQUE_KEYS" | grep -Evi 'russia' | shuf || true) | |
| RU_COUNT_TOTAL=$(echo "$RUSSIA" | grep -c . 2>/dev/null || echo "0") | |
| NONRU_COUNT_TOTAL=$(echo "$NON_RUSSIA" | grep -c . 2>/dev/null || echo "0") | |
| echo "RU keys in source: $RU_COUNT_TOTAL" | |
| echo "Non-RU keys in source: $NONRU_COUNT_TOTAL" | |
| # Создаем основной конфиг | |
| make_config_with_rules "config_${SOURCE_NUM}.json" "$RUSSIA" "$NON_RUSSIA" "source${SOURCE_NUM}_main" | |
| # Для _b конфига берем другие ключи (если возможно) | |
| # Перемешиваем заново, чтобы получить другой набор | |
| RUSSIA_B=$(echo "$UNIQUE_KEYS" | grep -Ei 'russia' | shuf || true) | |
| NON_RUSSIA_B=$(echo "$UNIQUE_KEYS" | grep -Evi 'russia' | shuf || true) | |
| # Создаем backup конфиг | |
| make_config_with_rules "config_${SOURCE_NUM}_b.json" "$RUSSIA_B" "$NON_RUSSIA_B" "source${SOURCE_NUM}_backup" | |
| echo "" | |
| done | |
| # ---------------- PATCH URLTEST НА 1 МИНУТУ ДЛЯ ВСЕХ КОНФИГОВ ---------------- | |
| patch_interval() { | |
| FILE=$1 | |
| INTERVAL=$2 | |
| if [ -f "$FILE" ]; then | |
| echo "Patching $FILE -> interval=$INTERVAL" | |
| jq --arg interval "$INTERVAL" ' | |
| (.outbounds[] | select(.type=="urltest") | .interval) = $interval | | |
| (.outbounds[] | select(.type=="urltest") | .tolerance) = 100 | |
| ' "$FILE" > tmp.json && mv tmp.json "$FILE" | |
| else | |
| echo "Warning: $FILE not found, skipping patch" | |
| fi | |
| } | |
| # Применяем патч 1 минута для ВСЕХ конфигов | |
| for file in config_*.json; do | |
| patch_interval "$file" "1m" | |
| done | |
| # Выводим список созданных файлов | |
| echo "==========================================" | |
| echo "Created configs:" | |
| ls -la config_*.json | |
| # Проверяем, что файлы не пустые | |
| echo "==========================================" | |
| echo "Config sizes:" | |
| for file in config_*.json; do | |
| if [ -f "$file" ]; then | |
| SIZE=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "0") | |
| echo "$file: $SIZE bytes" | |
| if [ "$SIZE" -lt 100 ]; then | |
| echo " WARNING: $file is too small, might be invalid!" | |
| fi | |
| fi | |
| done | |
| - name: Commit configs | |
| run: | | |
| git config user.name "github-actions" | |
| git config user.email "actions@github.com" | |
| git add config_*.json | |
| git commit -m "auto update sing-box configs (dynamic keys based on RU count)" || exit 0 | |
| git push | |
| - name: Upload to Yandex Disk via WebDAV | |
| env: | |
| YA_LOGIN: ${{ secrets.YANDEX_NAME }} | |
| YA_PASS: ${{ secrets.YANDEX_TOKEN }} | |
| run: | | |
| # Кодируем логин:пароль в base64 | |
| AUTH_HEADER=$(echo -n "$YA_LOGIN:$YA_PASS" | base64) | |
| # Создаём папку | |
| curl -s -X MKCOL "https://webdav.yandex.ru/SingBox_Configs/" \ | |
| -H "Authorization: Basic $AUTH_HEADER" || true | |
| # Загружаем все файлы config_*.json | |
| for file in config_*.json; do | |
| if [ -f "$file" ]; then | |
| echo "Uploading $file..." | |
| curl -s -T "$file" "https://webdav.yandex.ru/SingBox_Configs/$file" \ | |
| -H "Authorization: Basic $AUTH_HEADER" | |
| fi | |
| done | |
| echo "All configs uploaded to Yandex Disk" |