Release v0.9.0
Downloads
macOS (Universal) - Supports both Apple Silicon and Intel
Option 1: Installation Script (Recommended)
Install with a single command (version v0.9.0):
curl -fsSL https://raw.githubusercontent.com/Leadaxe/singbox-launcher/develop/scripts/install-macos.sh | bash -s -- v0.9.0The 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
- Download:
singbox-launcher-v0.9.0-macos.zip - Extract the ZIP file
- Remove quarantine attribute (required):
xattr -cr "singbox-launcher.app" && chmod +x "singbox-launcher.app/Contents/MacOS/singbox-launcher"
- Double-click
singbox-launcher.appto 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)
- Download:
singbox-launcher-v0.9.0-win64.zip - Extract the ZIP file to a folder, for example:
C:\Program Files\singbox-launcher\ - Run
singbox-launcher.exefrom that folder- You may need administrator rights to install to Program Files
- The launcher will automatically download
sing-boxandwintun.dllon first launch
Windows 7 (x86, legacy)
- Download:
singbox-launcher-v0.9.0-win7-32.zip - 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.
Highlights (EN)
- Connections redesign —
state.jsonv5 + per-source meta + raw cache — thestate.jsonlayout has been redesigned: top-levelmeta(version, timestamps, comment) andconnections(sources, outbounds, defaults) replace the legacy nestedparser_configblock. Each subscription source now persists its own metadata (profile_title,subscription-userinfoquota, expire date,support-url, last-fetched timestamp, last-status, error count, first-50 preview nodes) so the wizard can show quota progress, expiry badges, and per-source status without an extra refresh. Raw subscription bodies are cached atbin/subscriptions/<id>.raw(atomic.tmp+Rename) — Rebuild now parses.rawfiles directly without a network round-trip;bin/outbounds.cache.jsonis no longer used and is removed on first run after upgrade. New per-row Refresh button in the wizard fetches a single subscription without disturbing the others. Failed fetches keep the old.rawintact (per-source resilience). Old v2-v4 state files migrate automatically on first load. See SPEC 052. - Auto-update event-driven model — replaces the old all-or-nothing loop (single check across all sources, 2 retries × 20s, no event integration) with a per-source pipeline: 1-hour heartbeat walks each enabled source individually and refreshes only those past their effective
reload; on fetch failure a single 15s retry is scheduled (no recursion); VPN-state events (VpnStateChanged,ProxyActiveChanged) trigger immediate retry of failed sources. Per-source 5s cooldown protects against event storms (suspend/resume/auto-reconnect bursts no longer fan out N parallel fetches). State-level mutex (AppController.SubscriptionMu) serializes load→mutate→save cycles so a UI-driven Refresh during an in-flight heartbeat waits for the current write instead of overwriting it. DroppedshouldAutoUpdate(read v4parser.last_updated— gone in v5; old code triggered a parasitic Update on every launch). Part of SPEC 052. - Pinned sing-box and template — each launcher build now ships pinned to a specific sing-box version (
RequiredCoreVersion) and a specific commit ofwizard_template.json(RequiredTemplateRef, injected into the binary via-ldflags). The Core Dashboard no longer polls GitHub for newer sing-box releases or pushes "Update to vX.Y.Z" nudges; you upgrade the core by upgrading the launcher. On launcher upgrade, the locally cached template is invalidated and a fresh "Download Template" prompt appears so the format always matches the launcher version. Builds-from-source viago run .use a source-defaultRequiredTemplateRefthat the maintainer bumps toorigin/mainafter each release. See SPEC 046. - Single in-place subscription status panel under Exit — the old design (popup dialogs interrupting the user, a progress bar floating in the middle of the dashboard between unrelated blocks) is gone. Subscription operation feedback now lives in one card under the Exit button: while running it shows a neutral
⋯icon, the title "Refreshing subscriptions", and the current detail ("Fetching 3/5: …") with a live progress bar; on completion it swaps to a green✓(success) or red✗(error) icon, message, and a×close button, auto-hiding after 20s. Per-source progress is reported (Fetching N/M: <url>) instead of a long blank "Starting…" stage. - Auto-ping soft cap on huge subscriptions — auto-ping after VPN connect (and after system resume) is now skipped when the proxy list exceeds 150 nodes. With CIDR-derived subscriptions of several hundred nodes, the timer-driven
ping-allopened so many simultaneous TCP+TLS handshakes through TUN that game logins and other in-flight traffic could freeze for over a minute. The manual «Test» button on Servers,Cmd/Ctrl+P, and the/action/ping-alldebug-API endpoint are unaffected — only the automatic timer path is gated. The threshold is tunable viabin/settings.json→auto_ping_after_connect_max_proxies(positive int;0/ absent = the 150 default). See SPEC 039 §1.3. GET /debug/snapshot— new debug-API endpoint returning the four wizard-pipeline files (wizard_template.json,wizard_states/state.json, the in-memory parsed-cache,config.json) as one JSON object. Files that are missing or contain invalid JSON are reported viamissing[]/errors{}; the rest of the snapshot is still produced. No redaction: bearer-auth on the endpoint already gates access, masking secrets from the legitimate caller would be theatre. Useful for golden-data capture, bug-repros, and future MCP wrappers. See SPEC 038 SUB_SPEC_SNAPSHOT.md.- Typed event bus — new
core/eventspackage replaces the implicit observer pattern with a typedBusinterface,EventKindenum, and structured payloads (VpnStateChangedPayload,ProxyActiveChangedPayload, etc.). Sync dispatch with panic-isolation per handler; idempotentCancel; cancel-inside-handler safe. 11 race-detector-clean tests. Foundation for SPEC 045 (state/config decoupling) and the auto-update event triggers above. See SPEC 047.
Fixed
- Sing-box panic on VLESS outbound —
unknown packet encoding(issue #68). Subscription parsers used to copy the URI query parameterpacketEncodingverbatim into the generatedoutbounds[*]. Sing-box v1.13.x accepts only""/"xudp"/"packetaddr"and on any other value (e.g.none) does not return a clean error — it panics informat.ToStringwhile formatting the error message itself, because the upstreamE.New("unknown packet encoding: ", *string)passes a pointer where a string is expected. The trigger appeared when third-party subscription producers started emittingpacketEncoding=nonefor nodes that don't need xtls; the launcher dutifully wrote"packet_encoding":"none"intoconfig.json, sing-boxcheckexit-2'd, and the launcher couldn't start. Fix: parser-side allow-list —xudp/packetaddr(with case normalization),nonesilently dropped (semantically equivalent to omitting the field), anything else dropped with a warning. Upstream sing-box bug separately tracked at SPEC 049. - Multi-stage orphan GC no longer eats neighbouring stages' raw cache. Scenario: user has stages A (active) and B (saved as
work-only), each with different subscriptions. Update sweep on stage A used to callDeleteOrphanswith only A's IDs → B's.rawfiles were deleted as orphans → switching back to B forced full re-fetch of every subscription. Fix: GC now walksbin/wizard_states/*.jsonand unions everySource.ID; a.rawlives as long as any stage references it. Only runs at full Update sweeps (manual click / 1h heartbeat / VPN-event retry path) — Per-source Refresh, Rebuild, app startup, Save/Load do not trigger it. DeleteOrphansno longer nukes 3rd-party files inbin/subscriptions/. Files whose stripped name doesn't parse as a valid ULID-like ID are left alone (e.g.has space.rawsurvives). Properly-named files not in the known set are still removed, as before.
Technical / Internal
RequiredTemplateRefis injected at build time via-ldflagsfromgit rev-parse HEAD; CI builds always pin to their own commit. Localgo run .builds inherit a source-default that the maintainer bumps toorigin/mainafter each release (RELEASE_PROCESS §1.5).- Removed:
GetLatestCoreVersion,CheckVersionInBackground,ShouldCheckVersion, core-sideGetCachedVersion/SetCachedVersion,CheckForUpdates,CoreVersionInfo,FallbackVersion,shouldAutoUpdate,attemptAutoUpdateWithRetries,calculateAutoUpdateInterval. Launcher self-update path is unchanged. - New
core.InvalidateTemplateIfStale(execDir)called frommain.gobefore the UI starts; newSettings.LastTemplateLauncherVersionfield inbin/settings.json, written after a successful Download Template. state.jsonschema bumped from v4 to v5 with auto-migration on first load. New types:core/state/v5/{types,migration,raw_cache,ulid,adapter}.go.core/state/adapter.gobridges editable legacyParserConfigview ↔ canonicalConnectionsfor code paths that haven't migrated yet.core/build/parsed_cache.go(ParsedCachetype) replacescore/outboundscache.Snapshotas the in-memory carrier betweenbuildSnapshotFromRawCacheandBuildConfig. Thecore/outboundscachepackage is retired entirely.- New
events.Bus(sync dispatch) plusevents.{Subscribe,Publish,Cancel}API, payload structs incore/events/payloads.go. AppController.SubscriptionMu sync.Mutex— load→mutate→save serialization forrefreshSubscriptionsMetaAndCacheandRefreshSingleSubscription.AppController.autoUpdateRetryTimers map[ID]*time.Timer+autoUpdateEventLastFetch map[ID]time.Time, both guarded byautoUpdateRetryMu. Cleaned up viacancelAllRetryTimers()on shutdown.- New helper
effectiveReload(update *state.UpdateSpec, defaultReload string) time.Duration— per-source freshness logic, testable in isolation. - Wizard-side cleanup:
WizardModel.Sources / GlobalOutbounds / Defaultsare now canonical;ParserConfigandParserConfigJSONare derived caches refreshed byRefreshDerivedParserConfig().AppendURLsToSourcesreplaces the olderApplyURL/AppendURLshelpers and classifies input into Source(subscription) / Source(server) directly. - Source row in the configurator gains a subtitle line:
⚠ Nerrors ·⁙count ·↻interval ·🕒fetched-ago ·💾quota ·⏳expires. Edit / Refresh / Del are icon buttons; type indicator removed (clutter);profile_titlebecomes the primary label, URL falls back when title is missing. - Edit window split into Settings (editable: URL/URI/Tag/checkboxes), Preview (parses
.rawcache without network), Overview (read-only summary + raw body decoded viaDecodeSubscriptionContent), JSON (v5 Source layout). - Debug API default port changed
9269→9263so a single host can run desktop and mobile LxBox (both use9269) in parallel withoutaddress in use. User-set ports via Settings → Diagnostics → "Debug API port" are unaffected; only the value used whendebug_api_portis absent / 0 insettings.jsonchanged. - Build script:
build/build_darwin.sh -inow ad-hoc resigns the binary after replacing the executable in/Applications(macOS Sonoma+ kills mismatched signatures otherwise). Sign happens on a copy outside the bundle to avoid bundle-resource walking on root-ownedbin/logsfiles.
Migration notes
- After upgrading, your existing
bin/wizard_template.jsonwill be removed once on first launch and the UI will prompt to re-download. One click; no further action. bin/outbounds.cache.jsonis removed on first run after upgrade — the new raw-body cache (bin/subscriptions/*.raw) takes its place. Rebuild now parses.rawfiles directly, no network round-trip needed.- Old
state.json(v2/v3/v4) is read and rewritten in v5 layout on first load. The legacy nestedparser_configblock is moved intoconnections.outbounds/connections.sources; semantics are preserved 1:1. - If you ran a custom-modified template locally, back it up before upgrading — the invalidation is unconditional for non-dev builds.
Основное (RU)
- Connections redesign —
state.jsonv5 + per-source meta + raw cache — форматstate.jsonпеределан: top-levelmeta(version, timestamps, comment) иconnections(sources, outbounds, defaults) вместо старой вложенной обёрткиparser_config. Каждая подписка теперь хранит собственные метаданные:profile_title, квота изsubscription-userinfo, expire date,support-url, время последнего fetch, статус, счётчик ошибок, превью первых 50 нод — визард показывает progress-bar квоты, badge с днями до expire, индикатор статуса (●ok / ●err) без отдельного refresh. Raw-тела подписок кэшируются вbin/subscriptions/<id>.raw(atomic.tmp+Rename) — Rebuild теперь парсит.rawнапрямую без network call'а;bin/outbounds.cache.jsonбольше не используется и удаляется one-shot при первом запуске. Кнопка Refresh per-row в визарде делает fetch + meta + raw-cache одной подписки, не трогая остальные. Failed fetch сохраняет старый.raw(per-source resilience). Старые v2-v4 файлы автоматически мигрируются при первой загрузке. Подробности — SPEC 052. - Авто-обновление переписано на события — старый all-or-nothing цикл (один проход по всем подпискам, 2 ретрая по 20с, без интеграции с событиями) заменён на per-source конвейер: 1-часовой heartbeat обходит каждую включённую подписку отдельно и обновляет только просроченные по эффективному
reload; на ошибке фетча — один отложенный 15с ретрай (без рекурсии); события VPN (VpnStateChanged,ProxyActiveChanged) триггерят немедленный ретрай упавших источников. Per-source 5с cooldown защищает от штормов событий (последовательность suspend/resume/auto-reconnect больше не разваливается на N параллельных фетчей). State-level mutex (AppController.SubscriptionMu) сериализует load→mutate→save — UI Refresh поверх работающего heartbeat'а ждёт текущей записи, а не перетирает её. УдалёнshouldAutoUpdate(читал v4parser.last_updated, которого в v5 нет; старая логика дёргала Update на каждом запуске). Часть SPEC 052. - Pinned-ядро и шаблон — каждая сборка лаунчера теперь привязана к конкретной версии sing-box (
RequiredCoreVersion) и конкретному коммитуwizard_template.json(RequiredTemplateRef, инжектится в бинарник через-ldflags). Core Dashboard больше не опрашивает GitHub в поисках свежих релизов sing-box и не показывает «Update to vX.Y.Z» — обновляется ядро только вместе с лаунчером. При апгрейде лаунчера локальный шаблон инвалидируется, появляется кнопка «Download Template» — чтобы формат всегда соответствовал версии лаунчера. Сборкиgo run .используют source-defaultRequiredTemplateRef, который maintainer бампит наorigin/mainпосле каждого релиза. Подробности — SPEC 046. - Один in-place toast подписок под Exit — старый дизайн (попап-диалоги, прерывающие пользователя, прогресс-бар посреди дашборда между несвязанными блоками) ушёл. Вся обратная связь по операциям с подписками — в одной карточке под кнопкой Exit: пока идёт — нейтральная иконка
⋯, заголовок «Обновление подписок» и текущая деталь («Fetching 3/5: …») с живым прогресс-баром; по завершении — зелёная✓(успех) или красная✗(ошибка), сообщение и кнопка×, авто-скрытие через 20с. Per-source прогресс (Fetching N/M: <url>) вместо длинного пустого «Starting…». - Soft-cap для авто-пинга на больших подписках — авто-пинг через 5 секунд после connect'а (и после wake) теперь пропускается, если в списке нод больше 150. На подписках от CIDR-парсеров с сотнями нод одновременный ping-all открывал столько TCP+TLS-хэндшейков через TUN, что игровые клиенты могли подвисать на минуту-две на экране логина. Ручная «Test» в Servers,
Cmd/Ctrl+Pи/action/ping-alldebug-API — без cap'а, через них пингуется всё. Порог настраивается вbin/settings.json→auto_ping_after_connect_max_proxies(положительный int;0/ отсутствует = дефолт 150). Подробности — SPEC 039 §1.3. GET /debug/snapshot— новый эндпоинт debug-API: возвращает JSON-объект с четырьмя файлами wizard-pipeline'а (wizard_template.json,wizard_states/state.json, in-memory parsed-cache,config.json) разом. Отсутствующие или битые-как-JSON файлы попадают вmissing[]/errors{}соответственно, не валя весь snapshot. Без редакции: bearer-токен на endpoint'е и есть trust-boundary, маскировать секреты от своего же запроса — security theatre. Удобно для захвата golden-данных, bug-репродов, будущих MCP-обёрток. Подробности — SPEC 038 SUB_SPEC_SNAPSHOT.md.- Типизированная event-шина — новый пакет
core/eventsзаменяет неявный observer-паттерн на типизированный интерфейсBus, enumEventKindи структурированные payload'ы (VpnStateChangedPayload,ProxyActiveChangedPayloadи др.). Sync-dispatch с panic-isolation на handler, idempotentCancel, cancel-внутри-handler безопасен. 11 тестов, race-detector clean. Фундамент SPEC 045 (state/config decoupling) и event-триггеров авто-обновления выше. Подробности — SPEC 047.
Исправлено
- Паника sing-box в VLESS outbound —
unknown packet encoding(issue #68). Парсер подписок копировал значение query-параметраpacketEncodingдословно вoutbounds[*]. Sing-box v1.13.x понимает только""/"xudp"/"packetaddr"; на любое другое значение (например,none) он не возвращает honest error, а паникует прямо при форматировании сообщения об ошибке — апстримныйE.New("unknown packet encoding: ", *string)передаёт указатель там, где ожидается строка. Триггер появлялся, когда сторонние провайдеры подписок начали отдаватьpacketEncoding=noneдля нод без xtls; лаунчер послушно копировал"packet_encoding":"none"вconfig.json, sing-boxcheckпадал exit-2, лаунчер не стартовал. Фикс — allow-list на стороне парсера:xudp/packetaddr(с case-нормализацией),noneмолча дропается (семантически эквивалентно отсутствию поля), всё прочее дропается с warning'ом. Апстрим-баг отдельно отслежен в SPEC 049. - Multi-stage orphan GC больше не съедает raw-кэш соседних стейджей. Сценарий: у пользователя стейджи A (активный) и B (сохранённый как
work-only) с разными подписками. Update на стейдже A раньше вызывалDeleteOrphansтолько с ID'шниками A →.rawстейджа B удалялись как orphan'ы → переключение обратно на B заставляло заново фетчить все подписки. Фикс: GC теперь обходитbin/wizard_states/*.jsonи объединяет всеSource.ID—.rawживёт пока на него ссылается хоть один стейдж. Срабатывает только на полных Update-проходах (ручной клик / 1ч heartbeat / VPN-event retry); per-source Refresh, Rebuild, старт приложения, Save/Load не триггерят. DeleteOrphansбольше не сносит сторонние файлы изbin/subscriptions/. Файлы, чьё stripped-имя не парсится как валидный ULID-подобный ID, остаются нетронутыми (has space.rawуцелеет). Корректно названные файлы не из known-set всё так же удаляются, как и раньше.
Техническое / Внутреннее
RequiredTemplateRefинжектится на этапе сборки через-ldflagsизgit rev-parse HEAD; CI-сборки всегда pin'ятся на свой коммит. Локальныйgo run .берёт source-default, который maintainer бампит наorigin/mainпосле каждого релиза (RELEASE_PROCESS §1.5).- Удалены:
GetLatestCoreVersion,CheckVersionInBackground,ShouldCheckVersion, core-sideGetCachedVersion/SetCachedVersion,CheckForUpdates,CoreVersionInfo,FallbackVersion,shouldAutoUpdate,attemptAutoUpdateWithRetries,calculateAutoUpdateInterval. Self-update самого лаунчера не затронут. - Новая функция
core.InvalidateTemplateIfStale(execDir), вызывается вmain.goдо построения UI; новое полеSettings.LastTemplateLauncherVersionвbin/settings.json, записывается после успешного «Download Template». - Схема
state.jsonбампнута v4 → v5 с авто-миграцией на первой загрузке. Новые типы:core/state/v5/{types,migration,raw_cache,ulid,adapter}.go.core/state/adapter.goмостит editable legacyParserConfigview ↔ canonicalConnectionsдля code-path'ов, которые ещё не мигрировали. core/build/parsed_cache.go(ParsedCachetype) заменяетcore/outboundscache.Snapshotкак in-memory носитель междуbuildSnapshotFromRawCacheиBuildConfig. Пакетcore/outboundscacheполностью убран.- Новый
events.Bus(sync-dispatch) + APIevents.{Subscribe,Publish,Cancel}, структуры payload'ов вcore/events/payloads.go. AppController.SubscriptionMu sync.Mutex— load→mutate→save сериализация дляrefreshSubscriptionsMetaAndCacheиRefreshSingleSubscription.AppController.autoUpdateRetryTimers map[ID]*time.Timer+autoUpdateEventLastFetch map[ID]time.Time, оба подautoUpdateRetryMu. Cleanup черезcancelAllRetryTimers()на shutdown.- Helper
effectiveReload(update *state.UpdateSpec, defaultReload string) time.Duration— per-source freshness-логика, тестируется изолированно. - Wizard-side cleanup:
WizardModel.Sources / GlobalOutbounds / Defaultsтеперь канонические;ParserConfigиParserConfigJSON— derived-кэши, обновляютсяRefreshDerivedParserConfig().AppendURLsToSourcesзаменяет старыеApplyURL/AppendURLs, классифицирует ввод в Source(subscription) / Source(server) сразу. - Source-row в конфигураторе получил подстрочку:
⚠ Nошибок ·⁙count ·↻interval ·🕒fetched-ago ·💾quota ·⏳expires. Edit / Refresh / Del — иконки-кнопки; индикатор типа убран (clutter);profile_titleстал primary label, URL подставляется только если title пуст. - Edit-окно разбито на Settings (editable: URL/URI/Tag/чекбоксы), Preview (парсит
.rawбез сети), Overview (read-only summary + raw body черезDecodeSubscriptionContent), JSON (v5 Source layout). - Debug API дефолт-порт сменён
9269→9263, чтобы на одном хосте десктоп и мобильный LxBox (использует9269) могли работать параллельно безaddress in use. Пользовательский порт через Settings → Diagnostics → «Debug API port» не затронут — изменился только дефолт при пустомdebug_api_portвsettings.json. - Build-скрипт:
build/build_darwin.sh -iтеперь делает ad-hoc resign бинаря после замены executable'а в/Applications(macOS Sonoma+ убивает процессы с mismatched signature). Подпись делается на копии вне bundle, чтобы избежать bundle-resource walking'а по принадлежащим root файламbin/logs.
Миграция
- После апгрейда существующий
bin/wizard_template.jsonбудет удалён один раз при первом запуске; UI попросит перекачать. Один клик, никаких других действий. bin/outbounds.cache.jsonудаляется one-shot после апгрейда — его роль заняли raw-тела подписок вbin/subscriptions/*.raw. Rebuild теперь парсит.rawнапрямую, без сетевого вызова.- Старый
state.json(v2/v3/v4) читается и переписывается в v5-формате при первой загрузке. Старая вложенная обёрткаparser_configуезжает внутрьconnections.outbounds/connections.sources; семантика сохраняется 1:1. - Если у вас был локально-модифицированный шаблон — забэкапьте его до апгрейда: для не-dev сборок инвалидация безусловная.