Русский
Self-hosted
Три способа запустить trip2g на своём сервере. Выберите подходящий.
★ GitHub — github.com/trip2g/trip2g
| Вариант | Подходит когда |
|---|---|
| Один бинарник на Linux | Один сайт, одна VM. Без Docker, без Caddy, без MinIO. HTTPS встроен через Let's Encrypt. |
| Docker Compose (полный стек) | Нужны S3-совместимое хранилище и reverse proxy в одной конфигурации. |
| fly.io | Managed PaaS, без своего сервера. См. ru/user/fly. |
Один бинарник на Linux
Один бинарник, один systemd-юнит, HTTPS из коробки. Docker не нужен.
Бинарник trip2g содержит все frontend-ассеты внутри. На хосте нужны только git и ca-certificates.
Получите бинарник
Быстрее всего — установщик одной командой (тянет последний релиз, пишет конфиг, ставит systemd-юнит, поднимает HTTPS, печатает одноразовую ссылку для входа):
curl -fsSL https://raw.githubusercontent.com/trip2g/trip2g/main/scripts/simple-install.sh | sh
Или вручную. Три способа — в порядке предпочтения:
(а) Скачать с GitHub Releases (рекомендуется):
# Замените <version> на актуальный тег с https://github.com/trip2g/trip2g/releases
curl -L https://github.com/trip2g/trip2g/releases/download/<version>/trip2g_<version>_linux_amd64.tar.gz \
| tar xz trip2g-server
sudo mv trip2g-server /usr/local/bin/trip2g
sudo chmod +x /usr/local/bin/trip2g
(б) Извлечь из Docker-образа:
docker create --name tmp ghcr.io/trip2g/trip2g && \
docker cp tmp:/trip2g /usr/local/bin/trip2g && \
docker rm tmp
sudo chmod +x /usr/local/bin/trip2g
(в) Собрать из исходников — важно: фронтенд должен быть уже собран в assets/ui до сборки (в Dockerfile это делает сборщик $mol). Варианты (а) и (б) самодостаточны; (в) — только для полного чекаута с собранным фронтендом:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/server
sudo mv server /usr/local/bin/trip2g
sudo chmod +x /usr/local/bin/trip2g
Установите зависимости
apt-get update && apt-get install -y git ca-certificates
Бинарник вызывает git внутри себя и использует CA-сертификаты для проверки TLS.
Создайте директории для данных
mkdir -p /var/lib/trip2g/storage
Настройте конфигурацию
Создайте файл /etc/trip2g.env и ограничьте доступ к нему:
touch /etc/trip2g.env
chmod 600 /etc/trip2g.env
Вставьте и заполните значения:
ACME_DOMAIN=docs.example.com
PUBLIC_URL=https://docs.example.com
INTERNAL_LISTEN_ADDR=:8082
DB_FILE=/var/lib/trip2g/data.sqlite3
GIT_API_REPO_PATH=/var/lib/trip2g/git
STORAGE_BACKEND=local
STORAGE_LOCAL_DIR=/var/lib/trip2g/storage
LOG_LEVEL=info
DEV=false
OWNER_EMAIL=owner@example.com
JWT_SECRET=<openssl rand -hex 32>
DATA_ENCRYPTION_KEY=<openssl rand -hex 16>
SMTP_HOST=smtp.resend.com
SMTP_USER=resend
SMTP_PASS=<resend api key>
MAIL_FROM=no-reply@your-verified-domain
Что означает каждая настройка:
ACME_DOMAIN— домен для получения сертификата Let's Encrypt. Бинарник слушает:443(TLS-ALPN-01) и делает HTTP→HTTPS редирект на:80. Caddy не нужен.PUBLIC_URL— внешний адрес сайта. Используется в ссылках и email-флоу.INTERNAL_LISTEN_ADDR— внутренний адрес для healthcheck (/healthz).DB_FILE— путь к SQLite-базе.GIT_API_REPO_PATH— путь к внутреннему bare git-репозиторию trip2g.STORAGE_BACKEND=local— хранит загруженные файлы на диске вSTORAGE_LOCAL_DIR. trip2g отдаёт их сам по пути/_assets/.... Чтобы позже переключиться на S3, используйте те же переменныеMINIO_*, что и в Compose-варианте.LOG_LEVEL— уровень логов.info— хорошо для production.DEV=false— production-режим (secure cookies, без debug-вывода).OWNER_EMAIL— email владельца инстанса.JWT_SECRET— секрет для подписи сессионных токенов. После смены все старые сессии станут недействительными.DATA_ENCRYPTION_KEY— 32-символьный ключ для шифрования чувствительных данных.openssl rand -hex 16выдаёт ровно 32 символа.SMTP_HOST/SMTP_USER/SMTP_PASS— параметры SMTP. В примере используется шлюз Resend (smtp.resend.com, пользовательresend, пароль = API key). Без настроенного SMTP сервер запустится, но письма входа отправляться не будут — см. ниже про обходной путь для первого входа.MAIL_FROM— адрес отправителя. Должен принадлежать домену, подтверждённому в почтовом провайдере.
Требования ACME: до запуска сервиса DNS A/AAAA запись домена должна указывать на этот сервер, порты 80 и 443 должны быть открыты. Для быстрой проверки без собственного домена подойдёт wildcard DNS вроде <ip>.nip.io.
Сгенерируйте секреты заранее:
openssl rand -hex 32 # JWT_SECRET
openssl rand -hex 16 # DATA_ENCRYPTION_KEY (ровно 32 символа)
Создайте systemd-юнит
Создайте файл /etc/systemd/system/trip2g.service:
[Unit]
Description=trip2g publishing server
After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/trip2g.env
ExecStart=/usr/local/bin/trip2g
WorkingDirectory=/var/lib/trip2g
Restart=on-failure
[Install]
WantedBy=multi-user.target
Юнит запускается от root, чтобы бинарник мог занять порты 80 и 443.
Запустите и проверьте
systemctl daemon-reload
systemctl enable --now trip2g
Проверьте статус:
systemctl is-active trip2g
journalctl -u trip2g -f
Проверьте HTTPS:
curl -I https://docs.example.com/
Ожидаемый результат: HTTP/2 200 с сертификатом Let's Encrypt.
После этого откройте https://docs.example.com, войдите под OWNER_EMAIL и продолжайте в Начало работы.
Если SMTP не настроен: письмо со входом отправить некому, поэтому код получить нельзя — добавьте LOG_SIGN_IN_CODES=true в /etc/trip2g.env, перезапустите (systemctl restart trip2g), запросите вход и заберите код из journalctl -u trip2g. Флаг печатает коды в открытом виде, поэтому отключите его (или настройте SMTP) сразу после первого входа.
Docker Compose (полный стек)
trip2g + MinIO (S3-совместимое хранилище) + Caddy в одном docker-compose.yml.
flowchart TD
Net[Internet] -->|443| Caddy[Caddy<br/>ports 80/443]
Caddy -->|docs.example.com| T[trip2g :8081]
Caddy -->|files.example.com| M[MinIO :9000]
T -->|S3 API| M
subgraph compose_network_internal
T
M
end
Что важно не забыть
- Для публичного сервера нужен
HTTPS. Иначе secure-cookie для входа не будет работать. - Для входа по email нужен аккаунт в
resend.com, API key и подтвержденный домен или поддомен отправителя. - Для production обязательно задайте свои
JWT_SECRETиDATA_ENCRYPTION_KEY. Со значениями по умолчанию сервер работает небезопасно. - Наружу обычно публикуются только
80и443уcaddy.
Подготовка
Нужны:
- Linux-сервер с Docker и Docker Compose plugin
- домен для сайта, например
docs.example.com - поддомен для email-отправителя, например
mg.example.com - доступ к DNS
Создайте директорию, например /opt/trip2g, и положите туда два файла: docker-compose.yml и .env.
Проверьте сервер перед установкой
Если сервер не чистый, убедитесь до запуска:
- Порты
80и443свободны — compose отдаёт их Caddy:ss -tlnp | grep -E ':80 |:443 ' - Нет другого Caddy, Nginx или Traefik, уже слушающего эти порты. Если есть — его нужно остановить или перенести.
- Нет конфликтующих Docker-сетей от других проектов (редко, но бывает при нестандартном overlay).
Если порты заняты существующим reverse proxy (Nginx, Caddy, Traefik), убирать его не нужно — достаточно прописать trip2g как upstream в нём. Тогда сервис caddy из docker-compose.yml можно убрать, а порт 8081 опубликовать напрямую. То же самое касается MinIO: если у вас уже есть своё объектное хранилище — MinIO поднимать не нужно, достаточно указать его реквизиты в .env.
docker-compose.yml
services:
caddy:
image: caddy:2
restart: unless-stopped
depends_on:
trip2g:
condition: service_started
minio:
condition: service_healthy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
minio:
image: minio/minio:latest
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
volumes:
- minio-data:/data
expose:
- "9000"
- "9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 20
trip2g:
image: ghcr.io/trip2g/trip2g:latest
restart: unless-stopped
depends_on:
minio:
condition: service_healthy
env_file:
- .env
volumes:
- trip2g-data:/data
expose:
- "8081"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8082/healthz"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
volumes:
caddy-data:
caddy-config:
trip2g-data:
minio-data:
Что здесь важно:
caddyпубликует только80и443.trip2g-dataхранит данные trip2g и внутренний bare git-репозиторий.minio-dataхранит MinIO-объекты.trip2gиminioдоступны только внутри compose-сети.
.env
Минимальный .env для production:
PUBLIC_URL=https://docs.example.com
LISTEN_ADDR=0.0.0.0:8081
INTERNAL_LISTEN_ADDR=:8082
DB_FILE=/data/data.sqlite3
GIT_API_REPO_PATH=/data/git
LOG_LEVEL=info
DEV=false
OWNER_EMAIL=owner@example.com
MAIL_FROM=no-reply@mg.example.com
RESEND_API_KEY=re_xxxxxxxxx
JWT_SECRET=replace-with-long-random-secret
DATA_ENCRYPTION_KEY=0123456789abcdef0123456789abcdef
MINIO_ROOT_USER=trip2g
MINIO_ROOT_PASSWORD=replace-with-long-random-password
MINIO_ENDPOINT=minio:9000
MINIO_PUBLIC_URL=https://files.example.com
MINIO_ACCESS_KEY_ID=trip2g
MINIO_SECRET_KEY=replace-with-long-random-password
MINIO_BUCKET=trip2g
MINIO_REGION=us-east-1
MINIO_USE_SSL=false
MINIO_INIT_TIMEOUT=30s
MINIO_URL_EXPIRES_IN=10m
SIMPLE_BACKUP=true
FEATURES={}
# Для OpenAI embeddings:
# OPENAI_API_KEY=sk-...
# FEATURES={"vector_search":{"enabled":true,"model":"text-embedding-3-small"}}
# Для OpenAI-compatible embeddings API:
# OPENAI_API_KEY=provider-token-if-needed
# FEATURES={"vector_search":{"enabled":true,"model":"bge-m3","base_url":"https://embeddings.example.com/v1"}}
Префикс TRIP2G_
По умолчанию trip2g читает конфиг из обычных переменных окружения: LISTEN_ADDR, JWT_SECRET и т.д.
Если trip2g запускается рядом с другими сервисами, которые используют общий .env, можно добавить префикс TRIP2G_ ко всем переменным trip2g, чтобы не было конфликтов имён:
TRIP2G_PUBLIC_URL=https://docs.example.com
TRIP2G_LISTEN_ADDR=0.0.0.0:8081
TRIP2G_JWT_SECRET=replace-with-long-random-secret
# … и так далее для каждой настройки
Если переменная с префиксом TRIP2G_ присутствует, она имеет приоритет над одноимённой без префикса. Любая TRIP2G_* переменная, не соответствующая известной настройке, выводит предупреждение при старте — помогает замечать опечатки, не роняя сервер.
Что означает каждая настройка
PUBLIC_URL— внешний адрес вашего сайта. Используется в ссылках, email и интеграциях.LISTEN_ADDR— адрес, на котором слушает основной HTTP-сервер.INTERNAL_LISTEN_ADDR— внутренний адрес для healthcheck и служебных endpoint'ов.DB_FILE— путь к файлу данных внутри контейнера.GIT_API_REPO_PATH— путь к встроенному git-репозиторию trip2g.LOG_LEVEL— уровень логов сервера.DEV=false— production-режим. Не включайтеDEV=trueна публичном сервере.OWNER_EMAIL— email владельца инстанса.MAIL_FROM— адрес отправителя. Он должен принадлежать домену, который вы подтвердили в Resend.RESEND_API_KEY— API key для отправки кодов входа по email.JWT_SECRET— секрет для пользовательских сессий. После смены старые сессии станут недействительными.DATA_ENCRYPTION_KEY— 32-байтовый ключ для шифрования чувствительных данных. Сгенерировать:openssl rand -base64 32 | head -c 32MINIO_ROOT_USER/MINIO_ROOT_PASSWORD— root-учетка самого MinIO.MINIO_ENDPOINT— адрес MinIO из контейнераtrip2g.MINIO_PUBLIC_URL— публичный адрес MinIO, который попадет в presigned URL для файлов.MINIO_ACCESS_KEY_ID/MINIO_SECRET_KEY— ключи, которымиtrip2gходит в MinIO.MINIO_BUCKET— bucket для ассетов и backup-объектов.MINIO_REGION— регион S3-совместимого API. Для MinIO обычно оставляютus-east-1.MINIO_USE_SSL=false— между контейнерами обычно не нужен TLS.MINIO_INIT_TIMEOUT— сколько ждать MinIO на старте.MINIO_URL_EXPIRES_IN— срок жизни presigned URL для скачивания файлов.SIMPLE_BACKUP=true— включает простые backup'ы SQLite в MinIO.FEATURES— JSON-конфиг feature flags. Здесь включается векторный поиск.OPENAI_API_KEY— ключ для OpenAI или совместимого embeddings API.
Опционально для локального smoke-test без HTTPS:
PUBLIC_URL=http://SERVER_IP:8081
USER_TOKEN_INSECURE=true
Но это только для временной проверки. Для публичного сервера оставляйте secure cookies и ставьте TLS.
Расписание фоновых задач
Фоновые задачи работают по расписанию из таблицы cron_jobs; большинство — каждую минуту. Чтобы изменить любую задачу, задайте env-переменную с именем задачи — <JOB_NAME>_SCHEDULE — и cron-выражением (6 полей, с ведущим полем секунд):
# Запускать раз в час вместо каждой минуты
EXECUTE_CRON_WEBHOOKS_SCHEDULE=0 0 * * * *
SEND_SCHEDULED_TELEGRAM_PUBLISHPOSTS_SCHEDULE=0 0 * * * *
Имя переменной — это имя задачи в верхнем регистре плюс _SCHEDULE. Бóльший интервал означает, что задачи срабатывают менее точно (с задержкой до часа) — в обмен на меньшую нагрузку на базу.
Инстансы в облаке (managed) по умолчанию запускают их раз в час, чтобы не нагружать общую базу. Self-hosted-инстансы сохраняют дефолт «каждую минуту», пока вы не зададите override.
Caddyfile
Если хотите нормальный публичный setup, дайте сайту и файлам отдельные домены:
docs.example.com→ trip2gfiles.example.com→ MinIO
Положите рядом с docker-compose.yml файл Caddyfile:
docs.example.com {
encode zstd gzip
reverse_proxy trip2g:8081
}
files.example.com {
encode zstd gzip
reverse_proxy minio:9000
}
# Опционально, если нужна MinIO console снаружи:
# minio-admin.example.com {
# reverse_proxy minio:9001
# }
Для такой схемы в .env должны совпасть:
PUBLIC_URL=https://docs.example.com
MINIO_PUBLIC_URL=https://files.example.com
Зачем это нужно:
- trip2g работает на основном домене;
- ссылки на файлы отдаются с публичного MinIO-домена;
caddyходит к сервисам по именамtrip2gиminioвнутри docker-сети.
Внешнее объектное хранилище вместо MinIO
По умолчанию MinIO работает на том же сервере, что и trip2g. Это удобно для старта, но не защищает от потери сервера: если диск умрёт, пропадут и файлы, и резервные копии.
Для production рекомендуем вынести хранилище на отдельный S3-совместимый сервис: Backblaze B2, Hetzner Object Storage, Timeweb S3 и другие.
В этом случае сервис minio из docker-compose.yml можно убрать полностью, а в .env указать реквизиты внешнего сервиса:
MINIO_ENDPOINT=s3.us-east-005.backblazeb2.com
MINIO_PUBLIC_URL=https://files.example.com
MINIO_ACCESS_KEY_ID=your-key-id
MINIO_SECRET_KEY=your-secret
MINIO_BUCKET=trip2g
MINIO_REGION=us-east-005
MINIO_USE_SSL=true
Тогда SIMPLE_BACKUP=true будет складывать резервные копии SQLite уже на внешний сервис — автоматически, без дополнительных усилий и с защитой от потери сервера.
Репликация SQLite через Litestream
SIMPLE_BACKUP=true делает периодические snapshot'ы базы в MinIO. Если нужна непрерывная репликация SQLite с интервалом в 1 секунду, добавьте Litestream.
Litestream запускается на хосте как systemd-сервис и реплицирует файл базы напрямую в S3-совместимое хранилище. В infra/ уже есть готовая конфигурация:
infra/generate-litestream-config.sh— генерирует/etc/litestream.ymlиз переменных окруженияinfra/litestream.service— systemd unit
Конфигурация читает те же переменные, что и .env trip2g: MINIO_ACCESS_KEY_ID, MINIO_SECRET_KEY, MINIO_ENDPOINT, MINIO_BUCKET, DB_FILE. После установки litestream:
sudo cp infra/generate-litestream-config.sh /usr/local/bin/generate-litestream-config.sh
sudo chmod +x /usr/local/bin/generate-litestream-config.sh
sudo cp infra/litestream.service /etc/systemd/system/litestream.service
sudo systemctl enable --now litestream
Litestream и SIMPLE_BACKUP можно использовать одновременно — они не конфликтуют. Особенно полезна эта связка с внешним объектным хранилищем: тогда и файлы, и база данных хранятся вне сервера.
Как создать бесплатный аккаунт Resend
На resend.com:
- Зарегистрируйте бесплатный аккаунт.
- Добавьте домен или, лучше, поддомен отправителя, например
mg.example.com. - Подтвердите DNS-записи, которые покажет Resend.
- Создайте API key.
- Поставьте этот ключ в
RESEND_API_KEY. - Укажите
MAIL_FROMна адрес внутри подтверждённого домена, напримерno-reply@mg.example.com.
Почему лучше поддомен:
- проще изолировать почтовую репутацию;
- не нужно смешивать транзакционные письма trip2g с основной почтой домена.
Если домен в Resend не подтверждать, письма будут приходить только вам самому. Этого достаточно, если email-вход нужен только владельцу инстанса. Если по email должны входить другие пользователи, домен отправителя нужно подтвердить.
Векторный поиск через OpenAI или совместимый сервис
По умолчанию trip2g работает и без этого. Полнотекстовый поиск останется доступен.
Если нужен семантический поиск:
OpenAI
OPENAI_API_KEY=sk-...
FEATURES={"vector_search":{"enabled":true,"model":"text-embedding-3-small"}}
Рекомендуемая модель для старта: text-embedding-3-small.
OpenAI-compatible embeddings API
OPENAI_API_KEY=provider-token-if-needed
FEATURES={"vector_search":{"enabled":true,"model":"bge-m3","base_url":"https://embeddings.example.com/v1"}}
Важно: trip2g валидирует имя embedding-модели. Сейчас поддерживаются:
text-embedding-3-smalltext-embedding-3-largetext-embedding-ada-002multilingual-e5-basebge-m3
То есть «любой OpenAI-compatible сервис» подойдёт только если он умеет отдавать embeddings через совместимый /v1 API и вы используете одно из поддерживаемых имён моделей.
Запуск
В директории с docker-compose.yml:
docker compose up -d
Если у вас старый синтаксис Compose:
docker-compose up -d
Проверка:
docker compose ps
docker compose logs -f caddy trip2g
После старта:
- откройте
https://docs.example.com - войдите по email владельца из
OWNER_EMAIL - на пустом инстансе сервис сам предложит ссылку на скачивание ZIP с настроенным vault
- настройте Obsidian plugin на ваш
PUBLIC_URL
Дальше можно идти в Начало работы и продолжать уже с плагином Obsidian.
Что ещё легко забыть
- DNS
A/AAAAзапись дляPUBLIC_URL - DNS
A/AAAAзапись дляMINIO_PUBLIC_URL - TLS-сертификат на домен сайта
- DNS-записи Resend для домена отправителя
- сохранность volume
trip2g-data - не публикуйте MinIO console (
9001) без необходимости - мониторинг логов после первого входа и первой отправки письма
Если нужен простой старт, сначала поднимите инстанс без векторного поиска, проверьте вход по email, и только потом добавляйте FEATURES для embeddings.
fly.io
Запустите trip2g на fly.io без своего сервера. Полный гайд: ru/user/fly.