github Leadaxe/singbox-launcher v0.9.0
release v0.9.0

6 hours ago

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.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-v0.9.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-v0.9.0-win64.zip
  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 launcher will automatically download sing-box and wintun.dll on first launch

Windows 7 (x86, legacy)

  1. Download: singbox-launcher-v0.9.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.

Highlights (EN)

  • Connections redesign — state.json v5 + per-source meta + raw cache — the state.json layout has been redesigned: top-level meta (version, timestamps, comment) and connections (sources, outbounds, defaults) replace the legacy nested parser_config block. Each subscription source now persists its own metadata (profile_title, subscription-userinfo quota, 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 at bin/subscriptions/<id>.raw (atomic .tmp + Rename) — Rebuild now parses .raw files directly without a network round-trip; bin/outbounds.cache.json is 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 .raw intact (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. Dropped shouldAutoUpdate (read v4 parser.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 of wizard_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 via go run . use a source-default RequiredTemplateRef that the maintainer bumps to origin/main after 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-all opened 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-all debug-API endpoint are unaffected — only the automatic timer path is gated. The threshold is tunable via bin/settings.jsonauto_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 via missing[] / 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/events package replaces the implicit observer pattern with a typed Bus interface, EventKind enum, and structured payloads (VpnStateChangedPayload, ProxyActiveChangedPayload, etc.). Sync dispatch with panic-isolation per handler; idempotent Cancel; 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 parameter packetEncoding verbatim into the generated outbounds[*]. 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 in format.ToString while formatting the error message itself, because the upstream E.New("unknown packet encoding: ", *string) passes a pointer where a string is expected. The trigger appeared when third-party subscription producers started emitting packetEncoding=none for nodes that don't need xtls; the launcher dutifully wrote "packet_encoding":"none" into config.json, sing-box check exit-2'd, and the launcher couldn't start. Fix: parser-side allow-list — xudp / packetaddr (with case normalization), none silently 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 call DeleteOrphans with only A's IDs → B's .raw files were deleted as orphans → switching back to B forced full re-fetch of every subscription. Fix: GC now walks bin/wizard_states/*.json and unions every Source.ID; a .raw lives 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.
  • DeleteOrphans no longer nukes 3rd-party files in bin/subscriptions/. Files whose stripped name doesn't parse as a valid ULID-like ID are left alone (e.g. has space.raw survives). Properly-named files not in the known set are still removed, as before.

Technical / Internal

  • RequiredTemplateRef is injected at build time via -ldflags from git rev-parse HEAD; CI builds always pin to their own commit. Local go run . builds inherit a source-default that the maintainer bumps to origin/main after each release (RELEASE_PROCESS §1.5).
  • Removed: GetLatestCoreVersion, CheckVersionInBackground, ShouldCheckVersion, core-side GetCachedVersion / SetCachedVersion, CheckForUpdates, CoreVersionInfo, FallbackVersion, shouldAutoUpdate, attemptAutoUpdateWithRetries, calculateAutoUpdateInterval. Launcher self-update path is unchanged.
  • New core.InvalidateTemplateIfStale(execDir) called from main.go before the UI starts; new Settings.LastTemplateLauncherVersion field in bin/settings.json, written after a successful Download Template.
  • state.json schema 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.go bridges editable legacy ParserConfig view ↔ canonical Connections for code paths that haven't migrated yet.
  • core/build/parsed_cache.go (ParsedCache type) replaces core/outboundscache.Snapshot as the in-memory carrier between buildSnapshotFromRawCache and BuildConfig. The core/outboundscache package is retired entirely.
  • New events.Bus (sync dispatch) plus events.{Subscribe,Publish,Cancel} API, payload structs in core/events/payloads.go.
  • AppController.SubscriptionMu sync.Mutex — load→mutate→save serialization for refreshSubscriptionsMetaAndCache and RefreshSingleSubscription.
  • AppController.autoUpdateRetryTimers map[ID]*time.Timer + autoUpdateEventLastFetch map[ID]time.Time, both guarded by autoUpdateRetryMu. Cleaned up via cancelAllRetryTimers() 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 / Defaults are now canonical; ParserConfig and ParserConfigJSON are derived caches refreshed by RefreshDerivedParserConfig(). AppendURLsToSources replaces the older ApplyURL / AppendURLs helpers and classifies input into Source(subscription) / Source(server) directly.
  • Source row in the configurator gains a subtitle line: ⚠ N errors · count · interval · 🕒 fetched-ago · 💾 quota · expires. Edit / Refresh / Del are icon buttons; type indicator removed (clutter); profile_title becomes the primary label, URL falls back when title is missing.
  • Edit window split into Settings (editable: URL/URI/Tag/checkboxes), Preview (parses .raw cache without network), Overview (read-only summary + raw body decoded via DecodeSubscriptionContent), JSON (v5 Source layout).
  • Debug API default port changed 92699263 so a single host can run desktop and mobile LxBox (both use 9269) in parallel without address in use. User-set ports via Settings → Diagnostics → "Debug API port" are unaffected; only the value used when debug_api_port is absent / 0 in settings.json changed.
  • Build script: build/build_darwin.sh -i now 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-owned bin/logs files.

Migration notes

  • After upgrading, your existing bin/wizard_template.json will be removed once on first launch and the UI will prompt to re-download. One click; no further action.
  • bin/outbounds.cache.json is removed on first run after upgrade — the new raw-body cache (bin/subscriptions/*.raw) takes its place. Rebuild now parses .raw files directly, no network round-trip needed.
  • Old state.json (v2/v3/v4) is read and rewritten in v5 layout on first load. The legacy nested parser_config block is moved into connections.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.json v5 + per-source meta + raw cache — формат state.json переделан: top-level meta (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 (читал v4 parser.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-default RequiredTemplateRef, который 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-all debug-API — без cap'а, через них пингуется всё. Порог настраивается в bin/settings.jsonauto_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, enum EventKind и структурированные payload'ы (VpnStateChangedPayload, ProxyActiveChangedPayload и др.). Sync-dispatch с panic-isolation на handler, idempotent Cancel, 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-box check падал 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-side GetCachedVersion / 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 legacy ParserConfig view ↔ canonical Connections для code-path'ов, которые ещё не мигрировали.
  • core/build/parsed_cache.go (ParsedCache type) заменяет core/outboundscache.Snapshot как in-memory носитель между buildSnapshotFromRawCache и BuildConfig. Пакет core/outboundscache полностью убран.
  • Новый events.Bus (sync-dispatch) + API events.{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 дефолт-порт сменён 92699263, чтобы на одном хосте десктоп и мобильный 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 сборок инвалидация безусловная.

Don't miss a new singbox-launcher release

NewReleases is sending notifications on new releases.