Обзор
sing-box-vpn — локальный менеджер клиентских профилей для sing-box. Репозиторий содержит набор скриптов и шаблон конфигурации: скрипты читают *.json-профили, подставляют активный профиль в общий шаблон, валидируют результат и запускают sing-box через systemd.
В каждый момент времени активен ровно один профиль. Он подставляется в один outbound с тегом proxy-out; всё остальное — inbound'ы, DNS, маршрутизация и rule set'ы — живёт в общем шаблоне и не меняется при смене профиля.
Основные практические сценарии:
- локальный SOCKS/mixed-прокси на
127.0.0.1:12334для браузеров и CLI-утилит, поддерживающих SOCKS5 с удалённым DNS-резолвом; - переключение между несколькими outbound-серверами (разные провайдеры, протоколы, IPv4/IPv6);
- автопереключение на резервный профиль при недоступности активного через systemd-таймер.
Для кого это: тех, кто держит VPN-профили в git как обычные JSON-файлы, не хочет GUI, работает на Linux с systemd и готов к CLI. Если нужен Nekobox/Nekoray/Hiddify с per-app split tunneling и tray-индикатором — это не тот инструмент.
Быстрый старт
Минимальный сценарий: есть share-link и sing-box 1.13+, нужно завести локальный SOCKS-прокси за пять минут.
# 1. Клонировать
git clone https://github.com/alexk136/sing-box-vpn ~/sing-box-vpn
cd ~/sing-box-vpn
# 2. Добавить профиль из share-link
sudo ./vpn add my-profile 'vless://uuid@server:443?type=tcp&security=reality#name'
# 3. Установить (root, ~30 секунд)
sudo ./install.sh
# 4. Проверить, что SOCKS реально проксирует трафик
curl --socks5-hostname 127.0.0.1:12334 https://cloudflare.com/cdn-cgi/trace | grep -E '^(ip|colo|loc)='
В строке 4 IP в ответе должен совпадать с IP VPN-сервера, а не с провайдерским. Если не совпадает — см. §Диагностика. Для нестандартных share-link (WebSocket/gRPC/H2) вместо vpn add используйте vpn add-json с готовым JSON.
Что это и чем не является
Проект делает:
- хранит набор outbound-профилей как обычные JSON-файлы;
- выбирает активный профиль и рендерит финальный
config.jsonиз шаблона; - запускает и контролирует sing-box через systemd;
- валидирует конфигурацию через
sing-box check; - предоставляет локальный mixed/SOCKS endpoint и Clash API на loopback;
- опционально переключает профиль при отказе upstream.
Проект не делает:
- не разворачивает VPN-сервер и не генерирует серверные конфиги Hysteria2, VLESS, Xray и т. п.;
- не создаёт TUN/TAP-интерфейс и не проксирует системный трафик автоматически;
- не поставляет правил policy-routing/nftables для прозрачного проксирования (TPROXY inbound присутствует в шаблоне, но маршрутизация для него не настроена);
- не предоставляет GUI, индикатора трафика или per-app split tunneling;
- не управляет мобильными, macOS и Windows-клиентами — для них используются внешние приложения (Nekoray, v2rayN и т. п.).
Требования
| Компонент | Требование |
|---|---|
| ОС | 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-семействах. Поведение на других дистрибутивах не гарантируется.
Архитектура и поток данных
~/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)
▼
сервер
Слои:
- Source-профили — канонические
*.jsonв репозитории (на практике в~/sing-box-vpn/profiles/). Хранятся в Git, реальные секреты — в gitignore. - Runtime-профили — копии в
/etc/sing-box/profiles/с правами0640. Именно их читает генератор. - Active marker — файл
/etc/sing-box/active_profileс именем активного профиля. - Сгенерированный конфиг —
/etc/sing-box/config.json, собирается изsing-box-config.jsonпутём подстановки активного outbound'а. - Systemd unit —
sing-box.serviceзапускает/usr/local/bin/sing-box run -c /etc/sing-box/config.jsonс hardening-параметрами.
Шаблон sing-box-config.json содержит:
- два inbound:
mixed-inна::/12334иtproxy-inна::/12335(маршрутизация для TPROXY в проекте не настроена); - селектор
select,direct, и плейсхолдерproxy-out, который заменяется активным outbound'ом; - DNS-стек:
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; - маршруты: sniff, hijack порта 53, reject loopback:9090, bypass для
private-domainsrule set, bypass для ряда частных CIDR и.ru; - Clash API на
127.0.0.1:9090без аутентификации; route.default_domain_resolver = dns-remote(требует sing-box ≥ 1.13).
Установка
Стандартная установка запускается из каталога репозитория от root:
sudo ./install.sh
Скрипт:
- требует root и пишет лог в
/tmp/sing-box-install.log; - ищет
sing-boxвPATH; при отсутствии пробует AUR, иначе скачивает бинарь 1.11.0 (имейте в виду — см. раздел «Обновление sing-box»); - создаёт
/var/lib/sing-box, runtime-каталоги и копирует шаблон с профилями; - при отсутствии active marker выбирает
warp-client; - генерирует конфигурацию и прогоняет её через
sing-box check; - ставит и запускает
sing-box.service; - создаёт
/etc/sing-box/private-domains.txt(после старта сервиса); - по умолчанию ставит и включает failover; отключается переменной
SKIP_FAILOVER=1; - включает nftables и сохраняет текущий ruleset в
/etc/nftables.conf; - завершается прямой curl-проверкой внешнего IP без SOCKS.
До запуска 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 и шаблона.
Профили
Схема JSON
Один файл 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).
Добавление из URL
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 и временно делает его видимым в списке процессов.
Добавление из JSON
sudo ./vpn add-json my-profile ./profile.json
Проверяется только валидность JSON и наличие type, server, server_port. Полная проверка через sing-box check не выполняется; запустите её вручную.
IPv6-серверы
В 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 и т. п.).
CLI-команды
| Команда | 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 по каждому профилю вручную.
SOCKS, DNS и маршрутизация
SOCKS endpoint
- Протокол: mixed (SOCKS4, SOCKS5, HTTP CONNECT).
- Адрес по умолчанию:
127.0.0.1:12334(или[::1]:12334). - Inbound в шаблоне слушает на
::— это все IPv6-интерфейсы, а в зависимости от системы и IPv4. Без firewall это потенциально открывает прокси шире, чем loopback. - Аутентификация на inbound не задана.
Проверка:
curl --max-time 8 --socks5-hostname 127.0.0.1:12334 \
https://cloudflare.com/cdn-cgi/trace
--socks5-hostname обязателен: иначе DNS-резолв идёт мимо прокси.
DNS
dns-remote— DoHhttps://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.
Bypass
/etc/sing-box/private-domains.txt — rule set формата sing-box source JSONL. По умолчанию направляет эти домены в direct. Также в шаблоне:
- hijack DNS (порт 53 →
hijack-dns); - reject loopback:9090;
- direct для
.local,.localhost, ряда частных IPv4/IPv6 CIDR, домена.ru; - всё остальное →
proxy-out.
Полный список bypass-доменов и CIDR см. в sing-box-config.json и etc-sing-box-private-domains.txt.
Clash API
- Адрес:
127.0.0.1:9090. - Аутентификация не настроена.
- Это control plane (просмотр правил, live-трафик), а не ещё один proxy-endpoint.
Автопереключение (failover)
Алгоритм
failover.sh:
- проверяет активный профиль через SOCKS-запрос к
cloudflare.com/cdn-cgi/traceс таймаутомPROBE_TIMEOUT(8 секунд по умолчанию); - успех — наличие хотя бы одной из строк
ip=,colo=,loc=; - при отказе записывает timestamp в
/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 был бы выполнен, и не проверяет кандидатов.
Exit codes
| Код | Значение |
|---|---|
0 |
Текущий профиль исправен или переключение удалось |
1 |
Ни один кандидат не сработал |
2 |
Нет sudo или vpn не найден |
Systemd
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=~/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
Шаблон рассчитан на 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
Согласованность runtime
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-профиль отсутствуют.
Прямая vs SOCKS-проверка
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
IPv6
ip -6 route show default
Нет default route — IPv6-only профиль работать не будет, даже если сам сервер отвечает.
Ограничения и безопасность
Ограничения
- URL-парсер не покрывает все варианты share-link (transport WebSocket/gRPC/H2, plugin Shadowsocks, нестандартные схемы). Для них используйте
add-jsonс последующейsing-box check. - bracketed IPv6 (
[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.- Поведение на дистрибутивах вне Arch-based и Debian-семейства не проверялось.
- sing-box 1.11 (которую может скачать installer) несовместим с текущим шаблоном.
- sing-box 1.14+ потребует миграции DNS-схемы.
- Kill switch не реализован. При падении sing-box приложения, не настроенные на SOCKS, продолжат использовать direct network.
- Не гарантирует обход DPI или конкретных блокировок — это зависит от выбранного протокола и сервера.
Безопасность
- Секреты в профилях. Пароли, UUID и приватные ключи лежат в JSON-файлах в Git (если не в gitignore). Передача share-link в аргументах CLI оставляет секрет в shell history и делает его временно видимым в
ps. - Права на файлы. Source-профили от
vpn addсоздаются с umask пользователя, отvpn add-json—0644. Runtime-профили получают0640черезinstall.shиapply-profiles.sh. Это не шифрование и не защита от root. - Mixed inbound. Слушает на
::(все IPv6-интерфейсы и потенциально IPv4), аутентификация не задана. Без firewall прокси может быть доступен шире, чем loopback. - Clash API. Ограничен
127.0.0.1:9090, но аутентификация отсутствует. - Systemd hardening.
sing-box.serviceзапускается от root сCAP_NET_ADMIN,CAP_NET_RAW,CAP_NET_BIND_SERVICE; включеныNoNewPrivileges,ProtectSystem,ProtectHome,PrivateTmp,ProtectKernelTunablesи т. п. tls.insecure=true. В нескольких Hysteria2-профилях отключена проверка серверного сертификата. Это упрощает MITM и должно использоваться осознанно.- DNS-leaks. Гарантия отсутствия утечек возможна только для приложений, которые используют прокси и не выполняют собственный DoH/DoT. Уровень системы (systemd-resolved, NetworkManager) может ходить в обход.
- Bypass по дизайну.
.ru, private CIDR иprivate-domainsобходят VPN — это фича, а не баг, но читатель должен знать.
Rollback и удаление
rollback.sh отключает и останавливает sing-box.service, удаляет nftables table inet sing-box и отдельные policy-routing правила, пытается удалить iptables chain SINGBOX, пытается включить ранее отключённый Hiddify user-сервис. Рекомендует reboot.
rollback.sh не удаляет:
/etc/sing-box(конфиг и профили остаются);- source-профили;
- systemd unit;
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'ом.