Русский
Командная база знаний на голой VM
Разверните trip2g на одной голой VM — без MinIO, без облачного хранилища — и у команды появится общая база знаний с поиском, доступная любому AI-агенту через единый MCP-эндпоинт. Заметки закрыты по умолчанию; федеративный поиск позволяет агентам на других инстансах запрашивать вашу базу без доступа ко всему хранилищу.
Деплой на голой VM (пошаговый сценарий)
Все шаги проверены на реальном деплое в Yandex Cloud. В каждом шаге указана ошибка, которую он предотвращает.
Шаг 1. Создать VM — размер и сеть
Создайте VM с не менее 8 ГБ RAM (или настройте swap до сборки образа). Этап компиляции Go во время сборки Docker-образа молча потребляет 4–6 ГБ. На VM с 4 ГБ OOM killer срабатывает в процессе сборки — SSH-сессия зависает, и VM выглядит мёртвой.
- ОС: Ubuntu 22.04.
- Зарезервируйте статический IP. При пересоздании или изменении конфигурации VM эфемерный IP меняется — DNS и записи Cloudflare ломаются.
- Передайте SSH-ключ при создании VM через параметр метаданных (
--metadata ssh-keys=ubuntu:<ключ>в Yandex Cloud). По умолчанию образ включает OS Login, который игнорирует ключи, добавленные после создания. - Используйте SSH-ключ без кодовой фразы. Неинтерактивные шаги (pipe
git archive, скрипты) не могут ввести кодовую фразу.
Шаг 2. Установить Docker и собрать образ на VM
Установите Docker на VM, затем соберите образ из исходников прямо на VM. Не переносите локально собранный образ с ноутбука: если ваша машина arm64 (Apple Silicon), а VM — x86_64, образ откажется запускаться с ошибкой exec format error.
Передайте исходники на VM и соберите там:
# на вашей машине
git archive HEAD | ssh ubuntu@<ip-vm> 'mkdir -p ~/trip2g && tar x -C ~/trip2g'
# на VM
cd ~/trip2g
docker build -t trip2g:local .
Сборка занимает 5–10 минут. Этап компиляции Go не выводит логов — это нормально. Не прерывайте процесс.
Шаг 3. Запустить сервер
Смонтируйте том с данными и запустите контейнер. Используйте STORAGE_BACKEND=local — MinIO и S3 не нужны.
Сначала сгенерируйте секреты:
# DATA_ENCRYPTION_KEY — ровно 32 hex-символа (16 байт)
openssl rand -hex 16
# JWT_SECRET — любая длинная случайная строка
openssl rand -base64 32
Запуск:
docker run -d --name trip2g-kb \
--restart unless-stopped \
-p 80:80 \
-e LISTEN_ADDR=0.0.0.0:80 \
-e INTERNAL_LISTEN_ADDR=:8081 \
-e DB_FILE=/data/kb.sqlite3 \
-e STORAGE_BACKEND=local \
-e STORAGE_LOCAL_DIR=/data/storage \
-e OWNER_EMAIL=owner@yourteam.example \
-e PUBLIC_URL=https://kb.yourteam.example \
-e JWT_SECRET=<ваш-jwt-secret> \
-e DATA_ENCRYPTION_KEY=<ваши-32-символа-hex> \
-v /opt/trip2g-kb:/data \
trip2g:local
Важно:
DATA_ENCRYPTION_KEYдолжен отличаться от значения по умолчанию. Если переменная не задана или содержит плейсхолдер, сервер паникует при запуске:"in production, data encryption key must be changed from default".PUBLIC_URLдолжен быть финальнымhttps://-доменом, а неhttp://localhost. Auth-потоки и URL перенаправлений формируются из этого значения.LISTEN_ADDR=0.0.0.0:80открывает публичный порт напрямую. Если используете обратный прокси (Caddy/Nginx), привяжите к внутреннему порту и проксируйте с него.- Не указывайте
DEV=trueв продакшене — это отключает проверки безопасности и включает фиксированные коды входа. - Не указывайте
RESEND_API_KEY/SMTP_PASSWORD, если вход по email не нужен. - Не указывайте
GIT_API_REPO_PATH, если не используете встроенное git-зеркало.
Дождитесь готовности:
until curl -sf http://localhost:8081/readyz >/dev/null; do sleep 2; done && echo "ready"
Используйте /readyz, а не /healthz — этот эндпоинт возвращает 200 только после завершения прогрева базы данных.
Обязательные переменные окружения:
| Переменная | Назначение |
|---|---|
STORAGE_BACKEND=local |
Хранить ассеты на диске вместо S3/MinIO |
STORAGE_LOCAL_DIR=/data/storage |
Директория ассетов внутри смонтированного тома |
JWT_SECRET |
Подпись сессионных токенов. Дефолтное значение в проде не работает |
DATA_ENCRYPTION_KEY |
32-символьный hex-ключ для шифрования. Без него или на значении по умолчанию — паника при старте |
OWNER_EMAIL |
Email аккаунта администратора |
PUBLIC_URL |
Внешний URL для ссылок и auth-потоков. Укажите финальный https:// домен при первом запуске |
DB_FILE |
Путь к SQLite-базе внутри контейнера |
LISTEN_ADDR |
Основной адрес для приёма HTTP |
INTERNAL_LISTEN_ADDR |
Внутренний адрес для health/readiness-проверок |
Шаг 4. Привязать домен и настроить TLS
Привяжите домен к статическому IP VM через Cloudflare (проксируемый режим). Сервер слушает порт 80; Cloudflare завершает TLS.
Установите режим SSL в Cloudflare: Flexible (Cloudflare → origin по HTTP) или установите сертификат на VM и используйте Full.
PUBLIC_URL должен совпадать с финальным https://-доменом. При смене домена позже потребуется пересоздать базу или пропатчить сохранённые URL.
Шаг 5. Получить API-ключ администратора (HAT, без DEV-режима)
Без DEV=true фиксированного кода входа нет. Используйте HAT (Hot Auth Token) — короткоживущий JWT, подписанный JWT_SECRET, который обменивается на сессионный cookie.
# 1. Подпишите JWT: payload {"e":"<owner-email>","ae":true,"exp":<now+300>}
# Алгоритм: HS256, секрет: JWT_SECRET
# Через PyJWT:
JWT=$(python3 -c "
import jwt, time, os
print(jwt.encode({'e':'owner@yourteam.example','ae':True,'exp':int(time.time())+300},
os.environ['JWT_SECRET'], algorithm='HS256'))
")
# 2. Обменяйте на сессионный cookie
TOKEN=$(curl -s -c - -X POST https://kb.yourteam.example/_system/hat \
-H "Authorization: Bearer $JWT" \
| grep trip2g_token | awk '{print $NF}')
# 3. Создайте API-ключ
API_KEY=$(curl -s -X POST https://kb.yourteam.example/_system/graphql \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"mutation($i:CreateApiKeyInput!){admin{createApiKey(input:$i){__typename ... on CreateApiKeyPayload{value} ... on ErrorPayload{message}}}}","variables":{"i":{"description":"team-sync"}}}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); r=d['data']['admin']['createApiKey']; print(r['value'] if r['__typename']=='CreateApiKeyPayload' else r['message'])")
echo "API-ключ: $API_KEY"
Мутация createApiKey возвращает union — CreateApiKeyPayload{value} при успехе, ErrorPayload{message} при ошибке. Проверяйте __typename перед чтением value.
Полный справочник по HAT и выпуску ключей — ru/user/local-quickstart.
Шаг 6. Опубликовать контент через CLI синка
node obsidian-sync/dist/trip2g-sync.mjs \
--folder /путь/к/командному-хранилищу \
--api-key "$API_KEY" \
--api-url https://kb.yourteam.example/_system/graphql \
--verbose
Для непрерывной синхронизации во время редактирования:
node obsidian-sync/dist/trip2g-sync.mjs --watch \
--folder /путь/к/командному-хранилищу \
--api-key "$API_KEY" \
--api-url https://kb.yourteam.example/_system/graphql
Все флаги CLI синка — ru/user/local-quickstart.
Шаг 7. Настроить главную страницу
Главная страница сайта по адресу / должна быть заметкой с именем index.md или _index.md в корне хранилища. Сегмент index/_index в имени файла отбрасывается, и permalink становится /.
Поле frontmatter route: kb.yourteam.example/ не делает заметку главной страницей основного домена. Оно привязывает заметку к этому пути на кастомном домене, но слот / принадлежит заметке index/_index.
Шаг 8. Кастомные HTML-страницы — используйте Jet-layout
Сырой HTML в теле заметки санируется конвейером Markdown. Блок вида:
<div style="display:flex; height:100vh; ...">Не лезь.</div>
превращается в <!-- raw HTML omitted --> в отрендеренной странице.
Для полностью кастомной страницы — например, гостевого «заглушки» — используйте Jet-layout: серверный .html-шаблон, который не санируется.
- Создайте
_layouts/guard.htmlв хранилище — полный<!DOCTYPE html>-документ:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Только авторизованным</title>
<style>
body { margin: 0; display: flex; align-items: center; justify-content: center;
height: 100vh; font-family: system-ui, sans-serif; }
.msg { font-size: 3rem; font-weight: bold; text-align: center; }
a { display: block; margin-top: 1rem; font-size: 1rem; }
</style>
</head>
<body>
<div class="msg">
Не лезь — убьёт.<br>
<a href="/kb">Войти</a>
</div>
</body>
</html>
- В корневой
index.mdукажите layout:
---
free: true
layout: guard
---
Layout-файл рендерится на сервере — HTML не санируется. Ссылка /kb ведёт на любую закрытую заметку: поскольку у неё нет free: true, trip2g покажет анонимному посетителю встроенную страницу входа/пейвол. Отдельного URL /login нет — страница пейвола и есть точка входа.
Шаг 9. Подключить федеративный поиск
Чтобы агенты могли искать по другому хабу через /_system/mcp вашего инстанса, добавьте KB-заметку в хранилище:
---
free: true
mcp_federation_kb_url: https://hub.example.com/_system/mcp
mcp_federation_kb_id: hub-name
---
Использовать для: поиска по общей командной базе знаний и публичным справочникам.
free: true обязательно. Без этого поля сканирование федерации (accessibleKBNotes) игнорирует заметку, а federated_search возвращает "Federation is not configured". Это самая частая ошибка конфигурации.
После синхронизации /_system/mcp предоставляет federated_search, federated_similar, federated_note_html и federated_expand.
memcli hub <url> автоматизирует создание KB-заметки и синхронизацию. Подробнее — ru/user/memcli.
Подробный справочник
Получить аккаунт администратора и API-ключ
Войдите как владелец, затем выпустите один API-ключ для CLI синка. Вся команда использует этот ключ для публикации контента.
Способ A — HAT (Hot Auth Token), email не нужен.
HAT — беспарольный вход администратора. Сервер проверяет короткоживущий JWT, подписанный JWT_SECRET. Именно этот способ используют memcli и другие headless-инструменты. Эндпоинт — /_system/hat. Полный процесс — в ru/user/local-quickstart (раздел «Завести API-ключ») и ru/user/agent-memory.
Способ B — вход по email (продакшен).
Укажите RESEND_API_KEY, MAIL_FROM и верифицированный домен отправителя в Resend. Сервер отправит одноразовый код на OWNER_EMAIL. После входа создайте ключ в Администрирование → API-ключи → Создать.
Опубликовать контент через CLI синка
Публикуем папку хранилища на сервер. Запускайте из корня исходников trip2g:
node obsidian-sync/dist/trip2g-sync.mjs \
--folder /путь/к/командному-хранилищу \
--api-key "$API_KEY" \
--api-url https://kb.yourteam.example/_system/graphql \
--verbose
Полный справочник по CLI синка — ru/user/local-quickstart.
Подключить федеративный поиск
Федерация позволяет AI-агенту обратиться к единому MCP-эндпоинту вашей базы и выполнить поиск по всем подключённым базам знаний. Для этого нужна KB-заметка в хранилище.
Важно: free: true обязательно.
KB-заметка без free: true невидима для неаутентифицированных MCP-запросов. Без этого поля сканирование федерации (accessibleKBNotes) игнорирует заметку, а федеративные инструменты возвращают "Federation is not configured".
Создайте файл в хранилище (например, hub/peer-name.md):
---
free: true
mcp_federation_kb_url: https://hub.example.com/_system/mcp
mcp_federation_kb_id: hub-name
---
Использовать для: поиска по общей командной базе знаний и публичным справочникам.
Нажмите Sync. Локальный /_system/mcp теперь предоставляет federated_search, federated_similar, federated_note_html и federated_expand.
Пример MCP-вызова:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "federated_search",
"arguments": {
"query": "чеклист деплоя",
"kb_id": "hub-name"
}
}
}
Возвращает подходящие заметки из базы пира. Без kb_id вызов выполняется веерно по всем зарегистрированным KB-заметкам параллельно.
SSRF и глубина. Публичные хабы (внешние URL) разрешены по умолчанию. Для приватных/внутрисетевых адресов требуется MCP_FEDERATION_ALLOW_PRIVATE=true. Веерный поиск останавливается на глубине 3 (MCP_FEDERATION_MAX_DEPTH). Таймаут на один пир — 2 секунды (MCP_FEDERATION_FANOUT_TIMEOUT).
Полная настройка федерации — в ru/user/federation.
Управление доступом: кто что видит
/_system/mcp открыт публично только для заметок с free: true. Всё остальное требует аутентификации.
Для приватного или закрытого контента запросы проходят с Bearer-токеном:
Authorization: Bearer t2g_<токен>
или через параметр URL:
https://kb.yourteam.example/_system/mcp?token=t2g_...
Формат токена — t2g_<...>. Персональные токены создаются в разделе Пользователь → Токены в интерфейсе trip2g. Каждый участник команды, которому нужен доступ к закрытым заметкам, получает свой токен.
Подключение Claude Code или другого MCP-клиента:
{
"mcpServers": {
"team-kb": {
"command": "python3",
"args": ["/путь/к/trip2g_mcp_stdio_adapter.py"],
"env": {
"TRIP2G_MCP_URL": "https://kb.yourteam.example/_system/mcp",
"TRIP2G_TOKEN": "t2g_токен-участника"
}
}
}
}
Федерирование приватной базы в другой хаб.
Другой trip2g-инстанс может подключить вашу базу через федерацию. Это требует обмена HMAC-ключами (federation secrets), который точно определяет, какие подграфы видит пир. Подробнее — ru/user/federation (раздел «Добавить приватного пира»).
Итоговая таблица доступа:
| Запрашивающий | Токен | Видит |
|---|---|---|
| Анонимный агент / публичный хаб | Нет | Только заметки с free: true |
| Аутентифицированный участник | t2g_<токен> |
Заметки в scope подписки |
| Admin API-ключ | X-API-Key: <ключ> |
Все заметки |
| Федерированный пир с HMAC-секретом | Подписанный JWT | Заметки в scope секрета |
Смотрите также
- ru/user/local-quickstart — полный справочник по локальному запуску, флаги CLI синка, mint-поток через HAT
- ru/user/agent-memory — память для одиночного агента; подробности выпуска ключа через HAT
- ru/user/memcli — сервер + API-ключ + sync watcher одной командой; подкоманда
hubдля федерации - ru/user/federation — полная настройка федерации: публичные пиры, обмен HMAC-ключами, scope подграфов, граф федерации
- ru/user/selfhosted — Caddy + MinIO + TLS для продакшена (если нужен вход по email или внешнее хранилище объектов)
- ru/user/mcp — все MCP-инструменты, режимы аутентификации, персональные токены