# SSH Config: wildcards и подстановка переменных

Индекс LLMS: [llms.txt](/llms.txt)

---

SSH-клиент читает ~/.ssh/config строчку за строчкой, но без переменных файл быстро превращается в копипасту. Разбираю, как Host *, Match exec и подстановки %h, %r, %l сокращают конфиг в разы и закрывают реальные сценарии — от динамической маршрутизации до проброса агента через bastion.

## Шаблоны и wildcards в SSH config

SSH поддерживает glob-подобные шаблоны в директиве Host. Самая популярная — `Host *`, но работают и составные паттерны.

```bash
# Все хосты без явного описания получат общие настройки
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    IdentitiesOnly yes

# Все хосты домена example.com
Host *.example.com
    User ubuntu
    Port 22

# Хосты по маске: web-01, web-02, web-03
Host web-0?
    IdentityFile ~/.ssh/id_ed25519_web
```

> [!NOTE]
> SSH читает конфиг сверху вниз и применяет первую подходящую директиву Host. Более конкретные правила ставьте выше общих.

```bash
# Правильный порядок: частное перед общим
Host bastion
    HostName bastion.example.com
    User admin

Host *.internal
    ProxyJump bastion

Host *
    ServerAliveInterval 60
```

Шаблоны можно комбинировать в одном Host через пробел — это работает как логическое ИЛИ.

```bash
Host dev-* staging-*
    User deploy
    IdentityFile ~/.ssh/id_ed25519_deploy
```

## Переменные подстановки: %h, %r, %l

SSH подставляет переменные в значения директив на этапе обработки конфига. Три основных:

| Переменная | Значение | Пример |
|---|---|---|
| `%h` | Имя хоста из командной строки | `ssh web-01` → `web-01` |
| `%r` | Имя пользователя на удалённой машине | `ubuntu` |
| `%l` | Локальное имя пользователя | `alex` |

Подстановка работает в большинстве директив — ProxyCommand, IdentityFile, LocalCommand, RemoteCommand.

```bash
Host jump-*
    HostName %h.internal
    ProxyJump bastion.example.com
    User deploy

Host backup-*
    HostName %h.backup.local
    User backup
    # Логин совпадает с локальным пользователем
    IdentityFile ~/.ssh/id_ed25519_%r
```

Переменная `%h` заменяется на то, что вы передали после `ssh`. Это позволяет написать одно правило на сотню хостов.

```bash
# Вместо пяти отдельных блоков
Host server-{01..05}
    HostName %h.internal.corp
    User ansible
    ProxyJump bastion
```

> [!TIP]
> `%h` подставляет то, что вы набрали, а неresolved HostName. `ssh web-01` подставит `web-01`, а не его IP.

## Match exec и динамическая маршрутизация

Директива Match позволяет задавать правила на основе условий. Без exec это проверка по user, host, localuser. С `Match exec` — произвольная логика через shell-команду.

```bash
# Подключаться через bastion только для внутренних хостов
Match host 10.* exec "echo true"
    ProxyJump bastion.example.com
```

Условие exec выполняется на локальной машине. Команда должна вернуть 0 (успех) для применения блока Match.

```bash
# Прокидывать агента только при подключении к prod
Match exec "[ '%h' = prod-* ]"
    AddKeysToAgent yes
    IdentityAgent SSH_AUTH_SOCK
```

```bash
# Разная маршрутизация в зависимости от сети
Match exec "hostname -I | grep -q 192.168.1"
    ProxyCommand none  # локальная сеть, прямой доступ

Match exec "[ '%h' != prod-* ]"
    ProxyJump office-jump
```

> [!WARNING]
> Match exec выполняется shell-оболочкой. Обратные кавычки и переменные раскрываются. Для сложной логики выносите проверку в отдельный скрипт.

```bash
Match exec "/home/alex/.ssh/route-check.sh %h"
    ProxyJump bastion
```

```bash
# route-check.sh
#!/bin/bash
HOST="$1"
if [[ "$HOST" == prod-* ]]; then
    exit 1  # для prod нужна другая логика
fi
exit 0
```

## Escape символа: как написать %

Если нужно передать символ `%` literally — в RemoteCommand, ProxyCommand или LocalCommand — используйте `%%`.

```bash
# SSH в Docker-контейнер с eval
Host docker-*
    HostName %h
    User docker
    RemoteCommand docker exec -it %h /bin/sh -c "eval $(ssh-agent -s) && exec /bin/sh"
```

```bash
# Добавить timestamp в remote command
Host *
    RemoteCommand echo "%%Y-%%m-%%d %%H:%%M:%%S connected to %h" >> /tmp/ssh-log
```

```bash
# Проброс порта через nc на удалённом хосте
Host relay
    HostName relay.example.com
    ProxyCommand ssh -W %h:%p user@bastion
    # или классический nc
    ProxyCommand nc -Z %h 22
```

## Примеры для dev, staging, prod

Типичная схема: один файл, три окружения, общая база.

```bash
# ============================================
# Базовые настройки для всех
# ============================================
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    IdentitiesOnly yes
    AddKeysToAgent yes

# ============================================
# Окружения: динамический HostName
# ============================================
Host dev-*
    HostName %h.dev.internal
    User developer
    IdentityFile ~/.ssh/id_ed25519

Host staging-*
    HostName %h.staging.internal
    User deploy
    IdentityFile ~/.ssh/id_ed25519_deploy
    # Дополнительныйjump через staging-bastion
    ProxyJump staging-bastion

Host prod-*
    HostName %h.prod.internal
    User deploy
    IdentityFile ~/.ssh/id_ed25519_prod
    ProxyJump prod-bastion
    # Только через агент, без файлового ключа на диске
    IdentityAgent SSH_AUTH_SOCK

# ============================================
# Match: динамическая логика
# ============================================
# Прокидывать агента только на prod и staging
Match exec "[ '%h' = prod-* ] || [ '%h' = staging-* ]"
    ForwardAgent yes

# Отключать ForwardAgent для dev (параноидальная настройка)
Match host dev-* exec "true"
    ForwardAgent no

# ============================================
# Переменные подстановки в командах
# ============================================
Host db-*
    HostName %h.internal
    User dbadmin
    RemoteCommand psql -U postgres -c "SELECT now(), '%r'"

# ============================================
# Escape %% в аргументах
# ============================================
Host log-*
    HostName %h
    RemoteCommand echo "Connected at %%Y-%%m-%%d" >> /var/log/ssh-connections.log
```

После этого конфига `ssh prod-web-03` подключается к `prod-web-03.prod.internal` через prod-bastion с ключом `id_ed25519_prod`, а `ssh dev-app-01` — напрямую.

```bash
# Проверить, что SSH видит ваш конфиг
ssh -G dev-app-01 | grep -E "^hostname|^user|^proxyjump"

# Тест соединения без выполнения команды
ssh -v prod-db-01 echo "connected"
```

> [!NOTE]
> `-G` выводит все параметры, которые SSH получит после разбора конфига для указанного хоста. Используйте для отладки перед реальным подключением.

Комбинация Host-шаблонов, подстановки переменных и Match exec превращает SSH config из набора копипаст в систему с динамической маршрутизацией. Один файл покрывает dev/staging/prod без дублирования, а `%h`, `%r`, `%l` избавляют от ручной правки при добавлении новых хостов.
