- 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
- опция daily_quota_minutes у расписаний: лимит минут использования сети в сутки
- поминутный cron-тик: учёт активности через ip neigh, блок до полуночи при исчерпании
- nft-сеты tachyon_quota_block (MAC) и _ip (IPv4) с drop-правилами; самовосстановление после ребилдов таблицы
- сброс счётчиков после смены суток, TG-уведомление о блокировке
- validator: проверка диапазона 0..1440
- UI: поле и колонка квоты в Родительском контроле + RU-локализация
- новый компонент 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
- тесты: генерация конфига, смоук рантайма с моками, обновлена сигнатура состояния
- Восстановление кэша правил: восстановление .srs, .lst, .json файлов из постоянного каталога /etc/tachyon/rulesets/ при старте; защита от удаления кэша при остановке; добавление фоллбэка пустых ruleset в runtime.uc перед валидацией
- nftables: удаление невалидного auto-merge для интервальных составных сетов (ipv4_addr/ipv6_addr . inet_service)
- core/ip: исправление нормализации IPv6-адреса и порта для TPROXY без дублирования скобок
- dns block: исправление возвращаемого статуса nft_add_dns_block_rules_from_schedules при отсутствии правил блокировки
- watchdog & lifecycle: добавление флага блокировки /var/run/tachyon/starting для исключения сброса правил вочдогом во время старта
- ui & i18n: форматирование бейджей устройств в родительском контроле и обновление переводов
- Fix response parsing: check report field, not just message
- Pass user query text to LLM instead of ignoring it
- Append user question to diagnostic prompt with system context
- Increase timeout to 120s for LM Studio responses
- Add command_status_from_args to core/common.uc exports
Closes#38
ucode держит по одному открытому дескриптору на каждый require()-модуль всё
время жизни интерпретатора и не даёт ни закрыть их, ни пометить close-on-exec,
поэтому каждый фоновый процесс наследует весь набор. На роутере watchdog несёт
около 90 таких дескрипторов и передаёт их детям: logread, sh и nfqws2 найдены
держащими дескрипторы на собственных .uc-файлах Tachyon, часть уже удалена
обновлением пакета — старый инод прибит к диску процессом, которому он не нужен.
Против этого код писал литерал 1000<&- в 22 местах 13 модулей. Он не закрывал
ничего: дескриптор 1000 никогда не открывается, а все настоящие лежат в 6..89.
Хуже того, этого литерала требовал tests/service_start_trap.sh — именно поэтому
он и разошёлся по кодовой базе. Проверка пинила видимость защиты и продолжала бы
проходить вечно при живой утечке.
Замена оказалась не такой прямой, как выглядит. Счёт от 3 до потолка не годится:
в dash закрытие дескриптора 10 и выше фатально даже для номера, который никогда
не открывался — dash перемещает туда собственную служебную бухгалтерию, и
перенаправление убивает шелл целиком, а 2>/dev/null прячет сообщение, но не
смерть. Перечисление /proc/self/fd трогает только реально открытое, но сами
высокие дескрипторы никуда не деваются: набор модулей любого размера уводит их
за 10. Поэтому пролог сначала спрашивает шелл в подоболочке, которая поглощает
смерть, и опускает собственный потолок, если ответ отрицательный. busybox ash,
на котором работает OpenWrt, отвечает утвердительно и закрывает весь набор.
Три хелпера в core/common.uc задают форму фонового запуска — раньше каждое место
собирало перенаправления вручную, и они разошлись. Форма с пидом ставит exec
внутрь подоболочки, чтобы $! называл сам демон, а не обёртку, которая тут же
завершается; ведущие присваивания VAR=x при этом выносятся перед exec, иначе
шелл ищет программу с именем VAR=x. Для конвейеров, которые exec заменить не
может, есть отдельный хелпер. initd.uc и state.uc лежат на пути раннего старта и
несут собственные копии, как уже несут shell_quote и command_from_args.
Попутно вскрылись два настоящих дефекта. В лестнице эскалации watchdog строка
читалась как restart & rm -f lock &, то есть лок снимался параллельно с
рестартом, который он охраняет, и вторая эскалация могла начаться посреди
первой. В пакетном прогреве Zero-RTT знак & стоял внутри строки, поэтому
перенаправления после него цеплялись к пустой команде в родительском шелле,
а не к фоновому циклу.
Ещё четыре места запускали фон вообще мимо механизма; одно из них аудит
пропустил, его нашёл новый тест.
tests/background_spawn_fds.sh считает дескрипторы у ребёнка при обоих способах
запуска: структурная проверка такое показать не может, строку надо выполнить.
На роутере старый способ даёт ребёнку 7 дескрипторов, новый — 3.
- wget: remove -t flag incompatible with BusyBox 25.12+, add curl-first fallback
in download_to_file() with wget as fallback
- startup/reload speedup (5 improvements):
- cut sing-box verification timeout from 45 iterations to 15
- remove redundant second stability wait for sing-box process
- batch requirement check + runtime validation into single ucode fork
- replace dnsmasq full restart with procd reload (config-conditional bounce)
- move telegram and watchdog runtime start to background
- telegram: fix "API failure" spam - empty result[] from getUpdates is now
treated as a successful idle poll, not an error that triggers backoff
- watchdog SmartDetect: extract domain from proxy and VPN URLs for DNS
health check, use real domains (google.com/example.com) instead of
provider-specific endpoints that trigger false alerts
- tests: new invariants for wget -t prohibition, telegram empty-result
handling, DNS apply reload-vs-restart, and combined validation mode ownership
Дашборд:
- Добавлены карточки статуса Zapret, Zapret2, ByeDpi на панель мониторинга
- Показывают статус (работает/остановлен), количество перезапусков, предупреждения
- Баг: API возвращает boolean а не число — исправлено сравнение
Секция Hosts:
- Перенесены опции hosts из глобальных настроек в отдельный тип секции (action=hosts)
- Опции: пользовательские DNS записи, удалённые списки хостов, автообновление, интервал
- Миграция: автоматический перенос глобальных настроек hosts в секцию
- Вкладка «Условия» скрыта для секций типа Hosts (hosts записи применяются глобально)
- Возвращена кнопка «Import Hosts File» с модальным окном загрузки из файла
- Убраны неиспользуемые опции из settings.js (~280 строк)
Локализация:
- Переведены 6 новых строк для превью импорта hosts
Критические исправления:
- safe_proxy_restart: удаление lock-файла после фонового перезапуска
с защитой от stale lock (>300 сек)
- write_json_file: атомарная запись через tmp+mv вместо прямой
- write_reload_state: атомарная запись через tmp+mv
- state_add_list: отклонение значений с пробелами
Исправления безопасности:
- killall logread заменён на pkill -f 'logread -f'
- SIGTERM→SIGKILL для Telegram и sing-box при остановке
- Recover OOM scale: инкремент +0.05 каждые 10 минут
- nfqueue и byedpi: trap для корректного завершения дочерних процессов
- nfqueue: валидация стратегий и откат при ошибке запуска
- nfqueue: flush mangle_output перед добавлением правил
- Исправлен парсинг exit code в 14 файлах (status & 127)
Улучшения стабильности:
- init.d: trap для start_service/stop_service
- watchdog: signal traps для legacy fallback loop
- lifecycle: логирование ошибок при остановке компонентов
- singbox/priority: SIGTERM→SIGKILL при остановке
- telegram: exponential backoff (1x→300s) при ошибках API
- telegram: process_updates возвращает boolean для отслеживания успеха
- watchdog: кэш proxy port для уменьшения чтения конфига sing-box
- singbox/generator: упрощён atomic_write_json_file
- packages.uc: переупорядочивание apk_version (лёгкие запросы первыми)
- dns/apply: проверка PID sing-box перед настройкой dnsmasq
- server/service: cleanup_orphaned_server_certs перед вызовом
Проблема с FD-каскадом (зависание sing-box):
watchdog.uc открывал logread -f через popen() без O_CLOEXEC. Каждый
фоновый system('... &') порождал дочерний процесс, наследующий read-end
logread pipe. После kill watchdog — дочерний процесс держал pipe, logread
не получал SIGPIPE и оставался живым. За N перезапусков накапливалось
N orphan-процессов logread → исчерпание лимита 1024 FD → генератор
конфига sing-box не мог открыть .uc файлы → sing-box не стартовал.
Исправления FD-утечки:
- watchdog.uc: добавлена переменная logread_pipe_fd + помощник bg_system(),
который закрывает FD logread перед запуском фоновых процессов
- watchdog.uc: killall logread перед открытием нового logread -f в
setup_syslog_listener() для очистки orphan-процессов от предыдущих экземпляров
- watchdog.uc: все 7 вызовов system('... &') переведены на bg_system():
restart, reload_firewall (x3), WAN ifdown/ifup, subscription_update_async (x3)
- initd.uc: killall logread в stop_service() после остановки tachyon
- nft/apply.uc: добавлены </dev/null >/dev/null 2>&1 к голому reload_firewall &
- updates.uc: добавлены редиректы к tachyon reload &
- telegram.uc: добавлен </dev/null к reload_firewall &
Проблема с утечкой временных файлов (500MB в /tmp):
При каждом tachyon restart (SIGKILL) процессы-генераторы умирали на
полуслове, оставляя /tmp/tmp.* файлы (sing-box config JSON ~3.4MB,
reload state). За 8 дней накопилось 227 файлов = 506MB в tmpfs,
что занимало 25% всего ОЗУ (519MB из 2007MB shared).
Исправления утечки временных файлов:
- temp_path() в runtime.uc и updates.uc теперь создаёт /tmp/tachyon-XXXXXX
вместо /tmp/tmp.XXXXXX (безопасный для очистки префикс)
- nft/apply.uc и lifecycle.uc: аналогичное изменение mktemp
- lifecycle.uc start_impl(): автоматическая очистка /tmp/tachyon-* файлов
старше 30 минут при каждом старте сервиса
Результат на роутере 192.168.2.1:
logread после stop = 0 (было 1+)
logread после start = 1 (было 2+ в каскаде)
sing-box = запущен
/tmp shared = 27MB (было 519MB, освобождено ~490MB)
ОЗУ available = 1691MB (было 1195MB)
generator.uc: try/catch around hosts cache file read with guaranteed close
hosts.uc: try/catch around parse_hosts_file with guaranteed close
migration.uc: try/catch around remove_cache_path, checked unlink in remove_legacy
runtime.uc: cleanup stale temp config files on init_config startup
lifecycle.uc: log warning on nft table delete failure in stop_main
- Дефолтный путь cache.db изменён: /tmp → /etc/sing-box/cache.db
- При перезагрузке роутера fakeip маппинги сохраняются (overlay fs)
- При SIGHUP hot-reload sing-box читает полный кэш без потерь
- При tachyon stop кэш удаляется (clean state)
- UI настроек обновлён: Persistent выбран по умолчанию, RAM помечен как 'lost on reboot'
- Удалён устаревший Flash (/usr/share) вариант