L×Box v2.23.2
Tailscale nodes and “node sections”. A sing-box tailscale endpoint is
now a first-class node: it is accepted from JSON without an address, built
with its own state directory, and the new Add server → Tailscale wizard
creates it together with the DNS server, the .ts.net rule and the
100.64.0.0/10 route it needs. Those companion records live inside the
node (sections, in the contract 1.0 record form), show up in Routing and DNS
with a “from node” mark, and follow the node when it is renamed or moved. On
top of that: a route rule can carry several rule-sets, the QR engine is
zxing-cpp 3.1.1, the core is v1.14.0-lx.38 (Tailscale in the AAR, REALITY
against Xray 26.9.8+), a real adaptive icon, and three fixes from 4PDA
reports.
Узлы Tailscale и «секции узла». Endpoint tailscale sing-box стал
полноценным узлом: принимается из JSON без адреса, собирается со своим
каталогом состояния, а новый мастер Add server → Tailscale создаёт его
сразу вместе с DNS-сервером, правилом .ts.net и маршрутом 100.64.0.0/10.
Эта связка живёт внутри узла (sections, форма записей контракта 1.0),
видна в Routing и DNS с пометкой «from node» и едет за узлом при
переименовании и переносе. Сверх того: правило маршрута может нести
несколько rule-set’ов, движок QR — zxing-cpp 3.1.1, ядро — v1.14.0-lx.38
(Tailscale в AAR, REALITY против Xray 26.9.8+), настоящая адаптивная иконка и
три исправления по отчётам с 4PDA.
🇬🇧 English
🪢 Tailscale nodes and node sections (docs/spec/features/435, contract ## 13)
A Tailscale node is useless on its own: it needs a DNS server of type
tailscale bound to it, a DNS rule for *.ts.net and a route rule for
100.64.0.0/10. The same goes for a WireGuard node with subnets behind the
peer. From this release a free node (a single server or a folder member)
carries that bundle with it, and the config builder appends the records to
the common lists before the usual resolve.
| Before | Now |
|---|---|
type: tailscale in a pasted config was an unsupported protocol
| Accepted from outbounds[] and endpoints[] without server/server_port; the body is kept as is (auth_key, control_url, hostname, ephemeral, accept_routes, exit_node, …) and emitted into endpoints[]
|
| — | state_directory per node: <app files>/tailscale/<final tag> is written at build time only, never into the stored body — two tailnet nodes no longer share the core's default directory
|
| — | Add server → Tailscale: Tag, Auth key (secret), Control URL, Hostname, Ephemeral, Accept routes, Exit node; the wizard produces the node with the canonical bundle (@{self}-dns server, .ts.net rule, @{self} network route at position 945) and a note that a one-time key is consumed on the first login
|
| A whole sing-box config with one node imported the outbound only | The node arrives with its bundle: DNS servers whose detour/endpoint point at it, the DNS rules on those servers, the route rules with its outbound — references rewritten to the @self placeholder, so the bundle survives a rename or a folder prefix
|
| — | Routing shows a node's rules on the shared order axis with a “from node ” mark: the toggle and drag write into the node; there is no separate edit/delete. DNS shows the node's servers and rules read-only |
| — | The node editor accepts a bare body, a document with sections, or a sing-box document with dns/route; a Sections block shows the count, the stored JSON and a Clear button
|
A node without exit_node gives access to the tailnet only — it is not a
Direction candidate (and therefore not listed on Home, which shows the
selector's members), but it is a legal detour target and the target of its
own route rule. With exit_node it is a candidate like any other node.
Core gate. The AAR must be built with Tailscale (v1.14.0-lx.38 or
newer) — this release ships exactly that core. On an older core (a downgrade,
a custom build) a Tailscale node is stored and shown, but skipped at build
with a warning — the rest of the config builds. It survives a core update
and a backup from a desktop.
Backup. Sections are stored in the contract 1.0 record form (kind,
name/tag, enabled, num, body = the sing-box object as is) but are not
exported to the LX Backup until contract 1.0; the export names the loss, and a
0.12 file's servers[].sections is ignored silently.
🧩 A route rule can carry several rule-sets (docs/spec/tasks/434, contract ## 12)
| Before | Now |
|---|---|
One .srs URL per rule; a desktop backup with three sets kept one
| The URL field takes one address per line. The core gets a rule_set per file and a single rule with the list; with one address the config is byte-for-byte the same
|
| One cache file per rule | One cache file and one auto-update entry per set; the rule is on only when all files are downloaded. Delete and URL change clear all of them |
Backup ref
| ref stays the first address, refs lists all of them when there are two or more; import prefers refs — a desktop file round-trips without loss
|
🏷 NaïveProxy: a dropped extra-headers pair is visible on the node (docs/spec/tasks/433, contract 0.12.10)
A pair without : or with an invalid header name was already dropped at parse
time, but only the log knew. The node now carries an info warning with the
dropped pair; the other headers and the node itself do not change. For REALITY
nodes with a fingerprint outside the Chrome family the warning has existed since
§281; it is now bound to the contract code, and under REALITY the chrome
fingerprint is written explicitly even when the node has none.
📷 QR scanner: zxing-cpp 3.1.1 (docs/spec/tasks/432)
flutter_zxing 2.4 → 3.0. Small codes and version-1 QR are picked up better,
scanning on ARM is 10–40 % faster. Besides plain QR, Micro QR and rMQR with the
same link inside are read. The scanner screen and its settings did not change.
⚙️ Core v1.14.0-lx.38 (docs/KERNEL.md)
The pin was lx.34. lx.38 brings Tailscale into the Android AAR
(with_tailscale plus the ts_omit_* trims; the AAR grew by 2.6 MB, the
build time did not change) and the SPEC 084 hotfix (an ABBA deadlock of nested
selectors, fork issue #20); the upstream base is sing-box 1.14.0 + 33 commits
(lx.37 sync), submodules wireguard-go v0.0.6 / sing-tun v0.9.3. The libbox
Java surface is unchanged. On the way:
lx.35— core issue #14: XHTTP behind a CDN that resets HTTP/2 streams
could hold 100 % CPU until a restart (the reset error leaked with the HTTP/2
library's type into a client running through the node — DoH withdetour,
rule-sets, chains).lx.36— REALITY against Xray-core ≥ 26.9.8: the core itself stripped the
post-quantum key share the server now requires, and every REALITY node on an
updated server died silently. Verified on an AVD against a local Xray 26.9.9:
withlx.34the nodes failed withreality verification failed, with
lx.36they carry traffic.
📦 File pickers and dependencies (docs/spec/tasks/431)
Play Console flagged the outdated file_picker. file_picker 12, share_plus
13, device_info_plus 13, package_info_plus 10, connectivity_plus 7 and
smaller dependencies were bumped. A picked file now reaches the screens already
read (PickedFile): config, backup, rules and subscription import read text
the same way through UTF-8 — three screens used to decode bytes as UTF-16 and
mangled Cyrillic names. freezed, json_serializable, build_runner and
their annotations left the project (there was not a single generated model),
and so did Apache Tika (465 classes) pulled in by the old file_picker.
🐛 Fixes
- REALITY nodes with a non-Chrome fingerprint no longer die silently on Xray
26.9.8+ servers (docs/spec/tasks/281,
core SPEC 083). The updated REALITY server requires the post-quantum
X25519MLKEM768key share in the hello, which only the Chrome fingerprints
carry; a node withfirefox,safari,iosand the like was silently sent
to the camouflage site. Such a node gets a warning at import andchrome
goes into the core config; the stored value stays. The defaultrandomis
replaced without a warning. - The app icon is no longer in a white frame. Android 8+ wrapped the old
icon into an adaptive one by itself — shrunk it and put a white background
under it, visible in the launcher and in the F-Droid catalogue. The app now
has a real two-layer adaptive icon: an orange gradient across the whole
background and the artwork inside the safe zone, so it looks whole under any
mask. The legacy and catalogue icons got transparent corners. - A stale “VPN is running” notification from a killed process is cleared when
the app opens (docs/spec/tasks/430).
A 4PDA report: “L×Box [final = vpn-1] • 14 h” in the shade, Start in the app.
The process was killed by the system, there is no tunnel, and the
foreground-service notification stays because of a race inside Android
itself. The app cannot dismiss it, so on open the service comes up for an
instant under the same notification and stops normally — the system removes
the notification. - Buttons and list tails no longer slide under the system navigation bar
(docs/spec/tasks/429).
A 4PDA report: “Save” in the DNS-rule sheet half under the three-button bar.
All sheets moved to the sharedshowAppBottomSheetwith insets for the
keyboard and the bar; 18 list screens (About, Backup, Speed test, the
WARP/Add server wizards, direction editors, logs, Stats, Debug, the node list,
the config editor, QR) got a bottom inset; two contract tests keep it that
way.
🛠 Toolchain and process
- Google Play upload from CI (docs/spec/tasks/436):
on avX.Y.Ztag CI uploads the built AAB to the Play Console through the
Google Play Developer API as a service account. The track and the release
status are repository variables (PLAY_TRACK,PLAY_RELEASE_STATUS; by
defaultproductionanddraft— a person publishes the draft). The
release text comes from the samefastlane/…/changelogs/files F-Droid
reads; their length (≤ 500 characters) is now checked on every push. - Contract with the launcher synced to 0.12.11; the record codec for the
contract 1.0 form (lib/models/record_codec.dart) is the future root
parser ofcustom_rules/dns_options. docs/STORAGE.md,docs/PROTOCOLS.md(Tailscale),docs/ARCHITECTURE.md,
the Debug API reference (sectionsin/subsand folder members,
read-only).
🧪 Tests
flutter analyze (0 issues), the full test suite, all four l10n checkers and
the docs parity check. New: the record codec, node sections (round-trip,
@self substitution, containers), Tailscale parsing and emission, bundle
extraction from a whole config, build injection (order axis, DNS, the
endpoint sanitizer, state_directory, the core gate, Direction candidates),
backup (silent sections, named export loss), the Routing rows, the DNS form in
Tailscale mode, the wizard.
🇷🇺 Русский
🪢 Узлы Tailscale и секции узла (docs/spec/features/435, контракт ## 13)
Узел Tailscale сам по себе бесполезен: ему нужен DNS-сервер типа tailscale,
привязанный к нему, DNS-правило на *.ts.net и правило маршрута на
100.64.0.0/10. То же у WireGuard-узла с подсетями за пиром. С этого релиза
свободный узел (одиночный сервер или член папки) носит связку с собой, а
сборка конфига дописывает записи к общим спискам перед обычным резолвом.
| Было | Стало |
|---|---|
type: tailscale во вставленном конфиге — неподдерживаемый протокол
| Принимается из outbounds[] и endpoints[] без server/server_port; тело хранится как есть (auth_key, control_url, hostname, ephemeral, accept_routes, exit_node, …) и эмитится в endpoints[]
|
| — | state_directory на узел: <файлы приложения>/tailscale/<финальный тег> пишется только при сборке и никогда в хранимое тело — два узла tailnet больше не садятся в общий дефолтный каталог ядра
|
| — | Add server → Tailscale: Tag, Auth key (секрет), Control URL, Hostname, Ephemeral, Accept routes, Exit node; мастер отдаёт узел с канонической связкой (сервер @{self}-dns, правило .ts.net, маршрут @{self} network на позиции 945) и подсказкой, что одноразовый ключ расходуется при первом входе
|
| Целый sing-box-конфиг с одним узлом привозил только outbound | Узел приезжает со связкой: DNS-серверы, чьи detour/endpoint указывают на него, DNS-правила на эти серверы, правила маршрута с его outbound — ссылки переписаны в плейсхолдер @self, поэтому связка переживает переименование и префикс папки
|
| — | Routing показывает правила узла на общей оси порядка с пометкой «from node <тег>»: тумблер и перетаскивание пишут в узел; отдельного редактирования/удаления нет. DNS показывает серверы и правила узла read-only |
| — | Редактор узла принимает голое тело, документ с sections или sing-box-документ с dns/route; блок Sections показывает счётчик, хранимый JSON и кнопку Clear
|
Узел без exit_node даёт только доступ в tailnet — он не кандидат
Направлений (и потому не виден на Home, где показаны члены селектора), но
законная цель detour и цель собственного правила маршрута. С exit_node он
кандидат, как любой другой узел.
Гейт ядра. AAR должен быть собран с Tailscale (v1.14.0-lx.38 или
новее) — этот релиз несёт именно такое ядро. На более старом ядре (даунгрейд,
своя сборка) узел Tailscale хранится и показывается, но при сборке
пропускается с предупреждением — остальной конфиг собирается. Он переживает
обновление ядра и бэкап с десктопа.
Бэкап. Секции хранятся в форме записей контракта 1.0 (kind,
name/tag, enabled, num, body = объект sing-box как есть), но до
контракта 1.0 в LX Backup не экспортируются; экспорт называет потерю, а
servers[].sections из файла 0.12 игнорируется молча.
🧩 Правило маршрута может нести несколько rule-set’ов (docs/spec/tasks/434, контракт ## 12)
| Было | Стало |
|---|---|
Один URL .srs на правило; бэкап с десктопа с тремя наборами оставлял один
| В поле URL по одному адресу на строку. Ядро получает rule_set на каждый файл и одно правило со списком; с одним адресом конфиг байт-в-байт прежний
|
| Один файл кэша на правило | Файл кэша и запись автообновления на каждый набор; правило включается только когда скачаны все файлы. Удаление и смена URL чистят их все |
В бэкапе ref
| ref остаётся первым адресом, refs перечисляет все при двух и более; импорт предпочитает refs — файл с десктопа приезжает без потерь
|
🏷 NaïveProxy: отброшенная пара из extra-headers видна на узле (docs/spec/tasks/433, контракт 0.12.10)
Пара без : или с недопустимым именем и раньше выбрасывалась при разборе, но
об этом знал только лог. Теперь у узла есть информационное предупреждение с
отброшенной парой; остальные заголовки и сам узел не меняются. Для
REALITY-узлов с отпечатком не из семейства Chrome предупреждение существовало
с §281; теперь оно привязано к коду контракта, а под REALITY отпечаток chrome
пишется явно и тогда, когда в узле его нет вовсе.
📷 QR-сканер: zxing-cpp 3.1.1 (docs/spec/tasks/432)
flutter_zxing 2.4 → 3.0. Лучше берутся мелкие коды и QR первой версии,
сканирование на ARM быстрее на 10–40 %. Кроме обычного QR читаются Micro QR и
rMQR с той же ссылкой внутри. Экран сканера и его настройки не менялись.
⚙️ Ядро v1.14.0-lx.38 (docs/KERNEL.md)
Пин был lx.34. lx.38 приносит Tailscale в Android AAR
(with_tailscale плюс ts_omit_*; AAR вырос на 2,6 МБ, время сборки не
изменилось) и хотфикс SPEC 084 (ABBA-дедлок вложенных selector'ов, issue #20
форка); база апстрима — sing-box 1.14.0 + 33 коммита (синк lx.37),
сабмодули wireguard-go v0.0.6 / sing-tun v0.9.3. Java-поверхность libbox не
изменилась. По дороге:
lx.35— issue #14 ядра: XHTTP за CDN, сбрасывающим HTTP/2-стримы, мог
держать 100 % CPU до перезапуска (ошибка сброса утекала типом
HTTP/2-библиотеки в клиента, идущего сквозь узел, — DoH сdetour,
rule-set’ы, цепочки).lx.36— REALITY против Xray-core ≥ 26.9.8: ядро само вырезало из
приветствия постквантовый ключ, который сервер теперь требует, и все
REALITY-узлы на обновлённых серверах молча умирали. Проверено на AVD против
локального Xray 26.9.9: сlx.34узлы падали сreality verification failed, сlx.36ходят.
📦 Выбор файлов и зависимости (docs/spec/tasks/431)
Play Console указал на устаревший file_picker. Подняты file_picker 12,
share_plus 13, device_info_plus 13, package_info_plus 10,
connectivity_plus 7 и зависимости по мелочи. Выбранный файл приходит в
экраны уже прочитанным (PickedFile): импорт конфига, бэкапа, правил и
подписок читает текст одинаково через UTF-8 — три экрана раньше декодировали
байты как UTF-16 и портили кириллицу в именах. Из проекта ушли freezed,
json_serializable, build_runner и их аннотации (ни одной сгенерированной
модели в коде не было), а с ними и Apache Tika (465 классов), которую тянул
старый file_picker.
🐛 Исправления
- REALITY-узлы с отпечатком не из семейства Chrome больше не умирают молча на
серверах Xray 26.9.8+ (docs/spec/tasks/281,
ядро SPEC 083). Обновлённый REALITY-сервер требует в приветствии
постквантовый ключX25519MLKEM768, который несут только отпечатки Chrome;
узел сfirefox,safari,iosи им подобными сервер молча уводил на
камуфляжный сайт. Такой узел получает предупреждение при импорте, а в
конфиг ядра уходитchrome; сохранённое значение не меняется. Дефолтный
randomподменяется без предупреждения. - Иконка приложения больше не в белой рамке. Android 8+ сам заворачивал
старую иконку в адаптивную: уменьшал её и подкладывал белый фон — рамка была
видна и в лаунчере, и в каталоге F-Droid. Теперь у приложения настоящая
адаптивная иконка из двух слоёв: оранжевый градиент во весь фон и графика в
безопасной зоне, поэтому под любой маской она цельная. Иконки для старых
Android и для каталога получили прозрачные углы. - Зависшее уведомление «VPN работает» от убитого процесса снимается при
открытии приложения (docs/spec/tasks/430).
Жалоба с 4PDA: в шторке «L×Box [final = vpn-1] • 14 ч.», в приложении Start.
Процесс убит системой, туннеля нет, а уведомление foreground-сервиса остаётся
из-за гонки внутри самого Android. Снять его приложение не может, поэтому
при открытии сервис на мгновение поднимается под тем же уведомлением и
штатно останавливается — уведомление снимает система. - Кнопки и хвосты списков больше не уезжают под системную панель навигации
(docs/spec/tasks/429).
Жалоба с 4PDA: «Save» в шторке DNS-правила наполовину под трёхкнопочной
панелью. Все шторки переведены на общийshowAppBottomSheetс отступом под
клавиатуру и панель; 18 экранов со списками (About, Backup, Speed test,
мастера WARP/Add server, редакторы направлений, логи, Stats, Debug, список
узлов, редактор конфига, QR) получили нижний отступ; два контракт-теста не
дают вернуть проблему.
🛠 Тулчейн и процесс
- Выпуск в Google Play из CI (docs/spec/tasks/436):
на тегvX.Y.ZCI заливает собранный AAB в Play Console через Google Play
Developer API от имени сервисного аккаунта. Трек и статус выпуска —
переменные репозитория (PLAY_TRACK,PLAY_RELEASE_STATUS; по умолчанию
productionиdraft— черновик публикует человек). Текст выпуска берётся
из тех же файловfastlane/…/changelogs/, что читает F-Droid; их длина
(≤ 500 символов) теперь проверяется на каждом push. - Контракт с лаунчером синхронизирован до 0.12.11; кодек записей формы
контракта 1.0 (lib/models/record_codec.dart) — будущий корневой парсер
custom_rules/dns_options. docs/STORAGE.md,docs/PROTOCOLS.md(Tailscale),docs/ARCHITECTURE.md,
справочник Debug API (sectionsв/subsи у членов папок, read-only).
🧪 Тесты
flutter analyze (0 issues), полный набор тестов, все четыре l10n-чекера и
проверка паритета документации. Новое: кодек записей, секции узла
(round-trip, подстановка @self, контейнеры), разбор и эмиссия Tailscale,
извлечение связки из целого конфига, инъекция при сборке (ось порядка, DNS,
санитайзер endpoint, state_directory, гейт ядра, кандидаты Направлений),
бэкап (молчаливое sections, названная потеря при экспорте), строки Routing,
форма DNS в режиме Tailscale, мастер.
Install / Установка
adb install -r LxBox-v2.23.2-arm64-v8a.apkБез uninstall! Поверх существующей установки. Настройки и подписки сохранятся.
No uninstall needed — install over the existing one. Settings and
subscriptions are preserved.
Previous release / Предыдущий релиз: v2.23.1.