0034 — Github Actions для Hugo
Настроил сборку этого сайта с помощью GitHub Actions, поэтому в этой заметке зафиксирую всё, чему научился:
Создание GitHub Actions.
Настройка SSH.
Установка Hugo из репозитория проекта.
В репозиториях Ubuntu есть пакет hugo, но очень старой версии, которая не смогла собрать мой сайт.
Использование rsync.
Зачем?
Всё, что можно автоматизировать, нужно автоматизировать. Снижение человеческого фактора как правило всегда идёт на пользу.
Ну и делать лишние действия тоже не хочется. Почему бы не заставить машину делать то, что вынужден делать сам?
Теперь при создании PR в ветку main этого репозитория выполняется проверка корректности обновлённого кода. Если со сборкой есть проблемы, PR будет заблокирован.
Если PR успешен, то при слиянии с main будет выполнена автоматическая сборка проекта и его выкладка на хостинг. Удобно? Не то слово!
Подготовка к работе
Считаем, что проект у вас уже есть и размещается на GitHub.
Создайте в корне проекта подкаталог .github/workflows/:
mkdir -p .github/workflows/
Создайте ключ SSH, который будет использоваться для деплоя на сервер с помощью rsync:
ssh-keygen -t ed25519 -N "" -f ~/.ssh/<hosting> -C "GitHub Actions Deploy Key"
Здесь:
-t ed25519 — генерация ключа с помощью алгоритма ED25519.
В настоящее время этот алгоритм считается достаточно надёжным.
-N "" — создание ключа без парольной защиты.
Ключ будет использоваться в автоматическом режиме, поэтому парольная защита снимается. Наверное, можно как-то передать ключ через переменные окружения и GitHub Secrets, но я пока не разбирался.
-f ~/.ssh/<hosting> — имя файла, в который будет сохранён ключ. На самом деле в каталоге ~/.ssh/ будет создано два файла со следующими именами:
- <hosting> — приватная часть ключа;
- <hosting>.pub — публичная часть ключа.
-C "GitHub Actions Deploy Key" — необязательный комментарий к ключу. Тем не менее, стоит добавить — указанное в этом поле значение можно увидеть в журнале подключения к серверу по SSH.
Перейдите на страницу репозитория на GitHub.
Выберите вкладку Settings.
Разверните раздел Secrets and variables.
Выберите раздел Actions.
Используйте кнопку New repository secret для создания секретов.
В моём случае понадобились следующие секреты:
- FOLDER — путь к каталогу на сервере;
- HOST — адрес хоста, на который выкладывается сайт;
- SSH_KEY — приватная часть ключа SSH, с помощью которого можно подключаться к серверу;
- USER — имя пользователя, используемого для подключения к серверу по SSH.
Во вкладке Variables создайте переменную HUGO_VERSION со значением 0.120.3.
Актуальную версию уточняйте на странице релизов Hugo.
Разместите публичную часть ключа SSH на целевом сервере.
Тут я вам не помощник — читайте документацию используемого хостинга. Как правило, достаточно добавить содержимое файла key.pub в файл ~/.ssh/authorized_keys.
Настройка тестирования
Настройку тестирования делал исходя из того, что PR'ы в main не должны ничего там сломать. Если тестирование не проходит, PR не может быть влит.
В итоге получился такой файл:
--- name: Testing on: pull_request: jobs: Testing: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: submodules: recursive - name: Update APT index run: sudo apt-get update - name: Install DEB packages run: sudo apt-get install ruby python3-pip --yes - name: Install AsciiDoctor run: sudo gem install asciidoctor - name: Install rst2html run: sudo pip3 install rst2html - name: Install Hugo run : | wget https://github.com/gohugoio/hugo/releases/download/v${{ vars.HUGO_VERSION }}/hugo_${{ vars. HUGO_VERSION}}_linux-amd64.deb sudo dpkg -i hugo_${{ vars.HUGO_VERSION}}_linux-amd64.deb - name: Build Project run: hugo --panic-on-warning
Что тут вообще происходит?
Action называется Testing и срабатывает в случае создания PR. Используемая версия Hugo задана в переменной HUGO_VERSION. Процесс тестирования состоит из нескольких этапов, рассмотрим их подробнее.
Checkout code
Этот шаг использует встроенную функциональность GitHub Actions для получения нужной ревизии кода. По умолчанию код берётся из основной ветки проекта, но без суб-модулей Git.
- name: Checkout code uses: actions/checkout@v4 with: submodules: recursive
Можно использовать и команду git clone, но зачем?
Поскольку я использую стороннюю тему Hugo Mainroad, подключенную как суб-модуль, для корректной сборки необходимо для параметра submodules задать значение recursive.
Update APT index
Тут ничего необычного, просто обновление индекса пакетов APT.
sudo apt-get update
Я в курсе про apt, но предпочитаю средства, проверенные временем. К тому же, существующие версии apt до сих пор не имеют стабильного интерфейса, пригодного для использования в сценариях автоматизации.
Install DEB packages
Устанавливаются пакеты:
- openssh-client — для доступа к хостингу по SSH.
- ruby — для последующей установки GEM'а asciidoctor.
- python3-pip — для последующей установки rst2html.
Install AsciiDoctor
Установка GEM'а asciidoctor, необходимого для сборки страниц, написанных в разметке AsciiDoctor. В репозиториях Ubuntu есть одноимённый пакет, но он очень старый.
Install rst2html
Python-пакет rst2html используется для сборки страниц, написанных в формате ReStructured Text (ReST). К сожалению, он не даёт тех возможностей, что даёт Sphinx, однако, большая часть нужной мне разметки поддерживается.
Install Hugo
Установка Hugo состоит из нескольких простых шагов:
Получение DEB-пакета со страницы релизов на GitHub. Адрес ссылки определяется через переменную окружения HUGO_VERSION. Если мне вдруг понадобится обновить версию Hugo, я просто поменяю значение в переменной репозитория.
Ссылка на загрузку будет иметь примерно такой вид:
https://github.com/gohugoio/releases/download/v0.120.3/hugo_0.120.3_linux-amd64.deb
Как и версию пакета, целевую архитектуру можно задать через переменную окружения. Однако, мне это видится на текущем этапе излишним усложнением.
Установка загруженного пакета с помощью dpkg.
Build Project
Сборка проекта с помощью Hugo. Поскольку это тестовый этап, сжатие файлов не используется. Любое предупреждение расценивается как ошибка.
Настройка сборки
Для сборки и выкладки сайта на сервер используется файл deploy.yml. В нём описана задача Build and deploy.
Привожу содержимое файла целиком, но в описании шагов расскажу только про отличия в сравнении с testing.yml.
--- name: Build and deploy on: push: branches: - 'main' jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: submodules: recursive - name: Update APT index run: sudo apt-get update - name: Install DEB packages run: sudo apt-get install openssh-client ruby python3-pip --yes - name: Install AsciiDoctor run: sudo gem install asciidoctor - name: Install rst2html run: sudo pip3 install rst2html - name: Install Hugo run : | wget https://github.com/gohugoio/hugo/releases/download/v${{ vars.HUGO_VERSION }}/hugo_${{ vars.HUGO_VERSION }}_linux-amd64.deb sudo dpkg -i hugo_${{ vars.HUGO_VERSION }}_linux-amd64.deb - name: Build project run: hugo --minify --panic-on-warning - name: Set SSH settings run: | mkdir -p ~/.ssh/ echo "${{ secrets.SSH_KEY }}" > ~/.ssh/private.key chmod 600 ~/.ssh/private.key ssh-keyscan ${{ secrets.HOST }} >> ~/.ssh/known_hosts - name: Deploy files to server run: | eval $(ssh-agent -s) ssh-add ~/.ssh/private.key rsync --delete --quiet -e ssh --recursive --dirs --verbose ./public/* ${{ secrets.USER }}@${{ secrets.HOST }}:${{ secrets.FOLDER }} rsync --delete --quiet -e ssh --recursive --dirs --verbose .htaccess ${{ secrets.USER }}@${{ secrets.HOST }}:${{ secrets.FOLDER }}
Особенности
- Выполняемая здесь задача срабатывает только при push в ветку main.
- При сборке сайта используется сжатие того, что можно сжать — HTML, JS, CSS.
- Для выкладки сайта на хостинг используется rsync.
Build project
Сборка проекта с помощью Hugo. Как видно из аргументов вызова команды, используется оптимизация контента.
Set SSH settings
Создаётся каталог ~/.ssh/, если он не существует:
mkdir -p ~/.ssh/
Приватный ключ SSH из переменной SSH_KEY сохраняется в файл ~/.ssh/private.key:
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/private.key
Для корректной работы команды echo необходимо использовать кавычки вокруг имени переменной.
Чтобы ключ можно было использовать в ssh-agent, доступ к файлу ключа должен быть только у пользователя-владельца. Это значит, что режим доступа к файлу нужно изменить на 600:
chmod 600 ~/.ssh/private.key
При первом подключении по SSH проверяется отпечаток ключа сервера. Если отпечаток не входит в список доверенных, подключение нужно будет подтвердить вручную, а это значит, что автоматизация не работает. Поэтому список отпечатков ключей сервера, на котором находится хостинг, добавляется в файл ~/.ssh/known_hosts:
ssh-keyscan ${{ secrets.HOST }} >> ~/.ssh/known_hosts
Deploy files to server
Чтобы rsync мог работать с ключом SSH, тот должен быть доступен в ssh-agent. А ssh-agent в образе ubuntu:latest по умолчанию не запущен. Поэтому на этом шаге выполняются сразу 2 действия:
Запуск ssh-agent.
eval $(ssh-agent -s)
Добавление сохранённого ранее приватного ключа SSH в ssh-agent:
ssh-add ~/.ssh/private.key
Запуск rsync с необходимыми параметрами. Учётные данные и параметры хостинга берутся из секретов репозитория.