evgen@spb: ~/2026/09/openclaw-lichnyj-pomoshchnik-v-telegram-za-nat/

OpenClaw как личный помощник в Telegram: ВМ за NAT, GRE+IPsec и рублёвый API

Захотелось личного AI-агента, который живёт в Telegram, помнит контекст, умеет ходить в интернет и запускать скрипты. Взял OpenClaw - open-source агент-шлюз с поддержкой Telegram, skills и cron, - и поставил на домашнюю ВМ. По дороге пришлось решить две неочевидные задачи: вывести ВМ в интернет через свой сервер за границей и найти, где платить за модели рублями. Плюс один раз наступил на грабли с расходом токенов, о которых стоит рассказать отдельно. Ниже - что и как.

Схема

ВМ jarwise: Ubuntu 24.04, 4 vCPU, 4 ГБ RAM, стоит за домашним NAT, снаружи доступен только SSH через проброс порта. Отдельный VPS в Европе (хаб) - на нём уже был один GRE+IPsec туннель до другой площадки.

Задача формулируется так: весь исходящий трафик ВМ пустить через хаб, но при этом не потерять входящий SSH через проброс.

Туннель через NAT

Существующий туннель на хабе сделан в transport mode - через NAT так не выйдет: NAT переписывает адреса, а ESP (proto 50) большинство роутеров не пробрасывают. Рабочая схема - GRE внутри IPsec в tunnel mode с NAT-T (UDP 500/4500), где инициатор - сторона за NAT.

Конфиг хаба, strongSwan:

conn gre-openclaw
    type=tunnel
    keyexchange=ikev2
    authby=secret
    left=<hub-ip>
    leftsubnet=<hub-ip>/32
    leftprotoport=gre
    right=%any
    rightid=@openclaw
    rightsubnet=<vm-lan-ip>/32
    rightprotoport=gre
    ike=aes256-sha256-modp2048!
    esp=aes256gcm16!
    dpdaction=clear
    auto=add

Плюс интерфейс gre2 (10.10.11.1/30, remote = приватный IP ВМ, MTU 1360), MASQUERADE для 10.10.11.0/30 и MSS clamp. Отдельным правилом дропаем GRE от пира вне IPsec - чтобы туннель физически не мог подняться в открытом виде.

Сторона ВМ настраивается зеркально: left=<vm-lan-ip> leftid=@openclaw right=<hub-ip> auto=start dpdaction=restart closeaction=restart, интерфейс gre2 - 10.10.11.2/30.

Маршрутизация - самая интересная часть. Нужно свести три требования вместе:

  1. Хаб всегда достижим через локальный шлюз. Внешняя точка IPsec не должна ходить сама через себя: ip route add <hub-ip>/32 via <lan-gw>.
  2. Ответы на входящий SSH уходят обратно в локальный шлюз, а не в туннель. Здесь помогает policy routing: ip rule add from <vm-lan-ip> table 100, а в table 100 - default через <lan-gw> и link-route на LAN. Новые исходящие соединения сорсятся с адреса туннеля, под это правило не попадают и уходят в default основной таблицы.
  3. Default в интернет - через туннель: ip route add default via 10.10.11.1 dev gre2 metric 50. DHCP-default с metric 100 остаётся про запас, но GRE-интерфейс не падает при обрыве IPsec - поэтому трафик в обход не утечёт, просто пропадёт интернет. Это осознанный компромисс: лучше остаться без сети, чем случайно выпустить трафик мимо туннеля.

Всё это собрано в один идемпотентный скрипт, который дёргается через systemd unit gre2.service до старта strongSwan. При установке default route я ставил себе страховку: фоновый sleep 180 && ip route del ..., который откатывал маршрут, если за три минуты не убеждался, что SSH жив. Проверка после reboot прошла чисто: SA поднялась за минуту, curl ifconfig.me отдаёт адрес хаба, DF-пакет на 1332 байта проходит.

Установка OpenClaw

Ставил rootless - через install-cli.sh под отдельным пользователем openclaw (свой Node 24 в ~/.openclaw, системный не трогал). Дальше loginctl enable-linger, gateway как systemd user unit, bind только на loopback. Telegram-бот заводится через @BotFather, а доступ к нему я закрыл наглухо: dmPolicy: allowlist плюс allowFrom с моим user ID - писать боту могу только я.

Где платить за модель

Вот тут началось самое весёлое, и не по технической причине.

Первая попытка - Gemini напрямую. Ключ работает, но на первом же запросе прилетает 429 RESOURCE_EXHAUSTED: free tier для новых моделей даёт нулевую квоту, а billing из России не включается (OR_BACR2_44 при любых плясках с браузером). Anthropic - та же история с оплатой.

Значит, агрегаторы. OpenClaw умеет подключать любой OpenAI-совместимый endpoint как custom provider:

models: {
  providers: {
    polza: {
      baseUrl: "https://polza.ai/api/v1",
      apiKey: "${POLZA_API_KEY}",
      api: "openai-completions",
      models: [{ id: "google/gemini-3.8-flash", name: "Gemini 3.8 Flash", contextWindow: 1000000, maxTokens: 65536 }]
    }
  }
}

Ключ лежит в ~/.openclaw/.env, в конфиг не попадает. Сравнил цены на gemini-3.8-flash за 1M токенов ввода/вывода (по курсу ЦБ 86,19):

ПровайдерВводВывод
Google напрямую65 ₽323 ₽
OpenRouter Flex32 ₽162 ₽
Polza.ai45 ₽226 ₽
AITunnel15 ₽750 ₽
GPTunnel259 ₽1293 ₽
ProxyAPI455 ₽2275 ₽

Google и OpenRouter из России не оплатить, так что они выпадают. Из рублёвых реально конкурируют Polza и AITunnel: AITunnel дешевле на вводе, но вывод втрое дороже - а у 3.8 Flash thinking-токены идут как вывод. Точка равновесия - примерно 5,8% вывода от объёма ввода; у агента с инструментами обычно больше, так что остался на Polza. Любопытная деталь: цены Polza - ровно 0,7 от гугловских, похоже на Flex-тир с наценкой; под нагрузкой у Google Flex отвечает медленнее.

Как агент сжёг токены за ночь

А теперь обещанные грабли - на мой взгляд, самая полезная часть всей истории.

Утром обнаружил, что баланс заметно просел, хотя боту ночью никто не писал. Полез в лог gateway: с полуночи до 8 утра - 18 вызовов модели, ровно два в час. Это heartbeat, фоновая проверка «есть ли что-то, требующее внимания», по умолчанию раз в 30 минут. Сама по себе она дешёвая. Но гоняется она в главной сессии, а главная сессия после вечера, когда мы с ботом писали скрипты, весила 154k токенов. Каждые полчаса модели уходили все эти 154k - чтобы она ответила NO_REPLY. За ночь набежало около 2,8M входных токенов впустую.

Что сделал:

  1. agents.defaults.heartbeat.every: "0m" - у меня нет monitor scratch, heartbeat не делал ничего полезного. Все реальные проверки живут в cron, где у каждой задачи свой интервал и своя сессия.
  2. /compact в Telegram - главная сессия ужалась со 154k до 15k. Без этого каждое обычное сообщение тоже тащило бы все 154k.
  3. Если heartbeat всё-таки нужен - его спасают isolatedSession: true + lightContext: true + activeHours. В документации прямо сказано: ~100k токенов на вызов превращаются в 2-5k.

Отдельно проверил cron-задачу, которая опрашивает Zabbix каждые 3 минуты, - она оказалась чистым Python-скриптом без обращения к модели, её трогать не надо. Вообще, смотреть, кто тратит, проще всего по метке [model-fetch] в логе gateway: там есть провайдер, модель и время ответа, а runtime.llm отдаёт usage с costUsd.

Мелочи, которые сэкономили время

  • openclaw doctor --fix выключил 32 skill’а без бинарников (apple-notes, sonos, 1password и подобное) - они только раздували промпт.
  • Chrome для browser tool ставил .deb с dl.google.com, а не snap: browser.executablePath=/usr/bin/google-chrome, headless сам определился по отсутствию DISPLAY. Кириллица на скриншотах требует пакета fonts-noto.
  • Секреты - только через ${VAR} из .env. И openclaw config patch --dry-run перед каждым изменением конфига, чтобы не удивляться результату.

Что ещё предстоит

Семантическая память (embeddings) у меня пока на FTS-only: провайдер по умолчанию openai, а ключа к нему нет. Polza отдаёт /v1/embeddings, так что переключить - следующий шаг.

И ещё: с 1 января 2027 Google удваивает цену на 3.8 Flash, а агрегаторы наверняка подтянутся следом - так что таблицу с ценами выше к тому моменту стоит пересчитать заново.

evgen@spb: ~/contacts
evgen@spbping -c1 evgen.info
64 bytes from evgen.info: отвечу в течение рабочего дня
evgen@spbcat contacts.txt
mail      ya@evgen.info
telegram  @EvgenOne
github    github.com/onegin
blog      evgen.info/blog
evgen@spbecho $ЗАЧЕМ_ПИСАТЬ
интересная инфраструктурная задача · вопрос по Proxmox или Icinga ·
желание обсудить, почему memtest молчал, а память была битая
evgen@spb
© 2026 Евгений Подолинский · Санкт-Петербург
синий здесь – не тот, о котором вы подумали.