feat: geosite/geoip для Discord со сборкой из апстримов

Домены и сети Discord в geosite.dat и geoip.dat для Xray/3x-ui, плюс
sing-box, Clash/mihomo, AdGuard, текст и готовый фрагмент routing.

Рукописная база живёт в data/discord-base.txt и data/ip/discord-base.txt,
остальное tools/sync.py тянет из пяти публичных апстримов: JSON-исходники
парсятся напрямую, скомпилированные .srs распаковываются через sing-box.
Каждый источник кладётся в свою категорию, недоступный источник не
обнуляет данные, файлы без шапки GENERATED скрипт не трогает.

Ограничения по протоколу и портам (директивы !network / !port) доезжают
до sing-box rule-set и до xray/routing.json. Такие категории намеренно
не входят в сводную discord: там сети вроде 172.64.0.0/13, и без привязки
к UDP-портам правило утащило бы в туннель чужой трафик.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-08-10 09:24:08 +03:00
co-authored by Claude Opus 5
commit 61c0ea3558
23 changed files with 7278 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
name: build
on:
push:
branches: [main]
paths:
- "data/**"
- "tools/**"
- ".gitea/workflows/build.yml"
pull_request:
paths:
- "data/**"
- "tools/**"
- ".gitea/workflows/build.yml"
schedule:
- cron: "17 3 * * *"
workflow_dispatch:
env:
GITEA_URL: ${{ github.server_url }}
GITEA_REPO: ${{ github.repository }}
# Автоматического токена обычно хватает. Если у раннера нет прав на push
# или на релизы — заведите секрет RELEASE_TOKEN с правами repo:write.
AUTO_TOKEN: ${{ github.token }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Окружение
run: |
set -eu
missing=""
for cmd in python3 curl unzip tar git; do
command -v "$cmd" >/dev/null 2>&1 || missing="$missing $cmd"
done
if [ -n "$missing" ]; then
echo "не хватает:$missing — доустанавливаем"
sudo=""; [ "$(id -u)" -eq 0 ] || sudo=sudo
$sudo apt-get update -qq
$sudo apt-get install -y -qq python3 curl unzip tar git ca-certificates
fi
python3 --version
# Раньше сборки: sing-box нужен не только для .srs, но и синхронизации —
# три из пяти апстримов отдают уже скомпилированный rule-set.
- name: Внешние инструменты
run: |
sh tools/fetch-tools.sh "$GITHUB_WORKSPACE/bin"
echo "$GITHUB_WORKSPACE/bin" >> "$GITHUB_PATH"
- name: Синхронизация апстримов
run: python3 tools/sync.py
- name: Проверка исходников
run: python3 tools/build.py --check
- name: Тесты
run: python3 -m unittest discover -s tools -t tools
- name: Сборка
run: |
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) python3 tools/build.py
- name: Проверка .dat настоящим Xray
run: |
# Роутинг-правило заставляет Xray реально распарсить оба файла:
# битый .dat или отсутствующий тег дают ненулевой код возврата.
cat > /tmp/xray-test.json <<'EOF'
{
"inbounds": [{"port": 10800, "protocol": "socks"}],
"outbounds": [{"protocol": "freedom", "tag": "direct"}],
"routing": {
"rules": [
{"type": "field", "domain": ["ext:discord-geosite.dat:discord"], "outboundTag": "direct"},
{"type": "field", "ip": ["ext:discord-geoip.dat:discord"], "outboundTag": "direct"},
{"type": "field", "ip": ["ext:geoip.dat:discord-base"], "outboundTag": "direct"}
]
}
}
EOF
XRAY_LOCATION_ASSET="$GITHUB_WORKSPACE/dist" xray run -test -c /tmp/xray-test.json
- name: Проверка rule-set для sing-box
run: |
set -eu
sing-box rule-set decompile --output /tmp/site.json dist/sing-box/site/discord.srs
grep -q '"discord.com"' /tmp/site.json
sing-box rule-set decompile --output /tmp/ip.json dist/sing-box/ip/discord.srs
grep -q 'ip_cidr' /tmp/ip.json
# Ограниченные категории обязаны донести протокол и порты до .srs.
for src in dist/sing-box/ip/*-udp*.json; do
[ -e "$src" ] || continue
sing-box rule-set decompile --output /tmp/udp.json "${src%.json}.srs"
grep -q '"udp"' /tmp/udp.json
grep -q 'port_range' /tmp/udp.json
done
- name: Обновление data/ в main
if: github.event_name != 'pull_request'
run: |
set -eu
if git diff --quiet -- data; then
echo "апстримы не изменились"
exit 0
fi
token="${RELEASE_TOKEN:-$AUTO_TOKEN}"
[ -n "$token" ] || { echo "нет токена для push" >&2; exit 1; }
host="${GITEA_URL#*://}"
scheme="${GITEA_URL%%://*}"
git config user.name "gitea-actions"
git config user.email "gitea-actions@noreply.local"
git add data
git commit -q -m "sync: обновление списков из апстримов"
git push -q "$scheme://gitea-actions:$token@$host/$GITEA_REPO.git" HEAD:main
echo "data/ обновлён в main"
- name: Описание релиза
id: notes
run: |
set -eu
tag=$(date -u '+%Y.%m.%d-%H%M')
echo "tag=$tag" >> "$GITHUB_OUTPUT"
{
echo "Собрано из \`$(git rev-parse --short HEAD)\`."
echo
echo '| Категория | Доменов |'
echo '| --- | ---: |'
python3 -c "import json; [print(f'| {k} | {v} |') for k, v in json.load(open('dist/metadata.json'))['categories'].items()]"
echo
echo '| IP-категория | Сетей | Ограничения |'
echo '| --- | ---: | --- |'
python3 -c "
import json
for k, v in json.load(open('dist/metadata.json'))['ip_categories'].items():
limits = ' '.join(filter(None, ['/'.join(v.get('network', [])), v.get('ports', '')])) or '—'
print(f\"| {k} | {v['count']} | {limits} |\")
"
echo
echo "sha256(discord-geosite.dat) = \`$(cut -d' ' -f1 dist/discord-geosite.dat.sha256sum)\`"
echo "sha256(discord-geoip.dat) = \`$(cut -d' ' -f1 dist/discord-geoip.dat.sha256sum)\`"
} > /tmp/notes.md
cat /tmp/notes.md
- name: Публикация в ветку release
if: github.event_name != 'pull_request'
run: |
set -eu
token="${RELEASE_TOKEN:-$AUTO_TOKEN}"
[ -n "$token" ] || { echo "нет токена для push" >&2; exit 1; }
host="${GITEA_URL#*://}"
scheme="${GITEA_URL%%://*}"
cp README.md LICENSE dist/
cd dist
git init -q -b release
git config user.name "gitea-actions"
git config user.email "gitea-actions@noreply.local"
git add .
git commit -q -m "build $(date -u '+%Y-%m-%d %H:%M UTC') ($GITHUB_SHA)"
git push -qf "$scheme://gitea-actions:$token@$host/$GITEA_REPO.git" release
cd ..
rm -f dist/README.md dist/LICENSE
echo "ветка release обновлена"
- name: Релиз в Gitea
if: github.event_name != 'pull_request'
run: |
set -eu
export GITEA_TOKEN="${RELEASE_TOKEN:-$AUTO_TOKEN}"
# Собираем архив вне dist/, иначе tar пакует сам себя на лету.
tar -czf /tmp/discord-geodata-all.tar.gz -C dist .
mv /tmp/discord-geodata-all.tar.gz dist/
sh tools/gitea-release.sh "${{ steps.notes.outputs.tag }}" /tmp/notes.md \
dist/discord-geosite.dat \
dist/discord-geosite.dat.sha256sum \
dist/discord-geoip.dat \
dist/discord-geoip.dat.sha256sum \
dist/xray/routing.json \
dist/sing-box/site/discord.srs \
dist/sing-box/ip/discord.srs \
dist/metadata.json \
dist/SHA256SUMS \
dist/discord-geodata-all.tar.gz