Skip to content

Repository files navigation

urumi-the-bot

Telegram-бот: пишет в чат через LLM, ведёт историю сообщений, делает пересказы, копит заметки об участниках и умеет менять «настроение». Одна инсталляция может обслуживать сразу несколько групп — у каждой свои промпты, модели и настроение, список групп управляется прямо из админки. Управление — через веб-админку.

  • Python 3.12, aiogram 3.x, aiosqlite (сырой SQL, без ORM)
  • Админка: FastAPI + Jinja2 + htmx, тёмная тема, без CDN
  • LLM — через OpenRouter (OpenAI-совместимый endpoint)

Быстрый старт

git clone <repo> urumi-the-bot
cd urumi-the-bot
cp .env.example .env
$EDITOR .env          # заполнить BOT_TOKEN, OPENROUTER_API_KEY, ADMIN_PASSWORD
docker compose up -d --build
docker compose logs -f

Админка поднимется на http://<host>:8080 (порт из ADMIN_PORT), вход — по ADMIN_PASSWORD. Дальше — добавить бота в группу (см. ниже) и активировать её на странице /chats; GROUP_CHAT_ID в .env теперь нужен только опционально, для самого первого запуска.

Без Docker

python3.12 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env && $EDITOR .env
.venv/bin/python main.py

База создаётся автоматически при старте (DB_PATH, по умолчанию data/urumi.db), миграции применяются там же.

Установка в Proxmox VE (LXC + systemd)

scripts/proxmox-install.sh — самостоятельный интерактивный установщик в духе Proxmox VE Helper-Scripts. Он не является частью каталога community-scripts и не загружает их библиотеки. Запускается от root в Shell узла Proxmox, создаёт новый непривилегированный LXC с Ubuntu 24.04 (amd64) и устанавливает Python 3.12. Нужен актуальный Proxmox VE, поддерживающий этот шаблон, и доступ в интернет с узла и контейнера: каталог шаблонов, GitHub, репозитории Ubuntu и PyPI.

После публикации скриптов в ветке main этого репозитория запуск выглядит так:

curl -fsSL https://raw.githubusercontent.com/scatari69/urumi/main/scripts/proxmox-install.sh -o /root/urumi-install.sh
bash /root/urumi-install.sh

Можно также перенести scripts/proxmox-install.sh на узел вручную. Внутренний proxmox-guest.sh и код приложения скачиваются из выбранной ветки, тега или commit репозитория scatari69/urumi; эта версия должна содержать оба скрипта. Имена веток с / не поддерживаются, вместо них можно указать commit SHA.

Установщик спросит:

  • свободный ID, имя контейнера, CPU, RAM и диск (по умолчанию 1 ядро, 1024 МБ, 4 ГБ);
  • активные хранилища для шаблона и диска, сетевой мост, DHCP или IPv4/CIDR со шлюзом, VLAN (0 — без тега), DNS IPv4 (пусто — наследовать от узла);
  • порт админки (8080), версию кода, токен Telegram, ключ OpenRouter, пароль админки и ADMIN_USER_IDS в формате [123456789];
  • подтверждение создания контейнера и запуска бота.

Секреты вводятся скрыто. Последовательность ${ в секретах не поддерживается из-за интерполяции .env. До запуска остановите другой экземпляр бота с тем же токеном. Существующие контейнеры не перезаписываются и автоматически не удаляются даже при ошибке. При неудаче смотрите вывод команды и журнал в сохранённом LXC; повторный запуск основного установщика предназначен для нового контейнера.

В конце выводится http://<IP-контейнера>:8080 (или выбранный порт). Если в Proxmox заданы правила firewall, разрешите доступ к этому порту из своей сети и исходящий доступ контейнера. Войдите с заданным паролем, отключите privacy mode у BotFather, перезаведите бота в группу и активируйте её на /chats.

Внутри LXC:

Путь / команда Назначение
/opt/urumi Код и .venv; работает Python 3.12 без Docker
/etc/urumi/urumi.env Конфигурация, доступна root и группе urumi; .env в коде — ссылка
/var/lib/urumi/urumi.db SQLite-база, отдельная от кода
systemctl status urumi Статус сервиса
journalctl -u urumi -f Логи
systemctl restart urumi Перезапуск после изменения конфигурации

Сервис стартует вместе с контейнером, работает от пользователя urumi и перезапускается при сбое. Проверка установки ждёт сервис и HTTP /health; отправку и получение сообщений нужно проверить отдельно в Telegram. Для systemd в Ubuntu 24.04 контейнер создаётся с nesting=1.

Если установка остановилась с Temporary failure resolving, сначала проверьте сеть из контейнера (здесь и далее 104 замените на его ID):

pct exec 104 -- ip -4 -brief address
pct exec 104 -- ip -4 route
pct exec 104 -- cat /etc/resolv.conf
pct exec 104 -- getent ahostsv4 archive.ubuntu.com

Отсутствие IPv4 или маршрута по умолчанию требует проверки DHCP, моста, VLAN и шлюза. Если они есть, проверьте доступность DNS и правила firewall. DNS задаётся в Proxmox на вкладке DNS контейнера или через pct set 104 --nameserver <IP-DNS>: укажите сервер, доступный из сети контейнера. Не копируйте loopback-адрес DNS узла (127.0.0.1 или 127.0.0.53) в контейнер.

Если eth0 имеет состояние DOWN (unmanaged), а журнал systemd-networkd содержит Failed to parse configuration file: Permission denied, проверьте права: pct exec 104 -- namei -l /etc/systemd/network/eth0.network. Старый установщик передавал Proxmox umask 077: в таком контейнере даже /etc мог получить права 700, закрывающие доступ для systemd-networkd. Восстановите права на сетевой путь и перезапустите службу (без рекурсивного chmod):

pct exec 104 -- chmod 755 /etc /etc/systemd /etc/systemd/network
pct exec 104 -- chmod 644 /etc/systemd/network/eth0.network
pct exec 104 -- systemctl restart systemd-networkd

В исправленном установщике команды Proxmox выполняются с umask 022, даже если у вызывающей оболочки задан 077. Маска 077 действует только в отдельном подпроцессе записи ключей: временный каталог имеет права 700, файл — 600.

Установщик ждёт готовности IPv4, маршрута и DNS до установки пакетов, а при ошибке обновления индексов APT останавливается. Если сбой произошёл на этом этапе, конфигурация ещё лежит в /root/urumi.env, и после исправления сети можно продолжить установку в том же контейнере, без повторного ввода ключей. Обновите внутренний скрипт с узла Proxmox и запустите его:

curl -fsSL https://raw.githubusercontent.com/scatari69/urumi/main/scripts/proxmox-guest.sh -o /root/urumi-guest.sh
pct push 104 /root/urumi-guest.sh /root/urumi-install.sh --perms 0700
pct exec 104 -- bash /root/urumi-install.sh main

Для контейнера, созданного старой версией установщика без nesting, сначала включите его: pct set 104 --features nesting=1, затем pct reboot 104 и дождитесь запуска. Если в контейнере настроены другие features, сохраните их при изменении. Продолжение описанным способом поддерживается только до создания каталогов приложения; существующую установку внутренний скрипт не перезаписывает.

Для обновления сначала сделайте резервную копию LXC через Proxmox. Затем внутри контейнера выполните от root (замените main на нужный тег или commit при необходимости):

systemctl stop urumi
cd /opt/urumi
git fetch --depth 1 origin main
git checkout --detach FETCH_HEAD
.venv/bin/pip install --no-cache-dir -r requirements.txt
systemctl start urumi
systemctl status urumi

Выполняйте команды последовательно; при ошибке не переходите к следующей. Конфигурация и база остаются на месте. Этот способ обновляет код и зависимости; изменения самого systemd-unit из будущих версий установщика применяются отдельно.


Обязательный шаг: выключить privacy mode

Без этого бот не увидит сообщения в группе и не будет работать. По умолчанию Telegram отдаёт боту только команды (/...) и ответы на его собственные сообщения — остальные сообщения группы до него просто не доходят, а значит не будет ни истории, ни контекста, ни пересказов, ни заметок. Privacy mode — свойство самого бота (токена), не конкретной группы, так что настраивается один раз, а не в каждой новой группе — но требование "удалить и добавить заново" (шаг 4) применяется к каждой группе, куда бот заходит после этого момента.

  1. Открыть @BotFather
  2. /mybots → выбрать бота → Bot SettingsGroup Privacy
  3. Нажать Turn off — должно остаться «Privacy mode is disabled»
  4. Удалить бота из группы и добавить заново — настройка применяется только при следующем входе в группу, на уже добавленного бота она не подействует

Проверить: написать в группе обычное сообщение (без команд) и открыть в админке /c/<chat_id>/messages — оно должно там появиться. Если пусто, privacy mode всё ещё включён либо бот не был перезаведён в группу.


Токен бота

  1. @BotFather/newbot → имя и username
  2. Скопировать токен вида 123456789:AAE... в BOT_TOKEN
  3. Добавить бота в группу (после выключения privacy mode, см. выше) — группа появится на /chats в админке, останется её там активировать (см. «Ещё одна группа» ниже)

Для команды /summary в группе бот должен уметь читать сообщения; для ответов — просто быть участником. Права администратора не требуются.


Ключ OpenRouter

  1. Зарегистрироваться на openrouter.ai
  2. openrouter.ai/keysCreate Key
  3. Скопировать ключ sk-or-v1-... в OPENROUTER_API_KEY

Ключ нужен даже для бесплатных моделей — по нему считаются лимиты. Бесплатный тариф ограничен по запросам в сутки; при исчерпании OpenRouter отдаёт 402/403, и бот переключается на fallback_models (см. ниже).


Ещё одна группа

Список групп, где боту разрешено что-либо делать, живёт в базе (таблица chats), а не в .env — управляется на странице /chats в админке, а не через BotFather или переменные окружения.

  1. Добавить бота в новую группу (не забыв про privacy mode — см. выше, требование «удалить и добавить заново» действует для каждой новой группы).
  2. Бот сам замечает момент, когда его добавили (это не зависит от privacy mode — такие системные события Telegram присылает всегда), и заносит группу в /chats со статусом «ожидает». Никаких действий на этом шаге не выполняется — ни истории, ни ответов.
  3. Открыть /chats в админке и нажать Активировать — только с этого момента бот начинает логировать сообщения и отвечать в этой группе.

У новой группы — чистые настройки по умолчанию (system_prompt, модели, настроение и т.д.), независимые от остальных; поправить их можно сразу на /c/<chat_id>/settings. Каталог настроений (/c/<chat_id>/moods) — общий на все группы, а вот какое настроение сейчас активно — своё для каждой.

Деактивировать группу можно там же, кнопкой «Деактивировать» — бот перестаёт что-либо делать в ней, но история и заметки не удаляются. «Удалить» стирает саму запись о группе и её настройки (историю, профили и пересказы — нет).

Если группа не появилась в /chats сама

Такое бывает, если бот получил событие добавления не сразу (например был выключен дольше суток — Telegram столько хранит недоставленные обновления). В этом случае ID можно добавить вручную через форму внизу /chats.

ID группы — отрицательное число, у супергрупп с префиксом -100 (например -1001234567890). Узнать его:

Способ 1 — через Telegram API (бот при этом должен быть остановлен). Напишите что-нибудь в группе и откройте в браузере:

https://api.telegram.org/bot<BOT_TOKEN>/getUpdates

В ответе найдите "chat":{"id":-1001234567890,...} — это и есть нужный ID.

getUpdates нельзя вызывать, пока работает polling: Telegram ответит 409 Conflict. Сначала docker compose down, потом запрос. Пустой результат означает, что новых сообщений нет (напишите в группу ещё раз) либо включён privacy mode.

Способ 2 — сторонние боты вроде @getidsbot или @RawDataBot: добавить в группу, он покажет ID, после чего его можно удалить. Способ не требует останавливать своего бота.

Если группа была обновлена до супергруппы, ID меняется — на новый ID это будет уже другая запись в /chats, старую можно удалить.

ADMIN_USER_IDS — это ID пользователей (положительные числа), список в формате JSON: ADMIN_USER_IDS=[123456789, 987654321]. Свой ID можно узнать у @userinfobot. Это единственная настройка, общая на все группы: администраторы бота одни и те же везде (влияет на /profile и на mood_admin_only).


Пересланные сообщения

В истории текстов и фото репосты помечаются с указанием исходного автора или канала. Модель получает правило не приписывать переславшему чужие слова, опыт или взгляды — в ответах, пересказах и заметках. При ответе участника на репост боту также передаётся текст этого репоста, даже если он выпал из истории. Сообщения, скопированные без признака пересылки Telegram, распознать нельзя.

Короткие живые ответы (de-llmify)

На странице /c/<chat_id>/settings под system_prompt включите de-llmify и нажмите «Сохранить». Стиль предлагает модели отвечать одной короткой репликой, обычно в 1–2 предложения, без шаблонных вступлений, пересказа вопроса и ненужных предложений помощи. Явная просьба о подробностях допускает более длинный ответ.

Поле «Инструкции стиля» можно менять под свою группу; пустое поле возвращает стандартный текст. Это подключаемый блок системного промпта: базовый промпт → настроение → инструкции de-llmify. Он действует только на ответы в чате, не затрагивает пересказы и профили. Предпросмотр настроения учитывает сохранённый стиль. Настройки независимы для каждой группы, по умолчанию стиль выключен. Перезапуск после сохранения не нужен.

Это инструкции модели, а не жёсткое обрезание текста: точная длина и естественность зависят от выбранной модели и пользовательского текста инструкций.

Бесплатные модели живут недолго

Каталог бесплатных моделей OpenRouter постоянно меняется: модели появляются, дорожают и исчезают. Модель, прописанная в конфиге полгода назад, сегодня с большой вероятностью отдаёт 404.

Поэтому:

  • Список моделей подтягивается из API, а не зашит в код. /c/<chat_id>/settings показывает актуальный каталог (GET /api/v1/models), сгруппированный по вендорам, с фильтром «только бесплатные», поиском, ценой за 1M токенов и кнопкой «Тест». Кэш — час, общий на все группы (в памяти и в глобальной таблице settings, отдельно от per-group chat_settings), кнопка «Обновить список» сбрасывает его.
  • Фильтр «бесплатные» сравнивает цену как число (pricing.prompt == 0), а не ищет суффикс :free: часть бесплатных моделей его не носит.
  • Заполните fallback_models на /c/<chat_id>/settings — две-три запасные модели через запятую, для каждой группы отдельно. При 402/403 (лимит исчерпан) или 404 (модель убрали) клиент проходит список по порядку и логирует, какая в итоге ответила.

Блок «Состояние LLM» на /c/<chat_id>/settings и на дашборде (запросы, ошибки, последняя сработавшая модель) — общий на весь процесс, а не для одной группы: httpx-клиент и лимит параллельных запросов к OpenRouter (MAX_CONCURRENCY) один на все группы сразу, так как это один и тот же ключ и один и тот же дневной лимит OpenRouter.

Разумный набор: одна основная бесплатная + одна бесплатная другого вендора + одна дешёвая платная как последний рубеж.

Значение MODEL в .env — это только дефолт для трёх полей chat_model, summary_model, profile_model в каждой новой группе, если они не заданы в админке. Перед первым запуском стоит проверить на /c/<chat_id>/settings, что такая модель ещё существует: дефолт из .env.example (google/gemma-3-27b-it:free) в каталоге OpenRouter уже отсутствует.


Конфигурация

.env — только инфраструктура (см. .env.example):

Переменная Описание
BOT_TOKEN токен от BotFather
OPENROUTER_API_KEY ключ OpenRouter
MODEL дефолтная модель для новой группы, если в её настройках не выбрана другая
GROUP_CHAT_ID опционально: ID группы для самого первого запуска (см. «Ещё одна группа»)
ADMIN_USER_IDS JSON-список ID админов, напр. [123456789] — общий на все группы
ADMIN_PASSWORD пароль в админку (он же ключ подписи сессии)
DB_PATH путь к SQLite, по умолчанию data/urumi.db
HISTORY_TTL_HOURS сколько часов хранить сообщения
LOG_LEVEL INFO / DEBUG / ...
ADMIN_PORT порт админки

Всё остальное — промпты, модели, температура, настроения — правится в админке, отдельно для каждой группы, и живёт в таблице chat_settings (кэш 30 секунд на группу, сбрасывается при сохранении). Список самих групп — в таблице chats.


Админка

Страница Что там
/chats список групп: статус (ожидает/активна), активация/деактивация/удаление, ручное добавление по ID
/c/<chat_id>/ дашборд группы: сообщения за 24 ч, топ-10 активных, состояние LLM, последние смены настроения
/c/<chat_id>/settings промпты, три модели (чат / пересказы / заметки), фолбэки, температура, вкл-выкл — для этой группы
/c/<chat_id>/messages лента этой группы с фильтром по участнику, поиском и постраничностью
/c/<chat_id>/moods настроения: каталог общий на все группы, «включить» и текущее — для этой группы
/c/<chat_id>/profiles заметки об участниках этой группы: правка, пересборка, удаление
/c/<chat_id>/summaries история пересказов этой группы и кнопка «сгенерировать сейчас»

Переключатель группы — в шапке рядом с навигацией.


Команды в группе

Команда Кто может Что делает
/summary [часов] все пересказ за N часов (по умолчанию 24, максимум HISTORY_TTL_HOURS)
/mood все показать текущее настроение и список доступных
/mood <name> все, либо только админы при mood_admin_only переключить (не чаще раза в 30 с)
/mood reset там же вернуть настроение по умолчанию
/profile только ADMIN_USER_IDS показать заметку об авторе сообщения, на которое отвечаете
/createprofile все — свой профиль; ADMIN_USER_IDS — также чужой без реплая или реплаем на своё сообщение — создать или обновить свой профиль и показать его; администратор может ответить на сообщение другого участника для создания его профиля; использует историю этой группы за HISTORY_TTL_HOURS, без порога в 20 сообщений; отказ через /forgetme сохраняется
/forgetme все стереть свою заметку и отказаться от профилирования
/forgetcontext confirm только ADMIN_USER_IDS необратимо стереть историю сообщений этого чата (контекст для ответов/пересказов/заметок); без confirm — только предупреждение, ничего не удаляет

Бот отвечает в чате, когда его упомянули через @username, ответили на его сообщение, либо случайно — с вероятностью random_reply_chance.

Список команд в меню Telegram (кнопка "/") бот перезаписывает сам при каждом запуске — под scope chat для каждой активной на данный момент группы, отдельно для языков по умолчанию, ru и en. Это перекрывает то, что в этой же группе мог нарегать предыдущий бот/фреймворк на этом токене: правки через BotFather их не брали, потому что команды хранятся отдельно по каждой паре (scope, language_code), а BotFather штатно правит только scope по умолчанию без языка. Если старые команды всё же остались в других scope (например, default или all_group_chats) — на то, что видно в самой группе, они не влияют: scope chat имеет более высокий приоритет, — но подчистить их полностью можно через deleteMyCommands напрямую по Bot API.

Группа, активированная через /chats без перезапуска бота, получит меню команд только после следующего перезапуска — это чисто косметическая задержка, сами команды работают сразу.

Фото

Триггеры те же (упоминание в подписи, ответ на сообщение бота, случайный шанс). Если chat_model поддерживает изображения на входе — на /c/<chat_id>/settings в информации о модели это видно как image в строке вида text+image->text — бот скачивает фото (до 5 МБ, при нескольких размерах выбирается наибольший подходящий) и отправляет его модели вместе с подписью. Если модель не поддерживает изображения, или скачать не удалось, бот всё равно ответит — но только по тексту подписи, саму картинку не увидев.


Эксплуатация

docker compose ps                 # статус, в т.ч. health
docker compose logs -f
docker compose restart
docker compose down               # данные останутся в volume urumi-data
  • Данные лежат в именованном volume urumi-data, смонтированном в /app/data. Контейнер работает под пользователем urumi (uid 10001). Если хочется bind mount, замените том на ./data:/app/data и выполните sudo chown -R 10001:10001 ./data, иначе непривилегированный процесс не сможет писать в каталог.
  • Healthcheck дёргает /health внутри контейнера; состояние видно в docker compose ps.
  • docker stop шлёт SIGTERM: бот дослушивает текущий запрос, останавливает polling, гасит HTTP-сервер и закрывает httpx-клиент. На это отводится stop_grace_period: 20s.

Бэкап

docker compose exec bot python -c "import sqlite3,shutil; shutil.copy('data/urumi.db','data/backup.db')"
docker compose cp bot:/app/data/backup.db ./backup.db

Если что-то не работает

Симптом Причина
Бот молчит на обычные сообщения группа не активирована на /chats, либо privacy mode включён, либо бота не перезавели в группу
/c/<chat_id>/messages пустая то же самое
Группы нет в списке /chats бот был офлайн больше суток и не получил событие добавления — добавьте вручную по ID (см. «Ещё одна группа»)
TelegramUnauthorizedError неверный BOT_TOKEN
LLM отвечает 404 модель исчезла из каталога — выберите другую на /c/<chat_id>/settings
LLM отвечает 402/403 исчерпан бесплатный лимит — заполните fallback_models для этой группы
Админка недоступна проверьте ADMIN_PORT и проброс порта в compose
После супергруппы бот молчит у супергрупп ID меняется — старая запись в /chats больше не действует; если новая не появилась сама, добавьте её ID вручную и активируйте

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages