0034 — Github Actions для Hugo

Настроил сборку этого сайта с помощью GitHub Actions, поэтому в этой заметке зафиксирую всё, чему научился:

  • Создание GitHub Actions.

  • Настройка SSH.

  • Установка Hugo из репозитория проекта.

    В репозиториях Ubuntu есть пакет hugo, но очень старой версии, которая не смогла собрать мой сайт.

  • Использование rsync.

Зачем?

Всё, что можно автоматизировать, нужно автоматизировать. Снижение человеческого фактора как правило всегда идёт на пользу.

Ну и делать лишние действия тоже не хочется. Почему бы не заставить машину делать то, что вынужден делать сам?

Теперь при создании PR в ветку main этого репозитория выполняется проверка корректности обновлённого кода. Если со сборкой есть проблемы, PR будет заблокирован.

Если PR успешен, то при слиянии с main будет выполнена автоматическая сборка проекта и его выкладка на хостинг. Удобно? Не то слово!

Подготовка к работе

Считаем, что проект у вас уже есть и размещается на GitHub.

  1. Создайте в корне проекта подкаталог .github/workflows/:

    mkdir -p .github/workflows/
    
  2. Создайте ключ 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.

  3. Перейдите на страницу репозитория на GitHub.

  4. Выберите вкладку Settings.

  5. Разверните раздел Secrets and variables.

  6. Выберите раздел Actions.

  7. Используйте кнопку New repository secret для создания секретов.

    В моём случае понадобились следующие секреты:

    • FOLDER — путь к каталогу на сервере;
    • HOST — адрес хоста, на который выкладывается сайт;
    • SSH_KEY — приватная часть ключа SSH, с помощью которого можно подключаться к серверу;
    • USER — имя пользователя, используемого для подключения к серверу по SSH.
  8. Во вкладке Variables создайте переменную HUGO_VERSION со значением 0.120.3.

    Актуальную версию уточняйте на странице релизов Hugo.

  9. Разместите публичную часть ключа 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 состоит из нескольких простых шагов:

  1. Получение DEB-пакета со страницы релизов на GitHub. Адрес ссылки определяется через переменную окружения HUGO_VERSION. Если мне вдруг понадобится обновить версию Hugo, я просто поменяю значение в переменной репозитория.

    Ссылка на загрузку будет иметь примерно такой вид:

    https://github.com/gohugoio/releases/download/v0.120.3/hugo_0.120.3_linux-amd64.deb
    

    Как и версию пакета, целевую архитектуру можно задать через переменную окружения. Однако, мне это видится на текущем этапе излишним усложнением.

  2. Установка загруженного пакета с помощью 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 }}

Особенности

  1. Выполняемая здесь задача срабатывает только при push в ветку main.
  2. При сборке сайта используется сжатие того, что можно сжать — HTML, JS, CSS.
  3. Для выкладки сайта на хостинг используется rsync.

Build project

Сборка проекта с помощью Hugo. Как видно из аргументов вызова команды, используется оптимизация контента.

Set SSH settings

  1. Создаётся каталог ~/.ssh/, если он не существует:

    mkdir -p ~/.ssh/
    
  2. Приватный ключ SSH из переменной SSH_KEY сохраняется в файл ~/.ssh/private.key:

    echo "${{ secrets.SSH_KEY }}" > ~/.ssh/private.key
    

    Для корректной работы команды echo необходимо использовать кавычки вокруг имени переменной.

  3. Чтобы ключ можно было использовать в ssh-agent, доступ к файлу ключа должен быть только у пользователя-владельца. Это значит, что режим доступа к файлу нужно изменить на 600:

    chmod 600 ~/.ssh/private.key
    
  4. При первом подключении по SSH проверяется отпечаток ключа сервера. Если отпечаток не входит в список доверенных, подключение нужно будет подтвердить вручную, а это значит, что автоматизация не работает. Поэтому список отпечатков ключей сервера, на котором находится хостинг, добавляется в файл ~/.ssh/known_hosts:

    ssh-keyscan ${{ secrets.HOST }} >> ~/.ssh/known_hosts
    

Deploy files to server

  1. Чтобы rsync мог работать с ключом SSH, тот должен быть доступен в ssh-agent. А ssh-agent в образе ubuntu:latest по умолчанию не запущен. Поэтому на этом шаге выполняются сразу 2 действия:

    1. Запуск ssh-agent.

      eval $(ssh-agent -s)
      
    2. Добавление сохранённого ранее приватного ключа SSH в ssh-agent:

      ssh-add ~/.ssh/private.key
      
  2. Запуск rsync с необходимыми параметрами. Учётные данные и параметры хостинга берутся из секретов репозитория.