
Сетевой SSH-роутер — направляет соединения на разные IP-адреса/порты/ключи/шлюзы в зависимости от активной VPN-сети или сетевого подключения.
Сетевой SSH-маршрутизатор. Определяет вашу активную сеть или VPN и автоматически выбирает правильный хост, порт, файл ключа и шлюз для каждого SSH-подключения — без изменений в ~/.ssh/config.
Определите каждый логический хост один раз с профилем default и опциональными переопределениями для каждой сети. При каждом подключении sshroute определяет, в какой сети вы находитесь (VPN, офисная LAN, WireGuard-пир и т. д.), и разрешает правильные SSH-параметры перед передачей управления настоящему /usr/bin/ssh.
ssh myserver
→ sshroute определяет: corp-vpn активен
→ разрешает: 10.100.0.50:2222 через bastion.corp.internal
→ exec /usr/bin/ssh -p 2222 -i ~/.ssh/corp_key -J bastion.corp.internal 10.100.0.50
В вашей лаборатории, скорее всего, есть как минимум две реальности: вы либо дома в локальной сети, либо в отъезде и подключаетесь через WireGuard или другой VPN. Проблема в том, что ~/.ssh/config не знает, в какой из них вы находитесь — в итоге вам приходится создавать отдельные алиасы (server-lan, server-vpn), или шлюз, который работает только половину времени, или просто запоминать IP-адреса.
sshroute решает эту проблему, определяя текущую сеть перед каждым подключением. Когда интерфейс WireGuard активен и существует маршрут к пиру, он подключается напрямую к туннельному IP. Когда вы в локальной сети — использует локальный адрес. Если ни один из адресов не доступен, он переключается на публичное имя хоста. Один алиас, три реальности, ноль ручного переключения.
Кроме того, он прозрачно перехватывает SSH — git push, rsync, scp будут работать через него автоматически после настройки теневого режима. Никаких обёрток, шелл-функций или размышлений.
Корпоративные сети ещё сложнее. У вас есть публичный интернет, возможно, VPN «сайт-сайт», возможно, личный VPN с разделением трафика, а внутри — разные шлюзы в зависимости от целевой среды — dev, staging, prod, у каждой свой бастион и ключ. Поддерживать всё это в ~/.ssh/config означает либо один огромный конфиг, который ломается при любом изменении инфраструктуры, либо писать скрипт, который каждый член команды поддерживает по-своему.
sshroute позволяет определить логику маршрутизации декларативно, хранить её в версионируемом YAML-файле и распространять среди команды. Одна и та же конфигурация работает для всех — правильная сеть определяется автоматически на основе активных интерфейсов или маршрутов на каждой машине. Ключи, порты, пользователи и шлюзы разрешаются без необходимости думать об этом.
Teleport и Boundary — это другая категория: они добавляют контроль доступа, журналы аудита и аутентификацию на основе сертификатов поверх маршрутизации. Если вам нужно именно это — используйте их. sshroute предназначен для тех случаев, когда нужна интеллектуальная маршрутизация без операционных издержек центрального сервера аутентификации.
Загрузите последний релиз с GitHub Releases. Доступны бинарные файлы для Linux, macOS и Android на архитектурах AMD64 и ARM64.
go install github.com/thereisnotime/sshroute@latest
Загрузите архив android_arm64 с GitHub Releases, распакуйте и поместите бинарный файл в ~/.local/bin:
mkdir -p ~/.local/bin
curl -Lo "$TMPDIR/sshroute.tar.gz" \
https://github.com/thereisnotime/sshroute/releases/latest/download/sshroute_android_arm64.tar.gz
tar -xzf "$TMPDIR/sshroute.tar.gz" -C ~/.local/bin sshroute
chmod +x ~/.local/bin/sshroute
Добавьте ~/.local/bin в PATH в ~/.bashrc или ~/.profile, если ещё не добавлено:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
В качестве альтернативы скомпилируйте из исходного кода с Go от Termux. Поскольку официальный набор инструментов Go не публикует бинарные файлы для android/arm64, установите GOTOOLCHAIN=local, чтобы использовать то, что поставляется с Termux:
GOTOOLCHAIN=local go install github.com/thereisnotime/sshroute@latest
После установки укажите путь к SSH-бинарнику, так как в Termux нет /usr/bin/ssh:
# ~/.config/sshroute/config.yaml
ssh_binary: /data/data/com.termux/files/usr/bin/ssh
Или через переменную окружения: export SSHROUTE_SSH=$(which ssh)
docker run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
В системах с SELinux (Fedora, RHEL и т.д.) добавьте :Z к флагу тома:
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute:Z \
ghcr.io/thereisnotime/sshroute network
Установите sshroute как ssh раньше в $PATH. Все вызовы SSH — из терминала, git, rsync, scp — будут перехватываться автоматически. Хосты, отсутствующие в вашем конфиге, будут передаваться /usr/bin/ssh без изменений.
mkdir -p ~/.local/bin
ln -s $(which sshroute) ~/.local/bin/ssh
# Добавьте в ~/.bashrc или ~/.zshrc, если ещё нет:
export PATH="$HOME/.local/bin:$PATH"
# Добавьте хост с профилем по умолчанию
sshroute add myserver --host myserver.example.com --user alice --key ~/.ssh/id_ed25519
# Добавьте переопределение для конкретной сети
sshroute add myserver --network vpn --host 10.8.0.50 --port 2222 --jump bastion.vpn
# Подключитесь — сеть определяется автоматически
sshroute connect myserver
# Просмотрите разрешённую команду без выполнения
sshroute connect myserver --dry-run
# Посмотрите, какая сеть сейчас активна
sshroute network
Эти флаги применяются к каждой команде:
initСоздать стартовый файл конфигурации с примерами в комментариях. Завершается ошибкой, если файл уже существует.
| Флаг | По умолчанию | Описание |
|---|---|---|
--force | false | Перезаписать существующий файл конфигурации |
connect <alias>Определить активную сеть, разрешить SSH-параметры для alias и выполнить настоящий SSH-бинарник. Любые дополнительные аргументы после алиаса передаются SSH без изменений.
С --reconnect sshroute поддерживает ssh активным при разрывах (сон ноутбука, переключение Wi-Fi, роуминг между сетями). Поскольку при каждом переподключении сеть определяется заново, он может переключиться на другой маршрут: например, засыпание в локальной сети и пробуждение в хот-споте приведёт к переподключению через публичный маршрут вместо повторной попытки подключиться к теперь недоступному локальному адресу. Чистый выход (exit 0) или сбой аутентификации/удалённой команды останавливают цикл; переподключаются только настоящие обрывы. Переподключение запускает ssh как дочерний процесс (как и --fallback), поэтому sshroute остаётся резидентным на время сессии; SIGINT/SIGTERM завершают его. Сохранение состояния сессии при прерывании — задача вашего мультиплексора (tmux/zellij); комбинируйте --reconnect с -- tmux attach или -- zellij attach -c <имя>, чтобы сразу вернуться в сессию:
sshroute connect myserver --reconnect --fallback -- zellij attach -c work
listВывести список всех настроенных хостов и SSH-параметров, которые будут использоваться в текущей сети. Поддерживает -o table|json|yaml.
add <alias>Добавить хост или обновить существующий. Пропущенные флаги сохраняют текущее значение. Выполняйте несколько раз с разными значениями --network, чтобы создать переопределения для каждой сети.
remove <alias>Удалить все профили для alias из конфигурации.
networkВывести имя текущей обнаруженной сети (или default, если ни одна не подошла).
network listВывести список всех настроенных сетей с их приоритетом, правилами проверки и текущим состоянием активности. Поддерживает -o table|json|yaml.
network test <name>Выполнить все проверки для сети name и вывести результат (пройдено/не пройдено) для каждого правила. Полезно для отладки логики обнаружения.
configВывести разрешённый путь к файлу конфигурации.
config editОткрыть файл конфигурации в $EDITOR (по умолчанию nano). Создаёт файл и родительский каталог, если они не существуют.
resolve <alias>Вывести SSH-параметры, которые будут использоваться для alias в текущей сети. Полезно для отладки и написания скриптов. Используйте --network <name>, чтобы переопределить обнаруженную сеть. Поддерживает -o table|json|yaml.
| Флаг | По умолчанию | Описание |
|---|---|---|
--network | автоопределение | Профиль сети, для которого разрешать параметры |
copy <alias> <src> <dst>Копировать файлы на настроенный хост или с него с помощью scp с теми же разрешёнными параметрами (ключ, порт, шлюз), что и connect. Используйте синтаксис <alias>:<path> для удалённых путей:
sshroute copy myserver ./local.txt myserver:/remote/path/
sshroute copy myserver myserver:/remote/file.txt ./local/
Переменная окружения SSHROUTE_SCP переопределяет используемый бинарник scp.
versionВывести версию, коммит Git, дату сборки и информацию о среде выполнения Go.
updateОбновить sshroute на месте до последнего релиза GitHub. Он загружает архив для вашей платформы, проверяет его sha256 по checksums.txt, и — если установлен cosign — проверяет подпись релиза cosign, после чего атомарно заменяет запущенный бинарник.
sshroute update # загрузить, проверить и установить последний релиз
sshroute update --check # только сообщить, доступна ли более новая версия
sshroute update --force # переустановить последнюю версию, даже если уже актуальная
Если проверка sha256 (или cosign, если он установлен) не удалась, обновление прерывается, и бинарник остаётся нетронутым. Это предназначено для установок из релизного бинарника; если вы устанавливали через go install или пакетный менеджер, обновляйтесь с их помощью.
Расположение по умолчанию: ~/.config/sshroute/config.yaml
networks:
corp-vpn:
priority: 10 # меньше = проверяется первым
checks:
- type: interface
match: wg0
- type: route
match: 10.100.0.0
office:
priority: 20
checks:
- type: ping
host: 192.168.1.1
timeout: 500ms
hosts:
myserver:
default: # обязательный — используется, когда ни одна сеть не подходит
host: myserver.example.com
port: 22
user: alice
key: ~/.ssh/id_ed25519
options: # опционально — передаётся как флаги SSH -o Key=Value
ConnectTimeout: "10"
ServerAliveInterval: "30"
corp-vpn:
host: 10.100.0.50
port: 2222
key: ~/.ssh/corp_key
jump: bastion.corp.internal
options:
ConnectTimeout: "5" # переопределяет значение по умолчанию только для этой сети
office:
host: 192.168.1.50
Каждый хост должен иметь профиль default. Профили сетей могут указывать только поля, отличающиеся от default — неуказанные поля наследуются из default.
Ключи options объединяются из default в профили сети — значения из сети переопределяют соответствующие ключи, непересекающиеся ключи наследуются.
Сети оцениваются в порядке priority (наименьшее значение проверяется первым). Алфавитный порядок используется при равенстве приоритетов. Используется первая сеть, все проверки которой пройдены; если ни одна не подходит, применяется default.
Несколько проверок в одном определении сети используют логику И — все должны быть пройдены.
Готовые к использованию файлы конфигурации находятся в examples/:
Подробные руководства находятся в docs/:
Все команды списка поддерживают несколько форматов вывода:
sshroute list # таблица (по умолчанию)
sshroute list -o json # JSON — для скриптов
sshroute list -o yaml # YAML
sshroute network list -o json
Получить программу — загрузите готовый бинарный файл с Releases, установите через go install github.com/thereisnotime/sshroute@latest или соберите из исходников.
Обратная связь и сообщения об ошибках — откройте задачу на GitHub Issues. Используйте шаблон отчёта об ошибках для неожиданного поведения и шаблон запроса функции для идей.
Вклад — смотрите CONTRIBUTING.md о том, как настроить проект, запустить тесты и открыть pull request. Уязвимости в безопасности следует сообщать конфиденциально через GitHub Security Advisories.
git clone [email protected]:thereisnotime/sshroute.git
cd sshroute
just build # вывод в bin/sshroute
just build-all # кросскомпиляция linux/darwin × amd64/arm64
just test # запуск тестов с детектором гонок
just install # go install с injected ldflags версии
|
|
| Функция | ~/.ssh/config | Только WireGuard | Teleport / Boundary | sshroute |
|---|
| Определяет вашу текущую сеть | ❌ | ❌ | ❌ | ✅ |
| Автоматически выбирает лучший путь | ❌ | ❌ | ❌ | ✅ |
| Переключение при сбое подключения | ❌ | ❌ | ✅ | ✅ |
| Автопереподключение + смена маршрута при обрыве | ❌ | ⚠️ туннель кочует | ⚠️ через фиксированный прокси | ✅ |
| Одна команда на хост из любого места | ❌ | ⚠️ VPN должен быть включён | ✅ | ✅ |
| Размер конфига для 10 хостов × 4 пути | 📄 ~600 строк | 📄 ~600 строк + конфиг VPN | 📄 конфиг на сервере | 📄 ~60 строк |
| Мобильные устройства в роуминге | ⚠️ ручные алиасы | ⚠️ требуется VPN | ✅ | ✅ |
| Автоматическая цепочка шлюзов | ⚠️ ручной -J | ➖ не применимо | ✅ | ✅ |
| Работает с scp / rsync / git / Ansible | ✅ | ✅ | ⚠️ частично | ✅ |
| Не требует установки серверной части | ✅ | ❌ | ❌ | ✅ |
| Не требует сервера аутентификации или демона | ✅ | ❌ | ❌ | ✅ |
| Не требует клиентского агента | ✅ | ❌ | ❌ | ✅ |
| Open source, полностью самостоятельный | ✅ | ✅ | ⚠️ open-core | ✅ |
| Флаг | Переменная окружения | По умолчанию | Описание |
|---|
--config | SSHROUTE_CONFIG | ~/.config/sshroute/config.yaml | Путь к файлу конфигурации |
-o, --output | table | Формат вывода: table, json, yaml | |
-v, --verbose | SSHROUTE_VERBOSE=1 | false | Отладочный вывод в stderr |
--dry-run | false | Вывести разрешённую SSH-команду без выполнения |
| Флаг | По умолчанию | Описание |
|---|
--fallback | false | Пробовать каждый профиль в порядке приоритета, переходя к следующему только при сбое подключения (код выхода 255) |
--reconnect | false | Контролировать подключение и автоматически переподключаться при обрыве, каждый раз заново определяя активную сеть и маршрут |
--reconnect-delay | 2s | Пауза между попытками переподключения при включённом --reconnect |
| Флаг | По умолчанию | Описание |
|---|
--host | Имя хоста или IP-адрес | |
--port | 22 | SSH-порт |
--user | Имя пользователя SSH | |
--key | Путь к файлу ключа (поддерживается ~) | |
--jump | Шлюз — передаётся как -J в SSH | |
--network | default | Профиль сети, в который записать параметры |
| Поле | Тип | Описание |
|---|
host | string | Имя хоста или IP-адрес |
port | int | SSH-порт (по умолчанию: 22) |
user | string | Пользователь SSH |
key | string | Путь к файлу ключа (~ раскрывается) |
jump | string | Алиас шлюза или user@host |
options | map | Произвольные флаги SSH -o Key=Value (например, ConnectTimeout, StrictHostKeyChecking) |
comment | string | Описание, отображаемое в sshroute list |
tags | list | Теги для фильтрации с помощью sshroute list --tag |
| Тип проверки | Успешна, когда | Обязательные поля |
|---|
route | Подсеть/IP присутствует в таблице маршрутизации ядра | match |
interface | Именованный интерфейс существует и операционно поднят | match |
ping | Хост отвечает на ICMP echo в течение тайм-аута | host, timeout (опционально, по умолчанию 2s) |
exec | Команда оболочки завершается с кодом 0 | command |
| Файл | Сценарий использования |
|---|
basic.yaml | Один хост, VPN vs публичный запасной вариант |
multi-network.yaml | Офисная LAN, корпоративный VPN, удалённый VPN, публичный |
wireguard-backconnect.yaml | WireGuard-пир, подключающийся к вам |
jump-hosts.yaml | Разные бастионы для каждой сети |
multi-zone-roaming.yaml | Многозонная домашняя лаборатория с WireGuard-шлюзом и мобильными устройствами в роуминге |
| Руководство | Описание |
|---|
| Настройка домашней лаборатории | Многозонная домашняя лаборатория с WireGuard, шлюзами, NAS, узлами k3s |
| Многозонный роуминг | Несколько LAN, WireGuard-шлюз, мобильные устройства, перемещающиеся между сетями |
| Корпоративная / многопользовательская среда | Dev/staging/prod с бастионами для каждой среды и обнаружением VPN |
| Теневой режим | Прозрачная замена SSH — git, rsync, scp, Ansible |
| Завершение в оболочке | Динамическое завершение алиасов для bash, zsh, fish |
| Скриптинг и автоматизация | Использование resolve и copy в скриптах и CI-пайплайнах |