Commit Graph
100 Commits
Author SHA1 Message Date
Dushnilin fd629555c7 feat(dns): recommend multiple diverse servers for primary DoH, bootstrap, and fallback DNS 2026-09-02 18:38:30 +03:00
Dushnilin 5f4bfc7caf fix(dns): optimize benchmark probing with host caching and strict timeouts 2026-09-02 18:19:37 +03:00
Dushnilin de691c61ab feat(dns): prioritize encrypted DoH as primary DNS with verified UDP bootstrap resolution 2026-09-02 17:29:51 +03:00
Dushnilin 4a46d78e60 fix(singbox): restore strict unusable outbounds rejection while preserving country filter fallback 2026-09-02 16:52:00 +03:00
Dushnilin e8932d3f2d fix(core): prevent ruleset download_detour crash, resilient selector fallback, and clean legacy migration 2026-09-02 16:49:10 +03:00
Dushnilin ad960a173a fix(dns): two-phase cascade benchmark with verified bootstrap DNS resolution 2026-09-02 16:25:58 +03:00
Dushnilin d72e381018 fix(dns): probe DoH with RFC 8484 wireformat and direct IP resolution 2026-09-02 16:18:31 +03:00
Dushnilin 2e094a5c15 fix(tests): update applied_migrations expected fixtures in config_migration.sh 2026-09-02 15:58:07 +03:00
Dushnilin fa38422260 fix(tests): supply isolated cache dirs in config_migration dns tests 2026-09-02 15:54:30 +03:00
Dushnilin de61450e91 ci: include luci-app-tachyon and locales in backend-ci triggers 2026-09-02 15:45:03 +03:00
Dushnilin 8e1d613959 chore(locales): synchronize source and packaged Russian PO catalogs 2026-09-02 15:42:08 +03:00
Dushnilin f3b1feba31 chore(release): bump version to 1.3.14 2026-09-02 15:38:47 +03:00
Dushnilin 6b92b12135 fix(singbox): supply default /dns-query path and SNI for DoH/DoT IP addresses
- When users configure plain IP addresses (e.g. 8.8.8.8, 1.1.1.1, 77.88.8.8) with DoH (https in sing-box), sing-box requires a non-empty HTTP path (defaulting to /dns-query) and proper TLS SNI (server_name) matching the provider certificate.
- Added default /dns-query path fallback in server_from_options().
- Added KNOWN_DNS_SERVER_NAMES mapping for Google, Cloudflare, Yandex, Quad9, AdGuard, Mullvad, Control D, AliDNS, OpenDNS, DNS.SB to provide correct TLS SNI when connecting to raw DNS IPs.
- Added comprehensive DoH/DoT unit tests in tests/dns_failover.sh.
2026-09-02 15:34:27 +03:00
Dushnilin 84658d84dc feat(dns): add intelligent DNS Benchmark, Auto-Tuner & clean defaults
- Implement ucode module dns/benchmark.uc:
  * Probes major DNS servers (Yandex, Cloudflare, Google, Quad9, AdGuard, Comss, Mullvad, Control D) across UDP 53 and DoH.
  * Measures latency, packet loss, and detects ISP DNS poisoning/hijacking.
  * Intelligent recommendation engine computes optimal primary DNS (prefers encrypted DoH if fast), bootstrap plain UDP, fallback plain UDP from independent provider, and upstream mode (sequential/parallel).
  * Supports CLI execution (tachyon dns_benchmark, tachyon dns_autotune --apply) and background async worker mode for LuCI UI.
- LuCI Web UI modal (renderDnsBenchmarkModal.ts):
  * Real-time progress bar, responsive results table with latency rating badges.
  * Recommendation summary card with one-click 'Apply Recommended DNS' action.
  * Added '⚡ Auto-Tune & Benchmark DNS' button in Settings -> DNS tab.
- Added comprehensive unit tests in vitest (538/538 passed) and tests/dns_benchmark.sh.
- Added Russian PO translations.
2026-09-02 15:28:23 +03:00
Dushnilin a2eaf25c90 fix(runtime): auto-detect listen IP via ubus interface dump on x86/64
On x86/64 the LAN interface is often named 'eth1', 'enp2s0', etc.
in UCI - not 'lan' or 'br-lan'. The two-tier probe both fail,
producing 'Failed to determine the listening IP address' on startup.

Add a third-tier fallback using ubus network.interface dump:
- is_private_ipv4(): RFC-1918 address check
- network_interface_dump_first_private_ipv4(): iterates all UCI
  interfaces, skips wan/wan6/loopback, returns first private IPv4

Workaround (no update needed):
  uci set tachyon.settings.service_listen_address='192.168.x.x'
  uci commit tachyon && /etc/init.d/tachyon restart
2026-09-02 15:16:15 +03:00
Dushnilin 557058c772 fix(ui): reject DoH URLs and path-style DoH in dns_fallback_server field
Mirror the backend validator.uc checks in the LuCI settings UI.
Previously the validate function used validateDNS() which accepts
DoH path format (e.g. 'cloudflare-dns.com/dns-query'); now we gate
before that call with explicit checks matching the backend:
  - '://' → DoH/DoT URL (e.g. 'https://cloudflare-dns.com/...')
  - '/'   → path-style DoH (e.g. 'cloudflare-dns.com/dns-query')

Also add Russian translations for both new error messages.
2026-09-02 14:20:33 +03:00
Dushnilin 6b8e143d0b fix(validator): also reject path-style DoH in dns_fallback_server
A value like 'cloudflare-dns.com/dns-query' is the sing-box DoH
path format. The previous check only caught 'https://' URLs.
dns_server_value_valid() accepts it because it validates only the
host part — so 'cloudflare-dns.com' passed as a valid hostname.

Fallback servers are plain UDP only; any '/' in the value is the
DoH path separator and must be rejected.

Changes:
- validator.uc: add index(value, '/') >= 0 check before the
  dns_server_value_valid call
- config_validator_runtime.sh: add regression test for path-style
  DoH ('cloudflare-dns.com/dns-query') in dns_fallback_server
2026-09-02 14:18:32 +03:00
Dushnilin 6e45ecab6a fix(validator): validate dns_fallback_server entries, reject URL schemes
Fallback servers must be plain UDP (IP or IP:port). Before this fix a
user could accidentally enter a DoH URL (https://...) as a fallback
server — the validator passed it through, and it silently failed at
runtime because dns_failover treats all fallback entries as UDP.

Changes:
- validator.uc: after main/bootstrap validation, check all
  dns_fallback_server values: reject entries containing '://'
  (protocol URL scheme) with a clear error, then validate each
  with dns_server_value_valid for format correctness
- config_validator_runtime.sh: assert_rejects for a DoH URL in
  dns_fallback_server
2026-09-02 14:09:28 +03:00
Dushnilin b82a151c87 fix: inject DNS defaults on forkop->tachyon migration, add LuCI E2E check
Fixes Tachyon startup abort when migrated config has no dns_server or
bootstrap_dns_server (forkop/podkop did not require these fields).

Changes:
- migration.uc: add ensure_dns_server_defaults migration that injects
  77.88.8.8 as fallback when either dns list is empty
- default config: pre-mark orphan_section_interface_cleanup and
  ensure_dns_server_defaults as applied so fresh installs skip them
- tests/config_migration.sh: regression tests for both missing-DNS cases
  and stable named-marker assertion for the new migration
- tests/package_contract.sh: enforce that default config ships both new
  migration IDs in applied_migrations
- tests/docker_e2e_test.sh: also install luci-app-tachyon and verify
  menu.d + acl.d files are present after install (LuCI menu bug class)
2026-09-02 14:00:54 +03:00
Dushnilin 0f0eb90cdc fix: add missing command_status_from_args alias in singbox/runtime.uc
Fixes crash-loop introduced in 1.3.13. The function was called on lines
494-495 (stop/disable conflicting forkop/podkop services at startup) but
the alias was never declared, unlike command_success_from_args on line 48.
This caused a 'Type error: left-hand side is not a function' crash in
configure_service(), sending Tachyon into a 75-second crash loop.

Fixes #44
2026-09-02 13:20:19 +03:00
Dushnilin 60cd0d155a fix(subscription): eliminate forward-reference crashes in runtime_cache_missing
Remove helper functions section_cache_has_outbounds and section_has_source_cache
which called source_json_path (line 513), subscription_cache_is_usable (line 507)
and object_key_count (line 1115) — all defined AFTER their call site at line 334.
ucode has no function hoisting, so these caused 'left-hand side is not a function'.

Inline the logic directly in runtime_cache_missing using only primitives
available before line 334:
- read_json (line 59)
- object_or_empty, fs.stat, source_id (line 251)
- subscription_source_count (line 300)
- TMP_SUBSCRIPTION_FOLDER (line 30)
- for-in loop to check empty object (no object_key_count needed)
2026-09-02 08:09:37 +03:00
Dushnilin b03f4d8d5c fix(subscription): fix read_json_file -> read_json in section_cache_has_outbounds
Incorrect function name caused a Type error crash in CI:
  'left-hand side is not a function'

Also replace for-in early-return trick with object_key_count() which
already exists in this file (see line 1145).
2026-09-02 08:05:19 +03:00
Dushnilin 6dc6ed9486 fix(subscription): rebuild sing-box when section-cache empty but source data available
When a new subscription section is added that shares the same URL as an
existing section, the following race occurs:

1. Sing-box config is generated — source cache doesn't exist yet for the
   new section, so it is added with zero outbounds; section-cache file is
   written (empty outboundMetadata.names).
2. Background subscription download runs — content is identical to the
   existing section, so result is 'unchanged' (return 2), updated=0.
3. reload-service is triggered — runtime_cache_missing returns false
   because the section-cache file exists, so sing-box is NOT reloaded.
4. Servers from the new section never appear in the UI.

Fix: runtime_cache_missing now also returns true when a section-cache file
exists but contains no outbounds (names={}) while the source subscription
cache is already available in TMP_SUBSCRIPTION_FOLDER. This causes the
next reload cycle to regenerate sing-box config with the actual servers.
2026-09-02 08:01:38 +03:00
Dushnilin cc4ad5855a feat(dns): expand DNS server presets for UDP, DoH, DoT, DoQ and Bootstrap 2026-09-01 22:00:04 +03:00
Dushnilin 8b3c3e365f feat(dns): upstream mode (sequential/parallel) & explicit fallback DNS servers, bump 1.3.13 2026-09-01 20:49:21 +03:00
Dushnilin f55f37f804 fix(ui): добавить load() в EntryPoint — UCI не загружался до render(), tab_order всегда был default 2026-09-01 19:36:44 +03:00
Dushnilin 79a4d477db fix(ui): заменить input[checkbox] на button-тогл в виджете порядка вкладок (починить кликабельность) 2026-09-01 19:21:06 +03:00
Dushnilin 416a15a805 fix(parental): не устанавливать cron-джоб если нет расписаний с квотами (убирает cron.err spam) 2026-09-01 18:50:13 +03:00
Dushnilin 324569aa53 fix(ui): исправить чекбоксы виджета порядка вкладок (DummyValue→renderWidget) 2026-09-01 18:46:07 +03:00
Dushnilin a610c8fe2a chore: bump version to 1.3.12 2026-09-01 18:34:16 +03:00
Dushnilin 943ee1321d feat(ui): добавить управление порядком, видимостью и стартовой вкладкой меню в Настройки 2026-09-01 18:31:31 +03:00
Dushnilin 2d551bd2d1 fix(service): исправить fs.stat в lifecycle.uc, очистку кэша LuCI и перезапуск rpcd/uhttpd 2026-09-01 15:15:42 +03:00
Dushnilin a1563708c1 fix(ci): устранить падение тестов в CI runner (права доступа /usr/bin и исключение ucode_syntax_lint из xargs) 2026-09-01 12:48:37 +03:00
Dushnilin c33018fe8f fix(service): подавлять конкурирующие службы Forkop/Podkop из ROM при старте 2026-09-01 12:44:29 +03:00
Dushnilin c432220af5 feat(updates): добавить локальный бэкап и оффлайн-откат компонентов 2026-09-01 12:24:58 +03:00
Dushnilin 23aae78147 feat(migration): 1.3.11 - CLI команда import_settings и исправление миграции Forkop
- Добавлена CLI команда tachyon import_settings [файл] (и алиас import-settings) для автоматического сканирования и бесшовного импорта настроек из Forkop, Forkop Plus, Podkop, Netshift или резервных копий
- Поддержка in-place миграции прямо из активного /etc/config/tachyon при обнаружении унаследованных правил config rule
- Расширенное сканирование резервных копий и файлов конфигураций в /etc/config/, /root/ и /tmp/
- Исправлен режим миграции Forkop в Makefile, build.sh и install.sh (использование migrate-podkop для корректного преобразования старых секций config rule в config section)
- Добавлен интеграционный тест tests/import_settings.sh (119 тестов бэкенда пройдены)
2026-09-01 12:15:00 +03:00
Dushnilin 84cdf9d2f8 feat(updates): 1.3.10 - Автоматическое обновление компонентов (сразу при выходе и по расписанию)
- Backend: реализован запуск автоматического обновления компонентов в фоновом режиме (trigger_component_auto_update, component_auto_update_apply)
- Cron: поддержка окна обслуживания (schedule mode) с регистрацией задачи в crontab по заданному времени (HH:MM)
- UCI: новые опции component_auto_update_enabled, component_auto_update_mode, component_auto_update_time, component_auto_update_targets
- Web UI: элементы управления во вкладке настроек «Обновления» (чекбокс включения, переключатель режима, время и мультиселект разрешенных компонентов)
- Тесты: добавлен интеграционный тест component_auto_update.sh, обновлены cron-тесты updates_due.sh, все 118 тестов успешно пройдены в Docker
- Локализация: добавлены русские переводы для всех новых элементов интерфейса
2026-09-01 10:58:46 +03:00
Dushnilin f553352e25 release: 1.3.9 - Оптимизация кодовой базы (Ponytail Audit), очистка мертвого кода и ускорение Telegram-бота
- Frontend: полное удаление неиспользуемого renderAdvancedSettings.ts (-1354 строки), очистка каталогов локализации от фантомных ключей, инкапсуляция внутренних экспортов в runtimeTags и singBoxVariant
- Backend: удаление неиспользуемого singbox/dns_prefetch.uc (-243 строки) и его вызова в lifecycle.uc, удаление устаревших заглушек Forkop-эпохи в service_check.uc
- Telegram Bot: внедрение in-memory кэша cb_map_cache для устранения избыточных дисковых чтений/записей JSON при рендеринге inline-кнопок
- Версия: bump версии до 1.3.9 в package.json и package-lock.json
2026-09-01 08:48:29 +03:00
Dushnilin 706b35328c feat(ruleset): auto-assign download_detour for remote rulesets & add watchdog self-heal for ruleset DNS stalls 2026-08-30 21:22:23 +03:00
Dushnilin dfb32722bf release: 1.3.8 - OpenWrt network interfaces for WARP generator, DoH doctor URL normalization & doctor lock fixes 2026-08-30 20:05:16 +03:00
Dushnilin b3e0a73b2d fix(fuzzer): ensure direct outbound routing and robust zapret2 strategy testing 2026-08-30 10:21:52 +03:00
Dushnilin 123f5e6ccf release: 1.3.7 - Telegram alert deduplication & rate limiting, DNS detour deadlock resolution, ucode fs.opendir fixes 2026-08-29 21:28:41 +03:00
Dushnilin 939d9418a2 release: 1.3.6 - Family Profiles, Mobile Responsiveness, Mixed Proxy fix & AI Doctor Fuzzer improvements 2026-08-29 13:57:17 +03:00
Dushnilin 6248452e79 chore(i18n): sync fe-app-tachyon locales with luci-app-tachyon catalogs 2026-08-29 08:34:58 +03:00
Dushnilin 3c04a62479 feat(server): support per-user LAN isolation with auth_user and custom route rules 2026-08-29 08:33:24 +03:00
Dushnilin 3960660b9d fix(components): move core_url_module_or_null to top and fix same-release update lifecycle 2026-08-29 08:16:33 +03:00
Dushnilin 5543a6ce46 fix(updates): prevent premature self-update completion while background job is running 2026-08-29 07:55:36 +03:00
Dushnilin 404db8ae05 fix(dpi-bypass): TCP keepalive + conntrack tuning + nfqueue watchdog
- sysctl: tcp_keepalive=60/10/3, conntrack_established=600s (was 5 days)
- nfqws/nfqws2: --ctrack-timeouts=30,30,600,30,30,30,30,30 in default strategy
- watchdog: probe_nfqueue checks nfqws/nfqws2 process health every 300s
- watchdog: heal_nfqueue_stopped restarts tachyon if DPI bypass processes die

This eliminates the need for manual DNS flush + device restart when DPI
starts blocking connections. Dead connections are now detected in ~90s
instead of hours, and stale conntrack entries expire in 10 min instead
of 5 days.
2026-08-28 20:05:03 +03:00
Dushnilin 16ffc9f91f fix(diagnostics): check both ca-certificates.crt and ca-bundle.crt paths
On apk-based OpenWrt the file is ca-certificates.crt, not ca-bundle.crt.
Check both paths so the CA bundle detection works on opkg and apk.
2026-08-28 10:41:32 +03:00
Dushnilin 56b16b0884 feat(components): add jsdelivr.net CDN mirror for component updates 2026-08-27 19:49:41 +03:00
Dushnilin 4e57a9649d feat(installer): add jsdelivr.net CDN as primary mirror for download failover 2026-08-27 19:28:59 +03:00
Dushnilin 1a314147a9 fix(ai-doctor): debug-логирование LLM запросов + HTTP-Referer для OpenRouter
- Добавлен warn-вывод при ошибках LLM: статус curl,/raw response, отсутствие
  choices — теперь видно в logread почему AI Chat молча отдаёт оффлайн
- Добавлены HTTP-Referer и X-Title заголовки для OpenRouter (рекомендация
  провайдера для rate-limit ranking)
2026-08-27 17:54:27 +03:00
Dushnilin 1f2b7edd0d fix(fuzzer): убрал лишний ре-рендер таблицы результатов durante benchmark
renderResults() вызывался каждые 600мс и пересоздавал всю таблицу — при
100+ стратегиях UI зависал. Теперь таблица перерисовывается только при
появлении нового результата или завершении бенчмарка.
2026-08-27 16:24:56 +03:00
Dushnilin cd85e3e169 fix(i18n): синхронизация po/pot файлов 2026-08-27 15:35:17 +03:00
Dushnilin b620886b9e feat(fuzzer): DPI-детектор, авто-применение, история результатов
- DPI-детектор: перед бенчмарком определяет тип блокировки (RST/throttle/DNS)
  и переранжирует стратегии по приоритету (badseq для RST, multisplit для
  throttle, fake+TTL для DNS-блока)
- Авто-применение: галочка «Auto-apply best strategy» автоматически
  применяет выигравшую стратегию после завершения бенчмарка
- История: результаты бенчмарков сохраняются в /etc/tachyon/fuzzer_history.json
  (до 50 записей) — новая вкладка «History» в UI
- CLI: новые команды detect_dpi, auto_apply, history, clear_history
- Локализация: русские и английские строки для всех новых элементов UI
- Frontend: вкладка History, баннер DPI-детекции, чекбокс авто-применения
2026-08-27 15:29:07 +03:00
Dushnilin 4f486d368c fix(fuzzer): мгновенная индикация прогресса в UI, fast-fail для нерабочих зондов и оптимизация комбинаторики 2026-08-27 14:01:21 +03:00
Dushnilin b000d3024a feat(fuzzer): мульти-таргетные сьюты, конструктор паттернов, кастомные стратегии и минималистичный UI 2026-08-27 13:54:51 +03:00
Dushnilin 7ddb7f6d4d fix(warp): timeout при прокси, четная длина hex для awg_i1..i5, исправление выбора секции прокси (#43) 2026-08-27 13:06:31 +03:00
Dushnilin 627941c2df fix(fuzzer): изолированный sandbox probe, fwmark 0x40000000, priority -200 и расчет score 2026-08-27 07:57:58 +03:00
Dushnilin c58b7339f7 feat(fuzzer): алгоритмический комбинаторный генератор DPI-стратегий и выбор режима в LuCI 2026-08-27 07:50:21 +03:00
Dushnilin 3ead229264 feat(fuzzer): add RAG knowledge base and LLM-powered strategy synthesizer 2026-08-26 22:50:12 +03:00
Dushnilin dc6f0547b4 feat(fuzzer): автоматический подбор и тюнинг DPI-стратегий для Zapret v2, Zapret v1 и ByeDPI 2026-08-26 22:18:35 +03:00
Dushnilin 6a85384111 fix(tailscale): готовность демона, флаги tailscaled и нейтрализация standalone-сервиса
- удалён неподдерживаемый флаг --netfilter-mode=on, вызывавший ошибку старта на OpenWrt
- проба готовности парсит status --json (BackendState), а не exit-code: незалогиненный узел отвечает NeedsLogin с non-zero и раньше считался неготовым — корень «did not become ready» при первом входе
- перед стартом своего tailscaled останавливается и отключается init-скрипт официального пакета: его демон держал UDP 41641, из-за чего наш умирал мгновенно
- при сбое старта в лог пишется хвост tailscaled.log для диагностики
2026-08-26 19:03:16 +03:00
Dushnilin 9bc6fe28fd feat(failover): автоматическое переключение прокси-секций
- генератор: при section_failover_enabled catch-all указывает на селектор tachyon-failover со всеми секциями-кандидатами
- выбор по умолчанию переживает рестарты через /etc/tachyon/failover_section, устаревшее имя откатывается на первую секцию
- service/failover.uc: пробер каждой секции через Clash API delay, порог N подряд — переключение на живую, TG-уведомление
- watchdog: вызов проверки в slow-тире (модуль сам отключён при выключенной опции)
- настройки: флаг + порог во вкладке Advanced, RU-локализация
- тесты: генерация вкл/выкл/персист/устаревшее состояние + логика переключения со стабом Clash API
2026-08-26 14:15:23 +03:00
Dushnilin b12f2fbdee feat(parental): дневные квоты времени на устройства
- опция daily_quota_minutes у расписаний: лимит минут использования сети в сутки
- поминутный cron-тик: учёт активности через ip neigh, блок до полуночи при исчерпании
- nft-сеты tachyon_quota_block (MAC) и _ip (IPv4) с drop-правилами; самовосстановление после ребилдов таблицы
- сброс счётчиков после смены суток, TG-уведомление о блокировке
- validator: проверка диапазона 0..1440
- UI: поле и колонка квоты в Родительском контроле + RU-локализация
2026-08-26 14:00:40 +03:00
Dushnilin 2c6a1a7343 feat(tailscale): watchdog нативного режима и панель пиров тейлнета
- watchdog: проверка native-секции в slow-тире, авторестарт демона после 2 сбоев, TG-алерт раз в час
- AI Doctor: узел Tailscale (готов/не установлен/упал) только при настроенном native
- провайдер: режим peers — парсинг status --json через per-section socket
- RPC get_tailscale_status/get_tailscale_peers/tailscale_restart
- дашборд: карточка Tailscale (состояние, IP роутера, до 5 пиров с онлайн-статусом)
2026-08-26 13:43:23 +03:00
Dushnilin 7f0b085a07 feat(tailscale): нативный режим tailscaled и компонент Tailscale
- новый компонент Tailscale: установка пакета из фида, карточка в Обновлениях
- режим native для секций Tailscale: системный tailscaled вместо sing-box endpoint
- доступ к тейлнету с LAN без клиента: маршруты 100.64.0.0/10 и MagicDNS через dnsmasq
- masquerade и exit-node (opt-in) с обходом tproxy по зарезервированной маске
- validator: требование tailscaled и auth-key для native; bypass-правила в mangle
- тесты: генерация конфига, смоук рантайма с моками, обновлена сигнатура состояния
2026-08-26 11:30:52 +03:00
Dushnilin 3532fa4280 fix(ui): уточнить подсказки news и anime в community-списках
- news: gordonua.com → pravda.com.ua (gordonua.com нет в списке)
- anime: crunchyroll.com, anilist.co → bato.to, mangadex.org (crunchyroll в geoblock, anilist.co нет в списке)
2026-08-25 21:58:40 +03:00
Dushnilin 75147a1d4c fix(ui): исправить описания community-списков в section.js
- russia_inside/russia_outside: описания были перепутаны местами
- hodca: исправлено описание (домены на CDN, а не HDRezka)
2026-08-25 21:56:19 +03:00
Dushnilin 2fd435a509 docs: добавить ссылку на Telegram-канал в README 2026-08-25 21:40:51 +03:00
Dushnilin 77e8cdaad0 feat: AI доктор — 5 багфиксов + Telegram кнопка
Багфиксы:
- rag.uc: исправлен порядок аргументов push() — RAG-эмбеддинги снова строятся
- fix_uci_config: правильный путь бэкапа (/etc/backup/tachyon_config + fallback)
- apply_quick_fix: честные exit codes для всех 23 фиксов
- doctor(): прокидывается busy flag в JSON-вывод
- diagnose_json: строится из checks[] вместо парсинга эмодзи из текста

UI:
- Кнопка Telegram канал (https://t.me/tachyon_proxy) на странице диагностики

Тесты:
- 5 новых проверок в ai_doctor_local.sh (uci_backup, exit codes, busy, structured checks, rag push)
2026-08-25 21:37:15 +03:00
Dushnilin 2cec97371c fix(tests): update warp preflight for dynamic mixed-port check 2026-08-25 15:50:17 +03:00
Dushnilin e1247dad58 fix(install): ash-compatible install commands
- Replace sh <(wget -O - ...) with wget -O /tmp/... && sh /tmp/...
- BusyBox ash on OpenWrt does not support process substitution <(...)
2026-08-25 15:46:16 +03:00
Dushnilin c3172dd82d refactor: remove hardcoded port 4534 — dynamic port from config
- Added get_mixed_port() to core/common.uc (reads sing-box config.json)
- agent_api.uc, api.uc: use common.get_mixed_port()
- warp_generator.uc: use config port instead of hardcoded fallback
- watchdog.uc: use mixed_ports_from_config() for Clash API calls
- runtime.uc: dynamic port in conflict checks and proxy probe
2026-08-25 15:46:03 +03:00
Dushnilin 1aa69d5e03 feat(telegram): full i18n — ~230 Russian strings replaced with t() calls
- Added ~120 new i18n keys to both en and ru sections
- Replaced all remaining hardcoded Russian strings in telegram.uc
- Menu, status, connections, diagnostics, help, subscriptions, quiet hours,
  test rule, export, MAC blocking, devices, watchdog/AI, QoS, section
  management, DNS presets, exec, daily digest, update notifications,
  blocked activity, all navigation buttons
- All keys verified present via runtime check
2026-08-25 15:45:44 +03:00
Dushnilin 2c21dd93c7 fix(telegram): move get_mixed_port() before mixed_port_alive()
ucode doesn't hoist functions — get_mixed_port() must be defined
before mixed_port_alive() which calls it.
2026-08-24 21:09:27 +03:00
Dushnilin 9a3bb6054b fix(telegram): read actual mixed-port from sing-box config
- Add get_mixed_port() helper that reads config.json
- Replace all hardcoded 4534 in telegram.uc with get_mixed_port()
- mixed_port_alive() now checks the real port
- diagnose() checks the real port for proxy health
- Clash API calls use the real port
- Route failure alerts use i18n, remove hardcoded port number
- Fixes LuCI 'Check connection to Telegram' showing false negatives
  when sing-box uses a non-default mixed-port (e.g. 1602)
2026-08-24 21:06:17 +03:00
Dushnilin bd3db0789e fix(tests): update doctor test for i18n format strings 2026-08-24 20:59:56 +03:00
Dushnilin b3a425e524 feat(telegram): i18n en/ru + /lang command
- Create i18n.uc with en/ru string tables (~125 keys)
- Add t() binding at module level, set per-session via UCI telegram.language
- Replace hardcoded Russian in main menu, status, speed, ping, test,
  connections, outbounds, sections, diagnostics, restart, language
- Add /lang command: language picker with UCI persistence
- Setting schema labels translated at display time via get_schema_label()
- 175 t() calls, 245 remaining raw Russian (deep UI only)
2026-08-24 20:36:30 +03:00
Dushnilin 9d0761c14e fix(warp): расширен диапазон IP для генерации WARP
- Добавлены недостающие /24 диапазоны: 162.159.195.0/24, 162.159.204.0/24,
  188.114.96.0/24, 188.114.97.0/24 (ранее было только 4 из 8 диапазонов)
- probe_ips: 9 → 17 IP (по два представителя на каждый /24 + 8.6.112.0)
- resolve_ips/resolve_ips_probe для API-запросов: 4 → 8 IP
- engage.cloudflareclient.com DNS-резолв продолжает добавлять
  динамические IP в пул автоматически
2026-08-24 17:02:39 +03:00
Dushnilin 8f99c40f2e fix(frontend): dashboard status и latency для сервисных секций
- getZapret2Status/getZapretStatus/getSingBoxStatus/getByedpiStatus:
  добавлен allowNonZeroWithStdout — rpcd может вернуть не-zero exit при
  наличии полезного stdout (модуль-провайдер не найден, но JSON-статус
  всё равно отдан). Без этого флага callBaseMethod считал вызов
  провалившимся → serviceStatus undefined → «Неизвестный»
- customProxyLatencies: сервисные секции (SING_BOX, ZAPRET, ZAPRET2,
  BYEDPI) больше не перезаписываются кешем latency-теста. Ранее
  неудачный тест кешировал -1, перезаписывая latency:0 из секции →
  отображалось «-1ms» вместо статуса
2026-08-24 14:39:33 +03:00
Dushnilin 4b80deed5f fix(service): экспорт write_text_file и mixed-вотч по конфигу
- helpers.uc определяла write_text_file, но не экспортировала:
  биндинг в telegram-воркере был null, write_heartbeat падал каждый
  цикл — «Worker loop error: left-hand side is not a function»
  каждые ~25с, heartbeat не писался, воркер зря перезапускался
- watchdog: mixed-порт теперь читается из сгенерённого конфига
  sing-box (inbounds[].type=mixed), а не хардкод 4534. Когда mixed
  выключен (Main.mixed_proxy_enabled=0) — вотч встаёт в сторону
  вместо бесконечных «Restarting sing-box» с пересозданием nftables
  каждые ~5 минут
- проверено на живом роутере: 0 рестартов и 0 ошибок воркера после
  деплоя, E2E зелёный
2026-08-24 13:56:29 +03:00
Dushnilin 890450d619 fix(singbox): mtproxy-инбаунд вместо shadowsocks — корень «decode psk» (#23)
В ветке mtproto тип инбаунда перезатирался на shadowsocks-2022,
и ee-секрет уходил в password: SS2022 парсит его как base64-PSK —
отсюда FATAL «decode psk: illegal base64 data at input byte 60»
у всех, у кого включён MTProto-сервер.

- mtproto снова эмитит type=mtproxy (как в схеме extended 2.6.x),
  юзер — {name, secret}, а не password (регрессия fe0099a3)
- конфиг с MTProto и AWG проверен `sing-box check` на реальном
  sing-box-extended 1.13.18-extended-2.6.5 — PASS
- warp-генератор: прямой префлайт-зонд с --resolve (DNS-отравление
  больше не даёт ложное «API недоступен»), живость 4534 — локальная
  по /proc/net/tcp вместо сквозного запроса через туннель
- багрепорт маскирует ai_doctor_api_key и tuic_password
2026-08-24 11:26:28 +03:00
Dushnilin 6bbc37cfdd fix(service): необъявленные вызовы в watchdog и telegram-воркере
- watchdog: command_output_from_args использовался без биндинга —
  «Graceful degradation: check_mixed_proxy_port failed: left-hand
  side is not a function» на каждом проходе
- telegram: write_heartbeat звал write_text_file из core.helpers,
  который не импортировался — «Worker loop error» каждый цикл,
  heartbeat не писалcя, воркер зря перезапускался по стейлу
- E2E: ноль «left-hand side» в логах OpenWrt-контейнера после фикса
2026-08-24 10:53:06 +03:00
Dushnilin 7da51a62d7 fix(singbox): AWG-джанк в тегах и MTProto-секрет
- i1–i5/j1–j3: классический hex конвертируется в теги <b 0x…>;
  userspace WireGuard extended/lx молча игнорирует голый hex,
  handshake не завершался (#39 #41 #42)
- j1/j2/j3/itime эмитятся только для extended ≤2.6.0: в 2.6.1+
  поля удалены из схемы, sing-box падает на unknown fields
- j1–j3 больше не ломаются uci_bin_to_hex
- h1–h4: диапазоны «min-max» на обеих ветках
- MTProto: секрет канонизируется в ee-hex из любой формы
  (hex/base64), валидатор даёт понятную ошибку вместо
  FATAL decode psk (#23)
- XHTTP v2: парсинг и экспорт ключей подписок
- валидатор: warp/masque/openvpn/snell/mieru/sudoku — только extended
- тесты: stat→ucode (busybox), раннеры исключены из E2E,
  новые server_mtproto.sh и awg_variant_schema.sh
2026-08-24 10:13:33 +03:00
Dushnilin 5031eda81b docs(kb): верификация AmneziaWG в sing-box-extended
Факты расследования issues #39/#42: extended реализует amnezia целиком
(строгий парсер + I1-I5 obf-chain в wireguard-go форке), генерация
Tachyon совпадает со схемой; кейс kernel-vs-userspace остаётся внешним,
зафиксированы шаги диагностики.
2026-08-22 20:01:41 +03:00
Dushnilin 0241e1898f feat(doctor): честный вердикт по мёртвым WireGuard/AWG-туннелям
Жалобы вида «AWG не работает» (#39, #42) почти всегда означают: конфиг
секции применён корректно, но пир не отвечает (сервер лежит, провайдер
режет UDP до эндпоинта). Раньше доктор об этом молчал, и виноватым
выглядел Tachyon.

Новый чек 6c сканирует свежий лог ядра на ошибки outbound/wireguard
(operation timed out / handshake timeout): при свидетельствах мёртвого
туннеля - явный вердикт «проблема вне Tachyon» с подсказкой проверить
сервер и UDP-порт у провайдера. Если wireguard-трафика в логе нет вовсе,
чек не выдаёт ни одной строки - без шума для пользователей без AWG.
2026-08-22 10:46:36 +03:00
Dushnilin 44016ec95b fix(config): ложное предупреждение про AWG, сироты section_interface, дубль cron
- has_outbound_section: туннельные секции (awg/warp/vpn/openvpn/masque/
  anytls/snell/mieru/sudoku) теперь считаются исходящей конфигурацией -
  одинокая AWG-секция больше не получает вводящее в заблуждение
  «No proxy outbound sections found» (жалобы пользователей #39/#42)
- новая миграция orphan_section_interface_cleanup: удаление секции
  маршрутизации раньше оставляло после себя дочерние section_interface,
  ссылающиеся на несуществующего родителя (у пользователя - запись на
  секцию '2' при живой только '1'); сироты удаляются через новую операцию
  delete_section; функция объявлена до массива MIGRATIONS - ucode не
  поднимает объявления
- strip_unmarked_tachyon_cron_lines: старые версии писали cron-строки без
  маркер-комментария, они переживали фильтрацию по маркерам, копились при
  обновлениях и crond запускал component_updates_if_due дважды за тик;
  немаркированные строки с нашими командами теперь вычищаются при каждом
  refresh и remove
2026-08-22 10:08:05 +03:00
Dushnilin c788c86184 fix(warp): префлайт-проверка Cloudflare вместо XHR timeout
На сетях, где api.cloudflareclient.com недоступен (типичный случай из
issue #39), генератор сжигал весь временной бюджет - фронтенд умирал по
XHR timeout через 30 секунд, не показывая причины.

- префлайт: одна проба (4с connect / 6с max) напрямую и через доступный
  транспорт; если Cloudflare недостижим - мгновенная понятная ошибка
  «Cloudflare API недоступен...» вместо минутного зависания
- бюджеты ужаты до 20с на call_api и 20с на всю регистрацию, чтобы
  худший случай укладывался в XHR-таймаут LuCI
- все ошибочные выходы теперь печатают JSON-конверт и exit(0): фронтенд
  парсит data.message и показывает настоящую причину (ненулевой код
  заставлял его писать безликое Unknown error)
2026-08-22 10:07:40 +03:00
Dushnilin e446b370e3 feat(watchdog): самолечение порта 4534 и живости телеграм-воркера
- новая slow-проверка (раз в 5 мин): sing-box запущен, но mixed-порт 4534
  не слушается две проверки подряд (~10 мин) -> один авторестарт ядра;
  если не помогло - уведомление админу не чаще раза в час, без циклов
  бесконечных рестартов
- проверка живости телеграм-воркера: бот включён, pid жив, но heartbeat
  старше 15 минут (зависший long-poll/мёртвый цикл) или pid отсутствует ->
  рестарт воркера через telegram_start с ограничением раз в час
- обе проверки добавлены в perform_slow_checks через safe_call
2026-08-22 10:07:19 +03:00
Dushnilin 28c5c19645 feat(telegram): бот выживает при мёртвом прокси и сам сообщает о проблемах
Корень «бот молчит»: get_proxy_args считал достаточным условием маршрутизации
через 4534 сам факт запущенного sing-box - если mixed-inbound не поднялся,
каждый запрос вечно падал, воркер уходил в бесконечный backoff (468 ретраев
подряд у пользователя), хотя api.telegram.org был доступен напрямую.

- mixed_port_alive(): TCP-проба слушается ли 4534 (netstat) с кэшем 30с;
  маршрут через прокси выдаётся только при живом порте
- tg_request разделён на tg_request_via + fallback-цепочку: прокси не ответил -
  повтор без прокси; отключается UCI telegram.bot_direct_fallback='0'
- алерт админу однократно за эпизод после 5 неудачных опросов подряд с
  указанием причины; сбрасывается при восстановлении опроса
- heartbeat /var/run/tachyon_telegram.heartbeat после каждого успешного
  цикла - основа для вотчдог-проверки живости (следующий коммит)
- ротация tmpfs-лога воркера: потолок 512KB, хвост 100KB, проверка раз в час
- /doctor в боте рендерит человекочитаемую сводку (проблемы/исправлено/
  запланировано) вместо сырого JSON
- config_version убран из человекочитаемого вывода конфига (внутренний
  маркер миграций)
2026-08-22 10:06:56 +03:00
Dushnilin c910e6368e fix(build): po2lmo собирается через Makefile luci
Upstream openwrt/luci убрал template_lmo.c из дерева: po2lmo теперь
линкуется с src/lib/lmo и генерируемым lemon plural_formula, поэтому
прямой вызов gcc больше не работает - только in-tree Makefile.
Проверка наличия template_lmo.c заменена на сборку make po2lmo,
проверено на свежем клоне master.
2026-08-21 21:41:23 +03:00
Dushnilin f29db7d5a3 fix(build): каталог defaults перед install, устойчивый ensure_po2lmo
- build_backend_root ставил defaults/config до создания родительского
  каталога usr/lib/tachyon/defaults - релизная сборка падала на install
  (обнаружено сборкой тега 1.2.89)
- ensure_po2lmo: частичный клон feeds/luci приводил к компиляции без
  template_lmo.c; теперь при отсутствии исходников делается чистый
  переклон, а финальный fallback больше не печатает путь к несуществующему
  бинарнику - сразу явная ошибка
2026-08-21 21:34:08 +03:00
Dushnilin 4716792a95 ci(shellcheck): исключить usr/bin из проверки
tachyon/files/usr/bin/tachyon - ucode-скрипт с shebang #!/usr/bin/ucode,
shellcheck умеет проверять только sh/bash/dash/ksh и падает на нём.
Покрытие cgi-bin и hotplug остаётся.
2026-08-21 21:26:55 +03:00
Dushnilin 2b170c63eb ci(frontend): миграция workflow с yarn на npm
В репозитории лежит package-lock.json и нет yarn.lock, поэтому yarn-workflow
не кэшировал ничего (hashFiles по отсутствующему файлу) и ставил неразрешимые
деревья зависимостей заново каждый раз. Переведено на npm ci с npm-кэшем по
lockfile; проверка diff сборки ограничена выходной директорией LuCI htdocs,
которую build реально производит. Применена prettier-нормализация недавно
правленных исходников.
2026-08-21 21:05:10 +03:00
Dushnilin 41536b7988 test(shell_inventory): агентский CGI в ожидаемом списке shell-скриптов пакета
UTF-8 BOM раньше прятал shebang CGI от скана инвентаря; после удаления BOM
скрипт (корректно) определяется как shell, поэтому ожидаемый список
пополняется tachyon/files/usr/lib/cgi-bin/tachyon-agent.
2026-08-21 21:03:56 +03:00
Dushnilin d097494b23 test(infra): shellcheck не-.sh скриптов, фикс BOM, регрессионный тест RAG
- workflow shellcheck теперь покрывает агентский CGI, hotplug WAN-монитор и
  точки входа usr/bin; в CGI перед shebang стоял UTF-8 BOM, из-за которого
  Linux вообще не мог исполнить скрипт (bad interpreter) - удалён вместе со
  случайными BOM в исходниках фронтенда
- новый rag_embeddings.sh фиксирует исправленный порядок аргументов push(),
  инвалидацию кэша по отпечатку индекса, нормализацию null model_override и
  порядок объявлений - ни один из починенных багов RAG не сможет вернуться
  незаметно
- hosts_li
2026-08-21 20:58:51 +03:00
Dushnilin 0b4edc4789 test(run_all): параллельный запуск с логами по тестам и сводкой pass/fail
run_all.sh гонял набор последовательно с fail-fast: первый упавший тест
отменял всё остальное, а CI (xargs -P4) расходился с локальным порядком.
Теперь поддерживается -P N параллельность как в CI, ведётся лог по каждому
тесту, печатается сводка pass/fail и вывод упавших, принимаются фильтры имён
для запуска подмножества, а ошибки синтакс-проверки по-прежнему прерывают
всё до старта тестов.
2026-08-21 20:50:21 +03:00
Dushnilin fc18082bf6 perf(store): fast-path по ссылке, ограничение живущих всю сессию коллекций
- store.set выполнял JSON.stringify с сортировкой ключей над старым и новым
  значением каждого обновляемого ключа; ежесекундный ui-state poll дважды в
  секунду сериализовал весь payload секций даже без изменений. Дешёвая
  проверка по идентичности отсекает частый случай.
- Sets/Maps учёта задач (handled subscription/latency/component/service jobs,
  custom proxy latencies) росли всю сессию LuCI без границ; теперь они
  ограничены 500 записями через общий хелпер.
- AI-чат модалка: параллельные отправки перемешивали удаление плейсхолдера
  "печатает" и портили ветку диалога; поле ввода и кнопка блокируются на
  время запроса.
2026-08-21 20:41:32 +03:00
Dushnilin 5878e2dfd9 i18n(service-check): локализация захардкоженных русских строк
Модалка проверки доступности сервисов была полностью захардкожена русским
и невидима для po-инструментария: англоязычные пользователи получали русские
надписи. Все литералы обёрнуты в _() с английскими msgid, добавлены ru-
переводы и записи pot; пересобран main.js.
2026-08-21 20:36:51 +03:00