Repo was renamed podkop-evolution -> netshift. Update every code/docs
reference to the new repo so update checks, package downloads and the
default-config fetch hit netshift directly (podkop-evolution stays only
as a one-time compatibility bridge):
- install.sh: REPO + rate-limit check + raw config URL + banners
- bin/netshift: update-check API + issue URL
- README badges/star-history/config URL; ISSUE_TEMPLATE links
- realign install.sh banner frames after the shorter URL
- add docs/icon.png
Many providers don't honor the sing-box User-Agent (or run legacy panels)
and return a base64-encoded or plaintext list of proxy URIs instead of a
sing-box JSON config. Previously such subscriptions failed validation (rc 13).
Add normalize_subscription_to_singbox() (helpers.sh): when the downloaded
body is not valid sing-box JSON, try to recover it:
- detect & base64-decode a wrapped body (conservative: raw has no '://',
decoded does; self-pads to len%4 for older coreutils-base64)
- split into lines, skip blank and #comment/metadata lines
- keep known schemes (vless/trojan/ss/hysteria2/hy2/socks4/4a/5)
- build each URI into an outbound by reusing sing_box_cf_add_proxy_outbound,
isolated in a subshell so one bad/unknown key can't abort the run
- emit {"outbounds":[...]} so the existing validate + merge path is reused
Hook in download_subscription_into_cache (bin/netshift): on validation
failure, attempt the fallback before returning rc 13; covers both the
update and startup download paths.
Smoke tests (tests/entrypoint.sh): extend test_subscription with cases for
plaintext+metadata, base64-wrapped, mixed-with-garbage (one bad key must not
abort), and junk-only (must fall through). Verified in the OpenWrt rootfs
container against real provider samples (base64 -> 10 outbounds; plaintext
-> 43 vless), all validate OK. Suite: 44 passed / 0 failed.
Port the source-level smoke-test harness from the padkap fork, adapted to
NetShift. Runs the shell/jq/config-generation logic against a real OpenWrt
24.10.6 userland (sing-box, jq, nft) before flashing to a router.
- tests/Dockerfile: alpine downloads official OpenWrt rootfs tarball ->
FROM scratch; opkg installs sing-box curl jq coreutils-base64 bind-dig nftables
- tests/docker-compose.yml: service netshift-test, bind-mounts netshift/files,
NET_ADMIN/NET_RAW/SYS_ADMIN caps, host network
- tests/entrypoint.sh: 10 test groups (deps, syntax, config, helpers, jq,
config-manager, sing-box check, nft, diagnostics, subscription); adapted to
our config options (dns_type/connection_type/proxy_config_type) and helper
API (url_is_ipv6_literal); updater.sh added to syntax checks
- .github/workflows/openwrt-smoke-tests.yml: standalone CI on push/PR
- build.yml: smoke-tests job gates the release build (needs: smoke-tests)
- .dockerignore + .gitignore (tests/test-results/)
Verified locally: docker compose run netshift-test all -> 44 passed / 0 failed.
Layout top-to-bottom: title + badges, divider, screenshot, description,
divider, then sections: Функции, Вещи перед установкой, Установка NetShift,
Project Structure, Build Artifacts, Star History, Credits.
При холодном старте подкоп вызывает prepare_subscription_caches_for_startup
до запуска sing-box. Если включена опция download_lists_via_proxy=1,
функция пытается скачать подписку через прокси 127.0.0.1:4534
(SB_SERVICE_MIXED_INBOUND), который ещё не существует, потому что sing-box
запускается только после prepare_subscription_caches_for_startup.
В результате wait_for_subscription_connectivity безуспешно делает 12 попыток
по 5 секунд (60 секунд ожидания), после чего стартует retry worker, который
крутится в цикле каждые 10 секунд и тоже использует прокси — но sing-box
никогда не запустится без подписки. Получается deadlock: подписка нужна для
старта sing-box, а sing-box нужен для скачивания подписки через прокси.
Ситуация усугубляется тем, что /tmp/sing-box/subscriptions лежит в tmpfs,
поэтому на холодном старте кэш всегда пустой и cache_needs_refresh=1.
На этапе bootstrap скачиваем подписку напрямую, игнорируя
download_lists_via_proxy. После старта sing-box subscription_update и
list_update продолжают использовать прокси согласно настройке.