Discord-бот на VPS: запуск через systemd с автоперезапуском

Как поставить Python-бота на VPS, вынести токен в файл окружения и настроить systemd, чтобы бот поднимался после перезагрузки и сам перезапускался после падения.

Команда EGN 9 мин чтения
Discord-бот на VPS: запуск через systemd с автоперезапуском
Содержание 18
  1. Коротко
  2. Что понадобится
  3. Шаги
  4. Примеры
  5. Slash-команды вместо !-команд
  6. Как посмотреть бота в кабинете EGN
  7. Симптом → причина → решение
  8. Пример: ограничение прав через роли
  9. Пример: команда с ответом
  10. Частые ошибки
  11. Итог
  12. Чек-лист после запуска
  13. Как обновить код без простоя
  14. Вопросы и ответы
  15. Нужен ли мощный VPS для Discord-бота?
  16. Можно ли запускать бота на Node.js?
  17. Зачем отдельный пользователь botuser?
  18. Что делать, если токен утёк?

Бот на домашнем компьютере работает, пока компьютер включён. Как только вы его выключили, сообщество остаётся без бота. VPS решает эту проблему: машина работает круглосуточно, а systemd поднимает бота после перезагрузки и перезапускает его, если процесс упал. Ниже — полная схема для Python-бота на Ubuntu 24.04.

Коротко

Поставьте discord.py в виртуальное окружение, создайте в Developer Portal бота и включите в нём Message Content Intent. Токен положите в файл .env с правами 600, а службу опишите в /etc/systemd/system/discord-bot.service с Restart=always. Команда systemctl enable --now discord-bot запускает бота и включает автозапуск.

Что понадобится

Нужен VPS с Ubuntu 24.04 или Debian 12, доступ по SSH по ключу (ключи SSH настраиваются до установки бота) и аккаунт Discord с приложением, созданным в Discord Developer Portal. Бот подключается к Discord сам, через исходящее WebSocket-соединение, поэтому входящий порт открывать не нужно.

Схема: бот подключается к Discord исходящим соединением, systemd следит за процессом
Схема: бот подключается к Discord исходящим соединением, systemd следит за процессом

Шаги

  1. Создайте приложение в Developer Portal, на вкладке Bot нажмите «Reset Token» и скопируйте токен. Токен — это пароль бота: его нельзя публиковать и вставлять в код.
  2. На той же вкладке Bot в разделе «Privileged Gateway Intents» включите Message Content Intent. Без него бот в текстовых каналах не увидит содержимое сообщений.
  3. Установите Python-окружение и библиотеку:
Терминал
sudo apt update && sudo apt install -y python3-venv
sudo useradd -r -s /usr/sbin/nologin botuser
sudo mkdir -p /opt/discord-bot && sudo chown botuser:botuser /opt/discord-bot
cd /opt/discord-bot
python3 -m venv venv
./venv/bin/pip install -U discord.py
  1. Напишите bot.py. Токен читается из переменной окружения, в коде его нет:
Python
import os
import discord

intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)

@client.event
async def on_ready():
    print(f"Бот подключён как {client.user}")

@client.event
async def on_message(message):
    if message.author == client.user:
        return
    if message.content == "!ping":
        await message.channel.send("pong")

client.run(os.environ["DISCORD_TOKEN"])
  1. Создайте файл окружения /opt/discord-bot/.env (например, через sudo nano) с одной строкой:
server.properties
DISCORD_TOKEN=вставьте-токен-сюда

Затем закройте его от чужих глаз. Токен без кавычек и пробелов вокруг =:

Терминал
sudo chown botuser:botuser /opt/discord-bot/.env
sudo chmod 600 /opt/discord-bot/.env

Файл bot.py тоже должен принадлежать botuser или быть читаемым им: sudo chown botuser:botuser /opt/discord-bot/bot.py.

  1. Опишите службу systemd в файле /etc/systemd/system/discord-bot.service:
Конфиг
[Unit]
Description=Discord bot
After=network-online.target
Wants=network-online.target

[Service]
User=botuser
WorkingDirectory=/opt/discord-bot
EnvironmentFile=/opt/discord-bot/.env
ExecStart=/opt/discord-bot/venv/bin/python bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
  1. Примените настройки и запустите бота:
Терминал
sudo systemctl daemon-reload
sudo systemctl enable --now discord-bot
sudo systemctl status discord-bot
  1. Проверьте журнал. Логи бота попадают в journalctl, его можно смотреть в реальном времени:
Терминал
sudo journalctl -u discord-bot -f
  1. Пригласите бота на сервер через OAuth2 URL Generator в Developer Portal. Выберите scope bot и нужные права, например «Send Messages» и «Read Message History».
Жизненный цикл службы: процесс упал, systemd ждёт пять секунд и поднимает его снова
Жизненный цикл службы: процесс упал, systemd ждёт пять секунд и поднимает его снова

Примеры

Перезапуск после падения. Имитация: ошибка в коде, процесс завершился с ненулевым кодом. Благодаря Restart=always systemd через RestartSec=5 запускает бота заново. В журнале появятся две записи: остановка и новый старт.

Перезагрузка VPS. Команда sudo reboot. После загрузки служба стартует сама, потому что enable записал её в автозапуск. Проверьте командой systemctl is-enabled discord-bot, она должна вывести enabled.

Обновление кода. Положите новый bot.py в каталог, затем sudo systemctl restart discord-bot. Токен и окружение при этом не меняются.

Вынос токена. Чтобы сменить токен, отредактируйте .env и перезапустите службу. Если токен утёк, сбросьте его в Developer Portal, иначе старый продолжит работать у злоумышленника.

Slash-команды вместо !-команд

Discord развивает slash-команды, и для них не нужен Message Content Intent. В discord.py 2.x они описываются через app_commands:

Python
from discord import app_commands

tree = app_commands.CommandTree(client)

@tree.command(name="ping", description="Проверка связи")
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message("pong")

@client.event
async def on_ready():
    await tree.sync()

Команду нужно синхронизировать с Discord вызовом tree.sync(). Делайте это не при каждом запуске, если бот часто перезапускается: у синхронизации есть лимиты по частоте. Надёжнее вынести её в отдельную команду владельца или запускать один раз после изменения списка команд. Бота при этом нужно приглашать со scope applications.commands вместе с bot.

Как посмотреть бота в кабинете EGN

Если бот работает на VPS EGN, к нему можно попасть по SSH и через «Консоль» в карточке сервера: она открывает терминал в браузере. Консоль полезна, когда ключ SSH остался на другом компьютере или закрыт порт 22. Откройте карточку сервера, нажмите «Консоль» и выполните те же команды, что и в SSH-клиенте.

Верх карточки VPS: кнопка «Консоль» открывает терминал без SSH-клиента
Верх карточки VPS: кнопка «Консоль» открывает терминал без SSH-клиента

После входа полезны три команды:

Терминал
sudo systemctl status discord-bot --no-pager
sudo journalctl -u discord-bot -n 100 --no-pager
sudo systemctl restart discord-bot

Первая показывает, работает ли служба и когда она стартовала. Вторая выводит последние сто строк журнала. Третья перезапускает бота после правки кода или .env.

Симптом → причина → решение

Симптом Частая причина Что сделать
LoginFailure: Improper token Токен скопирован с ошибкой или сброшен Сбросьте токен в Developer Portal и обновите .env
Бот онлайн, но команды не работают Нет Message Content Intent или нет прав в канале Включите intent и проверьте роль бота в настройках канала
ModuleNotFoundError: No module named 'discord' Служба запускает не тот Python В ExecStart укажите /opt/discord-bot/venv/bin/python
Служба перезапускается каждые 5 секунд Ошибка в коде на старте Смотрите journalctl, исправьте исключение, перезапустите
Бот не отвечает на slash-команды Команды не синхронизированы с Discord Вызовите await tree.sync() (см. раздел про slash-команды ниже)
После reboot бота нет Нет enable Выполните sudo systemctl enable discord-bot

Пример: ограничение прав через роли

Не давайте боту права администратора без необходимости. Если боту нужно только писать в канал, выдайте роли право «Отправка сообщений» только в нужном канале. Права выдаются в настройках сервера Discord, а не в коде. Бот с правами администратора может удалить каналы и роли, поэтому это первое, что стоит проверить после приглашения.

Пример: команда с ответом

Добавьте в bot.py команду, которая отвечает текущим временем сервера. Это удобно для проверки часового пояса на VPS. Новый обработчик заменяет прежний on_message: в файле он должен быть один, поэтому объедините условия в одной функции:

Python
import datetime

@client.event
async def on_message(message):
    if message.author == client.user:
        return
    if message.content == "!time":
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        await message.channel.send(f"Время на сервере: {now}")

Если ответ отличается от местного времени на несколько часов, проверьте часовой пояс VPS командой timedatectl.

Частые ошибки

  • Бот в сети, но не отвечает на !ping. Чаще всего не включён Message Content Intent. Включите его в Developer Portal и перезапустите службу.
  • PrivilegedIntentsRequired. Discord не разрешает привилегированный intent, потому что он не включён в портале. Исправляется тем же шагом.
  • Токен в коде или в git. Так токен утекает. Держите его в .env с правами 600 и в .gitignore.
  • Служба падает сразу после старта. Проверьте путь в ExecStart: должен быть путь к Python внутри venv, а не системный python3. Журнал покажет ошибку импорта.
  • Бот не поднимается после перезагрузки. Нет enable, или в [Install] не указан WantedBy=multi-user.target.
  • Служба перезапускается по кругу. Если бот падает при каждом старте, Restart=always будет поднимать его снова и снова с паузой RestartSec=5. Это не лечит причину: найдите исключение в журнале. Пока бот падает, службу можно остановить: sudo systemctl stop discord-bot.

Итог

Бот на VPS работает круглосуточно, если у него есть три вещи: окружение Python с discord.py, токен в файле с правами 600 и служба systemd с Restart=always. Включённый Message Content Intent и журнал journalctl решают большинство проблем. Если рядом понадобятся сайт или база данных для статистики бота, посмотрите статьи Сайт на VPS с nginx и Let’s Encrypt и Docker Compose на VPS для нескольких сервисов.

Чек-лист после запуска

Пройдите этот список один раз, прежде чем считать бота готовым. Он занимает десять минут и экономит вечер в поддержке.

  1. Служба enabled и active (running) в systemctl status.
  2. После sudo reboot бот снова онлайн без ручного входа.
  3. Токен нигде не записан в открытом виде: ни в коде, ни в истории команд, ни в git.
  4. Файл .env имеет права 600 и владельца botuser.
  5. В журнале нет повторяющихся ошибок за последний час: journalctl -u discord-bot --since "1 hour ago".
  6. Бот не имеет прав администратора, если они не нужны для работы.
  7. На VPS открыт только SSH, а бот не слушает входящие порты. Это можно проверить командой sudo ss -tulpn.

Если пункт не выполняется, исправьте его до приглашения бота на большой сервер. Ошибка в правах или токене на тестовом сервере обходится дешевле, чем на сообществе из тысячи участников.

Как обновить код без простоя

Простой на время перезапуска короткий: systemctl restart занимает секунды. Но если бот обрабатывает длинные задачи, сначала дождитесь их завершения. Порядок такой: обновите файлы в каталоге, проверьте синтаксис командой ./venv/bin/python -m py_compile bot.py, затем перезапустите службу и проверьте журнал. Если ошибка, верните предыдущую версию файла из git или из резервной копии каталога.

Вопросы и ответы

Нужен ли мощный VPS для Discord-бота?

Для небольшого бота хватит самого простого тарифа: процесс на discord.py обычно занимает десятки мегабайт памяти. Больше ресурсов нужно, если бот работает с медиа, базой данных или большим числом серверов Discord. Реальное потребление смотрите командой htop или в «Мониторинге» карточки VPS.

Можно ли запускать бота на Node.js?

Да, в Discord есть библиотека discord.js. Схема та же: окружение Node, токен в .env, служба systemd с Restart=always.

Зачем отдельный пользователь botuser?

Бот не должен работать от root. Отдельный пользователь без shell ограничивает ущерб, если в коде найдётся уязвимость.

Что делать, если токен утёк?

Сбросьте его во вкладке Bot в Developer Portal, обновите .env и перезапустите службу. Старый токен после сброса перестаёт работать.

Сервер под игры за пару минут — egnetwork.ru

Читать далее

Вся рубрика →