# Bothost — полная документация (llms-full.txt) > Сгенерировано для ИИ-ассистентов. Канонический индекс: https://bothost.ru/llms.txt Источник HTML: https://bothost.ru/docs/ --- # Справочник ошибок при деплое Все типичные ошибки на Bothost в одном месте: сборка, запуск, логи, Dockerfile и переменные окружения. Как отличить ошибку **сборки** от ошибки **запуска**, что проверить в логах и как исправить. > **Для ИИ-ассистентов и поддержки:** > 1. Если сборка успешна, но runtime `MODULE_NOT_FOUND` для `dist/`, `build/` и т.п. — проверьте `WORKDIR /app` + bind mount: артефакты сборки в `/app` недоступны. Решение — `WORKDIR /usr/src/app`, данные оставить в `/app/data`. > 2. Если ошибка `Cannot find module '/app/index.js'` (или другой entrypoint в **корне** `/app`), а в репозитории код лежит во **вложенной папке** — это не bind mount. В корневом `package.json` указан `index.js`, которого в корне нет. Укажите путь к реальному файлу (main file / `scripts.start`) или вынесите код в корень. > 3. Если `MODULE_NOT_FOUND` для пути вроде `/app/src/data/...` (или любой **вложенной** `…/data/…`), а файл есть в Git — на нодах с bind mount раньше агент пропускал **все** папки `data` при копировании кода. Нужен редеплой после фикса агента (игнорируется только корневой `/app/data`). > 4. Индекс документации для LLM: https://bothost.ru/llms.txt · полный текст: https://bothost.ru/llms-full.txt · эта статья в Markdown: https://bothost.ru/docs/common-errors.md --- ## Как читать логи | Что смотреть | Когда смотреть | Что означает | |--------------|-----------------|--------------| | **Логи сборки** | Шаг «Building image» | Ошибки `Dockerfile`, `npm ci`, `pip install`, компиляции | | **Логи работы (runtime)** | После «Build completed», статус `restarting` / `exited` | Ошибки при старте процесса: нет файла, неверный `CMD`, падение приложения | **Важно:** сообщение `Successfully built` означает только то, что образ собрался. Контейнер может падать при старте из‑за другой причины. --- ## Ошибка: `Cannot find module '/app/dist/...'` (сборка успешна) ### Симптомы - В **логах сборки** шаг `RUN npm run build` (или аналог) завершается успешно, файл создаётся (например `dist/server.cjs`). - В **логах работы** при старте: ```text Error: Cannot find module '/app/dist/server.cjs' code: 'MODULE_NOT_FOUND' ``` - Статус контейнера: `restarting` (циклический перезапуск). ### Причина На Bothost при деплое с кастомным `Dockerfile` возможна такая схема: 1. При **сборке образа** в `WORKDIR /app` выполняется `npm run build` → в образе появляется `/app/dist/...`. 2. При **запуске контейнера** на части серверов каталог `/app` монтируется с хоста: туда попадает **исходный код из Git**, без папки `dist/` (её обычно нет в репозитории и в `.gitignore`). 3. Команда `CMD ["node", "dist/server.cjs"]` ищет файл в **примонтированном** `/app`, а не в слое образа → `MODULE_NOT_FOUND`. Типичный кейс: Node.js/TypeScript проект с esbuild/tsc, `WORKDIR /app`, `DATA_DIR=/app/data`, как в [Negodiay-Bot](https://github.com/Samnord2004/Negodiay-Bot). Платформа отдельно восстанавливает из образа на хост **`node_modules`** (для Node.js), но **не** каталоги вроде `dist/`, `build/`, `.next/` — их нужно либо не класть в `/app`, либо собирать при каждом старте. ### Решение Перенесите код приложения и артефакты сборки **вне** `/app`. Каталог `/app/data` оставьте для персистентных данных (БД, загрузки) — он как раз предназначен для volume. **Было (проблемный вариант):** ```dockerfile FROM node:20-alpine WORKDIR /app COPY . . RUN npm ci RUN npm run build ENV DATA_DIR=/app/data RUN mkdir -p /app/data && chmod 777 /app/data CMD ["node", "dist/server.cjs"] ``` **Стало (рекомендуется):** ```dockerfile FROM node:20-alpine WORKDIR /usr/src/app COPY . . RUN npm ci RUN npm run build ENV NODE_ENV=production ENV DATA_DIR=/app/data RUN mkdir -p /app/data && chmod 777 /app/data EXPOSE 3000 CMD ["node", "dist/server.cjs"] ``` Пояснения: - `WORKDIR /usr/src/app` — здесь лежат исходники и `dist/` из образа; bind mount на `/app` их не затирает. - `CMD` выполняется относительно `WORKDIR`, путь `dist/server.cjs` корректен. - `DATA_DIR=/app/data` — данные по-прежнему в volume Bothost ([хранение БД](database-storage)). ### Дополнительно проверить - В коде пути к данным берутся из `process.env.DATA_DIR` или `path.join('/app/data', ...)`, а не из `__dirname` относительно старого `/app`. - Папка `dist/` в `.gitignore` — это нормально; на Bothost она должна жить в образе (или вне `/app`), а не в Git. ### Альтернативы - Сборка при старте в `entrypoint.sh` (медленнее, но всё в `/app`): `npm run build && node dist/server.cjs`. - Multi-stage Dockerfile: финальный `COPY --from=builder` артефактов в `/usr/local/bin` или другой путь вне `/app`. --- ## Ошибка: `Cannot find module '/app/src/data/...'` (файл есть в Git) ### Симптомы - Сборка образа успешна. - В **логах работы**: `ERR_MODULE_NOT_FOUND` / `Cannot find module '/app/src/data/templates.js'` (или другой файл во **вложенной** папке `data`). - В GitHub файл на месте; в файл-менеджере бота папки `src/data/` может не быть. ### Причина На нодах с bind mount агент копирует код репозитория на хост и **не перезаписывает** корневой `/app/data` (персистентный volume). Раньше использовался `ignore_patterns('data')`, который пропускал **любую** папку с именем `data` на любой глубине — в том числе исходники `src/data/`, `lib/data/` и т.п. Это **не** кейс с `dist/` и **не** неверный entrypoint. ### Решение 1. Обновить агент на ноде (фикс: игнорируется только корневой `data/`). 2. Сделать **новый деплой** бота (одного рестарта мало — код на хосте нужно перекопировать). Временный обходной путь без обновления агента: переименовать вложенную папку (например `src/data` → `src/datasets`) и поправить импорты. --- ## Ошибка: `Cannot find module '/app/index.js'` (код во вложенной папке) ### Симптомы - Сборка образа проходит успешно (`Successfully built`). - В **логах работы** при старте: ```text Error: Cannot find module '/app/index.js' code: 'MODULE_NOT_FOUND' ``` - Статус контейнера: `restarting`. - В файл-менеджере бота в корне видны `package.json` и подпапка с кодом, но **нет** `index.js` в корне `/app`. ### Как отличить от ошибки с `dist/` | Критерий | Этот кейс | Кейс с `dist/` выше | |----------|-----------|---------------------| | Путь в ошибке | `/app/index.js`, `/app/main.js` и т.п. (исходник) | `/app/dist/...`, `/app/build/...` | | Файл в Git | **Нет** в корне репозитория | Обычно есть после `npm run build` в образе, но нет в Git | | Причина | Неверный entrypoint / nested-структура | Bind mount затирает артефакты сборки | | Фикс | Указать правильный путь к файлу | `WORKDIR` вне `/app` | ### Причина Агент Bothost (и автогенерация Dockerfile) читает **корневой** `package.json` и запускает то, что указано в `main` / `scripts.start` (часто `node index.js`). Если репозиторий устроен так: ```text repo/ ├── package.json ← "main": "index.js", "start": "node index.js" ├── README.md └── my-bot/ ← весь код здесь ├── index.js ├── package.json └── ... ``` то в корне **нет** `index.js`, а контейнер всё равно стартует с `CMD ["node", "index.js"]` → `Cannot find module '/app/index.js'`. Типичный пример: [FuF1iK/botds](https://github.com/FuF1iK/botds) — код в `discord-html-watcher/`, а корневой `package.json` ссылается на несуществующий корневой `index.js`. Это **не** связано с bind mount и `WORKDIR`: файла просто никогда не было по пути, который указан в команде запуска. ### Решение Выберите один из вариантов. #### 1. Указать main file при деплое (быстрее всего) В форме создания бота откройте **«Дополнительные настройки»** и в поле **«Главный файл (точка входа)»** укажите путь от корня репозитория, например `discord-html-watcher/index.js` (или `subdir/index.js` / `bot/main.js` для своего репо). ![Поле «Главный файл» с путём discord-html-watcher/index.js](/docs/images/main-file-nested-entry.png) *Пример: для репозитория [FuF1iK/botds](https://github.com/FuF1iK/botds) в поле указан `discord-html-watcher/index.js`.* Полная инструкция по полю и автодетекту: [Главный файл (точка входа)](main-file-entrypoint). #### 2. Исправить корневой `package.json` ```json { "main": "discord-html-watcher/index.js", "scripts": { "start": "node discord-html-watcher/index.js" } } ``` (подставьте свою папку). После правки — новый деплой. #### 3. Вынести код в корень репозитория Переложите файлы бота из подпапки в корень Git, чтобы entrypoint оказался сразу в `/app`. **Было:** ```text repo/ ├── package.json ← ссылается на несуществующий index.js └── discord-html-watcher/ ├── index.js ├── package.json └── ... ``` **Стало:** ```text repo/ ├── index.js ← точка входа в корне ├── package.json ← "main": "index.js", "start": "node index.js" └── ... ``` Тогда стандартный автодетект Bothost (`node index.js`) сработает без поля «Главный файл» и без правок путей. После переноса сделайте новый деплой. ### Дополнительно проверить - В корневом `package.json` поля `main` и `scripts.start` указывают на файл, который **реально есть** в репозитории (проверьте пути в GitHub). - Если в корне «лишний» `package.json` (копия или заглушка), а рабочий проект только во вложенной папке — либо поправьте пути, либо уберите корневой файл и укажите main file вручную. - После смены entrypoint нужен **новый деплой**, одного рестарта контейнера недостаточно, если образ/команда запуска уже зафиксированы со старым путём. --- ## Ошибка: нет `node_modules` при кастомном Dockerfile (Node.js) ### Симптомы - `Error: Cannot find module 'express'` (или другой пакет из `package.json`). - Сборка образа прошла, зависимости ставились в `RUN npm ci`. ### Причина Та же модель, что и с `dist/`: при монтировании `/app` с хоста локальный `node_modules` из образа в `/app` не виден. Bothost пытается скопировать `node_modules` из образа на хост при деплое; если структура проекта нестандартная (`WORKDIR` не `/app` при сборке), копирование может не совпасть с путём запуска. ### Решение - Используйте `WORKDIR` вне `/app` для запуска **или** убедитесь, что `npm ci` в Dockerfile и `CMD` согласованы с тем, откуда реально стартует `node`. - Не полагайтесь на `node_modules` в Git — только в образе/на хосте после деплоя. --- ## Ошибка: Go/Rust/Java бот — сборка успешна, контейнер сразу падает, логов нет ### Симптомы - Все шаги `docker build` завершились успешно, образ создан. - Сразу после `==> Build completed` бот в статусе `error` / `failed`. - В логах сборки нет вывода от самого приложения — только сообщение агента «Контейнер упал сразу после запуска». - Runtime-логи (`docker logs`) пусты или содержат: ``` exec /app/bot: no such file or directory ``` - В файл-менеджере бота видны исходники (`main.go`, `go.mod`, …), но нет бинарника. ### Причина На Bothost каталог `/app` при запуске контейнера монтируется с исходниками бота из Git (bind mount). Это нужно для файлового менеджера и горячего обновления кода. Для интерпретируемых языков (Python, Node.js) всё работает: исходник и есть запускаемый файл. Для компилируемых языков (Go, Rust, Java) бинарник/JAR **живёт в слое образа**, а bind mount его скрывает. ``` /app/bot ← скрыт bind mount'ом /app/server ← тоже скрыт /app/bin/server ← тоже скрыт (весь /app перекрыт) ``` ### Решение Положите скомпилированный артефакт **вне `/app`** — например в `/usr/local/bin/`. **Было (падает):** ```dockerfile FROM golang:1.22-alpine AS builder WORKDIR /src COPY . . RUN go build -o /app/bot . FROM alpine:3.20 WORKDIR /app COPY --from=builder /app/bot . # ← скрыт bind mount'ом CMD ["./bot"] # ← не найден ``` **Стало (работает):** ```dockerfile FROM golang:1.22-alpine AS builder WORKDIR /src COPY . . RUN go build -o /usr/local/bin/bot . FROM alpine:3.20 WORKDIR /app COPY --from=builder /usr/local/bin/bot /usr/local/bin/bot # ← вне /app RUN mkdir -p /app/data && chmod 777 /app/data CMD ["/usr/local/bin/bot"] # ← работает ``` Данные (БД, файлы) по-прежнему хранятся в `/app/data` — bind mount их видит и сохраняет между деплоями. ### Примечание: кастомный Dockerfile vs автогенерация Если у вас в репозитории **нет своего `Dockerfile`**, агент Bothost генерирует его автоматически — и уже кладёт бинарник в `/usr/local/bin/` корректно. Проблема возникает только при использовании **кастомного Dockerfile** из репозитория со старой схемой (`COPY ... /app/`). Самый быстрый способ исправить: снять галку «Использовать Dockerfile из репозитория» при деплое, тогда агент сгенерирует правильный вариант сам. ### Совместимость языков с bind mount на `/app` | Язык | Работает без изменений | Комментарий | |---|---|---| | Python | ✅ | Исходник запускается напрямую | | Node.js | ✅ | То же (артефакты `dist/` — отдельный кейс выше) | | PHP | ✅ | Исходник запускается напрямую | | Go | ❌ | Бинарник должен быть в `/usr/local/bin/` | | Rust | ❌ | То же | | Java / Kotlin | ❌ | JAR должен быть вне `/app` | --- ## Контейнер в статусе `restarting` ### Симптомы Бот не отвечает, в панели статус `restarting`, в логах одна и та же ошибка повторяется. ### Частые причины 1. Процесс сразу завершается с ошибкой (`MODULE_NOT_FOUND`, синтаксис, отсутствие env). 2. `CMD` запускает не тот файл или одноразовый скрипт вместо долгоживущего процесса (в т.ч. код во вложенной папке — см. раздел про `/app/index.js` выше). 3. Приложение слушает `127.0.0.1` вместо `0.0.0.0` (для вебхуков/домена). 4. Не заданы обязательные переменные (`BOT_TOKEN`, `GEMINI_API_KEY` и т.д.). ### Что делать 1. Откройте **логи работы** (не сборки). 2. Найдите **первую** ошибку в цикле перезапуска. 3. Сопоставьте с разделами этой статьи или [Кастомный Dockerfile](custom-dockerfile). --- ## Не работает DeepSeek / Gemini / OpenAI / Claude (ошибки API, timeout, geo) ### Симптомы Бот запущен, но запросы к нейросетям и зарубежным API падают: timeout, connection error, geo/region blocked, «хостинг блокирует исходящие запросы». ### Причина Bothost **не блокирует** исходящий трафик. У части нод IP определяется как российский, и внешний API (DeepSeek, Gemini, OpenAI, Claude и др.) отклоняет запросы. ### Решение 1. В [профиле](https://bothost.ru/profile.php) включите **«Зарубежный IP для API и нейросетей»**. 2. Задеплойте бота **заново** — существующие боты сами не переезжают на ноду с зарубежным IP. Подробнее и скриншот: [FAQ — нейросети и зарубежные API](faq#foreign-ip-api). --- ## Сборка успешна, но 502 / 504 на домене ### Симптомы Образ собран, контейнер `running`, сайт или webhook недоступны. ### Причина Приложение не слушает нужный порт или интерфейс. ### Решение - Слушать `0.0.0.0`, порт из переменной `PORT` (Bothost передаёт её в контейнер). - Внутренний порт в настройках бота = порту в коде / `EXPOSE`. - Подробнее: [Работа с доменами и портами](domains-and-ports), [Веб-приложения и домены](web-apps-domains). --- ## Ошибка: лог сборки обрывается после «Cloning into...» ### Симптомы - В логах сборки видно только: ```text ==> Cloning https://github.com/... Cloning into '...'... ``` - После этого поток обрывается, статус бота переходит в `failed`. - Никакого сообщения об ошибке не отображается. ### Причина Клонирование прошло успешно, но на следующем шаге чтения `Dockerfile` произошла ошибка. Чаще всего — `Dockerfile` сохранён в кодировке, отличной от UTF-8 (например, Windows-1251). Агент не смог прочитать файл и завершился без вывода в лог. ### Решение 1. Пересохраните `Dockerfile` в кодировке **UTF-8** (в VS Code: строка состояния снизу → кликнуть на кодировку → «Save with Encoding» → UTF-8). 2. Уберите кириллические комментарии из `Dockerfile` или переведите их на английский. 3. Запустите деплой повторно. ### Проверка кодировки ```bash file Dockerfile # OK: ASCII text или UTF-8 Unicode text # Плохо: ISO-8859, windows-1251, UTF-8 Unicode (with BOM) ``` --- ## Ошибки только в логах сборки | Сообщение | Возможная причина | |-----------|-------------------| | `npm ci` failed | Нет `package-lock.json`, несовместимые версии Node | | `go build` failed | Неверный модуль, CGO, отсутствие файлов в `COPY` | | `pip install` failed | Ошибка в `requirements.txt`, нужны системные пакеты (`apk`/`apt` в Dockerfile) | | `COPY failed` | Файл в `.dockerignore`, пустой контекст | Исправляйте `Dockerfile` и зависимости; runtime-логи здесь не помогут, пока образ не соберётся. --- ## Чеклист для кастомного Dockerfile (Node.js + сборка) - [ ] `WORKDIR` для кода и `dist/` **не** `/app` (например `/usr/src/app`). - [ ] Данные и БД — в `/app/data` или через `DATA_DIR`. - [ ] `CMD` указывает на файл, который реально появляется после `npm run build`. - [ ] Порт из `process.env.PORT`, хост `0.0.0.0` (если есть HTTP). - [ ] Секреты — в переменных окружения Bothost, не в образе. - [ ] После правок — новый деплой; проверены **и** логи сборки, **и** логи работы. --- ## См. также - [Кастомный Dockerfile](custom-dockerfile) - [Хранение базы данных](database-storage) - [Переменные окружения](environment-variables) - [FAQ](faq) **Не нашли ответ?** [Поддержка](https://t.me/bothostru) или тикет в панели Bothost. --- # Часто задаваемые вопросы (FAQ) ## 🤖 Общие вопросы ### Как создать бота? См. [Быстрый старт](getting-started). ### Какие языки программирования поддерживаются? - Python (aiogram, python-telegram-bot, Telethon, Pyrogram) - Node.js (telegraf, node-telegram-bot-api, grammy) - PHP (в разработке) ### Сколько ботов можно создать? Зависит от тарифа: - **Бесплатный**: 1 бот - **Базовый**: 5 ботов - **Pro**: 20 ботов ### Как обновить код бота? 1. **Автоматически** (только на платных тарифах): - Настройте webhook в GitHub/GitLab - При каждом push бот автоматически обновится 2. **Вручную**: - Нажмите "Обновить из Git" в панели управления - Бот пересоберется с последней версией кода ## 🔧 Технические вопросы ### Как указать главный файл для запуска? В разделе "Дополнительные настройки" при создании бота укажите главный файл (например: `userbot.py`, `bot.py`, `index.js`). Если не указать, система автоматически определит главный файл по приоритету. ### Нужен ли домен для webhook? **Да**, для webhook необходим домен с HTTPS. Telegram требует HTTPS для webhook. Включите опцию "Включить веб-интерфейс / админку / webhook" при создании бота — мы автоматически создадим домен и настроим SSL. ### Как работает автоматическое определение языка? Система анализирует файлы в репозитории: - `package.json` → Node.js - `requirements.txt` → Python - `composer.json` → PHP ### Можно ли использовать свой Dockerfile? Да. Если нужен нестандартный процесс сборки/запуска, используйте кастомный `Dockerfile` в корне репозитория. Важно: - Для вебхуков и домена приложение должно слушать `0.0.0.0` на порту из `PORT`. - После изменения Dockerfile/порта делайте повторный деплой. Подробно: [Кастомный Dockerfile](custom-dockerfile). ### Как посмотреть логи бота? В панели управления ботом: - Нажмите "Логи работы" для просмотра логов выполнения - Нажмите "Логи сборки" для просмотра логов сборки Docker образа ## 🐛 Проблемы и ошибки ### Ошибка: "EOF when reading a line" **Причина**: Бот пытается использовать интерактивный ввод (например, Telethon userbot). **Решение**: Используйте String Session. См. [Настройка Userbot](telegram-userbot-setup.md). ### Бот не запускается **Проверьте**: 1. Все зависимости установлены (проверьте `requirements.txt` или `package.json`) 2. Главный файл указан правильно 3. Переменные окружения установлены (если нужны) 4. Логи сборки на наличие ошибок ### Бот запускается, но не отвечает **Проверьте**: 1. Bot Token правильный и активный 2. Бот запущен (статус "running") 3. Логи работы на наличие ошибок 4. Код бота корректен ### Контейнер в статусе "restarting" **Причина**: Бот падает при запуске. **Решение**: 1. Проверьте логи работы 2. Убедитесь, что все зависимости установлены 3. Проверьте, что код бота корректен 4. Если проблема не решается, остановите контейнер принудительно: ```bash docker ps -a --filter "status=restarting" --format "{{.Names}}" | grep bot_ | xargs -r docker rm -f ``` ## 💰 Тарифы и лимиты ### Что включено в бесплатный тариф? - 1 бот - Long polling (без webhook) - Базовые функции ### Что доступно на платных тарифах? - Больше ботов (5 на Базовом, 20 на Pro) - Webhook и автоматическое обновление - Переменные окружения - Выбор локации развертывания - Веб-интерфейс и домены ### Как перейти на платный тариф? Перейдите на [страницу тарифов](https://bothost.ru/pricing.php) и выберите подходящий план. ### Оплата прошла, а тариф не активировался. Что делать? Откройте [Мои платежи](https://bothost.ru/check-payment.php) и: 1. Нажмите **«Восстановить тариф»**, если сверху жёлтое предупреждение; или 2. У платежа со статусом **«Ожидает»** нажмите **«Проверить и активировать»**. Подробная инструкция со скриншотом: [Платёж не зачислился](check-payment). ## 📚 Дополнительные ресурсы - [Документация](index.md) - [Примеры кода](examples.md) - [Поддержка](https://t.me/bothostru) --- **Не нашли ответ?** Обратитесь в поддержку: [@bothostru](https://t.me/bothostru) --- # Кастомный Dockerfile на Bothost Эта статья для случаев, когда стандартной автонастройки недостаточно и вы хотите полностью контролировать сборку и запуск контейнера. --- ## Когда нужен кастомный Dockerfile Используйте свой `Dockerfile`, если вам нужно: - установить системные пакеты (`apt`, `apk`) или нестандартные зависимости; - делать многоэтапную сборку (build stage + runtime stage); - явно контролировать команду запуска (`CMD`/`ENTRYPOINT`); - запускать веб-приложение вместе с ботом на конкретном порту; - использовать собственную структуру проекта, которую автоопределение не покрывает. Если ваш проект простой (например, Python-бот с `requirements.txt`), чаще всего достаточно стандартного сценария деплоя без кастомного Dockerfile. ## Как включить свой Dockerfile 1. Положите файл `Dockerfile` в **корень** репозитория. 2. В форме создания или редактирования бота откройте блок **«Дополнительные настройки»**. 3. Включите галочку **«Использовать собственный Dockerfile»**. ![Галочка «Использовать собственный Dockerfile» в дополнительных настройках бота](/docs/images/custom-dockerfile-checkbox.png) После этого система будет собирать образ по вашему `Dockerfile` вместо автоматически сгенерированного. ## Базовые требования Bothost Чтобы контейнер корректно работал с доменом/webhook на платформе: - приложение должно слушать `0.0.0.0`, а не `127.0.0.1`; - порт должен браться из переменной окружения `PORT`; - в настройках бота внутренний порт должен совпадать с портом, который реально слушает приложение; - после изменения порта/домена нужен повторный деплой. Для веб-сервисов (FastAPI, Express и т.д.) это критично: иначе получите `502/504` даже при успешной сборке. ## Минимальный шаблон для Python (бот + веб endpoint) ```dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # Приложение должно читать PORT из env и слушать 0.0.0.0 CMD ["python", "main.py"] ``` ## Минимальный шаблон для Node.js ```dockerfile FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . # Приложение должно читать PORT из env и слушать 0.0.0.0 CMD ["node", "index.js"] ``` ## Минимальный шаблон для Go (multi-stage) ```dockerfile FROM golang:1.22-alpine AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o /usr/local/bin/bot . FROM alpine:3.20 RUN apk --no-cache add ca-certificates WORKDIR /app # Бинарник кладём в /usr/local/bin/, а не в /app — # иначе он будет скрыт bind mount'ом при деплое. # Данные (БД и т.д.) храним в /app/data как обычно. COPY --from=builder /usr/local/bin/bot /usr/local/bin/bot RUN mkdir -p /app/data && chmod 777 /app/data CMD ["/usr/local/bin/bot"] ``` > **Важно для Go и других компилируемых языков:** на Bothost каталог `/app` > при запуске контейнера монтируется с исходниками из Git. Бинарник, скомпилированный > в образе и положенный в `/app`, будет скрыт этим mount'ом и не найдётся при старте. > Всегда кладите бинарники в `/usr/local/bin/` или другой путь вне `/app`. > Подробнее: [Справочник ошибок при деплое](common-errors). ## Типовые ошибки - Приложение слушает `localhost` вместо `0.0.0.0`. - Порт захардкожен в коде, а не берётся из `PORT`. - `CMD` запускает не тот файл/бинарник. - Нужные файлы не копируются в образ (`COPY` неполный). - Сборка успешна, но процесс сразу падает при старте (смотрите runtime-логи, а не только build-логи). - **`Cannot find module '/app/dist/...'`** при успешной сборке — часто из‑за `WORKDIR /app` и артефактов сборки (`dist/`, `build/`), которые не попадают в примонтированный с Git каталог `/app`. Решение: перенести `WORKDIR` в путь вне `/app` (например `/usr/src/app`), данные оставить в `/app/data`. Подробно: [Справочник ошибок при деплое](common-errors). ## Чеклист перед деплоем - `Dockerfile` лежит в корне репозитория. - Команда запуска стартует долгоживущий процесс (а не одноразовый скрипт). - Для веб-приложения есть обработчик `health` (или понятный endpoint для проверки). - Сервис поднимается локально с переменной `PORT`: ```bash PORT=8080 docker run --rm -p 8080:8080 ``` - После пуша выполнен деплой и проверены: - логи сборки, - логи запуска, - доступность домена/вебхука. --- ## См. также - [Работа с доменами и портами](domains-and-ports) - [Веб-приложения и домены](web-apps-domains) - [Установка библиотек: Python, Java и Go](installing-libraries) - [Главный файл (точка входа)](main-file-entrypoint) - [Справочник ошибок при деплое](common-errors) - [FAQ](faq) --- # Быстрый старт с Bothost Это руководство поможет вам создать и развернуть вашего первого бота на Bothost за 5 минут. ## 📋 Что вам понадобится 1. **Аккаунт на Bothost** — [зарегистрируйтесь](https://bothost.ru/register.php) 2. **Git репозиторий** с кодом бота (GitHub, GitLab и т.д.) 3. **Bot Token** от Telegram или Discord ## 🚀 Шаг 1: Подготовка репозитория 1. Создайте репозиторий на GitHub/GitLab 2. Добавьте код вашего бота 3. Убедитесь, что есть главный файл (`main.py`, `bot.py`, `index.js` и т.д.) ### Пример структуры для Python: ``` my-bot/ ├── main.py # Главный файл ├── requirements.txt # Зависимости └── README.md ``` ### Пример структуры для Node.js: ``` my-bot/ ├── index.js # Главный файл ├── package.json # Зависимости └── README.md ``` ## 🚀 Шаг 2: Создание бота на Bothost 1. Перейдите на [bothost.ru/create-bot.php](https://bothost.ru/create-bot.php) 2. Заполните форму: - **Название бота**: любое имя - **Платформа**: Telegram или Discord - **Библиотека**: выберите используемую библиотеку - **Bot Token**: токен от @BotFather (Telegram) или Discord Developer Portal - **Git URL**: ссылка на ваш репозиторий - **Ветка**: обычно `main` или `master` 3. Нажмите "Создать бота" ## 🚀 Шаг 3: Ожидание развертывания Бот автоматически: - Клонирует репозиторий - Определит язык проекта - Установит зависимости - Соберет Docker образ - Запустит бота Вы можете следить за процессом в реальном времени в логах. ## ✅ Готово! Ваш бот запущен и работает! Вы можете: - Просматривать логи в панели управления - Останавливать/запускать бота - Обновлять код через Git ## 📚 Что дальше? - [Настройка переменных окружения](environment-variables) - [Кастомный Dockerfile](custom-dockerfile) - [Настройка webhook](webhook-vs-polling.md) - [Автоматическое обновление](webhooks.md) - [Создание userbot](telegram-userbot-setup) --- **Проблемы?** Проверьте [FAQ](faq) или обратитесь в поддержку: [@bothostru](https://t.me/bothostru) --- # BotHost Mini App — Полное руководство *Источник: [https://manual.bothost.ru/](https://manual.bothost.ru/)* --- ## Содержание 1. [Что такое Mini App](#что-такое-mini-app) 2. [Подготовка к работе](#подготовка-к-работе) 3. [Создание файлов](#создание-файлов) 4. [Загрузка на GitHub](#загрузка-на-github) 5. [Настройка на BotHost](#настройка-на-bothost) 6. [Интеграция с Telegram](#интеграция-с-telegram) 7. [Продвинутые функции](#продвинутые-функции) 8. [Решение проблем](#решение-проблем) 9. [Примеры проектов](#примеры-проектов) --- ## Что такое Mini App ### Обзор технологии Mini App (ранее известные как Web App) — это веб-приложения, которые запускаются прямо внутри Telegram и выглядят как нативная часть мессенджера. Они используют стандартные веб-технологии (HTML, CSS и JavaScript), но при этом имеют доступ к специальным API Telegram. ![Рис. 1: Пример мини-приложения в Telegram](/docs/images/miniapp/miniapp_dark_theme.png "Пример мини-приложения в Telegram") Ключевые особенности Mini App: * **Быстрый доступ:** Открываются через кнопку меню в боте, прямые ссылки или через кнопки в сообщениях * **Адаптивный дизайн:** Автоматически подстраиваются под светлую и темную тему Telegram * **Доступ к данным:** Могут получать информацию о пользователе (с его согласия) * **Нативные элементы:** Используют компоненты интерфейса Telegram, например MainButton * **Безопасность:** Работают только по HTTPS и имеют ограничения для безопасности пользователей Mini App могут использоваться для множества сценариев: магазины, формы опросов, игры, дашборды, редакторы, каталоги и многое другое. ### Преимущества использования BotHost BotHost — это специализированная платформа для хостинга ботов и мини-приложений Telegram, которая существенно упрощает процесс их создания и поддержки: * **Комплексное решение:** Одна платформа для размещения как бота, так и мини-приложения * **Автоматизация:** Интеграция с GitHub для мгновенного обновления при изменении кода * **SSL из коробки:** Все домены автоматически получают HTTPS-сертификаты (обязательны для Mini App) * **Домены:** Возможность использовать поддомены bothost.ru или подключить свой домен * **Мониторинг и логи:** Удобный доступ к логам и статистике использования * **Масштабирование:** Простое увеличение ресурсов при росте нагрузки ![Рис. 2: Интерфейс панели управления BotHost](/docs/images/miniapp/bothost_setup.png "Панель управления BotHost") --- ## Подготовка к работе ### Необходимые инструменты Перед началом создания Mini App на BotHost, вам понадобятся: * **Аккаунт BotHost:** Зарегистрируйтесь на [bothost.ru](https://bothost.ru/) и выберите подходящий тарифный план * **GitHub аккаунт:** Репозиторий для хранения кода вашего приложения * **Telegram бот:** Созданный через @BotFather с полученным токеном * **Базовые знания:** HTML, CSS и JavaScript (минимальный уровень) * **Редактор кода:** VS Code, Sublime Text или любой другой текстовый редактор Для локального тестирования (опционально): * **Node.js:** Версия 18.x или выше (рекомендуется LTS) * **Git:** Для управления версиями кода ### Создание бота в Telegram Первым шагом будет создание Telegram бота, через который пользователи будут получать доступ к вашему мини-приложению: 1. Откройте [@BotFather](https://t.me/BotFather) в Telegram 2. Отправьте команду `/newbot` 3. Введите **название** бота (может содержать любые символы) 4. Введите **username** бота (должен заканчиваться на "bot" и быть уникальным) 5. Получите и сохраните **токен** бота — он понадобится для настройки на BotHost ![Рис. 3: Процесс создания бота через BotFather](/docs/images/miniapp/botfather_create.png "Создание бота в BotFather") > ⚠️ **Важно:** Никогда не публикуйте токен вашего бота в открытом доступе. Храните его в безопасном месте, так как он предоставляет полный доступ к управлению ботом. --- ## Создание файлов ### Структура проекта Для корректной работы Mini App на BotHost важно правильно организовать структуру файлов. Вот рекомендуемая структура: ``` repository/ ├── app.js # Серверный код Node.js ├── package.json # Настройки проекта └── public/ # Папка с файлами мини-приложения ├── index.html # Основной HTML файл ├── style.css # Стили ├── script.js # Клиентский JavaScript └── images/ # Папка для изображений ``` > ⚠️ Расположение файлов критически важно! HTML/CSS/JS файлы **обязательно** должны находиться в папке `public`, а не в корне репозитория. ### Серверная часть (app.js) Файл `app.js` в корне проекта отвечает за раздачу статических файлов из папки `public`. Вот готовый код сервера на Node.js: ```javascript // Сервер для раздачи статических файлов const http = require('http'); const fs = require('fs'); const path = require('path'); // Порт из переменных окружения BotHost или 3000 по умолчанию const PORT = process.env.PORT || 3000; // MIME-типы для разных файлов const mimeTypes = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.gif': 'image/gif', '.ico': 'image/x-icon' }; // Создаем HTTP-сервер const server = http.createServer((req, res) => { console.log(`Запрос: ${req.method} ${req.url}`); // Нормализуем URL (убираем query string, убираем ведущие слэши для безопасного join) let url = (req.url || '/').split('?')[0].replace(/^\/+/, '') || 'index.html'; // Определяем путь к файлу и проверяем, что он внутри public (защита от path traversal) const publicDir = path.join(__dirname, 'public'); const filePath = path.join(publicDir, path.normalize(url)); const resolvedPath = path.resolve(filePath); const resolvedPublic = path.resolve(publicDir); if (!resolvedPath.startsWith(resolvedPublic + path.sep) && resolvedPath !== resolvedPublic) { res.writeHead(403); res.end('Доступ запрещён'); return; } const extname = path.extname(resolvedPath); const contentType = mimeTypes[extname] || 'text/plain'; // Читаем файл fs.readFile(resolvedPath, (error, content) => { if (error) { if (error.code === 'ENOENT') { res.writeHead(404); res.end('Файл не найден'); } else { res.writeHead(500); res.end(`Ошибка сервера: ${error.code}`); } } else { res.writeHead(200, { 'Content-Type': contentType }); res.end(content, 'utf-8'); } }); }); server.listen(PORT, '0.0.0.0', () => { console.log(`✅ Сервер запущен на порту ${PORT}`); }); ``` Этот код создает простой HTTP-сервер, который отдает файлы из папки `public`, определяя правильный Content-Type для разных типов файлов. ### Настройка проекта (package.json) ```json { "name": "bothost-miniapp", "version": "1.0.0", "description": "Telegram Mini App на BotHost", "main": "app.js", "scripts": { "start": "node app.js", "dev": "nodemon app.js" }, "dependencies": {}, "devDependencies": { "nodemon": "^2.0.20" } } ``` Для простого статического мини-приложения нам не требуются внешние зависимости. `nodemon` добавлен как dev-зависимость для удобства локальной разработки. ### HTML-страница (public/index.html) ```html Мое мини-приложение

Мое Mini App

Разработано на BotHost

Добро пожаловать!

Это ваше первое мини-приложение в Telegram.

``` > ⚠️ Скрипт `telegram-web-app.js` **обязательно** должен быть подключен в head документа для корректной работы с Telegram! ### Стили (public/style.css) ```css :root { --bg-color: #ffffff; --text-color: #222222; --hint-color: #999999; --button-color: #50a8eb; --button-text-color: #ffffff; --card-bg: #f5f5f7; } body { background-color: var(--tg-theme-bg-color, var(--bg-color)); color: var(--tg-theme-text-color, var(--text-color)); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 0; font-size: 16px; line-height: 1.6; } .container { max-width: 450px; margin: 0 auto; padding: 20px 16px; } header h1 { font-size: 24px; font-weight: 700; } .card { background-color: var(--tg-theme-secondary-bg-color, var(--card-bg)); border-radius: 12px; padding: 20px; } .button { background-color: var(--tg-theme-button-color, var(--button-color)); color: var(--tg-theme-button-text-color, var(--button-text-color)); border: none; border-radius: 8px; padding: 12px 20px; } ``` CSS-переменные `--tg-theme-*` автоматически адаптируют приложение под светлую и темную тему пользователя в Telegram. ### JavaScript-логика (public/script.js) ```javascript const tg = window.Telegram.WebApp; tg.ready(); tg.expand(); // Обработчик MainButton настраиваем один раз (иначе при каждом клике добавлялся бы новый) tg.MainButton.onClick(function() { const data = { action: 'button_pressed', timestamp: new Date().toISOString() }; tg.sendData(JSON.stringify(data)); setTimeout(() => tg.close(), 1000); }); document.addEventListener('DOMContentLoaded', function() { const mainButton = document.getElementById('mainButton'); if (mainButton) { mainButton.addEventListener('click', function() { if (tg.HapticFeedback) { tg.HapticFeedback.impactOccurred('medium'); } tg.MainButton.setText('ГОТОВО'); tg.MainButton.show(); }); } if (tg.initDataUnsafe.user) { const user = tg.initDataUnsafe.user; console.log('Пользователь:', user.first_name, user.last_name); } }); ``` --- ## Загрузка на GitHub ### Создание репозитория 1. Зайдите на [GitHub](https://github.com/) и авторизуйтесь 2. Нажмите кнопку "+" в правом верхнем углу и выберите "New repository" 3. Введите имя для репозитория (например, "telegram-miniapp") 4. Установите видимость "Public" 5. Нажмите "Create repository" ### Загрузка файлов #### Способ 1: Через веб-интерфейс GitHub 1. В репозитории нажмите "Add file" → "Upload files" 2. Создайте папку `public` (введя "public/file.txt" в имени файла) 3. Загрузите все файлы в соответствующие папки 4. Обязательно соблюдайте структуру: app.js и package.json в корне, остальные — в `public` ![Рис. 4: Интерфейс загрузки файлов на GitHub](/docs/images/miniapp/github_upload.png "Загрузка на GitHub") #### Способ 2: Через Git (рекомендуется) ```bash git clone https://github.com/username/telegram-miniapp.git cd telegram-miniapp mkdir -p public/images touch app.js package.json public/index.html public/style.css public/script.js git add . git commit -m "Initial commit" git push origin main ``` ### Проверка структуры ``` telegram-miniapp/ ├── app.js ├── package.json └── public/ ├── index.html ├── style.css └── script.js ``` --- ## Настройка на BotHost ### Создание бота на платформе 1. Войдите в аккаунт на BotHost 2. Нажмите "Создать бота" или "Добавить бота" 3. Заполните форму: - **Название:** Имя для внутренней идентификации - **Платформа:** Telegram - **Библиотека/язык:** Node.js - **Bot Token:** Токен от BotFather - **Git URL репозитория:** URL вашего GitHub репозитория - **Локация:** Ближайшая к пользователям 4. Нажмите "Создать бота" ![Рис. 5: Форма создания бота на платформе BotHost](/docs/images/miniapp/bothost_create_bot.png "Создание бота на BotHost") ### Настройка домена #### Вариант 1: Поддомен bothost.ru 1. В панели найдите поле для ввода домена 2. Введите имя (например, `myapp`) 3. Получите адрес `https://myapp.bothost.ru` #### Вариант 2: Собственный домен 1. Приобретите домен 2. Настройте DNS (A-запись на IP BotHost) 3. Введите домен в панели 4. BotHost выпустит SSL-сертификат ![Рис. 6: Настройка домена для мини-приложения](/docs/images/miniapp/bothost_domain_setup.png "Настройка домена") > ⚠️ Telegram Mini App работает **ТОЛЬКО** по HTTPS! ### Конфигурация и запуск BotHost автоматически клонирует репозиторий и запускает приложение. Проверьте логи — должно быть сообщение `✅ Сервер запущен на порту 3000`. #### Автообновление из GitHub 1. В панели бота найдите раздел "Автообновление" 2. Скопируйте Webhook URL 3. В GitHub: Settings → Webhooks → Add webhook 4. Вставьте URL, формат "application/json" --- ## Интеграция с Telegram ### Настройка кнопки Web App в меню бота 1. Откройте [@BotFather](https://t.me/BotFather) 2. Отправьте `/mybots` и выберите бота 3. Bot Settings → Menu Button (или `/setmenubutton`) 4. Введите текст кнопки (например, "Открыть приложение") 5. Введите URL приложения (например, `https://myapp.bothost.ru`) ![Рис. 7: Настройка кнопки меню Mini App в BotFather](/docs/images/miniapp/botfather_menu_button.png "Кнопка меню в BotFather") ### Кнопка Web App в сообщениях **Вариант 1 — Inline-кнопка** (быстрый запуск, без отправки данных из приложения): ```javascript bot.sendMessage(chatId, 'Нажмите, чтобы открыть приложение:', { reply_markup: { inline_keyboard: [[{ text: 'Открыть приложение', web_app: { url: 'https://myapp.bothost.ru' } }]] } }); ``` **Вариант 2 — Клавиатурная кнопка** (нужна, если Mini App будет отправлять данные через `sendData`): ```javascript bot.sendMessage(chatId, 'Нажмите, чтобы открыть приложение:', { reply_markup: { keyboard: [[{ text: 'Открыть приложение', web_app: { url: 'https://myapp.bothost.ru' } }]], resize_keyboard: true } }); ``` > ⚠️ **Важно:** метод `sendData()` работает **только** при запуске Mini App с клавиатурной кнопки (`keyboard`), а не с inline-кнопки (`inline_keyboard`). ### Получение данных из Mini App Для приёма данных Mini App должна быть открыта **с клавиатурной кнопки** (см. выше). **В Mini App (sendData):** ```javascript const data = { name: document.getElementById('name').value, email: document.getElementById('email').value }; tg.sendData(JSON.stringify(data)); tg.close(); ``` **В боте (web_app_data):** ```javascript bot.on('message', (msg) => { if (msg.web_app_data) { const data = JSON.parse(msg.web_app_data.data); bot.sendMessage(msg.chat.id, `Получено: ${data.name}, ${data.email}`); } }); ``` --- ## Продвинутые функции ### Адаптация под темы Telegram ```css :root { --my-background: var(--tg-theme-bg-color, #ffffff); --my-text: var(--tg-theme-text-color, #222222); --my-button: var(--tg-theme-button-color, #50a8eb); } ``` ```javascript const isDarkTheme = tg.colorScheme === 'dark'; ``` ### Использование MainButton ```javascript tg.MainButton.setText('ОТПРАВИТЬ'); tg.MainButton.setParams({ color: '#2481cc', text_color: '#ffffff' }); tg.MainButton.show(); tg.MainButton.onClick(sendData); ``` ### Получение данных о пользователе ```javascript const user = tg.initDataUnsafe.user; if (user) { console.log(user.id, user.first_name, user.username); } ``` > ⚠️ **Безопасность:** данные из `initDataUnsafe` нельзя доверять на сервере — их можно подделать. Для проверки подлинности используйте поле `initData` и валидацию по [документации Telegram](https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app). ### Haptic Feedback ```javascript if (tg.HapticFeedback) { tg.HapticFeedback.impactOccurred('medium'); tg.HapticFeedback.notificationOccurred('success'); } ``` --- ## Решение проблем ### Ошибка "The web app was not found" - Проверьте URL в браузере - Убедитесь в использовании HTTPS - Проверьте логи сервера на BotHost ### Ошибка "White screen" - Проверьте консоль браузера - Убедитесь, что подключен `telegram-web-app.js` - Проверьте структуру HTML ### Проблемы с автообновлением из GitHub - Проверьте настройки webhook в GitHub - Убедитесь, что репозиторий публичный ### Отладка - **Chrome/Firefox:** F12 или Ctrl+Shift+I - **Safari:** Option+Command+I - Логи сервера в панели BotHost --- ## Примеры проектов ### AI Support Bot Бот технической поддержки с ИИ для ответов на вопросы. - [Открыть бота](https://t.me/BotHostAI_Support_bot) - [GitHub](https://github.com/Nikolay1123770/ai-support-bot.git) ### Mini App Manual Интерактивное руководство по созданию Mini App. - [Открыть бота](https://t.me/BotHostManualMiniApp_bot) - [GitHub](https://github.com/Nikolay1123770/Bothostapp.git) --- **🤖 Нужна помощь?** [Написать в поддержку](https://t.me/BotHostAI_Support_bot) © 2025 BotHost.ru — Платформа для хостинга ботов и Mini App --- # Генерация PDF из DOCX в Telegram-боте на Bothost: LibreOffice headless На платформе **Bothost** боты работают в Docker-контейнерах — это удобно для деплоя Telegram-, Discord- и MAX-ботов из Git, но стандартный образ не включает системные пакеты вроде LibreOffice. Если вашему боту нужно конвертировать файлы `.docx` в `.pdf` (договоры, справки, отчёты, счета по шаблону), установите **LibreOffice headless** через кастомный Dockerfile. LibreOffice headless — это полноценный офисный пакет в фоновом режиме без графического интерфейса. Такой подход подходит для Python-ботов на aiogram, Node.js-ботов и других проектов, развёрнутых на [хостинге ботов Bothost](https://bothost.ru/telegram-bots.html): пользователь отправляет `.docx` в чат, бот возвращает готовый PDF. --- ## Способ 1: Кастомный Dockerfile (рекомендуется) Добавьте в корень репозитория файл `Dockerfile` и поставьте галочку **«Использовать собственный Dockerfile»** при создании бота. ![Галочка «Использовать собственный Dockerfile» в форме создания бота](images/libreoffice-checkbox.png) ### Dockerfile для Python-бота ```dockerfile FROM python:3.11-slim WORKDIR /app # Устанавливаем LibreOffice headless + шрифты RUN apt-get update && apt-get install -y --no-install-recommends \ libreoffice-core \ libreoffice-writer \ fonts-dejavu \ fonts-liberation \ && rm -rf /var/lib/apt/lists/* # LibreOffice требует домашнюю директорию ENV HOME=/root COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "main.py"] ``` > **Важно:** `fonts-dejavu` и `fonts-liberation` — обязательны. Без них кириллица и стандартные шрифты (Arial, Times New Roman) в PDF будут отображаться некорректно. --- ## Конвертация DOCX → PDF в коде После добавления LibreOffice в образ используйте его через `subprocess`: ```python import subprocess import os def docx_to_pdf(docx_path: str, out_dir: str) -> str: """Конвертирует .docx в .pdf через LibreOffice headless.""" subprocess.run( [ "libreoffice", "--headless", "--norestore", "--convert-to", "pdf", "--outdir", out_dir, docx_path, ], check=True, timeout=60, ) basename = os.path.splitext(os.path.basename(docx_path))[0] return os.path.join(out_dir, basename + ".pdf") ``` ### Пример использования в aiogram 3 ```python import os import tempfile from aiogram import Bot, Dispatcher, F from aiogram.types import Message, FSInputFile bot = Bot(token=os.getenv("BOT_TOKEN")) dp = Dispatcher() @dp.message(F.document) async def handle_document(message: Message): doc = message.document if not doc.file_name.endswith(".docx"): await message.answer("Пришлите файл .docx") return with tempfile.TemporaryDirectory() as tmpdir: # Скачиваем файл docx_path = os.path.join(tmpdir, doc.file_name) await bot.download(doc, destination=docx_path) # Конвертируем pdf_path = docx_to_pdf(docx_path, tmpdir) # Отправляем обратно await message.answer_document( FSInputFile(pdf_path), caption="Готово! Вот ваш PDF." ) ``` --- ## Способ 2: Node.js-бот Для Node.js-проектов Dockerfile аналогичный: ```dockerfile FROM node:20-slim WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends \ libreoffice-core \ libreoffice-writer \ fonts-dejavu \ fonts-liberation \ && rm -rf /var/lib/apt/lists/* ENV HOME=/root COPY package*.json ./ RUN npm ci --omit=dev COPY . . CMD ["node", "index.js"] ``` Конвертация через `child_process`: ```javascript const { execFile } = require('child_process'); const path = require('path'); function docxToPdf(docxPath, outDir) { return new Promise((resolve, reject) => { execFile( 'libreoffice', ['--headless', '--norestore', '--convert-to', 'pdf', '--outdir', outDir, docxPath], { timeout: 60000 }, (err) => { if (err) return reject(err); const base = path.basename(docxPath, '.docx'); resolve(path.join(outDir, base + '.pdf')); } ); }); } ``` --- ## Замечания | Тема | Подробности | |------|-------------| | Размер образа | LibreOffice добавляет ~400–500 МБ. Первая сборка займёт 3–5 минут, повторные — быстрее за счёт кеша | | Кириллица | Без пакетов `fonts-dejavu` / `fonts-liberation` кириллические символы могут отображаться как квадраты | | Параллельные запросы | LibreOffice запускается как отдельный процесс при каждой конвертации. Для высокой нагрузки рассмотрите очередь задач | | Поддерживаемые форматы | Помимо `.docx`, LibreOffice конвертирует `.odt`, `.doc`, `.xlsx`, `.pptx` и другие офисные форматы | --- ## Типичные ошибки **`LibreOffice не запускается / ошибка профиля`** Убедитесь, что в Dockerfile есть `ENV HOME=/root`. LibreOffice создаёт временный профиль в домашней директории. **`Шрифты отображаются некорректно`** Установите `fonts-dejavu` и `fonts-liberation`. Для специфических шрифтов скопируйте `.ttf`-файлы в `/usr/local/share/fonts/` и выполните `fc-cache -f`. **`Процесс завис`** Добавьте `timeout=60` в `subprocess.run()`. LibreOffice иногда зависает на повреждённых файлах. --- ## Когда это нужно боту Типичные сценарии на Bothost: бот принимает Word-шаблон и отдаёт PDF клиенту; автоматическая выдача договоров и актов в Telegram; конвертация загруженных документов без отдельного сервера. Всё выполняется внутри контейнера вашего бота — достаточно добавить `Dockerfile` в репозиторий и включить опцию **«Использовать собственный Dockerfile»** при [создании бота](https://bothost.ru/create-bot.php). --- ## См. также - [Кастомный Dockerfile](custom-dockerfile) — как использовать собственный Dockerfile - [Установка библиотек: Python, Java и Go](installing-libraries) — стандартные зависимости - [Хранение базы данных](database-storage) — сохранение файлов между деплоями - [FAQ](faq) — частые вопросы по деплою --- # Bothost API Reference Документация по API для разработчиков ботов на платформе Bothost. ## Содержание - [Переменные окружения](#переменные-окружения) - [API для управления ботом](#api-для-управления-ботом) - [Примеры использования](#примеры-использования) --- ## Переменные окружения Каждый бот получает следующие переменные окружения: ### Основные переменные | Переменная | Описание | Пример | |------------|----------|--------| | `BOT_ID` | Уникальный ID бота | `bot_1764482446_5595_proxyrp` | | `BOT_TOKEN` | Токен бота (основной) | `8424863414:AAF1Plz...` | | `USER_ID` | ID пользователя (владельца) | `username123` | | `DOMAIN` | Доменное имя бота (если назначено) | `mybot.bothost.ru` | | `TEMPLATE` | Шаблон бота | `discord`, `telegram`, `n8n` | | `PORT` | Внутренний порт | `3000` | ### Токены (для совместимости) Для разных библиотек доступны альтернативные имена: - `BOT_TOKEN` - основной (рекомендуется) - `DISCORD_TOKEN` / `DISCORD_BOT_TOKEN` - для Discord ботов - `TELEGRAM_BOT_TOKEN` - для Telegram ботов - `TOKEN` - универсальное имя - `API_TOKEN` - альтернативное имя ### Как получить переменные в коде **Python:** ```python import os BOT_ID = os.getenv('BOT_ID') BOT_TOKEN = os.getenv('BOT_TOKEN') if not BOT_TOKEN: print("❌ Токен не найден!") exit(1) ``` **Node.js:** ```javascript const BOT_ID = process.env.BOT_ID; const BOT_TOKEN = process.env.BOT_TOKEN; if (!BOT_TOKEN) { console.error('❌ Токен не найден!'); process.exit(1); } ``` --- ## API для управления ботом ### ⚠️ Важно: Безопасность API агента доступен только из внутренней сети Docker. Бот может управлять только самим собой, используя свой `BOT_ID`. ### Базовый URL API агента доступен по адресу: - **Из контейнера бота:** `http://agent:8000` (внутренняя сеть Docker) - **Извне:** `http://agent.bothost.ru` или `http://msk1.bothost.ru` (зависит от ноды) **Рекомендация:** Используйте переменную окружения или определяйте URL автоматически. ### Endpoints #### 1. Перезапуск бота (самоперезапуск) ⭐ Рекомендуется **Endpoint:** `POST /api/bots/self/restart` **Описание:** Безопасный endpoint для самоперезапуска бота. Бот может перезапустить только сам себя. **Заголовки:** ``` X-Bot-ID: bot_1764482446_5595_proxyrp ``` **Или:** Бот может не передавать заголовок - система автоматически определит `BOT_ID` из имени контейнера. **Запрос:** ```http POST /api/bots/self/restart X-Bot-ID: bot_1764482446_5595_proxyrp ``` **Ответ (успех):** ```json { "ok": true, "message": "Бот bot_1764482446_5595_proxyrp перезапущен" } ``` **Ответ (ошибка):** ```json { "ok": false, "msg": "BOT_ID не найден. Передайте его в заголовке X-Bot-ID" } ``` **Пример использования:** ```python import os import aiohttp BOT_ID = os.getenv('BOT_ID') AGENT_URL = os.getenv('BOTHOST_AGENT_URL', 'http://agent:8000') async with aiohttp.ClientSession() as session: async with session.post( f"{AGENT_URL}/api/bots/self/restart", headers={'X-Bot-ID': BOT_ID} ) as response: result = await response.json() print(result) ``` #### 2. Перезапуск бота (стандартный) **Endpoint:** `POST /api/bots/restart` **Описание:** Перезапускает контейнер бота (требует bot_id и user_id). **Запрос:** ```json { "bot_id": "bot_1764482446_5595_proxyrp", "user_id": "username123" } ``` **Ответ (успех):** ```json { "ok": true, "message": "Бот bot_1764482446_5595_proxyrp перезапущен" } ``` **Ответ (ошибка):** ```json { "ok": false, "msg": "Ошибка перезапуска бота: ..." } ``` #### 2. Остановка бота **Endpoint:** `POST /api/bots/stop` **Запрос:** ```json { "bot_id": "bot_1764482446_5595_proxyrp", "user_id": "username123" } ``` #### 3. Запуск бота **Endpoint:** `POST /api/bots/start` **Запрос:** ```json { "bot_id": "bot_1764482446_5595_proxyrp", "user_id": "username123" } ``` #### 4. Получение логов **Endpoint:** `POST /api/bots/logs` **Запрос:** ```json { "bot_id": "bot_1764482446_5595_proxyrp", "lines": 100 } ``` **Ответ:** ```json { "ok": true, "logs": "Логи бота..." } ``` #### 5. Получение статистики **Endpoint:** `GET /api/bots/{bot_id}/stats` **Ответ:** ```json { "ok": true, "stats": { "cpu_percent": 15.5, "memory_usage": "125MB", "memory_percent": 12.3, "uptime": "2h 30m" } } ``` --- ## Примеры использования ### Python (discord.py) - Перезапуск бота ```python import os import aiohttp import discord from discord.ext import commands BOT_ID = os.getenv('BOT_ID') AGENT_URL = os.getenv('BOTHOST_AGENT_URL', 'http://agent:8000') USER_ID = os.getenv('USER_ID') bot = commands.Bot(command_prefix='!', intents=discord.Intents.default()) @bot.command(name='restart') async def restart_command(ctx): """Перезапустить бота через API""" if not BOT_ID: await ctx.send("❌ BOT_ID не найден в переменных окружения") return async with aiohttp.ClientSession() as session: try: async with session.post( f"{AGENT_URL}/api/bots/restart", json={ "bot_id": BOT_ID, "user_id": USER_ID }, timeout=aiohttp.ClientTimeout(total=10) ) as response: result = await response.json() if result.get('ok'): await ctx.send(f"✅ {result.get('message', 'Бот перезапущен')}") else: await ctx.send(f"❌ Ошибка: {result.get('msg', 'Неизвестная ошибка')}") except Exception as e: await ctx.send(f"❌ Ошибка подключения к API: {str(e)}") bot.run(os.getenv('BOT_TOKEN')) ``` ### Python (aiogram) - Перезапуск бота ```python import os import aiohttp from aiogram import Bot, Dispatcher, types from aiogram.filters import Command BOT_ID = os.getenv('BOT_ID') AGENT_URL = os.getenv('BOTHOST_AGENT_URL', 'http://agent:8000') USER_ID = os.getenv('USER_ID') bot = Bot(token=os.getenv('BOT_TOKEN')) dp = Dispatcher() @dp.message(Command("restart")) async def restart_command(message: types.Message): """Перезапустить бота через API""" if not BOT_ID: await message.answer("❌ BOT_ID не найден") return async with aiohttp.ClientSession() as session: try: async with session.post( f"{AGENT_URL}/api/bots/restart", json={"bot_id": BOT_ID, "user_id": USER_ID}, timeout=aiohttp.ClientTimeout(total=10) ) as response: result = await response.json() if result.get('ok'): await message.answer(f"✅ {result.get('message')}") else: await message.answer(f"❌ {result.get('msg')}") except Exception as e: await message.answer(f"❌ Ошибка: {str(e)}") if __name__ == '__main__': import asyncio asyncio.run(dp.start_polling(bot)) ``` ### Node.js (discord.js) - Перезапуск бота ```javascript const { Client, GatewayIntentBits } = require('discord.js'); const axios = require('axios'); const BOT_ID = process.env.BOT_ID; const AGENT_URL = process.env.BOTHOST_AGENT_URL || 'http://agent:8000'; const USER_ID = process.env.USER_ID; const client = new Client({ intents: [GatewayIntentBits.Guilds] }); client.on('ready', () => { console.log(`✅ Бот ${client.user.tag} запущен!`); }); client.on('messageCreate', async (message) => { if (message.content === '!restart') { if (!BOT_ID) { return message.reply('❌ BOT_ID не найден'); } try { const response = await axios.post( `${AGENT_URL}/api/bots/restart`, { bot_id: BOT_ID, user_id: USER_ID }, { timeout: 10000 } ); if (response.data.ok) { message.reply(`✅ ${response.data.message}`); } else { message.reply(`❌ ${response.data.msg}`); } } catch (error) { message.reply(`❌ Ошибка: ${error.message}`); } } }); client.login(process.env.BOT_TOKEN); ``` --- ## Определение URL агента ### Автоматическое определение Бот может автоматически определить URL агента: **Python:** ```python import os import socket def get_agent_url(): """Определяет URL агента автоматически""" # Пробуем подключиться к внутренней сети Docker try: socket.create_connection(('agent', 8000), timeout=1) return 'http://agent:8000' except: # Fallback на внешний URL return os.getenv('BOTHOST_AGENT_URL', 'http://agent.bothost.ru') ``` ### Через переменную окружения Можно установить переменную `BOTHOST_AGENT_URL` в настройках бота: - `http://agent:8000` - для внутренней сети Docker (рекомендуется) - `http://agent.bothost.ru` - для внешнего доступа - `http://msk1.bothost.ru` - для второй ноды --- ## Обработка ошибок ### Типичные ошибки 1. **Контейнер не найден** ```json {"ok": false, "msg": "Контейнер не найден"} ``` **Решение:** Убедитесь, что бот запущен 2. **Таймаут** ```json {"ok": false, "msg": "Таймаут при перезапуске"} ``` **Решение:** Увеличьте timeout или повторите запрос 3. **Ошибка подключения** - Проверьте доступность агента - Убедитесь, что используете правильный URL - Проверьте сетевые настройки Docker --- ## Безопасность ### ⚠️ Важные моменты 1. **Бот может управлять только самим собой** - Используйте `BOT_ID` из переменных окружения - Не передавайте чужие `bot_id` в запросах 2. **Внутренняя сеть Docker** - API доступен только из контейнеров - Внешний доступ ограничен 3. **Валидация** - Всегда проверяйте наличие `BOT_ID` перед вызовом API - Обрабатывайте ошибки корректно --- ## Дополнительные ресурсы - [Переменные окружения](./ENVIRONMENT_VARIABLES.md) - [Примеры ботов](./EXAMPLES.md) - [Troubleshooting](./TROUBLESHOOTING.md) --- **Версия документации:** 1.0 **Последнее обновление:** 2025-01-XX --- # Настройка Telegram Userbot Userbot работает от имени **вашего аккаунта** Telegram (не Bot API). На Bothost нет интерактивного терминала для ввода кода подтверждения — используйте **String Session**. --- ## Что понадобится 1. **API ID** и **API Hash** — создайте приложение на [my.telegram.org/apps](https://my.telegram.org/apps). 2. **String Session** — одна строка, которая заменяет файл `.session` (получается локально на своём ПК). 3. Главный файл бота в репозитории (например `userbot.py`). ## Переменные окружения на Bothost В настройках бота добавьте: | Переменная | Описание | |------------|----------| | `TELEGRAM_API_ID` | Числовой API ID | | `TELEGRAM_API_HASH` | API Hash | | `SESSION_STRING` | Строка сессии (см. ниже) | Подробнее о переменных: [Переменные окружения](environment-variables). При создании бота укажите **главный файл** (например `userbot.py`), если он не `main.py` / `bot.py`. --- ## Получение String Session (Telethon) Локально на компьютере (один раз): ```bash pip install telethon ``` Скрипт `gen_session.py`: ```python from telethon.sync import TelegramClient from telethon.sessions import StringSession api_id = int(input("API ID: ")) api_hash = input("API Hash: ") with TelegramClient(StringSession(), api_id, api_hash) as client: print("\nSESSION_STRING (скопируйте в Bothost):\n") print(client.session.save()) ``` Запустите `python gen_session.py`, войдите по номеру телефона и коду из Telegram. Скопируйте выведенную строку в переменную `SESSION_STRING` на Bothost. **Не коммитьте** session string в Git. --- ## Пример кода (Telethon) ```python import os from telethon import TelegramClient from telethon.sessions import StringSession api_id = int(os.environ["TELEGRAM_API_ID"]) api_hash = os.environ["TELEGRAM_API_HASH"] session = os.environ["SESSION_STRING"] client = TelegramClient(StringSession(session), api_id, api_hash) async def main(): await client.start() me = await client.get_me() print(f"Userbot запущен: {me.username or me.id}") with client: client.loop.run_until_complete(main()) ``` Для Pyrogram используйте `Session` / `export_session_string()` по документации библиотеки — принцип тот же: сессия в env, без `input()` в контейнере. --- ## Ошибка: `EOF when reading a line` **Причина:** код запрашивает телефон или код через `input()` — в контейнере Bothost интерактивного ввода нет. **Решение:** перейдите на String Session и переменные `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, `SESSION_STRING`. --- ## См. также - [Переменные окружения](environment-variables) - [FAQ](faq) - [Быстрый старт](getting-started) --- # Веб-приложения и домены Эта страница — для тех, кто публикует **веб-сервис** вместе с ботом: API, мини-приложение, панель, приём вебхуков Telegram и т.п. > Нужна база про `0.0.0.0`, Reverse Proxy и `PORT`? Начните со статьи [Работа с доменами и портами](domains-and-ports). > Здесь — практические сценарии: вебхук, деплой, типовые ошибки и диагностика. ![Опция «Использовать домен» в дополнительных настройках бота](/docs/images/domain-settings-use-domain.png) ## Когда читать эту страницу - Вы уже включили опцию **«Использовать домен»** и публикуете веб-сервис. - Нужна настройка webhook URL и проверка маршрутов в приложении. - Пытаетесь разобраться с `502/504`, `404` и запуском HTTP-обёртки. ## Что даёт включение домена - Запросы из интернета приходят на ваш домен по **HTTPS** (порт 443). - Платформа маршрутизирует трафик в **ваш контейнер** на выбранный **внутренний порт** приложения. - Для Telegram-бота можно указать **вебхук** на URL вида `https://ваш-домен/...` — Telegram будет слать обновления на этот адрес. ## Что проверить перед деплоем - Включена опция **«Использовать домен»**. - Веб-сервер слушает `0.0.0.0` и читает порт из `PORT`. - Порт в настройках бота совпадает с портом, который реально слушает приложение. - После смены домена/порта выполнен повторный деплой. Подробно про устройство трафика и портов: [Работа с доменами и портами](domains-and-ports). ## Автоматический поддомен (bothost.tech) При выборе автоматического домена для веб-интерфейса поддомен формируется из идентификатора бота. Символы **подчёркивания** в имени поддомена заменяются на **дефисы**, чтобы имя соответствовало правилам DNS (в имени хоста не должно быть `_`). Пример: `bot-1774542086-5810-user.bothost.tech` вместо `bot_1774...`. ## Кастомный домен Если вы подключаете свой домен, в DNS нужно настроить запись **на IP, который выдаст поддержка** (обычно это центральный сервер платформы). После проверки сертификатов запросы пойдут по той же схеме, что и для `*.bothost.tech`. ## Вебхук Telegram и путь в приложении - Вебхук задаётся URL вида `https://ваш-домен/webhook` (или другой путь — как настроите в коде). - Ваше приложение должно **реализовать HTTP-обработчик** на этом пути (например, POST для Telegram). - Если по корню `/` открывается **404**, это не всегда ошибка: многие боты не отдают страницу на `/`, а принимают только `/webhook`. ### Рекомендуемый минимум для webhook-бота - Приложение слушает `0.0.0.0` на `PORT` из переменных окружения. - Есть endpoint `POST /webhook` (или ваш путь, но он должен совпадать с URL в Telegram). - Endpoint быстро отвечает `200 OK` (Telegram ожидает быстрый ответ; тяжёлую обработку лучше выносить в фон). - В логах видно факт приёма апдейта (например, `update_id`, `chat_id`, размер payload). ### Пошаговая настройка webhook 1. Включите домен в настройках бота и сделайте деплой. 2. Убедитесь, что приложение отвечает по `https://ваш-домен/health` (или по вашему health-пути). 3. Настройте webhook в Telegram на `https://ваш-домен/webhook`. 4. Отправьте сообщение боту в Telegram. 5. Проверьте, что: - в логах контейнера есть входящий webhook-запрос, - бот обработал апдейт (например, ответил в чат), - нет цикла рестартов контейнера. Пример минимального тестового проекта: [bothost-tech/bothost_testwebhook](https://github.com/bothost-tech/bothost_testwebhook). ### Установка и проверка webhook через браузер Если нужно быстро проверить webhook без терминала, можно использовать прямые ссылки Telegram Bot API: - Без secret token: - `https://api.telegram.org/bot<ТОКЕН>/setWebhook?url=https://<твой-домен>/webhook` - С secret token: - `https://api.telegram.org/bot<ТОКЕН>/setWebhook?url=https://<твой-домен>/webhook&secret_token=<твой_секрет>` После установки webhook откройте: - `https://api.telegram.org/bot<ТОКЕН>/getWebhookInfo` Проверьте, что: - поле `url` заполнено вашим адресом `https://<твой-домен>/webhook`, - нет ошибок в `last_error_message`, - `pending_update_count` не растёт бесконечно. Пример успешной проверки (бот ответил в Telegram): ![Пример успешной проверки webhook](/docs/images/webhook-success-example.png) ### Что обычно ломает webhook - Указан один путь в Telegram, а в коде слушается другой (`/webhook` vs `/telegram/webhook`). - Бот слушает `127.0.0.1` вместо `0.0.0.0`. - Порт в коде не совпадает с `PORT`/`internal_port`. - Веб-сервер запускается медленно или падает при старте из-за исключения. - В обработчике нет ответа `200`, из-за чего Telegram повторяет доставку. ### Быстрая диагностика - Откройте `https://ваш-домен/health`: если ответа нет, сначала чините запуск приложения/порт. - Откройте `https://ваш-домен/` и `https://ваш-домен/webhook`: - `404` на `/` допустим, - для `/webhook` важно, чтобы `POST` обрабатывался вашим приложением. - Сверьте URL webhook в Telegram и фактический маршрут в коде. - Проверьте последние логи контейнера: старт сервера, порт, входящие POST-запросы, traceback. ### Полезно для надёжности - Добавьте проверку секретного токена webhook (`X-Telegram-Bot-Api-Secret-Token`). - Делайте идемпотентную обработку апдейтов (на случай повторной доставки). - Логируйте ошибки обработки отдельно от access-логов HTTP. ## Как работает деплой с доменом - Бот должен слушать `0.0.0.0` на порту из переменной `PORT` (не хардкодьте порт в коде). - Traefik маршрутизирует трафик с домена именно на этот внутренний порт контейнера. - Если в репозитории уже есть HTTP-сервер (например, `aiohttp`, `FastAPI`, `Flask`, `HTTPServer` из stdlib), приложение запускается напрямую — дополнительная обёртка не нужна. - Если HTTP-сервера в репозитории нет, но домен указан, платформа автоматически добавляет FastAPI-обёртку и запускает бота как subprocess, чтобы домен и health-check работали корректно. ## Частые проблемы ### 504 Gateway Timeout / «не доходит» трафик Часто это **не Telegram**, а несовпадение порта в настройках и в приложении, либо сетевые настройки на ноде. Проверьте: порт в панели = порт в коде, приложение слушает `0.0.0.0`, контейнер в статусе **Up**, не в цикле перезапусков. ### «Не могу открыть файл `/app/http_wrapper.py`» Если включена опция **HTTP-обёртки**, платформа ожидает в образе сгенерированный файл обёртки. Если образ собран без этого шага или код монтируется с хоста без этого файла — контейнер не запустится. Для полноценного веб-приложения лучше поднимать свой сервер (FastAPI, Express и т.д.) и не полагаться только на обёртку. ### 404 на `/` или `/health` Маршрутизация до контейнера может работать, а **маршрута в приложении нет** — тогда ответ 404 идёт от вашего кода. Добавьте нужный endpoint или проверьте путь вебхука. ## Рекомендации для продакшена - Явно задайте обработчики для путей, которые использует вебхук и браузер. - Логируйте старт сервера и порт, на котором слушаете. - После смены порта или домена сделайте **повторный деплой**, чтобы настройки и образ совпали. --- *Связанные материалы: [домены и порты](domains-and-ports), [переменные окружения](environment-variables).* *Если используете собственную сборку, см. также: [кастомный Dockerfile](custom-dockerfile).* --- # Как указать ветку репозитория при деплое При создании или обновлении бота на Bothost можно выбрать, **из какой ветки** Git-репозитория брать код. По умолчанию используется ветка `main`, но вы можете указать любую другую — например `master`, `dev` или `release`. Это удобно, когда рабочий (стабильный) код лежит в одной ветке, а эксперименты — в другой: вы деплоите именно ту ветку, которая нужна. ## Где узнать имя ветки в репозитории Имя ветки — это то, что написано в самом Git-хостинге (GitHub, GitLab, Bitbucket). Открыть его можно в переключателе веток вашего репозитория. На GitHub нажмите на кнопку с названием текущей ветки (слева над списком файлов) — откроется список **Switch branches/tags**. Ветка, помеченная как `default`, используется по умолчанию, если вы ничего не измените. ![Переключатель веток в репозитории GitHub](/docs/images/repo-branch-github.png) > В примере выше единственная ветка называется `main` и она же является веткой по умолчанию (`default`). Именно это имя нужно указать в Bothost. **Как быстро узнать ветку по умолчанию:** - **GitHub** — Settings → Branches → «Default branch». - **GitLab** — Settings → Repository → «Branch defaults». - Через терминал в самом репозитории: ```bash git branch --show-current ``` ## Как указать ветку в Bothost 1. В [личном кабинете](https://bothost.ru/) откройте форму создания бота (или редактирования существующего). 2. Найдите блок **«Репозиторий»**. 3. В поле **Git URL репозитория** вставьте ссылку на репозиторий, например `https://github.com/username/bot.git`. 4. В поле **Ветка** укажите нужную ветку. Если оставить поле пустым, будет использована ветка по умолчанию — `main`. ![Поле «Ветка» в форме создания бота на Bothost](/docs/images/repo-branch-bothost.png) После этого нажмите **Создать бота** (или **Сохранить** при редактировании) — Bothost склонирует и задеплоит код именно из указанной ветки. ## Ошибка: указана несуществующая ветка Если в поле **Ветка** указать имя, которого нет в репозитории, сборка упадёт на этапе клонирования. В логах вы увидите примерно такое: ```text fatal: Remote branch main not found in upstream origin ERROR: clone failed with code 128 ``` Здесь `main` — это то имя, которое вы вписали в поле «Ветка». Ошибка означает, что в репозитории **нет ветки с таким именем**. Как исправить: - Откройте переключатель веток на GitHub/GitLab и посмотрите, как **точно** называется нужная ветка. - Учтите регистр: `main` ≠ `Main` ≠ `MAIN`. - Проверьте, что нет лишних пробелов в начале или конце имени. - Если сомневаетесь — оставьте поле пустым, тогда будет использована ветка по умолчанию. ## Частые вопросы #### Что будет, если оставить поле «Ветка» пустым? Bothost возьмёт ветку по умолчанию — `main`. #### Можно ли поменять ветку у уже созданного бота? Да. Откройте настройки бота, измените поле **Ветка** и сохраните — при следующем деплое код будет взят из новой ветки. #### Работает ли это с приватными репозиториями? Да. Сначала настройте доступ по инструкции [«Подключение приватного репозитория»](/docs/git-repository-access), а затем так же укажите нужную ветку. --- # Главный файл (точка входа) При деплое Bothost нужно знать, **какой файл запускать**. Если оставить поле пустым, система подберёт его автоматически. Если имя файла нестандартное или код лежит во вложенной папке — укажите путь вручную. ## Когда указывать вручную Заполните поле **«Главный файл (точка входа)»**, если: - файл называется не `main.py` / `bot.py` / `index.js` и т.п. (например `userbot.py`, `start_bot.py`); - код бота лежит **во вложенной папке**, а не в корне репозитория (`bot/main.py`, `discord-html-watcher/index.js`); - автодетект выбирает не тот файл (в репозитории несколько кандидатов); - после деплоя в логах `Cannot find module '/app/index.js'` или аналог для Python/Go. Если проект простой и главный файл лежит в корне под стандартным именем — поле можно не трогать. ## Где найти настройку В форме создания или редактирования бота откройте блок **«Дополнительные настройки»**. Поле **«Главный файл (точка входа)»** находится между опциями домена и Dockerfile: ![Поле «Главный файл (точка входа)» в дополнительных настройках бота](/docs/images/main-file-entrypoint-settings.png) ## Как указать в Bothost 1. Откройте форму создания бота или настройки существующего. 2. Раскройте блок **«Дополнительные настройки»**. 3. В поле **«Главный файл (точка входа)»** укажите путь **от корня репозитория**. 4. Сохраните и выполните **новый деплой** (одного рестарта может быть недостаточно). Примеры значений: | Ситуация | Что указать | |----------|-------------| | Python-бот в корне | `main.py`, `bot.py`, `userbot.py` | | Node.js в корне | `index.js`, `bot.js`, `server.js` | | Код во вложенной папке | `discord-html-watcher/index.js`, `bot/main.py` | | Go | `main.go` (или путь к нужному `.go`, если структура нестандартная) | ## Как работает автоопределение Если поле пустое, Bothost ищет файл по приоритету (сначала в корне, затем по найденным файлам проекта): **Python:** `main.py` → `app.py` → `bot.py` → `server.py` → `index.py` → `run.py` **Node.js:** `index.js` → `main.js` → `app.js` → `bot.js` → `server.js` → `start.js` (также учитываются `package.json` → `main` / `scripts.start`) **Go:** `main.go` → `bot.go` → `app.go` → `server.go` ## Типичная ошибка: nested-репозиторий ```text repo/ ├── package.json ← "start": "node index.js" └── my-bot/ └── index.js ← реальный код здесь ``` Контейнер стартует `node index.js` из `/app`, файла в корне нет → `Cannot find module '/app/index.js'`. **Быстрый фикс:** в поле главного файла указать `my-bot/index.js` и задеплоить заново. ![Пример: в поле указан discord-html-watcher/index.js](/docs/images/main-file-nested-entry.png) Подробный разбор и альтернативы (правка `package.json`, перенос кода в корень): [Справочник ошибок при деплое](common-errors). ## Связь с кастомным Dockerfile Если включён **«Использовать собственный Dockerfile»**, команду запуска задаёт ваш `CMD` / `ENTRYPOINT` в Dockerfile. Поле «Главный файл» влияет на **автогенерацию** Dockerfile; при своём Dockerfile ориентируйтесь на `CMD` в репозитории. См. также: [Кастомный Dockerfile](custom-dockerfile). ## Чеклист - Путь указан от корня Git, без ведущего `/` (правильно: `bot/main.py`, неправильно: `/app/bot/main.py`). - Файл реально есть в выбранной ветке репозитория. - После смены точки входа выполнен **новый деплой**. - Если ошибка про `dist/` / `build/` — это другой кейс (bind mount `/app`), смотрите [справочник ошибок](common-errors). --- ## См. также - [Быстрый старт](getting-started) - [Ветка репозитория при деплое](repository-branch-deploy) - [Кастомный Dockerfile](custom-dockerfile) - [Справочник ошибок при деплое](common-errors) - [FAQ](faq) --- # Переменные окружения Переменные окружения позволяют передавать конфигурацию и секретные данные в ваш бот без хардкода в коде. ## 🔐 Безопасность - **Никогда не коммитьте секретные данные в Git** - Используйте переменные окружения для всех токенов, API ключей и паролей - Bothost автоматически скрывает значения переменных окружения в логах ## 📝 Добавление переменных окружения 1. При создании бота: - Перейдите в раздел "Переменные окружения" - Нажмите "Добавить переменную окружения" - Укажите ключ и значение 2. Для существующего бота: - Откройте настройки бота - Перейдите в раздел "Переменные окружения" - Добавьте или измените переменные ## 🔑 Стандартные переменные окружения Bothost автоматически устанавливает следующие переменные: ### Общие - `BOT_ID` — ID бота на платформе - `USER_ID` — ID пользователя (владельца бота) - `DOMAIN` — Домен бота (если настроен) - `TEMPLATE` — Используемый шаблон/библиотека ### Telegram - `BOT_TOKEN` — Токен Telegram бота - `API_TOKEN` — Альтернативное имя для `BOT_TOKEN` (совместимость) - `TELEGRAM_BOT_TOKEN` — Альтернативное имя для `BOT_TOKEN` ### Discord - `DISCORD_BOT_TOKEN` — Токен Discord бота ## 📚 Примеры использования ### Python ```python import os # Получение переменной окружения bot_token = os.getenv('BOT_TOKEN') api_id = os.getenv('TELEGRAM_API_ID') api_hash = os.getenv('TELEGRAM_API_HASH') # С значением по умолчанию port = int(os.getenv('PORT', '3000')) ``` ### Node.js ```javascript // Получение переменной окружения const botToken = process.env.BOT_TOKEN; const apiId = process.env.TELEGRAM_API_ID; const apiHash = process.env.TELEGRAM_API_HASH; // С значением по умолчанию const port = process.env.PORT || 3000; ``` ## 🎯 Для Telegram Userbot Если вы создаете userbot (Telethon/Pyrogram), вам понадобятся: - `TELEGRAM_API_ID` — API ID от [my.telegram.org](https://my.telegram.org/apps) - `TELEGRAM_API_HASH` — API Hash от [my.telegram.org](https://my.telegram.org/apps) - `SESSION_STRING` — String Session (см. [инструкцию](telegram-userbot-setup)) ## 📖 Дополнительная информация - [Настройка Userbot](telegram-userbot-setup) - [Быстрый старт](getting-started) --- # Платёж не зачислился — проверка и активация тарифа Иногда оплата проходит у ЮKassa или в криптовалюте, а тариф на Bothost остаётся бесплатным. Обычно это задержка webhook или возврат со страницы оплаты до автоматической проверки. Активировать тариф можно вручную на странице [Мои платежи](https://bothost.ru/check-payment.php). ## Куда зайти 1. Войдите в аккаунт Bothost. 2. Откройте [Мои платежи](https://bothost.ru/check-payment.php) (также: меню профиля → «Мои платежи» или кнопка на [странице тарифов](https://bothost.ru/pricing.php)). ![Страница Мои платежи: Восстановить тариф и Проверить и активировать](/docs/images/check-payment-activate.png) *Жёлтый блок сверху — «Восстановить тариф»; у платежа со статусом «Ожидает» — кнопка «Проверить и активировать» (на скриншоте отмечена стрелкой).* ## Способ 1. Восстановить тариф Если сверху жёлтый блок с текстом вроде «Найден оплаченный платёж, но тариф не активирован»: 1. Нажмите **«Восстановить тариф»** (оранжевая кнопка справа в жёлтом блоке). 2. Дождитесь сообщения об успехе и обновите страницу. 3. В блоке текущего тарифа должен появиться оплаченный план (Базовый / Pro) и статус «Подписка активна». Этот способ подходит, когда платёж уже отмечен как оплаченный в системе, но подписка не применилась. ## Способ 2. Проверить и активировать конкретный платёж Если в списке **«Все платежи»** есть запись со статусом **«Ожидает»**: 1. Найдите нужный платёж (тариф, сумма, дата). 2. Нажмите фиолетовую кнопку **«Проверить и активировать»** (на скриншоте выше отмечена стрелкой). 3. Система запросит статус у платёжного провайдера и, если оплата подтверждена, активирует тариф. После успеха статус станет **«Оплачено»**, внизу карточки — **«Тариф активирован»**. ## Что должно получиться | Было | Стало | |------|--------| | Текущий тариф: Бесплатный | Базовый / Pro (с датой окончания) | | Статус платежа: Ожидает | Оплачено | | Кнопка «Проверить и активировать» | «Тариф активирован» | ## Если не помогло 1. Подождите 1–2 минуты и повторите **«Проверить и активировать»** — иногда статус у ЮKassa обновляется с задержкой. 2. Убедитесь, что вошли в тот же аккаунт, с которого оформляли оплату. 3. Проверьте в письме или в кабинете ЮKassa, что списание прошло успешно. 4. Если оплата подтверждена, а тариф всё ещё бесплатный — напишите в поддержку [@bothostru](https://t.me/bothostru) и приложите ID платежа (строка `#…` в карточке) и дату оплаты. --- ## См. также - [FAQ](faq) - [Быстрый старт](getting-started) --- # Подключение приватного репозитория в bothost.ru **Публичные** репозитории в разделе «Git репозитории» добавлять не нужно: для них достаточно указать обычный HTTPS- или SSH-URL при создании бота, без токена и без этой формы. Ниже описано подключение **только приватных** репозиториев (токен, deploy key и т.п.). **Источники иллюстраций** - Скриншоты **дашборда Bothost** и **формы admin-repos** — материалы bothost.ru (см. разделы ниже). - Скриншоты **интерфейса GitHub** (настройки, Developer settings, токены) **взяты из ответов** в теме Stack Overflow [git — Clone a private repository (GitHub)](https://stackoverflow.com/questions/2505096/clone-a-private-repository-github); изображения с хостинга Stack Exchange (`i.sstatic.net`). Пользовательский контент на Stack Overflow распространяется по лицензии [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) с указанием источника. ## Дашборд Bothost: куда нажать В [личном кабинете](https://bothost.ru/) откройте раздел **Git репозитории** — фиолетовая кнопка с иконкой ветки в верхней панели (рядом с «Создать бота» и «Тикеты поддержки»). Оттуда настраивается привязка репозитория к боту. --- ![Дашборд Bothost: кнопка «Git репозитории» в верхнем меню](/docs/images/dashboard-git-repositories.png) ## Форма «Подключение приватного репозитория» Откроется страница [**Подключение приватного репозитория**](https://bothost.ru/admin-repos.php): выберите способ доступа — **SSH (deploy key)** или **HTTPS** (логин/пароль или токен). В блоке **«Подключить репозиторий»** заполните поля: | Поле | Что указать | |------|-------------| | **URL репозитория** | SSH или HTTPS, например `git@github.com:org/repo.git` или `https://github.com/org/repo.git` | | **Ветка** | Обычно `main` или `master` | | **Способ доступа** | Для токена GitHub — **HTTPS (логин/пароль или токен)** | | **Username (для HTTPS)** | Для GitHub с PAT часто **`x-access-token`**, для GitLab — **`oauth2`**, либо ваш логин | | **Пароль/Токен** | Personal Access Token или пароль (если допускает хостинг) | Нажмите **Сохранить**. --- **Подсказки на странице:** для GitHub при использовании токена рекомендуется username `x-access-token`, для GitLab — `oauth2`. Если в конце URL нет суффикса `.git`, он может быть добавлен автоматически при сохранении. ![Страница подключения приватного репозитория (admin-repos)](/docs/images/admin-repos-connect.png) ## 🎯 Минимальные права для деплоя Для деплоя бота из приватного репозитория клиенту нужно дать **только права на чтение (Read)**. ### GitHub **Минимальные права:** - ✅ **Read** (чтение кода) - достаточно для клонирования **Заметка:** для приватного репозитория нельзя полагаться на URL вида `git://github.com/...` (только чтение по этому протоколу). Нужен **HTTPS с токеном** или **SSH**. Если у аккаунта включена **2FA**, при `git clone` по HTTPS вместо пароля используйте **Personal Access Token**. **Как дать доступ:** #### Вариант 1: Personal Access Token (рекомендуется) **Создание classic-токена (Tokens (classic)) — пошагово:** 1. Откройте **Settings** профиля на GitHub: ![Settings — меню профиля GitHub](https://i.sstatic.net/3VrIs.png) 2. Внизу списка слева выберите **Developer settings**: ![Developer settings](https://i.sstatic.net/uDsJm.png) 3. **Personal access tokens** → **Tokens (classic)** → **Generate new token (classic)**: ![Personal access tokens — Generate new token](https://i.sstatic.net/QtFtT.png) 4. Укажите название, срок действия и отметьте **`repo`** (для приватных репозиториев без этой области клонирование не получится). Для только публичных репозиториев достаточно `public_repo`: ![Выбор scope repo](https://i.sstatic.net/3HshU.png) 5. Нажмите **Generate token**: ![Generate token](https://i.sstatic.net/Nxmr0.png) 6. **Сразу скопируйте токен** — полный текст потом может быть недоступен: ![Сгенерированный PAT](https://i.sstatic.net/9XEDY.png) **Кратко (тот же путь текстом):** Settings → Developer settings → Personal access tokens → Tokens (classic) → Scopes: `repo` (приватные) или `public_repo` (публичные). **Опционально — Fine-grained token** (узкие права, в том числе на один репозиторий): Settings → Developer settings → **Fine-grained tokens** → Generate new token. Для сценария «только чтение кода для деплоя» задайте **Repository access** и для выбранного репозитория право **Contents: Read-only**. ![Settings — вход в настройки](https://i.sstatic.net/3L5yG.png) ![Developer settings](https://i.sstatic.net/kSHhT.png) ![Fine-grained tokens](https://i.sstatic.net/meWTk.png) ![Generate new token](https://i.sstatic.net/s9VH8.png) ![Только выбранные репозитории](https://i.sstatic.net/nIx2P.png) ![Contents — Read-only](https://i.sstatic.net/nwuCK.png) 7. В Bothost при добавлении репозитория: - Тип: HTTPS - Username: `x-access-token` (автоматически) - Token: вставляется токен клиента **Права токена:** ``` ✅ repo (для приватных репозиториев) - repo:status - repo_deployment - public_repo - repo:invite - security_events ``` #### Вариант 2: Deploy Key (для одного репозитория) 1. На manager ноде сгенерировать SSH ключ: ```bash ssh-keygen -t ed25519 -C "bothost-deploy" -f ~/.ssh/bothost_deploy_key ``` 2. Добавить публичный ключ в репозиторий: - Settings → Deploy keys → Add deploy key - Title: `Bothost Deploy` - Key: содержимое `~/.ssh/bothost_deploy_key.pub` - ✅ Allow write access: **НЕ включать** (только чтение) **Права Deploy Key:** - ✅ Read-only доступ к репозиторию - ❌ Не может изменять код - ❌ Не может создавать issues/pull requests #### Вариант 3: GitHub App (для организации) Для организаций можно создать GitHub App с минимальными правами: - ✅ Contents: Read (чтение кода) - ✅ Metadata: Read (метаданные) --- ### GitLab **Минимальные права:** - ✅ **Guest** или **Reporter** роль **Как дать доступ:** #### Вариант 1: Personal Access Token Создайте **Personal Access Token** в GitLab: - **User Settings** → **Access Tokens** - **Scopes:** только `read_repository` В Bothost при подключении репозитория укажите: - Тип: **HTTPS** - **Username:** `oauth2` - **Token:** ваш токен **Права токена:** достаточно scope `read_repository` (чтение репозитория, клонирование для деплоя). #### Вариант 2: Deploy Token 1. В репозитории: - Settings → Repository → Deploy tokens - Name: `Bothost Deploy` - Scopes: только `read_repository` - Expires: по желанию 2. Использовать: - Username: из GitLab - Token: из GitLab --- ### Bitbucket **Минимальные права:** - ✅ **Read** доступ **Как дать доступ:** #### Personal Access Token 1. Клиент создает App Password: - Personal settings → App passwords - Permissions: только `Repositories: Read` 2. В Bothost: - Тип: HTTPS - Username: username клиента - Token: app password --- ## 🔒 Безопасность ### ✅ Что безопасно: 1. **Deploy Key (SSH)** - самый безопасный вариант - Привязан к одному репозиторию - Не может изменять код - Можно отозвать в любой момент 2. **Personal Access Token с минимальными правами** - Только чтение - Можно ограничить по времени - Можно отозвать 3. **Deploy Token (GitLab)** - Только для деплоя - Ограничен по времени - Минимальные права ### ❌ Что НЕ безопасно: 1. **Полный доступ к аккаунту** - Никогда не просите пароль от аккаунта - Не используйте токены с правами на запись 2. **Токены с правами на изменение кода** - Не нужны для деплоя - Риск компрометации --- ## 📝 Инструкция для клиента ### GitHub Скриншоты интерфейса GitHub см. в разделе **«Вариант 1: Personal Access Token»** выше. 1. Перейти в Settings → Developer settings → Personal access tokens → Tokens (classic) 2. Нажать "Generate new token (classic)" 3. Название: `Bothost Deploy` 4. Expiration: выбрать срок (рекомендуется 1 год) 5. Scopes: выбрать только `repo` (для приватных) или `public_repo` (для публичных) 6. Нажать "Generate token" 7. **Скопировать токен** (показывается только один раз!) 8. В Bothost при добавлении репозитория: - Вставить URL репозитория - Выбрать "Приватный репозиторий" - Вставить токен в поле "Token" ### GitLab 1. Перейти в User Settings → Access Tokens 2. Token name: `Bothost Deploy` 3. Expiration date: выбрать срок 4. Scopes: выбрать только `read_repository` 5. Нажать "Create personal access token" 6. **Скопировать токен** 7. В Bothost: - URL репозитория - Выбрать "Приватный репозиторий" - Вставить токен --- ## 🔄 Обновление токенов Если токен истек или был скомпрометирован: 1. Клиент создает новый токен 2. В Bothost обновляет репозиторий: - Перейти в настройки репозитория - Обновить токен - Сохранить 3. Старый токен можно отозвать в настройках GitHub/GitLab --- ## ✅ Рекомендации ### Для клиента: 1. **Использовать отдельный токен для Bothost** - Не использовать основной токен аккаунта - Легче отозвать при необходимости 2. **Установить срок действия** - Не создавать токены без срока - Рекомендуется 1 год 3. **Минимальные права** - Только чтение кода - Не давать права на запись ### Для Bothost: 1. **Хранить токены в зашифрованном виде** - Использовать base64 кодирование (как сейчас) - Не логировать токены 2. **Использовать HTTPS вместо SSH** - Проще для клиентов - Не нужны SSH ключи на сервере 3. **Проверять доступность репозитория** - Валидировать токен перед сохранением - Показывать ошибки если токен неверный --- ## 🆘 Решение проблем ### Ошибка: "Repository not found" или "Authentication failed" **Причины:** 1. Токен неверный или истек 2. Токен не имеет прав на репозиторий 3. Репозиторий удален или переименован **Решение:** 1. Проверить токен в настройках GitHub/GitLab 2. Создать новый токен с правильными правами 3. Обновить токен в Bothost ### Ошибка: "Permission denied" **Причины:** 1. Токен не имеет прав на чтение 2. Репозиторий приватный, но токен для публичных **Решение:** 1. Создать токен с правами `repo` (для приватных) 2. Проверить что репозиторий доступен с этим токеном --- ## 📋 Чеклист для клиента - [ ] Создан Personal Access Token - [ ] Токен имеет только права на чтение (`repo` или `read_repository`) - [ ] Установлен срок действия токена - [ ] Токен скопирован и вставлен в Bothost - [ ] Репозиторий успешно добавлен в Bothost - [ ] Тестовый деплой прошел успешно --- # Работа с SQLite базами данных через терминал дашборда ## ✅ Возможности В дашборде есть встроенный терминал, который позволяет выполнять команды внутри контейнера бота. Через этот терминал можно работать с SQLite базами данных, используя утилиту `sqlite3`. ![Web Terminal с примером команды SQLite](/docs/images/sqlite_terminal_console_1.png "Терминал дашборда с примером команды sqlite3") *Пример интерфейса терминала дашборда с доступными командами и примером работы с SQLite* ## 📍 Расположение баз данных Базы данных ботов хранятся в папке `/app/data/` внутри контейнера. Эта папка сохраняется между перезапусками контейнера. ## 🔧 Доступные команды ### Проверка наличия sqlite3 ```bash sqlite3 --version ``` ### Просмотр списка баз данных ```bash ls -la /app/data/*.db ``` или ```bash find /app/data -name "*.db" -o -name "*.sqlite" -o -name "*.sqlite3" ``` ### Просмотр структуры базы данных ```bash sqlite3 /app/data/bot.db ".schema" ``` ### Просмотр списка таблиц ```bash sqlite3 /app/data/bot.db ".tables" ``` ### Выполнение SELECT запроса ```bash sqlite3 /app/data/bot.db "SELECT * FROM users LIMIT 10;" ``` ### Добавление строки в таблицу ```bash sqlite3 /app/data/bot.db "INSERT INTO users (name, email) VALUES ('Иван', 'ivan@example.com');" ``` ### Обновление данных ```bash sqlite3 /app/data/bot.db "UPDATE users SET email='new@example.com' WHERE id=1;" ``` ### Удаление строки ```bash sqlite3 /app/data/bot.db "DELETE FROM users WHERE id=1;" ``` ### Интерактивный режим Для более сложных операций можно использовать интерактивный режим: ```bash sqlite3 /app/data/bot.db ``` В интерактивном режиме доступны команды: - `.tables` - список таблиц - `.schema` - структура базы - `.schema users` - структура конкретной таблицы - `.mode column` - форматирование вывода в виде таблицы - `.headers on` - показывать заголовки колонок - `.quit` или `.exit` - выход ### Экспорт данных в CSV ```bash sqlite3 /app/data/bot.db ".mode csv" ".headers on" "SELECT * FROM users;" > /app/data/users_export.csv ``` ### Импорт данных из SQL файла ```bash sqlite3 /app/data/bot.db < /app/data/import.sql ``` ## ⚠️ Ограничения терминала Терминал блокирует некоторые опасные команды и разделители: - Запрещены: `;`, `||`, `&&`, `` ` ``, `$()` - Команды должны быть в кавычках, если содержат пробелы - Используйте простые команды без сложных конструкций ## 📝 Примеры использования ### Пример 1: Добавление тестового пользователя ```bash sqlite3 /app/data/bot.db "INSERT INTO users (username, created_at) VALUES ('test_user', datetime('now'));" ``` ### Пример 2: Просмотр последних 5 записей ```bash sqlite3 /app/data/bot.db "SELECT * FROM users ORDER BY id DESC LIMIT 5;" ``` ### Пример 3: Подсчет записей ```bash sqlite3 /app/data/bot.db "SELECT COUNT(*) FROM users;" ``` ### Пример 4: Резервная копия базы ```bash cp /app/data/bot.db /app/data/bot.db.backup ``` ### Пример 5: Восстановление из резервной копии ```bash cp /app/data/bot.db.backup /app/data/bot.db ``` ## 🔒 Безопасность - Все команды выполняются внутри контейнера бота - Изменения в базе данных сохраняются в volume - Рекомендуется делать резервные копии перед важными операциями - Используйте транзакции для критичных операций (через интерактивный режим) ## 💡 Советы 1. **Проверяйте путь к базе**: Убедитесь, что база находится в `/app/data/`, а не в `/app/` 2. **Делайте резервные копии**: Перед изменением данных создавайте копию базы 3. **Используйте интерактивный режим**: Для сложных операций удобнее использовать интерактивный режим 4. **Проверяйте результат**: После INSERT/UPDATE проверяйте изменения через SELECT ## 🐛 Решение проблем ### Команда sqlite3 не найдена Если команда `sqlite3` не найдена, это означает, что контейнер был создан до обновления. Пересоберите контейнер бота через панель управления. ### Ошибка "database is locked" База данных заблокирована другим процессом. Убедитесь, что бот не запущен или остановите его перед выполнением операций. ### Ошибка "no such table" Проверьте правильность имени таблицы: ```bash sqlite3 /app/data/bot.db ".tables" ``` --- # Работа с доменами и портами На платформе Bothost вы можете подключить собственный домен или использовать бесплатный поддомен платформы для доступа к боту или веб-сервису через браузер (в зависимости от тарифа и настроек это может быть зона `*.bothost.ru` или автоматический поддомен вида `*.bothost.tech`). Эта статья — базовая: как работает прокси, какой порт нужен приложению, зачем `0.0.0.0`, и как не получить `502/504` из-за несовпадения настроек. **Практика (вебхук, деплой, частые ошибки, диагностика):** [Веб-приложения и домены](web-apps-domains). ![Опция «Использовать домен» в дополнительных настройках бота](/docs/images/domain-settings-use-domain.png) ## Когда читать эту страницу - Нужна базовая модель работы домена и прокси на платформе. - Настраиваете порт впервые и хотите избежать типичных ошибок. - Проверяете, почему сервис не открывается по домену. ## Как это работает Когда вы включаете опцию **«Использовать домен»**, Bothost автоматически настраивает **Reverse Proxy** (обратный прокси). Это работает следующим образом: 1. **Внешний мир:** Пользователь заходит по адресу вашего домена (например, `https://my-bot.bothost.ru` или `https://my-bot.bothost.tech`). Все запросы приходят на стандартный порт 443 (HTTPS). 2. **Шлюз хостинга:** Система Bothost принимает запрос, проверяет SSL-сертификат и перенаправляет трафик внутрь вашего Docker-контейнера. 3. **Ваш контейнер:** Внутри контейнера ваш Python/Node.js сервер принимает этот запрос на том порту, который вы указали в настройках (по умолчанию 3000 или 8000). ## Важность адреса 0.0.0.0 Для того чтобы Reverse Proxy смог передать запрос вашему коду, сервер внутри контейнера должен слушать на всех сетевых интерфейсах. * ❌ **Неправильно:** `127.0.0.1` или `localhost`. Это «внутренний адрес» контейнера. Если сервер запущен на этом адресе, его увидит только сам бот внутри себя, а внешняя система проксирования получит ошибку соединения. * ✅ **Правильно:** `0.0.0.0`. Это означает, что сервер готов принимать входящие соединения со всех сторон, включая шлюз хостинга. ### Пример на Python (FastAPI/Uvicorn) ```python import uvicorn from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} if __name__ == "__main__": # Указываем host="0.0.0.0" обязательно! uvicorn.run(app, host="0.0.0.0", port=8000) ``` ### Пример на Node.js (Express) ```javascript const express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); // Слушаем на 0.0.0.0 app.listen(port, '0.0.0.0', () => { console.log(`Server running on port ${port}`); }); ``` ## Настройка порта в панели В настройках бота есть поле **«Порт»**. Значение в этом поле должно строго совпадать с тем портом, который вы указываете в коде своего приложения. ### Использование переменной окружения PORT Система Bothost автоматически передает выбранный вами порт в переменную окружения `PORT`. Рекомендуется использовать её в коде, чтобы при изменении порта в панели управления вам не приходилось менять код: ```python import os import uvicorn port = int(os.getenv("PORT", 8000)) uvicorn.run(app, host="0.0.0.0", port=port) ``` Если вы изменили порт в коде вручную (например, жестко прописали `5000`), обязательно обновите его и в панели управления Bothost, иначе система будет пытаться отправить запросы на старый порт и вы увидите ошибку **502 Bad Gateway** или **504 Gateway Timeout**. ### Порт в панели и переменная PORT Поле **«Порт»** в панели задаёт, на какой порт внутри контейнера смотрит прокси. Переменная окружения `PORT` в контейнере должна совпадать с этим значением. Если вы задаёте свой порт только в переменных окружения, но не меняете порт в настройках бота, прокси и приложение «разъедутся» — снаружи будет таймаут или ошибка шлюза. ### Автоматический поддомен и символы в имени Для автоматически выданных поддоменов в имени хоста **подчёркивания** в идентификаторе бота заменяются на **дефисы** — так имя соответствует правилам DNS. ## Автоматическая HTTP-обертка Если вы используете домен, но в вашем коде нет веб-сервера (например, вы просто хотите видеть статус бота через веб), система может автоматически создать легкую HTTP-обертку. Однако для полноценных веб-приложений (Dashboard, Webhooks, API) рекомендуется использовать собственный сервер на базе FastAPI, Flask или Express. --- *Дальше по теме: практический гайд [Веб-приложения и домены](web-apps-domains).* --- # Установка библиотек: Python, Java и Go На платформе Bothost боты собираются в Docker-контейнерах. Зависимости устанавливаются автоматически при сборке образа, если в репозитории есть стандартные файлы зависимостей. В этом руководстве описано, как добавить библиотеки для проектов на **Python**, **Java** и **Go**. --- ## Python ### Файлы зависимостей Платформа распознаёт следующие файлы: | Файл | Описание | |------|----------| | `requirements.txt` | Список пакетов pip (основной способ) | | `pyproject.toml` | Современный формат (PEP 518), поддерживается для определения версии Python и зависимостей | ### requirements.txt Добавьте в **корень репозитория** файл `requirements.txt`. При сборке выполняется: ```bash pip install --no-cache-dir -r requirements.txt ``` **Пример `requirements.txt`:** ```text aiogram>=3.0.0 aiohttp python-dotenv ``` С указанием версий (рекомендуется для стабильной сборки): ```text aiogram==3.2.0 aiohttp==3.9.0 python-dotenv==1.0.0 ``` ### pyproject.toml Для проектов с `pyproject.toml` укажите зависимости в секции `[project]`: ```toml [project] name = "my-bot" version = "0.1.0" requires-python = ">=3.11" dependencies = [ "aiogram>=3.0.0", "aiohttp", "python-dotenv", ] ``` Платформа может определять версию Python из `requires-python` и устанавливать пакеты через `pip install .` или `pip install -e .` при наличии соответствующего Dockerfile. ### Рекомендации для Python - Храните `requirements.txt` или `pyproject.toml` в корне репозитория. - Фиксируйте версии в продакшене (`package==1.2.3`), чтобы сборки были воспроизводимы. - Не коммитьте виртуальное окружение (папки `venv`, `.venv`) в Git. --- ## Java Для Java-ботов зависимости задаются через **Maven** (`pom.xml`) или **Gradle** (`build.gradle` / `build.gradle.kts`). Сборка выполняется внутри Docker-образа. ### Maven (pom.xml) Добавьте в корень проекта файл `pom.xml` с зависимостями в ``: ```xml 4.0.0 ru.example telegram-bot 1.0-SNAPSHOT jar 17 17 UTF-8 org.telegram telegrambots 6.8.0 org.apache.maven.plugins maven-jar-plugin 3.3.0 ru.example.BotMain ``` При сборке образа обычно выполняется: ```bash mvn clean package -DskipTests ``` Исполняемый JAR будет в `target/*.jar`. В Dockerfile команда запуска указывает на этот JAR. ### Gradle (build.gradle) Пример `build.gradle` (Kotlin DSL — `build.gradle.kts`): ```groovy plugins { id 'java' id 'application' } group = 'ru.example' version = '1.0-SNAPSHOT' java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } repositories { mavenCentral() } dependencies { implementation 'org.telegram:telegrambots:6.8.0' } application { mainClass = 'ru.example.BotMain' } jar { manifest { attributes 'Main-Class': 'ru.example.BotMain' } } ``` При сборке: ```bash ./gradlew build # или для fat JAR: ./gradlew shadowJar # при использовании плагина shadow ``` ### Рекомендации для Java - Держите `pom.xml` или `build.gradle` в корне репозитория. - Указывайте конкретные версии зависимостей для предсказуемых сборок. - В Dockerfile используйте образ с Maven или Gradle, либо многоэтапную сборку (build stage → runtime stage с JRE). --- ## Go Зависимости в Go описываются в **go.mod** (и при необходимости в **go.sum**). Модуль должен находиться в корне репозитория. ### go.mod Инициализация модуля (если ещё нет `go.mod`): ```bash go mod init github.com/username/my-bot ``` **Пример `go.mod`:** ```go module github.com/username/telegram-bot go 1.21 require ( github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 ) ``` После добавления импортов в код выполните: ```bash go mod tidy ``` Это обновит `go.mod` и создаст/обновит `go.sum`. ### Сборка в Docker Типичные шаги в Dockerfile для Go: ```dockerfile FROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o /bot . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /bot . CMD ["./bot"] ``` Платформа может определять версию Go по директиве `go` в `go.mod` и использовать её при сборке образа. ### Рекомендации для Go - Храните `go.mod` и `go.sum` в корне репозитория и коммитьте оба файла. - Используйте `go mod tidy` перед коммитом, чтобы зависимости были согласованы. - Для минимального образа используйте многоэтапную сборку и статическую линковку (`CGO_ENABLED=0`). --- ## Краткая сводка | Язык | Файл зависимостей | Команда установки / сборки | |-------|------------------------|-----------------------------------| | Python| `requirements.txt` | `pip install -r requirements.txt` | | Python| `pyproject.toml` | Зависит от Dockerfile (pip/poetry)| | Java | `pom.xml` (Maven) | `mvn clean package` | | Java | `build.gradle` (Gradle)| `./gradlew build` | | Go | `go.mod` + `go.sum` | `go mod download && go build` | Убедитесь, что выбранный файл зависимостей лежит в корне репозитория и закоммичен в Git — тогда при деплое на Bothost зависимости будут установлены или собраны в рамках сборки Docker-образа. --- ## См. также - [Быстрый старт](getting-started) — создание первого бота - [Кастомный Dockerfile](custom-dockerfile) — ручная настройка сборки и запуска - [Переменные окружения](environment-variables) — передача токенов и настроек - [FAQ](faq) — частые вопросы по деплою --- # Хранение базы данных бота При сборке и развертывании бота автоматически создается папка хранилища `data`. Эта папка предназначена для хранения данных, которые должны сохраняться между обновлениями бота из Git репозитория. ## 📁 Зачем хранить базу в папке data? Основная причина — **защита данных при обновлении из Git**. При обновлении бота из репозитория происходит: 1. Клонирование/обновление кода из Git 2. Пересборка Docker образа 3. Перезапуск контейнера Если база данных хранится в корне проекта или в других директориях, которые синхронизируются с Git, она может быть перезаписана или потеряна при обновлении. Папка `data` специально исключена из синхронизации с Git и предназначена для персистентного хранения данных. ## 🗄️ Способы размещения базы данных ### Способ 1: Загрузка через файловый менеджер (первый запуск) Если у вас уже есть готовая база данных, которую нужно использовать: 1. **Подготовьте файл базы данных** (например, `bot.db` для SQLite) 2. **Загрузите файл в папку data через файловый менеджер** в панели управления ботом 3. **Укажите путь к базе в переменных окружения**: ``` DATABASE_PATH=/app/data/bot.db ``` или ``` DB_PATH=/app/data/scheduler.db ``` (в зависимости от того, какую переменную использует ваш бот) 4. **После первого запуска удалите файл базы из Git репозитория** (если он там был), чтобы избежать конфликтов при обновлениях ### Способ 2: Программное создание базы (рекомендуется) Лучший подход — создавать базу данных программно при первом запуске, если она еще не существует: #### Пример для Python (SQLite): ```python import os import sqlite3 from pathlib import Path # Путь к папке data DATA_DIR = Path("/app/data") DATA_DIR.mkdir(parents=True, exist_ok=True) # Путь к базе данных DB_PATH = DATA_DIR / "bot.db" # Создаем базу, если её нет if not DB_PATH.exists(): conn = sqlite3.connect(str(DB_PATH)) # Создаем необходимые таблицы conn.execute(''' CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, username TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() conn.close() print(f"База данных создана: {DB_PATH}") else: print(f"База данных уже существует: {DB_PATH}") ``` #### Пример для Node.js (SQLite): ```javascript const sqlite3 = require('sqlite3').verbose(); const path = require('path'); const fs = require('fs'); // Путь к папке data const dataDir = '/app/data'; if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } // Путь к базе данных const dbPath = path.join(dataDir, 'bot.db'); // Создаем базу и таблицы при первом запуске const db = new sqlite3.Database(dbPath, (err) => { if (err) { console.error('Ошибка открытия базы данных:', err); return; } console.log(`База данных подключена: ${dbPath}`); // Создаем таблицы, если их нет db.run(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, username TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `, (err) => { if (err) { console.error('Ошибка создания таблицы:', err); } else { console.log('Таблицы созданы или уже существуют'); } }); }); ``` ## 📝 Настройка переменных окружения В панели управления ботом добавьте переменную окружения с путем к базе данных: - `DATABASE_PATH=/app/data/bot.db` (для SQLite) - `DB_PATH=/app/data/scheduler.db` (альтернативное имя) - `DATABASE_NAME=tgbot_okz_db.db` (если ваш код использует такое имя) Убедитесь, что путь указывает на папку `/app/data`, а не на корень проекта. ## ✅ Преимущества хранения в папке data - ✅ База данных не перезаписывается при обновлении из Git - ✅ Данные сохраняются между перезапусками контейнера - ✅ Удобно делать резервные копии (вся папка `data`) - ✅ Изолированное хранение данных от кода приложения ## 🔄 Миграция существующей базы Если у вас уже есть база данных в другом месте: 1. Остановите бота 2. Скопируйте файл базы данных в папку `data` через файловый менеджер 3. Обновите переменные окружения, указав новый путь (`/app/data/имя_базы.db`) 4. Запустите бота ## ⚠️ Важные замечания 1. **Не добавляйте папку `data` в Git** — она должна быть в `.gitignore` 2. **Делайте резервные копии** папки `data` регулярно 3. **Проверяйте права доступа** — контейнер должен иметь права на запись в папку `/app/data` 4. **Для MySQL/PostgreSQL** используйте внешние серверы БД или Docker volumes, а не файловую систему контейнера ## 📚 См. также - [Переменные окружения](environment-variables.md) - [Быстрый старт](getting-started.md) - [FAQ](faq.md) --- **Вопросы?** Обратитесь в поддержку: [@bothostru](https://t.me/bothostru)