github Leadaxe/singbox-launcher v2.0.0
release v2.0.0

2 hours ago

Release v2.0.0

Downloads

macOS (Universal) - Supports both Apple Silicon and Intel

Option 1: Installation Script (Recommended)

Install with a single command (version v2.0.0):

curl -fsSL https://raw.githubusercontent.com/Leadaxe/singbox-launcher/develop/scripts/install-macos.sh | bash -s -- v2.0.0

The script will:

  • Download the release archive
  • Extract and install to /Applications/
  • Fix macOS quarantine attributes and permissions
  • Launch the application automatically

Option 2: Manual Installation

  1. Download: singbox-launcher-v2.0.0-macos.zip
  2. Extract the ZIP file
  3. Remove quarantine attribute (required):
    xattr -cr "singbox-launcher.app" && chmod +x "singbox-launcher.app/Contents/MacOS/singbox-launcher"
  4. Double-click singbox-launcher.app to run
    • If macOS blocks the app, go to System Settings → Privacy & Security and click "Open Anyway"
    • Alternatively, right-click the app and select "Open" (first time only)

Windows (amd64)

  1. Download: singbox-launcher-v2.0.0-win64.zip
    • or singbox-launcher-v2.0.0-win64-full.zipfull bundle: sing-box.exe, wintun.dll, the config template and Mesa3D (mesa3d/, used only when no hardware OpenGL — RDP, VMs) are already inside, nothing is downloaded on first launch
  2. Extract the ZIP file to a folder, for example: C:\Program Files\singbox-launcher\
  3. Run singbox-launcher.exe from that folder
    • You may need administrator rights to install to Program Files
    • The regular archive downloads sing-box and wintun.dll on first launch

Windows 7 (x86, legacy)

  1. Download: singbox-launcher-v2.0.0-win7-32.zip
  2. Extract the ZIP file to a folder and run singbox-launcher-win7-32.exe
    • For Windows 7 / 32-bit or legacy compatibility only

Linux Support

⚠️ Linux build temporarily unavailable - мы ищем тестировщика для ручного тестирования перед включением автоматической сборки.

Checksums

See checksums.txt for SHA256 checksums of all files.

Release notes — v2.0.0

EN

2.0.0 marks the start of a unified ecosystem with LxBox: one data contract (contract/registry + corpus), parsing of share links / Xray JSON / wg-quick .conf and building share links all go through a single registry-driven engine, with the same behaviour on the same subscriptions. This is the start of convergence, not its completion: LxBox v2.25.0 ships on contract 1.1.37, the launcher on 1.1.46 — the gap is additive.

Highlights

  • 2.0.0 — one contract with LxBox. Desktop and mobile now read the same protocol tables: a subscription parsed here produces the same node bodies, warning codes and share links as LxBox on the same input. What changed is not a longer feature list but the end of parallel copies that had drifted apart.
  • Every node now takes one path into your config: whatever it came from — a share link, a pasted JSON object, a subscription, a backup or a WireGuard .conf — it is checked against a single set of protocol rules and written out by a single emitter. Before, those rules lived in three separate copies that had drifted apart, so the same broken node could be accepted from a link and rejected from JSON.
  • A node that lost something now says so. Fields the core would reject are removed with a named reason attached to the node (⚠ in the lists, details in the node card) instead of disappearing silently. Previously a subscription could "update" and quietly strip a node's obfuscation or certificate with nothing to show for it. Three new warning codes name what was dropped: an unrecognised link parameter (uri_param_unknown), an unrecognised JSON field (json_field_unknown), or an empty password (password_empty).
  • Shadowsocks default port is now 8388 (was 443), matching the scheme canon and LxBox.
  • The Servers list can be narrowed without endless scrolling: a filter window matches names by regex, protocol, transport, source and a latency threshold, with optional emoji chips on Direction names.
  • While the core runs, the Core window shows your Tailscale tailnet status — connected peers, your own address, and whether a remote state_directory is in play.
  • One bad server no longer costs you the whole VPN. The core checks a config as a whole and refuses to start on the first node it does not accept — so a single line in a single node of a subscription used to leave you with no connection at all. Now that node is turned off by itself, the config is rebuilt and checked again, and the VPN starts on everything else. Each server turned off this way says why, in the core's own words (✖ on its row, the text in its card), and config.json is only replaced by a config the core has accepted — a rejected build leaves the previous working one on disk. Turn a server back on with its checkbox and the next build will try it again. A bar on the Local tab reports what was turned off and opens the full list; if the count reaches ten, you are asked whether to keep checking or stop. The same check now runs on the Configurator's last step before Save (and when writing a remote machine's config); on macOS daemon mode a rejected real start also names the node and retries. The bar is not brought back after an app restart — the node's own mark still shows why it is off.

Fixed

  • Copying a node's link now writes it the way other clients read it. The link is built from the same protocol rules that read one back, so a node and its link can no longer drift apart. Five things that used to be lost are now carried: keep-alive settings, XHTTP field names in the spelling Xray expects, tuic's zero-RTT flag, spaces inside paths (%20, not +, which servers were answering with 404), and a hysteria2 node with an empty password — which previously could not be shared at all. TLS is now stated explicitly on vless/trojan/anytls links, and the "skip certificate check" flag is written under the name each protocol's clients actually read.

  • An Xray link that spells the XHTTP transport the old way (type=splithttp) no longer arrives as a plain TCP node with nothing to show for it — the transport and all its settings are kept.

  • A tuic link with an empty password is accepted and marked, instead of being dropped. The server will most likely refuse it, and now the node says so rather than looking broken for no reason.

  • A link that carries a node's private key is only copied after you confirm it. SSH nodes with an inline key and WireGuard/AmneziaWG nodes hand out a key that lets anyone connect as you, so "Copy server link", "Copy jump server link" and the bulk export now ask first — once per operation, not once per node. Links with only a password or a UUID are copied as before. An SSH node whose key is a path to a file carries no key in the link and is not affected.

  • An SSH node with an inline private key can be copied as a link at all. It used to fail with "this outbound cannot be turned into a share link"; the link is now written as ?private_key= — the same form the mobile app writes and this launcher already reads back.

  • macOS: "Hide app from Dock" now survives a restart. The tray toggle is saved to bin/settings.json (hide_app_from_dock) and applied on the next launch before the window appears. Only the Dock icon is hidden: the main window still opens at startup (use -tray to start without it).

  • Core pinned to sing-box-lx 1.14.1-lx.8: a REALITY short_id longer than 16 hex characters no longer crashes the core (it is a config error now), an unknown tuic.udp_relay_mode is rejected at load instead of silently becoming native, MASQUE standard profile without uri gets one clear error, core errors name the node type and tag (initialize outbound[0] vless[proxy-de-1]: …), and a gRPC service_name starting with / is taken as a ready-made request path.

  • gRPC: a serviceName written in Xray's absolute-path form (/my-service/Tun, /a/b/Tun, /a/Stream) now reaches the server at that exact path, so a node that used to answer 404 Not Found works (issue #130). The core reads the leading slash itself and escapes each segment separately; the launcher passes the value through untouched. Without a leading slash nothing changes — it is a service name and the core appends /Tun as before.

  • vmess: channel cipher list now matches the core exactly — aes-128-ctr (which the core never supported and which killed the whole config) is gone, aes-128-cfb is accepted instead of silently falling back to auto.

  • xhttp: mode, seq_placement, session_placement, uplink_data_placement, x_padding_placement and x_padding_method values outside the core's enum are dropped with an xhttp_param_reset warning instead of being passed through and aborting the whole config.

  • A hand-written JSON object added as a source is no longer copied into the config verbatim. A typo in a key used to abort the whole config — leaving you with no VPN at all and no hint which node was at fault; now the unknown key is removed and the node says why.

  • Several node shapes that the core rejects outright no longer reach your config: an empty server address, a WireGuard node with jmin but no jmax, a WebSocket/HTTP path with broken percent-encoding, and a naive node carrying TLS options naive does not read. Each of these used to abort the entire config.

  • AmneziaWG obfuscation survives. The h1h4 magic headers, the packet-size fields and the WARP reserved triplet are written in the numeric form the core expects, so an AWG node keeps the obfuscation it was configured with.

  • Fields your core is too old for (currently tls.reality.key_share, which needs 1.14.1-lx.4) are left out when the config is built, and the node keeps working without them. The node itself is unchanged, so the field comes back as soon as you update the core.

  • Long but valid share links are accepted again: the launcher's own 8 KB limit is gone in favour of the 64 KB limit the contract defines, which is what the mobile app already used. Amnezia and MASQUE links with full key material were being rejected on the desktop and accepted on the phone.

  • Shadowsocks nodes using the older ciphers the core still supports (rc4-md5, aes-128/192/256-cfb and -ctr, chacha20-ietf, xchacha20) are no longer thrown away. Nine working ciphers were being dropped together with the node, leaving you with neither the node nor an explanation; they now connect and carry a note that the cipher is dated.

  • A link spelling a parameter the other way round no longer loses it: insecure in all nine spellings providers use, hysteria2 bandwidth as up_mbps/down_mbps as well as upmbps/downmbps. The name table comes from the contract, so both apps read the same link the same way.

  • naive+https://secret@host reads the single credential as the password, matching the spec, the mobile app, and the links this launcher itself produces. A link shared from here used to be read back with the credential in the wrong slot.

  • The Xray ech= parameter is now removed with a stated reason instead of vanishing: it carries another client's key and the handshake cannot succeed with it. A tls.ech block from a sing-box configuration is untouched and still works.

  • A junk REALITY public key is reported instead of being dropped in silence, and keys that merely look like base64 (enabled, true) no longer slip through — the key is checked to be 32 bytes after decoding, which is what the core checks.

  • socks4:// and socks4a:// links work again — the protocol version is carried in the scheme name, not guessed from the port.

  • Windows: the tray icon is sharp again — Fyne was picking a 16-colour thumbnail layer instead of the full icon (#83).

  • Port hopping no longer costs you the whole VPN. A provider who writes the address and the port list as one value (mport=198.51.100.24:443,20000-30000) used to put the host name inside the port list, and the core refused to load the entire configuration with «bad port range». Now that one entry is dropped with a stated reason and the ranges written correctly keep working. A bogus ALPN entry is handled the same way.

  • A REALITY key written with the other base64 alphabet now works. The key is the same 32 bytes either way, but the core decodes this particular field in one spelling only and answered «decode public_key: illegal base64 data» — refusing the whole configuration. The spelling is now normalised on the way in.

  • A subscription whose port ranges are written both in the address and in mport= (host:443,20000-30000?mport=40000-50000) no longer loses half of them: port hopping used only one of the two lists, and it did so silently — the node connected and looked healthy.

  • A subscription packed into base64 twice is now readable. Before, it produced no nodes and no error at all: the launcher mistook the inner layer for plain text and folded the whole subscription into one unreadable element.

  • A subscription file or .conf saved with a BOM (Windows Notepad writes one) is read normally. The invisible leading character threw off format detection, and the configuration turned into a list of nonexistent links.

  • Xray subscriptions that fragment TLS ClientHello through a freedom outbound (dialerProxyfragment) no longer lose their servers: the node is kept with tls.fragment instead of being dropped as a broken chain hop.

Technical / Internal

  • Contract 1.1.46: registry-driven link mapper (SPEC 133), node insurance (SPEC 132), warning copy review in warnings.json — self-sufficient list subtitles, aligned params placeholders, updated developer go pointers.
  • Linux builds now obtain Wayland header paths from pkg-config and fall back to X11 when the optional native Wayland/EGL development files are incomplete, fixing local builds on openSUSE (PR #128, issue #127).
  • New docs/TROUBLESHOOTING.md: known problems by platform with links to their solutions (first entry — repeated systemd-resolved password prompts on Linux, issue #126).
  • Node bodies are produced by Sanitize + Emit over the contract registry (contract/registry/**): the per-protocol emitter chain, the TLS/transport field allowlists and the special-case naive filter are gone. Adding a field to a protocol is now a registry edit, not four code edits.
  • The per-field core gate is table-driven from the registry's min_core/platform instead of one probe per field; RealityKeyShareSupportProbe and its cache are removed. Node-level gates (naive/chain/tailscale/AWG3) are unchanged — they drop a node, which the registry does not express.
  • warnings are recomputed once on load for nodes saved before the pipeline. A node's stored body is rewritten only when the sanitizer actually removes or coerces something, and each such rewrite is a WARN line naming the node and the codes.
  • The contract corpus now asserts the body the launcher stores, not the output of the old emitter, and a new test feeds every corpus case into one config and runs the pinned core's check over it.
  • Share URIs are emitted by the same registry engine that parses them — the per-protocol shareuri_*.go encoders are gone with the hand-written parsers.
  • Scheme parsers are mappers now: they translate a link's dialect into the sing-box key paths and decide nothing about values. 21 value rules were deleted as duplicates of the registry, 6 moved into it (base64_32, hex_only, conditional advisory, requires+equals, default_when), 14 stay as genuine dialect translation. Per-scheme parsers shrank to a screen each.
  • Rules that existed in three or four copies are single now: the insecure spelling set (six copies), the REALITY fingerprint hint (four), the hysteria v1 bandwidth default (three), the shadowsocks cipher list (three). A link and the same node as JSON produce identical bodies and identical warning codes with identical paths — the corpus pair vless/junk_pair_with_body asserts it.
  • The registry-sync watchdog no longer lies: greping parse_warnings.go would have declared a dozen live rules dead once the codes moved. A counter-test now requires every node-field code to have a producer — a Go constant or a registry rule.
  • A Go 1.21+ construct in shared code is now caught on every pull request instead of at release time. The new guard tools/win7guard fails the test job on slices/maps imports, builtin min/max/clear, range over an integer and Request.PathValue — the things the Windows 7 build's Go 1.20 toolchain cannot compile. It reads the AST and the build tags, so a comment mentioning min, a method named clear and the go1.22 twin files stay untouched; the same step also runs in build-win7. Coverage upload moved to codecov-action@v5 (its file input is gone, the report is named in files), and the Ubuntu runner is pinned to ubuntu-24.04 instead of the floating label.
  • Dead legacy helpers left after the registry-driven link mapper (orphaned URI transport builders, uri_params.go, node_ref.go, detour_topo.go, legacy JSON transport emitter) removed; LEGACY_AUDIT addresses updated to match.
  • Contract 1.1.43–1.1.46: merge: append on registry entries is executed as list concatenation (delta D133-41), a doubly wrapped base64 document is unwrapped up to the declared depth (D133-42), a BOM is stripped at source-kind detection (D133-43), and the label.value_map replacement order plus the on_len_gt source chain are brought to the norm (D133-44). Counterpart task for LxBox — contract/TASKS_LXBOX.md §39.

Campaign report

RU

2.0.0 — начало единой экосистемы с LxBox: один контракт данных (contract/registry + corpus), разбор ссылок / Xray JSON / wg-quick .conf и сборка share-ссылок идут общим движком от таблиц реестра, одинаковое поведение на одних и тех же подписках. Это начало сближения, не завершение: LxBox v2.25.0 выходит на контракте 1.1.37, лаунчер на 1.1.46, разница аддитивная.

Основное

  • 2.0.0 — один контракт с LxBox. Десктоп и телефон читают одни таблицы протоколов: подписка, разобранная здесь, даёт те же тела узлов, коды предупреждений и share-ссылки, что LxBox на том же входе. Изменилось не количество фич, а конец параллельных копий правил, которые разъезжались.
  • Узел попадает в конфиг одной дорогой: откуда бы он ни пришёл — ссылкой, вставленным JSON-объектом, подпиской, бэкапом или .conf WireGuard, — его проверяет один набор правил протокола и записывает один эмиттер. Раньше эти правила жили в трёх разошедшихся копиях, и один и тот же битый узел ссылкой принимался, а объектом — нет.
  • Если у узла что-то сняли, он об этом говорит. Поля, которые ядро отвергает, снимаются с названной причиной на самом узле (⚠ в списках, подробности в карточке) вместо молчаливого исчезновения. Прежде подписка «обновлялась», у узла молча срезали обфускацию или сертификат, и увидеть это было негде. Три новых кода предупреждений называют, что снято: неизвестный параметр ссылки (uri_param_unknown), неизвестное поле JSON (json_field_unknown) или пустой пароль (password_empty).
  • Порт Shadowsocks по умолчанию — 8388 (был 443), как в каноне схемы и в LxBox.
  • Список Servers можно сузить без бесконечной прокрутки: окно фильтра отбирает по regex имени, протоколу, транспорту, источнику и порогу пинга, с чипами-эмодзи у имён Направлений.
  • Пока ядро работает, окно Core показывает статус Tailscale tailnet — подключённые пиры, свой адрес и задействован ли удалённый state_directory.
  • Один негодный сервер больше не стоит вам всего VPN. Ядро проверяет конфиг целиком и отказывается стартовать на первом же узле, который не принимает, — одна строка в одном узле подписки оставляла без связи вовсе. Теперь такой узел выключается сам, конфиг пересобирается и проверяется заново, а VPN поднимается на всех остальных. Каждый выключенный так сервер объясняет причину словами ядра (✖ в его строке, текст в карточке), а config.json заменяется только тем конфигом, который ядро приняло: отвергнутая сборка оставляет на диске предыдущий рабочий. Вернуть сервер — его же чекбоксом, следующая сборка проверит узел заново. О выключенных сообщает плашка на вкладке Local, она же открывает полный список; если их набралось десять, приложение спросит, проверять дальше или остановиться. Та же проверка идёт на последнем шаге Конфигуратора до Save и при записи конфига удалённой машины; в daemon-режиме на macOS отказ реального старта тоже называет узел и повторяет apply. Плашку после перезапуска приложения не восстанавливаем — на самом узле значок и причина остаются.

Исправления

  • Копирование ссылки узла теперь пишет её так, как её читают другие клиенты. Ссылка собирается по тем же правилам протокола, по которым читается, и узел со своей ссылкой больше не расходятся. Пять потерь исправлены: настройки keep-alive, имена полей XHTTP в написании Xray, флаг zero-RTT у tuic, пробелы в путях (%20, а не + — на него серверы отвечали 404) и узел hysteria2 с пустым паролем, которым прежде нельзя было поделиться вовсе. TLS у ссылок vless/trojan/anytls теперь указывается явно, а флаг «не проверять сертификат» пишется тем именем, которое читают клиенты каждого протокола.

  • Ссылка Xray со старым написанием транспорта XHTTP (type=splithttp) больше не приезжает голым TCP-узлом без объяснений — транспорт и все его настройки сохраняются.

  • Ссылка tuic с пустым паролем принимается с пометкой, а не отбраковывается. Сервер её, скорее всего, отклонит — и теперь узел об этом говорит, а не выглядит сломанным без причины.

  • Ссылка с приватным ключом узла копируется только после подтверждения. У SSH-узла со встроенным ключом и у WireGuard/AmneziaWG в ссылке лежит ключ, по которому подключиться от вашего имени сможет любой, поэтому «Копировать ссылку сервера», «Копировать ссылку узла-перехода» и массовый экспорт сначала спрашивают — один раз на операцию, а не на каждый узел. Ссылки, где есть только пароль или uuid, копируются как раньше. У SSH-узла, где ключ задан ПУТЁМ к файлу, ключа в ссылке нет — его это не касается.

  • SSH-узел со встроенным приватным ключом вообще стало можно скопировать ссылкой. Раньше он отбивался сообщением «этот outbound нельзя преобразовать в share-ссылку»; теперь ключ пишется как ?private_key= — в той же форме, что пишет мобильное приложение и что этот лаунчер уже умеет читать обратно.

  • macOS: «Скрыть из Dock» переживает перезапуск. Переключатель в трее сохраняется в bin/settings.json (hide_app_from_dock) и применяется при следующем запуске до показа окна. Скрывается только иконка Dock: главное окно при старте открывается как обычно (запуск без окна — флагом -tray).

  • Ядро закреплено на sing-box-lx 1.14.1-lx.8: REALITY short_id длиннее 16 hex больше не роняет ядро паникой (теперь ошибка конфигурации), неизвестный tuic.udp_relay_mode отвергается при загрузке, а не молча становится native, MASQUE-профиль standard без uri получает одну понятную ошибку, ошибки ядра называют тип и тег узла (initialize outbound[0] vless[proxy-de-1]: …), а gRPC-service_name, начинающийся с «/», понимается как готовый путь запроса.

  • gRPC: serviceName в Xray-форме «абсолютного пути» (/my-service/Tun, /a/b/Tun, /a/Stream) теперь приходит на сервер ровно этим путём, и узел, отвечавший 404 Not Found, работает (issue #130). Ведущий «/» разбирает само ядро, экранируя каждый сегмент по отдельности; лаунчер значение не трогает. Без ведущего «/» ничего не меняется — это имя сервиса, и ядро дописывает /Tun как раньше.

  • vmess: набор шифров канала сверен с ядром — aes-128-ctr, которого ядро не знало и который валил весь конфиг, убран; aes-128-cfb принимается вместо молчаливого отката к auto.

  • xhttp: значения mode, seq_placement, session_placement, uplink_data_placement, x_padding_placement и x_padding_method вне набора ядра снимаются с кодом xhttp_param_reset, а не уезжают в конфиг, роняя его целиком.

  • Ручной JSON-объект больше не копируется в конфиг дословно. Опечатка в имени ключа валила весь конфиг — человек оставался вообще без VPN и без подсказки, какой узел виноват; теперь неизвестный ключ снимается, а узел объясняет почему.

  • Несколько форм узла, которые ядро отвергает сразу, до конфига больше не доходят: пустой адрес сервера, WireGuard с jmin без jmax, путь WebSocket/HTTP с битым percent-кодированием и naive с TLS-полями, которых naive не читает. Каждая из них роняла конфиг целиком.

  • Обфускация AmneziaWG больше не теряется: магические заголовки h1h4, поля размеров пакетов и тройка reserved у WARP пишутся в числовой форме, которую ждёт ядро, и AWG-узел сохраняет настроенную обфускацию.

  • Поля, которых не знает ваше ядро (сейчас это tls.reality.key_share, ему нужно 1.14.1-lx.4), при сборке конфига опускаются, и узел продолжает работать без них. Сам узел не меняется, так что поле вернётся сразу после обновления ядра.

  • Длинные, но валидные ссылки снова принимаются: собственный предел лаунчера в 8 КБ заменён контрактными 64 КБ — теми же, что давно у мобильного приложения. Ссылки Amnezia и MASQUE с полным ключевым материалом отбивались на десктопе и принимались на телефоне.

  • Узлы shadowsocks на старых шифрах, которые ядро по-прежнему принимает (rc4-md5, aes-128/192/256-cfb и -ctr, chacha20-ietf, xchacha20), больше не выбрасываются. Девять рабочих шифров отбивались вместе с узлом — человек не получал ни узла, ни объяснения; теперь они подключаются с пометкой, что шифр устарел.

  • Ссылка, написавшая параметр иначе, больше не теряет его: insecure во всех девяти написаниях, которыми пользуются панели, полоса hysteria2 и как up_mbps/down_mbps, и как upmbps/downmbps. Таблица имён берётся из контракта, поэтому обе программы читают одну ссылку одинаково.

  • naive+https://secret@host читает единственный кредентиал как пароль — как велит спека, как читает мобильное приложение и как этот же лаунчер такие ссылки выписывает. Ссылка, отданная отсюда, прежде читалась обратно с кредентиалом не в том поле.

  • Параметр ech= формата Xray снимается с названной причиной, а не исчезает молча: он несёт ключ чужого клиента, и рукопожатие с ним не состоится. Блок tls.ech из конфигурации sing-box не затрагивается и продолжает работать.

  • Мусорный публичный ключ REALITY теперь называется, а не снимается молча, и ключи, которые лишь ПОХОЖИ на base64 (enabled, true), больше не проходят: ключ проверяется на 32 байта после декода — ровно так, как проверяет ядро.

  • Ссылки socks4:// и socks4a:// снова работают — версия протокола несётся в имени схемы, а не угадывается по порту.

  • Windows: иконка в трее снова чёткая — Fyne брал 16-цветный слой-миниатюру вместо полноцветной (#83).

  • Прыжки по портам больше не стоят всего VPN. Провайдер, записавший адрес и список портов одним значением (mport=198.51.100.24:443,20000-30000), укладывал имя хоста внутрь списка портов, и ядро отказывалось загрузить весь конфиг с «bad port range». Теперь такая запись снимается с названной причиной, а диапазоны, записанные правильно, продолжают работать. С негодной записью ALPN — так же.

  • Ключ REALITY, записанный другим алфавитом base64, теперь работает. Байты ключа в обоих написаниях одни и те же, но ядро декодирует именно это поле в одном написании и отвечало «decode public_key: illegal base64 data», отвергая конфиг целиком. Написание приводится к одному на входе.

  • Подписка, у которой диапазоны портов записаны И в адресе, и в mport= (host:443,20000-30000?mport=40000-50000), больше не теряет половину: прыжки по портам шли только по одному из двух списков, причём молча — узел подключался и выглядел исправным.

  • Подписка, упакованная в base64 ДВАЖДЫ, теперь читается. Прежде из неё не получалось ни одного узла и не появлялось ни одной ошибки: лаунчер принимал внутренний слой за текст и складывал всю подписку в один нечитаемый элемент.

  • Файл подписки или .conf, сохранённый с меткой BOM (так пишет, например, «Блокнот» Windows), читается как обычно. Невидимый символ в начале сбивал распознавание формата, и конфигурация превращалась в список несуществующих ссылок.

  • Xray-подписки с фрагментацией TLS ClientHello через freedom-outbound (dialerProxyfragment) больше не теряют серверы: узел сохраняется с tls.fragment вместо отбраковки как битый хоп цепочки.

Техническое / Внутреннее

  • Контракт 1.1.46: движок linkmap по реестру (SPEC 133), страховка узла (SPEC 132), вычитка текстов предупреждений в warnings.json — самодостаточные подзаголовки в списке узлов, согласованные params, обновлённые служебные go.
  • Linux-сборка теперь получает пути к заголовкам Wayland через pkg-config и использует X11 при неполном наборе опциональных Wayland/EGL-файлов разработки, исправляя локальную сборку в openSUSE (PR #128, issue #127).
  • Новый docs/TROUBLESHOOTING.ru.md: известные проблемы по платформам со ссылками на решения (первая запись — повторные запросы пароля systemd-resolved на Linux, issue #126).
  • Тело узла делают Sanitize + Emit по реестру контракта (contract/registry/**): per-scheme цепочка эмиттера, allowlist-ы полей TLS и транспорта и частный фильтр naive сняты. Новое поле протокола — правка реестра, а не четырёх мест в коде.
  • Полевой гейт ядра стал табличным (min_core/platform реестра) вместо пробы на каждое поле; RealityKeyShareSupportProbe и её кэш удалены. Узловые гейты (naive/chain/tailscale/AWG3) не тронуты — они выбрасывают узел, и реестром это не выражается.
  • warnings разово досчитываются при загрузке у узлов, сохранённых до конвейера. Тело переписывается, только если санитайзер реально что-то снял или привёл, и каждая такая перезапись — строка WARN с тегом узла и кодами.
  • Корпус контракта сверяет тело, которое лаунчер СОХРАНЯЕТ, а не вывод старого эмиттера; добавлен тест, который собирает все кейсы корпуса в один конфиг и прогоняет check ядром пина.
  • Share-ссылки собирает тот же движок реестра, что их читает — эмиттеры shareuri_*.go ушли вместе с рукописными парсерами.
  • Парсеры схем стали мапперами: переводят диалект ссылки в пути sing-box и не решают ничего о значениях. 21 правило значения снято как дубль реестра, 6 перенесено в реестр (base64_32, hex_only, условный advisory, requires+equals, default_when), 14 осталось настоящим переводом диалекта. Пер-схемные парсеры ужались до экрана каждый.
  • Правила, лежавшие в трёх-четырёх копиях, стали одним: набор написаний insecure (шесть копий), подсказка об отпечатке REALITY (четыре), дефолт полосы hysteria v1 (три), список шифров shadowsocks (три). Ссылка и тот же узел объектом дают одинаковые тела И одинаковые коды с одинаковыми путями — это сверяет пара корпуса vless/junk_pair_with_body.
  • Сторожевой тест реестра перестал врать: грепа parse_warnings.go после переезда кодов хватило бы, чтобы объявить дюжину живых правил мёртвыми. Встречная проверка требует, чтобы у каждого кода полей узла был производитель — константа Go или правило реестра.
  • Конструкция Go 1.21+ в общем коде теперь ловится на каждом pull request, а не на выпуске. Новый страж tools/win7guard красит джобу тестов на импорт slices/maps, builtin min/max/clear, range по целому и Request.PathValue — то, что не собирает тулчейн Go 1.20 сборки Windows 7. Он смотрит AST и build-теги, поэтому min в комментарии, метод с именем clear и файлы-близнецы за go1.22 его не интересуют; тот же шаг стоит и в build-win7. Выгрузка покрытия переехала на codecov-action@v5 (входа file у него нет, файл отчёта называется в files), а раннер Ubuntu пригвождён к ubuntu-24.04 вместо плавающей метки.
  • Снят мёртвый легаси после перевода разбора на реестр: осиротевшие транспортные хелперы URI, uri_params.go, node_ref.go, detour_topo.go, эмиттер transport в outbound_jsonbuilder.go; адреса в LEGACY_AUDIT пересажены на живые файлы.
  • Контракт 1.1.43–1.1.46: merge: append у записей реестра исполняется слиянием списков (дельта D133-41), двойная оболочка base64 раскрывается до объявленного предела (D133-42), BOM снимается на входе определения вида источника (D133-43), порядок замен label.value_map и цепочка источников on_len_gt приведены к норме (D133-44). Встречная задача LxBox — contract/TASKS_LXBOX.md §39.

Отчёт кампании

Don't miss a new singbox-launcher release

NewReleases is sending notifications on new releases.