golang

Sing-Box Launcher теперь на macOS

  • суббота, 20 декабря 2025 г. в 00:00:09
https://habr.com/ru/articles/978634/

Месяц назад я описывал wizard-подход к настройке sing-box без ручного JSON. Теперь это полноценный нативный macOS-клиент и Windows-клиент с TUN, системным прокси, menu bar, быстрым переключением узлов и решением бага Fyne через Objective-C.


О чём была первая статья

В прошлой публикации я разбирал две главные боли при работе с sing-box: ручное редактирование JSON и теги узлов, которые «плывут» после обновления подписки. Решением стал wizard — он собирает конфиг из проверенных блоков, а не редактирует текст напрямую. Статья зашла, в комментариях спрашивали про macOS. Вот он.

Если не читали первую статью и хотите понять контекст глубже — ссылка выше. Здесь я не буду повторять теорию, а расскажу, что изменилось на практике.

Ответы на вопросы из комментариев

Это vibe coding проект?

Я очень активно использую AI-помощников при разработке. Честно говоря, без них я бы лет пять назад даже не взялся за такое.

Последние годы я больше CEO и фронтмен своей команды, чем разработчик. Было интересно самому понять, насколько можно поднять производительность, используя новые инструменты. Оказалось — можно. Go я до этого проекта не писал, Fyne не трогал, CGO с Objective-C — тем более. AI помогает разобраться быстрее, чем читать документацию с нуля.

Отдельный бонус — документация. Раньше я бы её точно забросил: писать README и гайды по шаблонам — не самое увлекательное занятие. С AI получилось сделать подробную документацию на двух языках, описать формат шаблоновнастройку парсера. Без AI на это просто не хватило бы времени.

Текст писал я сам, это не генерированный мусор от GPT :)

Хотите сделать лучший клиент?

Нет. Хочу сделать удобный. Однокнопочный (или минимум лишних кнопок), точно не IDE вокруг конфигов.

У того же Hiddify много функционала. Но я не представляю, как объяснить этот функционал друзьям или родне. А вот моё приложение они ставят легко: скачал, запустил, прошёл wizard, работает.

Цель — не «самый мощный клиент», а «клиент, который можно дать человеку без технического бэкграунда».

Зачем это нужно: кейс с путешествиями

Часто езжу с ноутбуком и удивляюсь, когда из Стамбула не открываются госуслуги. Правильно настроенная маршрутизация позволяет открывать и госуслуги, и Gemini, и GitHub, смотреть Netflix и слушать Яндекс.Музыку — из любой точки мира. Один клиент, одна кнопка.

Как это работает:

1. Парсер группирует узлы по регионам на основе тегов из подписки. В @ParserConfig указываем фильтры:

{
  "tag": "us-ca-selector",
  "type": "selector",
  "options": { "default": "us-ca-fast" },
  "filters": { 
    "tag": "/(🇺🇸|US|USA|United States|🇨🇦|CA|Canada)/i" 
  },
  "comment": "Ручной выбор узлов США/Канады"
}

Парсер находит все узлы с тегами, содержащими флаги или названия стран, и добавляет их в этот selector.

2. Правила маршрутизации направляют трафик. В шаблоне конфига это выглядит так:

/**   @SelectableRule
      @label Gemini через США/Канаду
      @default
      @description Gemini требует узлы из США или Канады для работы
      { "rule_set": "gemini", "outbound": "us-ca-selector" },
*/
/**   @SelectableRule
      @label Российские домены через российские узлы
      @default
      { "rule_set": "ru-domains", "outbound": "ru-selector" },
*/

В wizard эти правила превращаются в чекбоксы с выпадающим списком outbound'ов. Пользователь просто ставит галочки — не нужно редактировать JSON.

Результат из любой точки мира:

  • ✅ Госуслуги открываются (видят российский IP)

  • ✅ Gemini работает (видит IP из США)

  • ✅ Яндекс.Музыка играет (геоблокировка обходится)

  • ✅ Netflix показывает контент

  • ✅ Локальные проекты (localhost*.local) идут напрямую

  • ✅ Рабочий VPN работает параллельно — корпоративные ресурсы не ломаются

Всё автоматически, без ручного переключения. Всё это настраивается один раз в wizard и продолжает работать при обновлении подписки.

Кейс: раздать людям инструмент

Если вы админ или просто «тот человек, который настраивает интернет» для друзей и родни — знаете эту боль. Нужно раздать 100 людям работающий прокси. Каждому объяснять, что такое JSON и где менять server — не вариант.

С wizard это решается иначе:

  1. Создаёте свой шаблон — один раз. Прописываете правила маршрутизации, selectors, rule-sets. Формат полностью открыт и задокументирован.

  2. Кладёте шаблон в папку приложения — config_template.json.

  3. Раздаёте людям приложение + ссылку на подписку. Они запускают wizard, вводят подписку, жмут «Сохранить». Всё.

Шаблон — ваш. Вы контролируете, какие правила будут у пользователей. Они видят только простой интерфейс с чекбоксами. Обновилась подписка — пользователь прошёл wizard заново, конфиг пересобрался с теми же правилами.

Это не «дать человеку JSON и надеяться, что он не сломает». Это «дать готовый инструмент, где сломать нечего».

От прототипа к клиенту

На момент первой статьи был рабочий Windows-клиент и идея wizard-подхода. Парсер подписок работал, но в базовом виде.

Сейчас:

  • Полноценный macOS-клиент (версия 0.5.0)

  • Wizard и парсер отработаны на реальных сценариях

  • Системный прокси с автоматической регистрацией

  • Трей с быстрым переключением прокси

Wizard показал себя хорошо: с одной стороны, его можно гибко конфигурировать через шаблон, с другой — для пользователя он выглядит максимально просто.

Почему macOS — не порт, а отдельная работа

Перенести приложение на macOS — это не «пересобрать с другим флагом». Пришлось разбираться с платформенными нюансами.

Системный прокси

На Windows лаунчер работает через TUN-интерфейс (wintun.dll). На macOS — другой подход: системный прокси.

Как это устроено:

  • В конфиге sing-box используется mixed inbound с флагом set_system_proxy: true

  • sing-box сам регистрирует прокси в системных настройках

  • Браузеры и большинство приложений подхватывают настройки автоматически

  • Не нужны права администратора для базового сценария

Для определения текущих настроек прокси приложение использует networksetup — стандартную утилиту macOS. Парсит вывод, определяет приоритет сетевых интерфейсов (Wi-Fi, Ethernet и т.д.) и находит активный.

Для 90% задач — достаточно. Если приложение игнорирует системный прокси (некоторые игры, торрент-клиенты) — да, TUN был бы нужен. Но TUN на macOS требует Network Extension, Apple Developer Account ($99/год) и нотаризацию. Для open-source проекта это пока избыточно.

Трей и меню

macOS-специфика, которую ожидаешь от нативного приложения:

  • Иконка в menu bar

  • Быстрое переключение прокси без открытия главного окна

  • При закрытии окна приложение остаётся в трее, а не завершается

Dock: пришлось писать на Objective-C

Здесь была интересная техническая проблема.

Fyne (GUI-фреймворк на Go) имеет известный баг: после Hide() окна при клике на иконку в Dock окно показывается, но становится неотзывчивым — не реагирует на клики, кнопки close/minimize/maximize не работают.

Стандартного решения в Fyne нет. Пришлось написать обработчик на Objective-C через CGO: динамически создать класс DockReopenHandler, добавить метод applicationShouldHandleReopen:hasVisibleWindows: через Objective-C runtime и установить его как NSApplicationDelegate. Когда пользователь кликает на иконку в Dock — вызывается Go-callback, который корректно показывает окно.

Код обработчика Dock (Objective-C + CGO)
// Создаём класс динамически через Objective-C runtime
Class delegateClass = objc_allocateClassPair(nsObjectClass, "DockReopenHandler", 0);

// Добавляем метод applicationShouldHandleReopen:hasVisibleWindows:
SEL selector = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
class_addMethod(delegateClass, selector, (IMP)handleApplicationShouldHandleReopen, "c@:@c");

// Регистрируем класс и назначаем делегатом NSApp
objc_registerClassPair(delegateClass);
[NSApp setDelegate:dockDelegate];

Полный код: internal/platform/dock_handler.go

Теперь клик по Dock работает как ожидается: окно появляется и реагирует на ввод. Решение универсальное — работает на всех версиях macOS от 11 до 15, протестировано на Intel и Apple Silicon.

Wizard на практике

Не буду в деталях, как устроен wizard — это в документации. Расскажу, что показало практическое использование.

Что подтвердилось

Конфиг не ломается при обновлениях. Wizard собирает его заново из тех же блоков. Шаги те же — результат предсказуемый.

Валидация до сохранения ловит ошибки. Раньше типичный сценарий: сохранил конфиг → sing-box не стартует → ищешь, где ошибка в JSON. Теперь wizard проверяет структуру до записи на диск.

Резервное копирование спасает. Перед каждым сохранением нового конфига приложение делает копию старого. Когда экспериментируешь с правилами маршрутизации — это важно.

Что доработали после первой статьи

После публикации пришёл фидбек, часть вещей поправили:

  • Улучшили обработку ошибок при парсинге подписок

  • Добавили поддержку большего количества форматов узлов

  • Исправили краевые случаи в миграции конфигов между версиями

Парсер подписок

Полная документация: ParserConfig

Поддержка протоколов

Парсер поддерживает основные протоколы:

  • VLESS (включая Reality) — мой основной выбор

  • VMess (base64 JSON)

  • Trojan

  • Shadowsocks (SIP002)

  • Hysteria2

Стабильность тегов

Напомню проблему одним предложением: провайдер переименовал узлы после обновления, и правила маршрутизации перестали работать.

Пример: было HK-1, стало Hong Kong #12. Правило ссылалось на HK-1 — теперь оно не матчится ни с чем.

Что делает парсер:

  • Нормализует теги (можно задать префиксы, постфиксы, маски с переменными)

  • Группирует узлы в селекторы по фильтрам (regex, literal, negation)

  • При дубликатах тегов автоматически добавляет суффикс (HK-1HK-1-2HK-1-3)

  • Правила ссылаются на группы, а не на конкретные имена узлов

Результат: обновление подписки не ломает маршрутизацию. Узлы могут переименовываться — группы остаются стабильными.

Быстрый старт на macOS

Пять шагов от скачивания до работающего прокси. Подробнее — в README.

  1. Скачать .zip с GitHub Releases

  2. Распаковать, перетащить в Applications

  3. Запустить. Gatekeeper спросит разрешение — это нормально для приложений не из App Store

  4. Приложение само скачает sing-box — ничего ставить руками не нужно

  5. Пройти wizard: указать подписку, выбрать правила, сохранить. Запустить.

Системный прокси включится автоматически. Проверить можно здесь: System Preferences → Network → (ваше подключение) → Advanced → Proxies.

Если уже есть рабочий config.json — положите его в папку приложения. Wizard сделает бэкап перед перезаписью.

Что не сделано

Честно о том, чего нет.

TUN-режим. Требует Apple Developer Account, Network Extension и нотаризацию. Пока не в приоритете. Если это критично для вас — напишите в issues, посмотрим на спрос.

Автообновление приложения. Пока ручное. Проверяйте релизы на GitHub.

Часть опций sing-box. Это не «IDE для sing-box». Фокус — на повторяемой настройке и стабильных обновлениях подписок. Если нужны все опции — есть ручной JSON.

Что дальше

Не roadmap с датами, а направления.

Главное — развитие wizard. Идея оказалась рабочей: шаблон + визуальный выбор правил + автоматическая сборка конфига. Хочется развивать:

  • Больше готовых шаблонов под разные сценарии (путешествия, работа, gaming)

  • Упрощение создания своих шаблонов, (может быть колекции шаблонов?)

  • Визуальный редактор правил маршрутизации

Парсер подписок: больше протоколов, обработка edge-кейсов.

Linux-версия: нужна помощь с тестированием в разных DE и дистрибутивах.

Если хотите помочь — пишите в issues или PR.

Итого

Wizard-подход оказался рабочим. Теперь это не прототип, а полноценный macOS-клиент с системным прокси, треем и предсказуемым поведением.

Если пользуетесь sing-box на маке — попробуйте. Буду рад фидбеку в комментариях или в issues на GitHub.

Ссылки:

Опрос

Что важнее всего для вас в macOS-клиенте sing-box?

  • Стабильная работа системного прокси

  • Быстрое переключение прокси из трея

  • Wizard для настройки без JSON

  • TUN-режим (даже если сложнее в настройке)