Skip to content

Instantly share code, notes, and snippets.

@backmeupplz
Created November 15, 2018 02:00
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save backmeupplz/e18cce7f965950e1c084c6c538898646 to your computer and use it in GitHub Desktop.
Save backmeupplz/e18cce7f965950e1c084c6c538898646 to your computer and use it in GitHub Desktop.
Xakep

Деплой

Осталось лишь задеплоить нашего бота на настоящий сервер в облаке — чтобы он был доступен 27/7. К счастью, благодаря специфике работы серверов для Телеграм-ботов, нам практически не придется думать о защите от тех же DDoS аттак, так как сам сервер будет максимально спрятан от конечного пользователя и общаться только с сервером Телеграма.

Я нахожу самым простым для прототипирования проектов использовать Digital Ocean. По сравнению с сервисами от Amazon, Google и Microsoft, я нахожу его гораздо более привлекательным и дружелюбным новичкам. Хероку не рассматриваю на текущий момент из-за их конской наценки на сервера с высокой нагрузкой и недостаточной гибкости. Например, большинство моих ботов-экспериментов крутится на одном и том же сервере в качестве разных сервисов.

Перед тем, как окунуться в пучину администрирования веб-серверов, вам нужно разобраться в том, что такое SSH. Вкратце: это безопасный туннель к командной строке на другом компьютере. То есть вы буквально подключаетесь из своего терминала в терминал, скажем, машины в облаке — и выполняете на нем команды. У SSH есть публичный и приватный ключи — нам нужен будет только ваш публичный ключ. Об их разнице вы можете спросить тот же Гугл. Для генерации и отображения своего SSH ключа выполните следующие команды:

$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa

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

Зайдите на Digital Ocean, заведите аккаунт, добавьте свой публичный SSH ключ, создайте свой первый "дроплет" (так называется сервер на языке разработчиков цифрового океана) на последней доступной версии Ubuntu, указав свой публичный SSH ключ. Вам выдадут IP-адрес сервера, туда и заходите при помощи:

$ ssh 162.243.162.24 # Замените на IP своего сервера

Вуаля! Вы "внутри" своего сервера. Есть простое, но очень важное правило в администрировании серверов: никогда и ничего не делать из-под пользователя с именем root, которое, как вы уже догадались, мы сегодня нарушим. Это руководство не о командной строке и не об администрировании, а просто о том, как быстро и "грязно" запустить свой сервер. Мы будем подключаться к серверу прямо пользователем root для упрощения руководства. Но на реальных боевых серверах этого делать нельзя.

Для нашего легковесного CI сервиса мы будем использовать мой небольшой самописный скриптик ci-ninja. Это простой сервер на Node.js, который висит на 61439 порте и выполняет указанный bash скрипт каждый раз, когда получает вызов. Идеально подходит для вебхуков ГитХаба.

[ INFO

Вебхуки — это название ряда механизмов, когда после определенного действия внешний сервер отправляет запрос на ваш сервер.

]

Создайте репозиторий на ГитХабе и запушьте свой код туда по инструкции, которую вам предоставит ГитХаб. Зайдите в настрйоки репозитория, перейдите в настройки вебхуков и добавьте вебхук на событие push на адрес http://162.243.162.24:61439/ (естественно, замените IP на адрес своего сервера). Теперь при каждом пуше в ваш репозиторий ГитХаб будет отправлять запрос на ваш сервер. Время настроить ответы на этот запрос!

На своем сервере (на который вы зашли через SSH) перейдите в домашнюю дирректорию при помощи $ cd /home. Теперь вам нужно склонировать репозиторий своего проекта (у меня https://github.com/backmeupplz/shieldy) и ci-ninja (отсюда: https://github.com/backmeupplz/ci-ninja). Таким образом, прописав $ ls, вы увидите две папки: одну для своего бота, вторую для ci-ninja.

Вероятно, вам потребуется установить на сервере то же самое, что вы установили на своей локальной машине — NVM, Node.js, yarn, mongodb и остальное. Думаю, вы должны с этим справиться.

Следующим шагом мы запустим два сервиса на убунте. Сервисы — это способ запуска программ, который можно настраивать самым разным образом. Мы сделаем так, чтобы сервер всегда держал эти два сервиса запущенными, перезапускал при их падении и запускал их после перезагрузки. Перейдем в папку, где все сервисы храним: $ cd /etc/systemd/system — и создадим два файла: ci-ninja.service и shieldy.service следующего содержания:

[Unit]
Description=Service to start ci-ninja
After=network.target

[Service]
WorkingDirectory=/home/ci-ninja
ExecStart=/usr/bin/node /home/ci-ninja/index.js
Restart=always

[Install]
WantedBy=multi-user.target
[Unit]
Description=Service to start Telegram bot
After=mongodb.service

[Service]
WorkingDirectory=/home/shieldy
ExecStart=/usr/bin/yarn start
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Во-первых, заметьте синтаксис, который мы используем для сервисов. Во-вторых, мы прописываем абсолютные пути к приложениям, которые хотим запускать. В-третьих, не забудьте заменить некоторые места на собственный код. В-четвертых, мы запускаем нашего бота только после запуска сервиса MongoDB — хоть пока что мы и не используем монгу, позже мы начнем, поэтому проще подготовиться пораньше.

В обеих папках стоит установить все зависимости при помощи $ yarn install. После, можно запустить оба сервиса при помощи $ systemctl enable shieldy и $ systemctl enable ci-ninja. systemctl — это утилита, которая позволяет вам управлять сервисами, у нее есть такие удобные команды, как start, end, restart и status. Но для просмотра статуса сервиса (вдруг он упал или вам просто интересно глянуть логи) я совеутю использовать journalctl. А в частности, journalctl -u shieldy с флагами -e для просмотра прошлых логов или -f для просмотра логов в реальном времени.

На данный момент у вас уже должен работать как сервис вашего Телеграм-бота, так и сервис ci-ninja; но пуш в репозиторий все еще ничего не делает, так как скрипт-то мы еще не написали. Создайте папку scripts в дирректории /home/ci-ninja и добавьте туда файл shieldy-master.sh, предав ему следующий вид:

systemctl stop shieldy
cd /home/shieldy
rm -rf node_modules
git reset --hard
git pull
yarn install
systemctl start shieldy

Ну и не забудьте выдать правильные права пользователям по отношению к этому файлу: $ chmod +x shieldy-master.sh. Таким образом, его можно будет запускать.

Иии... готово! Теперь по каждому пушу в ваш репозиторий, этот скриптик будет исполняться. В принципе, вы можете модифицировать его как душе угодно. Например, добавить curl 'https://api.telegram.org/bot{токен_бота}/sendMessage?chat_id={id_чата}&text=%E2%9C%85%shieldy%20has%20been%20successfully%20deployed!' в конце,чтобы вам ваш же бот и присылал сообщения после каждого деплоя.

Поздравляю! С этого момента вам тольео и остается, что просто спокойно писать код, пушить его в репозиторий — и смотреть, как код магически деплоится прямо в руки ваших пользователей. Ну не великолепно ли это? Ради подобных моментов мы и работаем, программисты.

Зачем нам была MongoDB, еще раз?

Ух, пришло время подключать MongoDB! Для чего, спросите вы?

Во-первых, для более постоянной работы бота. Например, если ваш бот на данный момент внезапно упадет посреди решения, кикать пользователя или нет, то при перезапуске он просто забудет про него — и не забанит. А это достаточно плохо, не так ли?

Во-вторых, нам нужно как-то хранить настройки для каждого чата. Язык, сколько времени давать новым пользователям, какой тип капчи использовать. Все это нужно где-то запоминать, да так, чтобы между перезапусками ничего не пошло не так. Время перейти к хардкорному кодингу!

Для описания моделей данных нашей базы мы будем использовать Typegoose — от слов mongoose (популярная обертка вокруг Монго) и TypeScript. Это удобная библиотека, которая позволяет очень просто описать типы данных, которые мы будем использовать. Установим его при помощи $ yarn add typegoose.

Создайте новую папку models с двумя файлами: index.ts и chat.ts. Первый будет входной точкой к моделям и запускать саму прослойку к БД, второй — самой моделью. Опишите файлы следующим образом:

// index.ts
// Dependencies
import * as mongoose from 'mongoose'

// Connect to mongoose
mongoose.connect(
  process.env.MONGO,
  { useNewUrlParser: true }
)

// Export models
export * from './Chat'
// Chat.ts
// Dependencies
import { prop, Typegoose } from 'typegoose'

enum Language {
  ENGLISH = 'en',
  RUSSIAN = 'ru',
}

enum CaptchaType {
  SIMPLE = 'simple',
  NUMBERS = 'numbers',
  BUTTON = 'button',
}

// Winner class definition
export class Chat extends Typegoose {
  @prop({ required: true, index: true, unique: true })
  id: number
  @prop({ required: true, enum: Language, default: Language.ENGLISH })
  language: Language
  @prop({ required: true, enum: CaptchaType, default: CaptchaType.SIMPLE })
  captchaType: CaptchaType
  @prop({ required: true, default: 60 })
  timeGiven: number
}

// Get Chat model
const ChatModel = new Chat().getModelForClass(Chat, {
  schemaOptions: { timestamps: true },
})

Разберем файлы по полочкам. index.ts — это файл, который будет импортироваться при import * from './models, и он сразу будет включать все модели данных с их функциями; помимо этого, он запускает и саму прослойку к базе данных.

Второй файл описывает модель данных "Чат". Каждый чат будет иметь уникальный идентификатор, который нам любезно предоставит Телеграм; язык чата; тип капчи и время, которое будет даваться новым участникам чата на решение. Языка будет два для старта: русский и английский. Типов капчи будет три на выбор: простая (напишите что угодно), с уравнением (решите простой математический пример) и кнопкой (нажмите кнопку).

Модели — это, конечно, хорошо, но они бесполезны без добавления и изменения обхектов в базе данных. Поэтому мы добавим следующую функцию в User.ts:

// Get or create chat
export async function findChat(id: number) {
  let chat = await ChatModel.findOne({ id })
  if (!chat) {
    chat = await new ChatModel({ id }).save()
  }
  return chat
}

В случае, если чат, который мы ищем, еще не существует, мы создаем чат и возвращаем его. После того, как мы что-либо сделаем с чатом, мы можем его просто сохранить при помощи функции save().

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

// Add chat to context
bot.use(async (ctx, next) => {
  const chat = await findChat(ctx.chat.id)
  ctx.chat = chat
  next()
})

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment