Данный сайт использует распространенный вариант хостинга с использование Github - при каждом изменении с помощью github-actions генерируется статичный сайт со всеми страницами. Из-за этого нет возможности использовать динамический контент напрямую, например, комментарии к статьям. Есть возможность использовать Disqus, но в таком случае появляется зависимость от сторонних сервисов.

Лучшим вариантом я считаю хранить комментарии у себя в репозитории, которые будут появляться на сайте в момент генерации сайта. Сервисов для реализации такого функционала очень много - как бесплатных, так и платных. Я остановлюсь на бесплатном варианте - Staticman от Eduardo Bouças, как наиболее продвинутом и удобном.

Как это работает:

  1. Посетитель блога отправляет комментарий, используя форму отправки под любым постом.
  2. В форме есть javascript, который отправляет результат на специальную API-прокладку, запущенную в виде приложения на Heroku Render. Если у пользователя отключен JS, то будет сформирован простой POST запрос.
  3. В свою очередь приложение производит валидацию комментария и, в случае успешного результата, создает pull request в репозиторий сайта, содержащий файл .yml с комментарием и мета данными.
  4. После принятия мной PR github автоматически генерирует новый сайт и комментарий появляется на сайте.

Сейчас у Staticman есть три версии API и основная сложность его подключения заключается в деплое приложения API-прокладки и настройки его. Мы будем использовать вторую версию, которая не сильно отличается от третьей (в третьей появилась возможность использовать Gitlab).

Создание аккаунта бота

Для создания pull request’ов в нашу репу используется гитхабовский personal access token, поэтому необходимо создать отдельный аккаунт для этого токена, так как этот токен будет использован для приложения Heroku Render, а, следовательно, может быть скомпрометирован и кто-то может получить доступ к вашему основному аккаунту. Кроме этого, использование второго аккаунта позволяет удобно разделить коммиты комментариев от ваших правок в репозитории сайта.

Аккаунт бота ничем не отличается от обычного аккаунта Github, поэтому запускаем вкладку в инкогнито режиме, чтобы не выходить из своего аккаунта и создаем новый аккаунт с именем, которое вам нравится, пусть для статьи это будет github-bot. Можно сразу создать README.md в личной репе (название такое же как у аккаунта) и написать, что это бот:

Аккаунт нашего трудяги

Аккаунт нашего трудяги

После создания аккаунта нам необходимо сделать две вещи:

  1. Создать personal access token, для использования его в приложении.
  2. Выдать доступ к репозиторию сайта основного аккаунта.

Personal access token

Создание токена производится в настройках аккаунта в разделе Developer settings -> Personal access tokens. Там нажимаем Generate new token, вводим пароль для безопасности, придумываем название токена и выдаем разрешение к двум разделам - repo и user:

Нужные нам права доступа

Нужные нам права доступа

После этого нажимаем Generate token и сохраняем где-нибудь отображенный токен - далее Github не даст его подсмотреть в целях безопасности. Далее в статье этот токен я будут обозначать как github-token. Подробнее прочитать, что такое токен и для чего он может быть использован можно в официальной документации Github.

Доступ к репозиторию

Чтобы бот мог коммитить напрямую в наш репозиторий ему необходимо выдать права к репозиторию. Сделаем мы это через добавление бота в коллабораторы репы. Для этого переходим в настройки репы, там Manage access, а далее жмем кнопку Invite collaborator. В окошке вводим имя бота и отправляем приглашение.

Отправка приглашения

Отправка приглашения

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

Приложение API

UPD. 01.12.2022 Heroku с 28.11.2022 отключил бесплатные варианты использования хостинга, поэтому приложение переехало на Render

Старый вариант инструкции с Heroku Теперь нужно подготовить приложение для API-прослойки, которое будет принимать комментарии и создавать PR в наш репозиторий. Делать я это буду на Heroku, так как там имеется возможность использовать бесплатный инстанс, который по расчетам хватит примерно на ~1100 комментариев в месяц для аккаунта без кредитной карты (550 бесплатных часов) или ~4000 комментариев, если добавить кредитную карту в аккаунт (2000 бесплатных часов). Расчет таков - при отправке комментария приложение на Heroku просыпается и засыпает через 30 минут, таким образом в первом случае это 550/0.5 и 2000/0.5 во втором.

Теперь нужно подготовить приложение для API-прослойки, которое будет принимать комментарии и создавать PR в наш репозиторий. Делать я это буду на Render, который позволяет запускать бесплтаные Web-сервисы, а ограничения в 720 часов в месяц хватит примерно ~2880 комментариев. Расчет таков - при отправке комментария приложение на Render просыпается и засыпает через 15 минут, таким образом в это 720/0.25.

Деплой приложения в Heroku Render

UPD. 01.12.2022 Heroku с 28.11.2022 отключил бесплатные варианты использования хостинга, поэтому приложение переехало на Render

Старый вариант инструкции с Heroku

После регистрации в Heroku нам необходимо создать свое приложение, проще всего это сделать из репозитория Staticman, где в README.md есть прямая ссылка не деплой:

Второй вариант - использовать для деплоя прямую ссылку. Если сделали все правильно, то вместо стандартной формы создания приложения мы увидим создание приложения Staticman:

Вводим имя приложения (далее я его буду обозначать как app-name). Дожидаемся окончания деплоя и сборки нашего приложения, открываем его по кнопке Open app и убеждаемся, что ничего не запустилось 😀. Все потому, что приложению не хватает обязательных переменных, поэтому переходим к его настройке.

После регистрации в Render нам необходимо создать свое приложение, проще всего это сделать из репозитория Staticman, для этого заходим дэшборд, выбираем New -> Web service:

Создаем новый сервис

Создаем новый сервис

Далее нам предложат подключить аккаунт Github для возможность выбрать приватный репозиторий, но нам нужен вариант с публичным репозиторием внизу страницы, вводим туда путь к репозиторию Staticman и жмем Continue:

https://github.com/eduardoboucas/staticman

Адрес репозитория для создания сервиса

Адрес репозитория для создания сервиса

Вводим имя приложения (далее я его буду обозначать как app-name). Region выбираем любой (для России я выбрал Franfurkt), остальные значения оставляем как есть и прокручиваем до секции Advanced, нажимаем её. Здесь нас интересуют два раздела - Environment variables и Auto-deploy, к первому мы еще вернемся, а во втором нужно выключить, чтобы Render не обновлял приложение после каждого изменения в репозитории Staticman:

Выключаем авто деплой

Выключаем авто деплой

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

Генерация RSA ключа

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

Ошибка запуска

Ошибка запуска

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

# ssh-keygen -m PEM -t rsa -b 4096 -C "staticmankey" -f ~/.ssh/staticman_key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in staticman_key.
Your public key has been saved in staticman_key.pub.
The key fingerprint is:
SHA256:VSMjbe....................iwPb67BXU8 staticmankey

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

Проверить, что сгенирирован правильный ключ можно, открыв его в любом редакторе, либо же выполнить команду:

# head -2 ~/.ssh/staticman_key
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAyBStHD4kR9GjI9XZDf4V+QfNyuCbJRXde66WTRD3rDiyzaXC

Строка BEGIN RSA PRIVATE KEY означает, что все сделано правильно, любой другой вариант (например, OPENSSH PRIVATE KEY) означает, что что-то пошло не так и этот вариант не заработает.

Настройка приложения

UPD. 01.12.2022 Heroku с 28.11.2022 отключил бесплатные варианты использования хостинга, поэтому приложение переехало на Render

Старый вариант инструкции с Heroku

После генерации ключа осталось настроить наше приложение, сделать это можно двумя путями - через web интерфейс, либо через командную строку.

В первом случае открываем дэшборд Heroku, выбираем наше приложение, далее Settings, там нас интересует раздел Config Vars. Жмем кнопку Reveal Config Vars и вводим две переменные:

  1. RSA_PRIVATE_KEY - наш приватный ключ, вносить его можно прямо из файла, можно удалять перенос строк, можно не удалять - разницы никакой, Staticman поймет его в любом случае.
  2. GITHUB_TOKEN - наш personal access token бота github-token, который мы создали ранее

Если все правильно, то должно получится так:

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

Обратите внимания, что вместо app-name и github-token следует использовать ваши значения. Проверить, что все правильно можно командой:

Теперь открываем наше приложение по ссылке https://app-name.herokuapp.com и мы должны увидеть приветствие Staticman:

Если его нет, то значит в каком-то пункте вы ошиблись и стоит посмотреть логи Heroku. Теперь мы можем попробовать принять приглашение в репозиторий сайта, для этого нужно открыть ссылку в формате:

https://app-name.herokuapp.com/v2/connect/<main-user-account>/<site-repository>

В моем случае это

https://app-name.herokuapp.com/v2/connect/xbreaker/xbreaker.github.io

В ответ мы должны получить OK! - это означает, что бот принял приглашение, а, следовательно, приложение мы настроили правильно. Если сайт отвечает Invitation not found, то либо мы уже приняли приглашение ранее, либо забыли бота пригласить.

Еще один способ проверить правильность настроек - в настройках основного аккаунта в разделе Manage access, аккаунт бота перейдет в статус Collaborator:

После генерации ключа осталось настроить наше приложение, возвращаемся на страницу создания приложения в Render и жмем Add Environment Variable, после чего вводим наши переменные:

  1. RSA_PRIVATE_KEY - наш приватный ключ, вносить его можно прямо из файла, можно удалять перенос строк, можно не удалять - разницы никакой, Staticman поймет его в любом случае.
  2. GITHUB_TOKEN - наш personal access token бота github-token, который мы создали ранее

Если все правильно, то должно получится так:

Переменные приложения

Переменные приложения

Теперь нажимаем Create Web Service и ждем пока в логах запуска не отобразится Staticman API running on port 10000, это значит, что наше приложение запущено:

Окончание сборки и деплоя приложения

Окончание сборки и деплоя приложения

Теперь открываем наше приложение по ссылке https://app-name.onrender.com и мы должны увидеть приветствие Staticman:

Hello from Staticman version 3.0.0!

Если его нет, то значит в каком-то пункте вы ошиблись и стоит посмотреть логи Render. В дешборд открывает Теперь мы можем попробовать принять приглашение в репозиторий сайта, для этого нужно открыть ссылку в формате:

https://app-name.onrender.com/v2/connect/<main-user-account>/<site-repository>

В моем случае это

https://app-name.onrender.com/v2/connect/xbreaker/xbreaker.github.io

В ответ мы должны получить OK! - это означает, что бот принял приглашение, а, следовательно, приложение мы настроили правильно. Если сайт отвечает Invitation not found, то либо мы уже приняли приглашение ранее, либо забыли бота пригласить.

Еще один способ проверить правильность настроек - в настройках основного аккаунта в разделе Manage access, аккаунт бота перейдет в статус Collaborator:

Аккаунт бота с доступом

Аккаунт бота с доступом

Настройка сайта

Пора подготовить наш сайт к использованию комментариев, для этого нам нужно выполнить четыре вещи:

  1. Создать файл настроек для Staticman
  2. Внести в настройки сайта URL нашего API-приложения
  3. Создать шаблон комментариев, который будет использован для генерации комментариев к постам
  4. Настройка и включение reCAPTCHA

Настройка Staticman

Подробно настройка описана в официальной документации, нам нужно создать файл staticman.yml, полный пример заполнения можно найти здесь. Я же приведу сокращенный вариант, который настроен для этого сайта:

Поля thread, parentName, parent используются для вложенных комментариев. Так же обратите внимание на настройки reCAPTCHA, если планируете ее использовать.

Настройки в config.yml

Здесь нам необходимо внести настройки Staticman, которые будут использоваться при отправке комментария - ссылку на API, плюс вариативные настройки сервиса reCAPTCHA. Настройки вносятся в файл config.eml, у меня они расположены в params.staticman:

65
66
67
68
69
70
71
72
params:
	comments: true
  	staticman:
    	api: https://app-name.onrender.com/v2/entry/xbreaker/xbreaker.github.io/main/comments
    	recaptcha:
    		enabled: false
    		sitekey: 'recaptcha-sitekey'
    		secret: 'recaptcha-secret'

Ссылка на API должна быть вида:

https://app-name.onrender.com/v2/entry/<ваш github user>/<ваш github репозиторий>/main/comments

Обратите внимание на бранч вашего репозитория, для старых репозиториев это master, для новых - main.

Шаблон комментариев

Самый объемный пункт, так как нам нужно подготовить шаблон для своей темы, не забыть про JS, а так же про стили оформления. На моём сайте используется тема PaperMod, поэтому шаблон я буду делать для этой темы. Если вы используете другую тему, то адаптировать мой вариант будет не сложно.

Я не стал особо фантазировать, а решил использовать что-нибудь простое, интересный вариант нашелся здесь. Он послужил основой нашего шаблона, который был доработан под нужды статичного сайта - добавлен аватар и удалена голосовалка, так как в условиях статики ее нам не реализовать. Сам шаблон состоит из нескольких файлов, первым будет comments.html - целиком я его приводить не буду, так как он слишком большой, найти его можно 🧾 здесь, я же остановлюсь на паре моментов.

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

 6
 7
 8
 9
10
11
12
13
14
15
  {{ $c := len $comments }}
  {{ if (and (eq (mod $c 10) 1) (ne (mod $c 100) 11)) }}
    <h3>{{ $c }} комментарий</h3>
  {{ else }}
    {{ if (and (ge (mod $c 10) 2) (le (mod $c 10) 4) (or (lt (mod $c 100) 10) (ge (mod $c 100) 20))) }}
      <h3>{{ $c }} комментария</h3> 
    {{ else }}
      <h3>{{ $c }} комментариев</h3> 
    {{ end }}
  {{ end }}

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

65
66
params:
	emailhash: 23463b99b62a72f26ed677cc556c44e8

Файл comments.html следует скопировать в каталог \layouts\partials\.

Второй файл - это файл стилей comments.css, чтобы он попал в общий файл CSS стилей, придется пойти на хитрость - создать каталог assets\css\common\ и скопировать файл в данный каталог. Исходный код файла можно найти 🧾 здесь.

Вместе с файлом стилей нам нужен файл javascript логики, который добавит интерактивности нашим комментариям и форме отправки - comments.js. Я использовал чистый JS (vanilla), чтобы исключить зависимость от какого-либо фреймворка. Интересный вариант отправки формы я взял отсюда и отсюда. Сам файл можно скопировать в каталог assets\js\, подключим мы его в следующем абзаце. Исходный код файла забираем 🧾 здесь.

Чтобы подключить наш JS файл нам следует создать специальный файл темы extend_head.html он позволяет добавить в HEAD сайта свои инструкции, чем мы и воспользуемся - добавим загрузку и сжатие нашего comments.js. Кроме того, я добавил в него опциональную загрузку файла reCAPTCHA, если она включена в настройках. Копируем 🧾 содержимое и копируем его в каталог \layouts\partials\.

Все файлы сразу расположены в этом 💾 gist, я специально не стал давать ссылки на репозиторий этого сайта, а выложил их отдельно. Так как в будущем файлы сайта могут измениться (или некоторые вообще могут быть удалены) и перестанут соответствовать тому, что здесь написано. А gist останется актуальным и нетронутым.

Настройка и включение reCAPTCHA

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

Для начала следует сходить на официальный сайт и зарегистрироваться. Нам нужна reCAPTCHA v2 Checkbox версия, выбираем ее при регистрации:

reCAPTCHA v2 Checkbox

reCAPTCHA v2 Checkbox

Получаем site key и secret key, которые будут использованы далее:

Наши секреты

Наши секреты

Если site key сразу можно внести в настройки сайта и Staticman, то secret key не предназначен, чтобы передавать его в открытом виде. Поэтому его нужно зашифровать, поможет нам в этом наше ранее созданное приложение API.

Вызываем его по урлу вида:

https://app-name.onrender.com/v2/encrypt/secretKey

И результат, который оно возвратит и есть наш secret key для настроек. Сохраните обязательно где-нибудь оригинальный secret key на будущее.

Вносим настройки в config.yml и staticman.yml. Вот и все, готово 👍! Можно приступать к тестированию комментариев.

reCAPTCHA

reCAPTCHA

Итого

В этом посте можно посмотреть результат реализации таких комментариев, создаваемые PR после отправки комментария можно найти в репозитории.

Если у вас есть что добавить или поправить в этой статье - можно вносить правки напрямую.