Development
August 30, 2022

Свой приватный Интернет-клуб (на платформе Vas3k.Club) #2

ЧАСТЬ 2: Запускаем клуб локально

Первая часть доступна тут: https://teletype.in/@toptuk/pmiclub1

Покодить локально (Запускаем Клуб локально)

Моя инструкция здесь: https://github.com/TopTuK/pmi.moscow.club/blob/master/docs/setup.md

Установка pipenv (Python)

Нужно локально установить pipenv. Для этого в директории, куда склонирован форк нужно выполнить команды:

pip3 install --user pipenv
pipenv install --dev

После успешной установки pipenv нужно проверить, что он работает без ошибок. Для этого выполнить:

pipenv shell
# На этом шаге должна запуститься консоль Пайтона.
# Чтобы выйти из нее нужно выполнить команду:
exit()

Редактируем docker-compose.yml

Вот пример работающего файла конфигурации

version: "3.7"

services:
  club_app: &app
    build:
      dockerfile: Dockerfile
      context: .
    command: make docker-run-dev
    container_name: club_app
    environment:
      - MODE=dev
      - DEBUG=true
      - PYTHONUNBUFFERED=1
      - POSTGRES_DB=pmi_club
      - POSTGRES_USER=pmiclub
      - POSTGRES_PASSWORD=pmiclub
      - POSTGRES_HOST=postgres
      - REDIS_DB=0
      - REDIS_HOST=redis
    restart: always
    volumes:
      - .:/app:delegated # enable hot code reload in debug mode
    depends_on:
      - postgres
      - redis
      - queue
      - webpack
    ports:
      - "8000:8000"

  queue:
    build:
      dockerfile: Dockerfile
      context: .
    command: make docker-run-queue
    environment:
      - MODE=dev
      - DEBUG=true
      - PYTHONUNBUFFERED=1
      - POSTGRES_DB=pmi_club
      - POSTGRES_USER=pmiclub
      - POSTGRES_PASSWORD=pmiclub
      - POSTGRES_HOST=postgres
      - REDIS_DB=0
      - REDIS_HOST=redis
    restart: always
    volumes:
      - .:/app:delegated
    depends_on:
      - postgres
      - redis
  
  # bot:
    # build:
      # dockerfile: Dockerfile
      # context: .
    # command: make docker-run-bot
    # environment:
      # - MODE=dev
      # - DEBUG=true
      # - PYTHONUNBUFFERED=1
      # - POSTGRES_DB=pmi_club
      # - POSTGRES_USER=pmiclub
      # - POSTGRES_PASSWORD=pmiclub
      # - POSTGRES_HOST=postgres
      # - REDIS_DB=0
      # - REDIS_HOST=redis
    # restart: always
    # volumes:
      # - .:/app:delegated
    # depends_on:
      # - club_app
      # - postgres
      # - redis

  postgres:
    image: postgres:11
    container_name: club_postgres
    environment:
      - POSTGRES_USER=pmiclub
      - POSTGRES_PASSWORD=pmiclub
      - POSTGRES_DB=pmi_club
    ports:
      - "5432:5432"

  redis:
    image: redis:alpine
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    ports:
      - "6379:6379"

  webpack:
    image: node:14-slim
    command: npm run watch
    restart: always
    volumes:
      - .:/app:delegated
    working_dir: /app/frontend

networks:
  default:
      name: pmi_network

Тут сделаны несколько изменений по сравнению с оригинальным docker-compose.yml файлом Вастрика:

  • Изменены значения параметров POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
  • Добавлена секция "networks". Все запускаемые сервисы будут подключены к сети с названием "pmi_network". Это понадобится в будущем для настройки обратного прокси.
  • Закомментировала секция для сервиса Bot.

Создаем файл конфигурации ".env"

Первое, что нужно сделать, чтобы иметь возможность что-то локально покодировать необходимо подготовить конфигурационный файл окружения .env. В нем будут содержать ключевые параметры, необходимые для работы Клуба.

Эти параметры не должны попасть в публичный репозитарий, поэтому, первое, что необходимо сделать - это обновить в корне проекта Клуба файл “.gitignore”. Требуется убедиться в наличии следующих строк (если их нет, то добавить):

club/.env
club/.env.example
.env

В директории "club" вы обнаружите файл ".env.example". Его нужно скопировать в файл с названием ".env" также в директории "club" (Прим.: Вастрик при деплое кладет его в корень, что по моему мнению несколько усложняет отладку).

cd club
cp .env.example .env

Изучаем и изменяем файл settings.py

Открываем и изучаем файл settings.py в директории club.

После секции импорта необходимых модулей можно заметить вызов функции:

load_dotenv()

Эта функция - не что иное, как загрузка параметров окружения из конфигурационного файла .env. Значения параметров получаются путем вызова функций: “os.getenv()”.

В файле settings.py есть некоторые настройки, которые не задаются в .env файле. Необходимо их указать вручную:

  • ALLOWED_HOSTS - вписать в список возможные домены клуба. У меня так:
    ALLOWED_HOSTS = ["*", "127.0.0.1", "localhost", "0.0.0.0", "pmi.moscow"]
  • ADMINS - в параметр ADMINS нужно указать email адреса Администраторов. Судя по коду, это влияет только на отправку тестовых ежедневных и еженедельных дайджестов. Список может быть пустой.
  • Словарь DATABASES - нужно задать “свои” параметры по умолчанию (кроме PASSWORD. Его в паблике светить не стоит, чтобы GitHub не ругался). Понадобится в дальнейшем для удобства локальной разработки. Можно и не делать, но хуже не будет точно.
    У меня так:
DATABASES = {
  "default": {
    "ENGINE": "django.db.backends.postgresql_psycopg2",
    "NAME": os.getenv("POSTGRES_DB") or "pmi_club",
    "USER": os.getenv("POSTGRES_USER") or "pmiclub",
    "PASSWORD": os.getenv("POSTGRES_PASSWORD") or "",
    "HOST": os.getenv("POSTGRES_HOST") or "localhost",
    "PORT": os.getenv("POSTGRES_PORT") or 5432,
  }
}
  • Задать параметры в секции “# Email”. Требуется указать:
    • Значение по умолчанию для EMAIL_HOST.
    • Значение для DEFAULT_FROM_EMAIL.
  • Задать параметры в секции “# App”:
    • Указать APP_NAME.
    • Задать APP_DESCRIPTION.
  • Задать значение для параметра “MEDIA_UPLOAD_URL”. Это ссылка на ваше сервис Pepic, формируется как <% Домен Pepic %> + “upload/multipart/“.
    Пример: MEDIA_UPLOAD_URL = "https://media.pmi.moscow/upload/multipart/"
  • Задать параметры для сервиса OGIMGD. Нужно изменить домен для сервиса OgImgd:
    • Ссылку OG_IMAGE_GENERATOR_URL.
    • Ссылку OG_IMAGE_DEFAULT.
    • Ссылку OG_MACHINE_AUTHOR_LOGO.
    • В словаре OG_IMAGE_GENERATOR_DEFAULTS задать ссылку для параметра logo.
  • Задать некоторые параметры для Telegram бота и чатов:
    • TELEGRAM_BOT_WEBHOOK_URL - заменить доменную часть в ссылке на домен своего Клуба
    • TELEGRAM_BOT_URL - это ссылка на вашего TG бота. До сих пор мы пока не делали чатов и TG бота. Пока оставим “как есть”, но в будущем поменяем. Вообще, я не нашел мест, где бы использовался этот параметр по умолчанию.
  • Задаем аватар по умолчанию:
    • Нужно задать свою ссылку на картинку в параметре DEFAULT_AVATAR.
    • Для удобства, ничего не мешает вам загрузить свою картинку в ваш сервис Pepic, а после использовать полученную ссылку. Потренироваться сделать такое можно следующим способом:
      • Зайти по URL адресу своего сервиса Pepic.
      • Ввести код для аутентификации в сервисе.
      • Загрузить релевантную картинку для аватара по умолчанию.
      • Скопировать полученную ссылку на файл в буфер обмена.
    • Пример: DEFAULT_AVATAR = "https://media.pmi.moscow/30095075d17a92786cfea143a73d68f5f1b3e71173e3f4ecf16f90d25834e45e.png"

Что еще потребуется сделать в будущем:

  • Задать ссылку на TELEGRAM_BOT_URL.
  • Задать ключи (приватный и публичный) для JWT токена.
  • После того, как поднимем свой Клуб, то указать ссылки для следующих параметров: POSTING_GUIDE_URL, CHATS_GUIDE_URL, PEOPLE_GUIDE_URL, PARLIAMENT_GUIDE_URL.

Создаем JWT-токены

  • Гайд как создавать ключи для JWT токена тут: https://gist.github.com/ygotthilf/baa58da5c3dd1f69fae9
  • Пошаговый алгоритм для создания ключей:
    • Создать локальную новую директорию. Название - произвольное, например: "jwt"
    • Перейти в созданную директорию и выполнить следующие команды в указанной последовательности:
# Создаем приватный ключ.
# После выполнения будет запрошен пароль. Его нужно оставить пустым.
ssh-keygen -t rsa -b 4096 -m PEM -f club_private.key
# Создаем открытый ключ
openssl rsa -in club_private.key -pubout -outform PEM -out club_public.key.pub
    • После выполнения команд в новой директории будут созданы файлы “club_public.key.pub” и “club_private.key”.
  • После создания файлов необходимо указать параметр JWT_PUBLIC_KEY в файле settings.py. Требуется скопировать содержимое файла “club_public.key.pub” (копировать “как есть”) и вставить в settings.py. Должно получится что-то типа:
JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY---—  MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA03rHsNGQ3HUfHIqSYXCh
…
-----END PUBLIC KEY-----"""
  • В файле club_private.key содержится приватный ключ, который мы будем использовать в конфигурационном файле .env

Редактируем конфигурационный файл .env

Сейчас в этом файле нам нужно задать ключевые параметры, без которых невозможна локальная разработка клуба. По мере дальнейшей кастомизации платформы Vas3k’а мы будем еще добавлять новые параметры. Приступим!

Задаем следующие параметры:

  • SECRET_KEY - это какой-то настолько секретный ключ, что поиск по коду не дает результатов, где же он используется в реальности. Однако, пускай будет. Задаем в кавычках “свой” код.
  • Указываем параметры POSTGRES_DB, POSTGRES_USER и POSTGRES_PASSWORD - такие же, как в docker-compose.yml файле. Нет ничего страшного, что в продакшен среде у вас будут простые пароли от сервиса Postgress, ведь все равно он будет доступен только “локально на сервере”.
  • Задаем параметр MEDIA_UPLOAD_URL - это <% Домен сервиса Pepic %> + “/upload/“. Пример: MEDIA_UPLOAD_URL="https://media.pmi.moscow/upload/“. (Почему тут путь отличается от значения по умолчанию в settings.py - загадка. Но, “работает - не трожь”).
  • Задаем параметр MEDIA_UPLOAD_CODE - из названия понятно, что это секретный код для сервиса Pepic
  • Задаем параметры для отправки писем (у вас должна быть отдельная учетная запись для Клуба. От имени этой учетной записи Клуб будет отправлять различные письма, в т.ч. - приветственные):
    • EMAIL_HOST - хост почтового сервиса. Задается без протокола, т.е. без “https://“. В моем случае: EMAIL_HOST="mail.pmi.moscow"
    • EMAIL_HOST_USER - почтовый адрес пользователя от имени которого Клуб будет отправлять письма.
    • EMAIL_HOST_PASSWORD - пароль для аутентификации и авторизации пользователя выше в почтовом сервисе.
  • Задаем JWT_PRIVATE_KEY - копируем содержимое файла club_private.key из раздела выше. Копируем “как есть” по подобию JWT_PUBLIC_KEY.

Следующие параметры стоит сразу указать в .env файле и оставить пустыми: STRIPE_API_KEY, STRIPE_PUBLIC_KEY, STRIPE_WEBHOOK_SECRET, PATREON_CLIENT_ID, PATREON_CLIENT_SECRET и WEBHOOK_SECRETS. Должно быть что-то типа:

STRIPE_API_KEY=""
STRIPE_PUBLIC_KEY=""
STRIPE_WEBHOOK_SECRET=""

PATREON_CLIENT_ID=""
PATREON_CLIENT_SECRET=""

Какие параметры нам осталось задать в конфигурационном файле .env:

  • Все параметры TELEGRAM_* для бота и чатов.
  • Параметр SENTRY_DSN для отчета по ошибкам в работе клуба.

Создание учетной записи в сервисе Sentry.io

Сервис Sentry.io - один из самый известный сервисов, который позволяет в режиме реального времени отслеживать ошибки в работе Клуба, мониторить его состояния и получать различные статистические данные. Для маленьких проектов данный сервис является бесплатным, так что цель данного этапа - создать учетную запись Sentry, создать новый проект для Клуба, получить токен для доступа в Sentry и прописать этот токен в конфигурационном файле .env.

Алгоритм:

  • Переходим на сайт Sentry https://sentry.io/welcome/
  • Регистрируем новую учетную запись, если другой никакой нет.
  • Создаем новую организацию и новый проект.
    • Platform: Django
  • Переходим в настройки проекта (в поем случае, организация - PMI Moscow, а проект - pmi-moscow-club)
  • Переходим в раздел SDK Setup / Cline Keys (DSN)
  • Копируем "как есть" DSN, вместе с https://.

После создания проекта и копирования DSN прописываем его в конфигурационный файл .env в параметр SENTRY_DSN. Должно быть что-то вида:

SENTRY_DSN="https://<xxxyyyyzzz>@123456.ingest.sentry.io/98765432"

Создание телегам бота (ака “бездушная машина”), базовых чатов и каналов

На данном шаге мы выполним минимально необходимый набор шагов для интеграции Клуба с Телеграммное, а именно:

  • Создадим бота, получим для него токен и URL, добавим бота в релевантные чаты и каналы.
  • Создадим главный чат Клуба и чат Модераторов. Получим необходимые параметры для этих чатов.
  • Создадим TG-каналы для выборочного постинга новостей и канал для “Прямого эфира”.

Создаем Телеграмм бота

В Телеграмме находим бота для создания ботов @BotFather

Алгоритм:

  1. Создаем нового бота через команду /newbot
  2. Задаем имя для Бота.
  3. Задаем username для Бота. В конце должно быть Bot или _bot, чтобы пользователи знали, с кем имеют дело.
  4. После создания бота копируем токен в формате <userid>:<token> и коробку ссылку на бота.
    1. Опционально, создаем аватарку для Бота.
  5. В конфигурационный файл .env в следующие параметры задаем токен и ссылку на бота:
    1. TELEGRAM_TOKEN = "<userid>:<token>"
    2. TELEGRAM_BOT_URL = "<ссылка на бота>"

Создаем чаты для Модераторов и общий чат для всех

Для Клуба нужно минимально создать 2 чата в Телеграмм:

  • Чат Модераторов, где будут аппрувиться посты, новые участники и все в таком духе.
  • Общий чат членов Клуба.

После создания нужно скопировать ссылки на чаты и получить их идентификаторы.

Если с получением ссылок не возникает много вопросов (ссылки есть в настройках чатов), то с получением идентификаторов придется повозиться:

  • Нужно добавить в чаты бота @RawDataBot.
  • Написать сообщение.
  • Бот в чате отобразит дамп,
  • Среди дампа бота нужно из секции "chat" скопировать значение атрибута "id" вместе со знаком "-". Пример:
"chat": { 
  "id": -1001234567890,
  "title": "PMI Club Russia Admin", 
  "type": "supergroup" 
 }, 

Полученные параметры нужно записать в конфигурационный файл .env.

TELEGRAM_ADMIN_CHAT_ID="<Идентификатор чата Модераторов>"

TELEGRAM_CLUB_CHAT_URL="<Ссылка на общий чат Клуба>"
TELEGRAM_CLUB_CHAT_ID="<Идентификатор общего чата Клуба>"

Создаем каналы для прямого эфира и выборочного постинга новостей

Для запуска Клуба нужно в Телеграмм создать 2 канала:

  • Канал для прямого эфира.
  • Канал для выборочного постинга новостей.

После создания каналов потребуется получить ссылку на созданные каналы, а также идентификаторы этих каналов.

Ссылку на каналы можно получить в настройках каналов. С идентификаторами сложнее, нужно форварднуть сообщения из каналов боту @getidsbot.

В ответ получите следующее сообщение:

Нужно скопировать id каналов в конфигурационный файл .env. Копировать вместе со знаком "-". Заполнить нужно следующие параметры:

TELEGRAM_CLUB_CHANNEL_URL="<Ссылка на канал для выборочного постинга новостей>"
TELEGRAM_CLUB_CHANNEL_ID="<Идентификатор канала для выборочного постинга новостей>"

TELEGRAM_ONLINE_CHANNEL_URL="<Ссылка на канал для прямого эфира>"
TELEGRAM_ONLINE_CHANNEL_ID="<Идентификатор канала для прямого эфира>"

Запускаем клуб локально

Ну штош, коллеги, вы большие молодцы! Настало время научится локально поднимать Клуб с заданными параметрами. Проверим, что Клуб работает, и что мы можем локально что-то покодировать!

В первой части мы запускали клуб через поднятие docker сервисов:

docker-compose up -d

Этот способ работает до сих пор. Однако, сейчас мы запустим Клуб вручную, чтобы была простая возможность покодировать.

Для локального запуска Клуба я рекомендую запустить несколько консолей в VSCode для удобной дальнейшей отладки и остановки.

На данный момент локально у нас уже должен быть установлен python, pipenv, Docker. Значения одноименных или “схожих” параметров всегда должны быть синхронизированы между *.yml файлами и конфигурационным файлом .env.

Запускаем сервис Postgres

Для запуска сервиса Postgress достаточно выполнить в консоли команду:

docker-compose -f docker-compose.yml up -d postgres

Все вот так просто, а сложнее см пример тут: setup.md

Запускаем Front

В консоли переходим в директорию “frontend”. В этой директории выполняем команду “npm run watch”, которая неявно выполнит команду “npm ci”. В результате запуска и ожидания в консоли вы должны увидеть что-то типа:

То ли у меня руки кривые, то ли у Vas3k’а какие-то проблемы с зависимостями, но команда “npm run watch” стабильно возвращала ошибку вида:

Чтобы обойти эту ошибку нужно использовать в команде параметр “--legacy-peer-deps”. Итого команда должна быть следующая:

npm run watch --legacy-peer-deps

Результат, что все хорошо:

Итого:

  • В директории frontend выполняем команду либо “npm run watch”, либо “npm run watch --legacy-peer-deps” (если первая завершилась с ошибкой)
  • ДОЛГО ждем запуска Front’а, пока в консоли не появится сообщение:

Запускаем “все остальное”

В директории club у нас есть сконфигурированный файл .env. В корневой директории проекта есть файл docker-compose.yml, где мы в первой части задали свои параметры. После запуска Postgres и Front’а нужно выполнить следующую последовательность команд:

# Запускаем сервис Redis в Detach режиме. Если нужен консольный вывод, то уберите флаг “-d” (в этом случае запускать в отдельной консоли)
docker-compose -f docker-compose.yml up -d redis

# В отдельной консоли запускаем сервис задач
pipenv run python manage.py qcluster

# В “общей” консоли запускаем создание необходимых таблиц баз данных
pipenv run python manage.py migrate

# Запускаем обновление тегов для профилей пользователей (Без этой команды теги по умолчанию будут пустыми)
pipenv run python manage.py update_tags

# В отдельной консоли запускаем локальный веб-сервер
pipenv run python manage.py runserver 0.0.0.0:8000

Во время запуска смотрите, нет ли каких-то ошибок! Если есть, добро пожаловать в комментарии.

Процесс запуска занимает некоторое время! Дождитесь, пока все запуститься. В консоли локального веб-сервера должно отображаться что-то типа:

Запуск qcluster
Запуск обновления тегов профиля
Запуск веб-сервера Клуба

Для проверки работы Клуба надо в браузере перейти на сайт http://localhost:8000.

  • Для логина в качестве обычного пользователя перейти на страницу: http://localhost:8000/godmode/random_login/
  • Пересборка бэкенда и фронтеда осуществляется автоматически при каждом изменении кода.

Останавливаем Клуб

Чтобы оставить сервисы, которые запущены локально нужно выполнить следующие шаги:

  • Во всех "отдельных" консолях, где пишутся какие-то логи нужно нажать комбинацию клавиш Ctrl+C (Mac: Cmd+C)
  • После остановки сервисов в отдельных консолях где-то выполнить команду: “docker-compose down -v”.
  • После остановки Docker сервисов все лишние консоли можно закрывать.

Последовательность команд для локального запуска Клуба

Итого, последовательность команд для локального запуска Клуба должна получаться следующая:

# В общей консоли:
docker-compose -f docker-compose.yml up -d postgres

# В общей консоли:
docker-compose -f docker-compose.yml up -d redis

# В новой отдельной консоли:
cd frontend
npm run watch --legacy-peer-deps

# В новой отдельной консоли:
pipenv run python manage.py qcluster

# В “общей” консоли
pipenv run python manage.py migrate

# В “общей” консоли
pipenv run python manage.py update_tags

# В отдельной консоли
pipenv run python manage.py runserver 0.0.0.0:8000

Заключение

В этой статье мы разобрались как подготовить конфигурационные файлы и запустить Клуб локального. Все готово для начала кодирования, о котором мы поговорим в следующей статье:

  • Реализуем бесплатное членство в Клубе на время его раскрутки.
  • Обновим список тегов для Профилей.
  • Кастомизуем некоторые основные страницы Клуба.

Всем Meow!