sing-box-vpn — менеджер VPN-профилей для sing-box
CLI-обёртка вокруг sing-box: набор JSON-профилей, share-link, генератор конфигурации, systemd-сервис и автопереключение при отказе upstream.
CLI-обёртка вокруг sing-box: набор JSON-профилей, share-link, генератор конфигурации, systemd-сервис и автопереключение при отказе upstream.
sing-box-vpn — локальный менеджер клиентских профилей для sing-box. Репозиторий содержит набор скриптов и шаблон конфигурации: скрипты читают *.json-профили, подставляют активный профиль в общий шаблон, валидируют результат и запускают sing-box через systemd.
В каждый момент времени активен ровно один профиль. Он подставляется в один outbound с тегом proxy-out; всё остальное — inbound'ы, DNS, маршрутизация и rule set'ы — живёт в общем шаблоне и не меняется при смене профиля.
Основные практические сценарии:
127.0.0.1:12334 для браузеров и CLI-утилит, поддерживающих SOCKS5 с удалённым DNS-резолвом;Проект делает:
config.json из шаблона;sing-box check;Проект не делает:
| Компонент | Требование |
|---|---|
| ОС | Linux с systemd ≥ 245 |
| Ядро | 4.18+ |
| Bash | фактически обязателен (используются bash-массивы и [[ ]]) |
| Python | 3.8+ (для URL-парсера) |
| sing-box | 1.13.0+ (шаблон использует route.default_domain_resolver и action: sniff) |
| Прочие утилиты | jq, curl, systemctl, journalctl, nft/iptables, GNU coreutils |
| Права | root для install, apply, test-all, on/off/restart, use/add/add-json/del |
| Сеть | рабочий IPv4 или IPv6 egress; проверки зависят от https://cloudflare.com/cdn-cgi/trace и https://1.1.1.1/dns-query |
Проверялся на Arch-based и Debian-семействах. Поведение на других дистрибутивах не гарантируется.
/home/alex/sing-box-vpn/profiles/<name>.json
│
│ apply-profiles.sh (копирует source → runtime)
▼
/etc/sing-box/profiles/<name>.json
│
│ generate-config.sh (читает шаблон + active profile)
▼
/etc/sing-box/config.json
│
│ sing-box.service
▼
mixed/SOCKS на 127.0.0.1:12334
│
│ proxy-out → upstream (Hysteria2 / VLESS / Shadowsocks / VMess / Trojan)
▼
сервер
Слои:
*.json в репозитории (на практике в ~/sing-box-vpn/profiles/). Хранятся в Git, реальные секреты — в gitignore./etc/sing-box/profiles/ с правами 0640. Именно их читает генератор./etc/sing-box/active_profile с именем активного профиля./etc/sing-box/config.json, собирается из sing-box-config.json путём подстановки активного outbound'а.sing-box.service запускает /usr/local/bin/sing-box run -c /etc/sing-box/config.json с hardening-параметрами.Шаблон sing-box-config.json содержит:
mixed-in на :: / 12334 и tproxy-in на :: / 12335 (маршрутизация для TPROXY в проекте не настроена);select, direct, и плейсхолдер proxy-out, который заменяется активным outbound'ом;dns-remote (DoH 1.1.1.1 через proxy), dns-direct (тот же DoH напрямую), dns-lan (UDP на 192.168.0.1:53), dns-system-hosts, dns-local; final = dns-remote, strategy = prefer_ipv4;private-domains rule set, bypass для ряда частных CIDR и .ru;127.0.0.1:9090 без аутентификации;route.default_domain_resolver = dns-remote (требует sing-box ≥ 1.13).Стандартная установка запускается из каталога репозитория от root:
sudo ./install.sh
Скрипт:
/tmp/sing-box-install.log;sing-box в PATH; при отсутствии пробует AUR, иначе скачивает бинарь 1.11.0 (имейте в виду — см. раздел «Обновление sing-box»);/var/lib/sing-box, runtime-каталоги и копирует шаблон с профилями;warp-client;sing-box check;sing-box.service;/etc/sing-box/private-domains.txt (после старта сервиса);SKIP_FAILOVER=1;/etc/nftables.conf;До запуска install.sh в репозитории уже должен лежать runtime-пригодный профиль (по умолчанию ожидается warp-client.json). Без него шаг генерации конфигурации завершится ошибкой. Финальное сообщение VPN OK означает лишь успех прямой проверки egress и не подтверждает работу SOCKS.
Альтернативная установка без failover:
sudo SKIP_FAILOVER=1 ./install.sh
| Путь | Назначение |
|---|---|
~/sing-box-vpn/ |
Каталог репозитория |
~/sing-box-vpn/profiles/*.json |
Source-профили (в Git) |
~/sing-box-vpn/vpn |
CLI-скрипт |
~/sing-box-vpn/failover.sh |
Скрипт автопереключения |
~/sing-box-vpn/generate-config.sh |
Генератор конфигурации |
~/sing-box-vpn/sing-box-config.json |
Шаблон конфигурации |
/usr/local/bin/sing-box |
Бинарь sing-box |
/usr/local/libexec/sing-box-vpn/ |
Каталог, куда installer копирует vpn и failover.sh |
/etc/sing-box/ |
Runtime-каталог (настраивается через RUNTIME_DIR) |
/etc/sing-box/config.json |
Сгенерированная активная конфигурация |
/etc/sing-box/profiles/*.json |
Копии профилей, используемые runtime |
/etc/sing-box/active_profile |
Имя активного профиля |
/etc/sing-box/private-domains.txt |
Rule set для bypass-доменов |
/etc/systemd/system/sing-box.service |
Systemd unit |
/etc/systemd/system/vpn-failover.{service,timer} |
Failover unit + таймер |
/var/log/sing-box.log |
Файловый лог sing-box |
/var/lib/sing-box |
Каталог, разрешённый systemd sandboxing'ом |
/var/tmp/sing-box-vpn-broken/ |
Чёрный список временно нерабочих профилей |
/tmp/sing-box-install.log |
Лог установщика |
| Переменная | Default | Где действует |
|---|---|---|
PROJECT_DIR |
каталог, где лежит vpn |
CLI, генератор, failover, installer |
RUNTIME_DIR |
/etc/sing-box |
CLI, генератор, apply, installer |
PROFILES_DIR |
<PROJECT_DIR>/profiles (CLI/apply) / <RUNTIME_DIR>/profiles (generator) |
семантика различается по скриптам |
RUNTIME_PROFILES_DIR |
$RUNTIME_DIR/profiles |
CLI, apply |
ACTIVE_FILE |
$RUNTIME_DIR/active_profile |
CLI, генератор |
TEMPLATE |
$PROJECT_DIR/sing-box-config.json |
генератор |
OUT |
$RUNTIME_DIR/config.json |
генератор |
GEN_SCRIPT |
$PROJECT_DIR/generate-config.sh |
CLI |
CLI |
$PROJECT_DIR/vpn |
apply, test-all |
SERVICE_NAME |
sing-box |
CLI/apply/installer |
MIXED_PORT |
12334 |
CLI, failover; в шаблоне порт жёстко задан и не подменяется |
SING_BOX_BIN |
/usr/local/bin/sing-box |
installer |
SING_BOX |
/usr/local/bin/sing-box |
apply |
LOG |
/tmp/sing-box-install.log или rollback-лог |
installer, rollback |
SKIP_FAILOVER |
0 |
installer |
TRACE_HOST |
https://cloudflare.com/cdn-cgi/trace |
failover |
PROBE_TIMEOUT |
8 секунд |
failover |
DRY_RUN |
0 |
failover |
BROKEN_DIR |
/var/tmp/sing-box-vpn-broken |
failover |
BROKEN_TTL_SEC |
1800 |
failover |
Часть переменных заявлена в vpn как override, но фактически не читается unit'ом и шаблоном: RUNTIME_DIR, SING_BOX_BIN, MIXED_PORT (в шаблоне всегда 12334), CONFIG_TEMPLATE/CONFIG_OUT (генератор использует TEMPLATE/OUT). Нестандартные значения этих переменных считайте неподдерживаемыми без ручной правки unit и шаблона.
Один файл profiles/<name>.json соответствует одному outbound. Имя файла должно совпадать с полем внутри (хотя vpn add-json это не проверяет — см. ниже).
Минимальный набор полей:
{
"type": "hysteria2",
"server": "example.com",
"server_port": 443,
"password": "..."
}
Поддерживаются поля, специфичные для протокола: tls, transport, multiplex, flow и т. д. Они почти без изменений переносятся в сгенерированный outbound; исключаются только служебные (name, description, type, tag, server, server_port).
sudo ./vpn add my-profile 'vless://uuid@server:443?type=tcp&security=reality#name'
Поддерживаемые схемы: vless://, hy2:// / hysteria2://, ss://, vmess://, trojan://. Парсер не покрывает все варианты share-link (транспорт WebSocket/gRPC, плагины Shadowsocks, нестандартные схемы) — для таких случаев используйте add-json.
Передача URL в аргументах сохраняет секрет в shell history и временно делает его видимым в списке процессов.
sudo ./vpn add-json my-profile ./profile.json
Проверяется только валидность JSON и наличие type, server, server_port. Полная проверка через sing-box check не выполняется; запустите её вручную.
В JSON-файле адрес IPv6 указывается без квадратных скобок:
{ "type": "hysteria2", "server": "2001:db8::1", "server_port": 443, ... }
Для работы IPv6-only профиля на хосте должен быть IPv6 default route. Проверка: ip -6 route show default.
Реальные профили исключены из Git через .gitignore. Это не шифрование и не гарантия, что секреты не попали в историю коммитов до того, как были добавлены в gitignore. Для шеринга между машинами используйте отдельный зашифрованный канал (gpg-агент, age, vault и т. п.).
| Команда | Root | Что делает |
|---|---|---|
./vpn on |
да | Запускает sing-box.service через systemd; если уже активен — генерирует конфиг и выполняет SOCKS-trace |
./vpn off |
да | Останавливает sing-box.service. nftables/iptables не сбрасываются |
./vpn restart |
да | systemctl restart sing-box. Конфиг предварительно не генерируется |
./vpn status |
нет | Показывает состояние сервиса, active marker, source/runtime-пути и Clash API; проверяет внешний IP напрямую, не через SOCKS |
./vpn list |
нет | Печатает source-профили с description и маркером активного |
./vpn current |
нет | Печатает имя активного профиля или (none) |
./vpn use <name> |
да | Записывает active marker, генерирует конфиг, перезапускает сервис только если он уже был активен; sing-box check не выполняет |
./vpn add <name> <URL> |
да | Создаёт source-профиль из share-link; runtime и сервис не трогает |
./vpn add-json <name> <file> |
да | Копирует JSON как source-профиль с правами 0644; базовая валидация (JSON + три обязательных поля) |
./vpn del <name> |
да | Удаляет source- и runtime-файл; активный профиль удалить нельзя; конфиг/сервис не перегенерирует |
./vpn test |
нет | До трёх SOCKS-запросов к Cloudflare; ожидает строки ip=, colo=, loc=. Из-за set -euo pipefail неуспешный pipeline может прервать команду до повторов |
./vpn help |
нет | Печатает справку |
Имена профилей в mutating-командах не санитизируются. Безопасный формат — ^[A-Za-z0-9._-]+$.
Помимо vpn, есть три вспомогательных скрипта:
sudo ./generate-config.sh — только собирает /etc/sing-box/config.json из шаблона и active profile. Не валидирует и не перезапускает сервис.sudo ./apply-profiles.sh — копирует source-профили в runtime, перебирает все профили, для каждого меняет active marker, генерирует конфиг и прогоняет SOCKS-тест; при полном успехе восстанавливает исходный active (или warp-client). Stale runtime-файлы не удаляет.sudo ./test-all.sh — запускает сервис, перебирает и тестирует все source-профили; исходный активный профиль не восстанавливает.Из-за set -euo pipefail в этих скриптах первая неудачная проверка может прервать выполнение до сбора полного summary. Если нужна полная картина — запускайте vpn test по каждому профилю вручную.
127.0.0.1:12334 (или [::1]:12334).:: — это все IPv6-интерфейсы, а в зависимости от системы и IPv4. Без firewall это потенциально открывает прокси шире, чем loopback.Проверка:
curl --max-time 8 --socks5-hostname 127.0.0.1:12334 \
https://cloudflare.com/cdn-cgi/trace
--socks5-hostname обязателен: иначе DNS-резолв идёт мимо прокси.
dns-remote — DoH https://1.1.1.1/dns-query через proxy-out (трафик DNS идёт через туннель).dns-direct — тот же DoH напрямую.dns-lan — UDP на 192.168.0.1:53 для локальных имён.dns-system-hosts и dns-local — для /etc/hosts и коротких имён.final = dns-remote, strategy = prefer_ipv4./etc/sing-box/private-domains.txt — rule set формата sing-box source JSONL. По умолчанию направляет эти домены в direct. Также в шаблоне:
hijack-dns);.local, .localhost, ряда частных IPv4/IPv6 CIDR, домена .ru;proxy-out.Полный список bypass-доменов и CIDR см. в sing-box-config.json и etc-sing-box-private-domains.txt.
127.0.0.1:9090.failover.sh:
cloudflare.com/cdn-cgi/trace с таймаутом PROBE_TIMEOUT (8 секунд по умолчанию);ip=, colo=, loc=;/var/tmp/sing-box-vpn-broken/<profile> (TTL = BROKEN_TTL_SEC, 30 минут);vpn list, исключает активный и недавно отказавшие;sudo vpn use <candidate> и повторяет probe;1; активным может остаться последний проверенный.TRACE_HOST, PROBE_TIMEOUT, MIXED_PORT, LOG_TAG, DRY_RUN, BROKEN_DIR, BROKEN_TTL_SEC, VPN (путь к vpn).
DRY_RUN=1 только печатает, какой vpn use был бы выполнен, и не проверяет кандидатов.
| Код | Значение |
|---|---|
0 |
Текущий профиль исправен или переключение удалось |
1 |
Ни один кандидат не сработал |
2 |
Нет sudo или vpn не найден |
System-wide таймер: первый запуск через 2 минуты после boot, затем каждые 5 минут, persistent.
TimeoutStartSec=30s на сервисе. Полный обход нескольких профилей может не уложиться (один probe — до 8 секунд, плюс sleep 2 между кандидатами). При большом числе профилей увеличьте таймаут через drop-in.
Стандартный install.sh копирует vpn и failover.sh в /usr/local/libexec/sing-box-vpn. После этого vpn начинает считать этот каталог PROJECT_DIR, но шаблона и profiles/ там нет. Для корректной работы rotation задайте в /etc/default/vpn-failover:
VPN=/home/alex/sing-box-vpn/vpn
Per-user инструкция в docs/FAILOVER.md копирует только unit-файлы, тогда как unit ожидает скрипты в %h/.local/bin/ и %h/.local/libexec/sing-box-vpn/. Положите их туда вручную или используйте system-wide установку.
Для журналов надёжнее:
journalctl -u vpn-failover.service
а не journalctl -t vpn-failover (скрипт не вызывает logger, в unit не задан SyslogIdentifier).
sudo ./vpn status # состояние сервиса и active profile
./vpn list # все source-профили
./vpn current # имя активного профиля
sudo ./vpn use my-profile # переключиться
sudo ./vpn add new-profile 'vless://...' # добавить из share-link
sudo ./vpn del old-profile # удалить профиль
sudo systemctl restart sing-box # применить изменения вне CLI
journalctl -u sing-box -f # лог sing-box в реальном времени
journalctl -u vpn-failover.service # лог failover
systemctl list-timers vpn-failover.timer # состояние таймера
В репозитории нет файлов с shell-aliases (vpnon, vpnuse и т. п.), которые упоминаются в docs/OPERATIONS.md. Если нужны — добавьте сами в ~/.bashrc или в /etc/profile.d/.
Шаблон рассчитан на sing-box ≥ 1.13 и использует route.default_domain_resolver и action: sniff. При обновлении до 1.14+ потребуется миграция DNS-схемы (см. CHANGELOG.md).
Рекомендуемый порядок:
# 1. проверить версии
/usr/local/bin/sing-box version
sing-box version # новый бинарь
# 2. бэкап
cp -a /etc/sing-box /etc/sing-box.bak
cp -a ~/sing-box-vpn ~/sing-box-vpn.bak
# 3. валидация новым бинарём
sudo /usr/local/bin/sing-box check -c /etc/sing-box/config.json
# 4. перегенерация и перезапуск
sudo ~/sing-box-vpn/generate-config.sh
sudo systemctl restart sing-box
# 5. проверка
~/sing-box-vpn/vpn test
systemctl status vpn-failover.timer
В комплекте есть scripts/run-after-upgrade.sh, но он жёстко привязан к конкретному списку профилей и перезапускает сервис до замены симлинка. Для продакшна используйте его только как отправную точку.
~/sing-box-vpn/vpn status
~/sing-box-vpn/vpn current
~/sing-box-vpn/vpn list
systemctl status sing-box
systemctl is-active sing-box
active_profile, runtime-профиль и сгенерированный конфиг должны соответствовать друг другу:
cat /etc/sing-box/active_profile
ls /etc/sing-box/profiles/
sudo sing-box check -c /etc/sing-box/config.json
Генератор завершится ошибкой, если active marker или runtime-профиль отсутствуют.
curl https://cloudflare.com/cdn-cgi/trace
curl --socks5-hostname 127.0.0.1:12334 https://cloudflare.com/cdn-cgi/trace
Сравните поля ip= и colo=. vpn status выполняет прямую проверку, не SOCKS — учитывайте это.
journalctl -u sing-box -n 200 --no-pager
journalctl -u sing-box -f
tail -f /var/log/sing-box.log
journalctl -u vpn-failover.service -n 100 --no-pager
ip -6 route show default
Нет default route — IPv6-only профиль работать не будет, даже если сам сервер отвечает.
ps.vpn add создаются с umask пользователя, от vpn add-json — 0644. Runtime-профили получают 0640 через install.sh и apply-profiles.sh. Это не шифрование и не защита от root.:: (все IPv6-интерфейсы и потенциально IPv4), аутентификация не задана. Без firewall прокси может быть доступен шире, чем loopback.127.0.0.1:9090, но аутентификация отсутствует.sing-box.service запускается от root с CAP_NET_ADMIN, CAP_NET_RAW, CAP_NET_BIND_SERVICE; включены NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, ProtectKernelTunables и т. п.tls.insecure=true. В нескольких Hysteria2-профилях отключена проверка серверного сертификата. Это упрощает MITM и должно использоваться осознанно..ru, private CIDR, private-domains обходят VPN по дизайну.add-json с последующей sing-box check.[2001:db8::1]:443) в URL обрабатывается некорректно; в JSON пишите IPv6 без скобок.vpn test, apply-profiles.sh и test-all.sh используют set -euo pipefail — первая неуспешная проверка может прервать скрипт до сбора полного summary.vpn status показывает внешний IP через прямой запрос, не через SOCKS. Для проверки именно туннеля используйте ручной curl --socks5-hostname.apply-profiles.sh не удаляет stale runtime-файлы при удалении соответствующего source-профиля.rollback.sh — частичный emergency rollback, не uninstall.rollback.sh отключает и останавливает sing-box.service, удаляет nftables table inet sing-box и отдельные policy-routing правила, пытается удалить iptables chain SINGBOX, пытается включить ранее отключённый Hiddify user-сервис. Рекомендует reboot.
rollback.sh не удаляет:
/etc/sing-box (конфиг и профили остаются);vpn-failover.timer;/usr/local/bin/vpn и /usr/local/bin/vpn-failover;Для полного удаления вручную:
sudo systemctl disable --now sing-box vpn-failover.timer
sudo rm -f /etc/systemd/system/sing-box.service \
/etc/systemd/system/vpn-failover.service \
/etc/systemd/system/vpn-failover.timer
sudo systemctl daemon-reload
sudo rm -rf /etc/sing-box /usr/local/libexec/sing-box-vpn
sudo rm -f /usr/local/bin/vpn /usr/local/bin/vpn-failover
После — откатите изменения в /etc/nftables.conf, если они были сделаны installer'ом.
См. раздел «CLI-команды».
См. раздел «Переменные окружения».
sing-box.service — основной сервис.vpn-failover.service + vpn-failover.timer — автопереключение.journalctl -u sing-boxjournalctl -u vpn-failover.service/var/log/sing-box.log/tmp/sing-box-install.log