- telegram.uc: добавлены таймауты --max-time 35 и --connect-timeout 10 для всех запросов curl,
ограничение длины сообщений до 3900 символов (предотвращение 400 Bad Request от Telegram API),
безопасная обработка callback_query в try...catch с гарантированным вызовом answerCallbackQuery,
защита основного цикла worker от падений при сбоях сети
- api.uc: добавлены таймауты --max-time и --connect-timeout для всех curl вызовов,
устраняющие зависание подпроцессов при опросе Clash API и проверках соединения через прокси
- watchdog.uc: добавлена функция safe_reload_firewall, устранившая ошибки
'left-hand side is not a function' в процессах ai_heal_nftables и ai_heal_qos
- action.uc: killall logread перед restart_tachyon_after_successful_change,
предотвращает FD-каскад при reinstall/update (orphan logread pipe FD)
- action.uc: current_sha/latest_sha в ответе check_update для Tachyon
- updates.uc: SHA пробрасывается через кэш проверки обновлений
- fe/types.ts: outdated_same_release добавлен в union статусов,
поля current_sha/latest_sha в ComponentActionResult,
AI Watchdog поля в ConfigSection (pre-existing TS2339 fix)
- fe/store.service.ts: outdated_same_release и SHA в состоянии updatesChecks
- fe/initController.ts: рендер SHA hash-badge для outdated_same_release,
setCheckResult принимает SHA, applyCachedCheckResults/handleCheckResult обновлены
- fe/styles.ts: .sha-info — monospace badge для отображения коммит-хешей
Проблема с 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)
- action.uc: compare release target_commitish (full 40-char SHA) with
installed short SHA by prefix instead of exact match; fixes false
'same release update found' when commit is actually the same
- runtime.uc: generator_failure_reason now returns the first error line
instead of the ucode 'Near here ---^' position marker, and logs full
generator output at debug level on failure for better diagnostics
Arbitrary .map file read via untrusted sourceMappingURL when from is
unset. Added npm overrides to force postcss@8.5.23 across all transitive
deps (tsup, vitest→vite).
Backend:
- constants.uc: add TACHYON_COMMIT_SHA constant (placeholder __COMPILED_COMMIT_SHA__)
- build.sh: capture git rev-parse HEAD and sed-replace commit SHA placeholder
- Makefile: same commit SHA sed replacement for OpenWrt builds
- action.uc: modify check_tachyon() to compare installed commit SHA with
target_commitish from GitHub release, new status 'outdated_same_release'
- action.uc: reuse release_json for both version and commit SHA extraction
- updater.uc: add release_commit_sha helper to extract target_commitish
- updates.uc: extend cacheable status to include outdated_same_release
Frontend:
- main.js: handle outdated_same_release status in renderComponentCard
- main.js: include outdated_same_release in shouldShowInstallAfterCheck
- main.js: include outdated_same_release in applyCachedCheckResults
i18n:
- Add 'Update is available for current release' and 'Update is available
for current release:' to .pot and .ru.po files
78 tests pass.
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
migration.uc:
- try/catch around migration.run() with error logging
- try/catch around apply_operations() with error logging
- Error checking for UCI cursor operations in apply_operations
- Checked fs.writefile return values in ensure_runtime_cache_format
updates.uc:
- Write pending marker on lock acquisition failure for next retry
- watchdog: fix 'left-hand side is not a function' tick crashes
- safe_call: replace String(e)/instanceof Error with as_string(e)
- ai_heal_empty_sections: list_option/read_json_file -> common.*
- read_dns_recovery_state/ai_heal_dns_loop: read_json_file -> common.*
- common.is_connections_action -> connections.is_connections_action
(add missing 'config.connections' module import)
- wrap check_singbox_process/check_auto_resume_pause/ai_full_health_audit
in safe_call to prevent uncaught exceptions in tick loop
- state: SIGHUP race condition: fall back to sing-box start instead of
exit(1) when process dies between config generation and signal delivery
- telegram: fix String(e)/instanceof Error crash in update catch block
On some OpenWrt builds (e.g., FriendlyWRT with ip-full conflict), apk
reports '1 error' even though the package installed successfully
(pre/post scripts ran). The install script now checks if the package
is actually installed after apk returns non-zero, and continues if so.
Ключевые изменения:
1. core/helpers.uc: file_is_usable(path, min_bytes) + file_size_value(path)
для единообразной проверки файлов (минимальный размер порог).
2. singbox/dns.uc: WAN fallback DNS серверы (fallback_wan_main) теперь
используют прямой UDP без detour — исправлена петля когда
DNS шёл через мёртвую прокси-секцию.
Health probes для ВСЕХ DNS серверов (включая DoH) идут direct —
failover может определить доступность DNS серверов.
3. watchdog.uc: ai_heal_empty_sections() — автообнаружение пустых
proxy-секций с подписками → запуск subscription_update_async.
ai_heal_dns_loop() — при обнаружении DNS-петли (dead proxy +
dead DNS) → временно отключает DNS detour → загружает подписку →
восстанавливает настройки.
4. Единая file_is_usable: nft/apply.uc, generator_routes.uc,
components/{updates,action}.uc, config/validator.uc,
subscription/cache.uc — все используют helpers.file_is_usable.
5. Тест tests/file_usable.sh для проверки helpers CLI.
6. Исправлен components_updater_job.sh mock lib — добавлен core/helpers.uc
- generator_routes.uc: ensure_custom_ruleset() returns null and local
file references with size <100 bytes are skipped (previously would
silently reference a nonexistent/broken path, leaving sing-box with
no domain matchers)
- generator_routes.uc: ruleset/rule_sets_with_subnets loops guard
against null ensured result before accessing .tag/.kind
- nft/apply.uc: community-subnets .lst files with size <50 bytes are
skipped before handing data to nftables
Tiny or zero-byte .srs files can appear after interrupted downloads or
failed remote fetches. If such a file is picked as a local ruleset, the
section silently loses its domain matchers. Raise the size threshold to
100 bytes so damaged snapshots fall back to the remote rule_set URL.