GitHub Actions як CI/CD для сайту на статичному генераторі та GitHub Pages
Історичний приклад автоматичної генерації сайту Pelican із приватного репозиторію й завантаження готових сторінок у репозиторій GitHub Pages. Автор пояснює кроки workflow та додає сповіщення через Telegram.
Translated by ChatGPT GPT-6.1 Sol
Відомості про першоджерело
GitHub Actions как CI/CD для сайта на статическом генераторе и GitHub Pages
Архівна публікація. Посади, показники й технічні інструкції наведено станом на дату першої публікації.

Трохи переглянувши Habr, здивувався тому, що дуже мало опубліковано статей на тему (beta-)функції GitHub — Actions.
Здавалося б, можна пояснити таку недомовленість тим, що функціональність іще на тестуванні, нехай і “beta”. Але саме корисна особливість бети дає змогу використовувати цей інструмент у приватних репозиторіях. Саме про роботу з цією технологією я розповім у цій статті.
Передісторія
Якщо починати по порядку, то варто, мабуть, згадати той момент, що в процесі пошуку швидкого, зручного, легкого й безкоштовного варіанта зберігання персонального сайту “Про мене” довелося витратити кілька ночей і переглянути безліч статей.
Хтось вибирає хостинг, хтось — хмарний сервер, а тим, кому не хочеться розбиратися в роботі, взаємодії та оплаті всього цього, — припадає до душі вивантаження статичних сайтів у репозиторій, благо зараз це можна зробити і на GitHub, і на GitLab.
Звісно, це особистий вибір кожного.
Мій остаточний вибір був на користь GitHub Pages.
Про Pages
Хто не в курсі, gh-pages — це такий варіант зберігання документації у вигляді сайту, і надається він безкоштовно, а крім документації пропонується зберігати також персональні сайти. Ця функціональність надається GitHub усім користувачам і доступна в налаштуваннях репозиторію.
Для репозиторію проєкту використовується гілка gh-pages, для користувацького сайту — окремий репозиторій із назвою username.github.io з вихідними файлами сайту в гілці master.
Докладніше можна подивитися в документації, але зазначу лише те, що GitHub із дивовижною щедрістю дозволяє кожному прив’язати власний домен до такого сайту, просто додавши файл CNAME із назвою домену й налаштувавши DNS свого домен-провайдера на сервери GitHub.
Упевнений, що статей про те, як розгорнути такий сайт, тут знайдеться безліч, тому далі не про це.
Виникнення проблеми
Проблема полягала в тому, що під час використання статичного генератора є необхідність вигадувати додаткові милиці скрипти й використовувати бібліотеки для спрощення процесу генерації сторінок та їхнього завантаження в репозиторій. Простіше кажучи, якщо зберігати вихідні файли в окремому приватному репозиторії, то щоразу за будь-якої зміни на сайті було необхідно розгортати локальне середовище для подальшої генерації статичних сторінок і публікації в основному репозиторії сайту.
Існує безліч статичних генераторів, і всі вони мають таку саму проблему. Ці дії займають надто багато часу й сил, а зрештою гальмують роботу над сайтом, особливо після кількох міграцій з ОС на ОС або інцидентів із втратою даних на жорстких дисках (так було в моєму випадку).
Буквально нещодавно чи то у спливному сповіщенні на сайті, чи то в розсилці від GitHub було помічено нововбудований CI/CD, який дозволив виконувати ці дії з мінімальними зусиллями.
Про генератори статичних сторінок
Не буду приділяти цьому підпункту особливої уваги, але поділюся парою тез, до яких прийшов за час вибору й використання таких:
-
вибирати генератор варто під свою мову програмування або такий, який був би максимально зрозумілим. До цієї ідеї я прийшов у той момент, коли самому довелося дописувати деяку функціональність для роботи сайту, ставити милиці для його більшої стійкості й автоматизації. Крім того, це гарний привід самому написати додаткову функціональність у вигляді плагінів;
-
на якому саме генераторі зупинятися — це особистий вибір, але варто враховувати, що для початкового занурення в роботу функціональності GitHub Pages необхідно спочатку встановити собі Jekyll. Благо, він дає змогу генерувати сайт із вихідних файлів прямо в репозиторії (це я й повторю зі своїм вибором).
Мій вибір генератора ґрунтується на першому пункті. Pelican, написаний на Python, із легкістю замінив чужий для мене Jekyll (користувався майже рік). У результаті навіть створення й редагування статей, робота над сайтом дають додатковий досвід у цікавій для мене мові.
__
Постановка завдання
Головним завданням буде написати такий скрипт (насправді файл конфігурації), який дозволив би автоматично генерувати статичні сторінки з приватного репозиторію. У рішенні буде задіяно функціональність віртуального середовища. Скрипт сам додаватиме готові сторінки в публічний репозиторій.
Інструменти для розв’язання
Інструменти, які використовуватимемо для розв’язання завдання:
-
GitHub Actions;
-
Python 3.7;
-
Pelican;
-
Git;
-
GitHub Pages.
Розв’язання проблеми
Отже, трохи ознайомившись із документацією й розібравшись, як пишуться скрипти для Actions, стало зрозуміло, що цей механізм повністю розв’яже проблему, яка виникла. На момент написання статті для використання цієї функціональності необхідно підписатися на бета-тестування!

Опис нової функціональності самим GitHub
Написання Actions-скрипту починається зі створення іменованого файлу в папці .github та її підпапці workflows. Зробити це можна як вручну, так і з редактора у вкладці Actions на сторінці репозиторію.

Приклад порожнього бланка скрипту
Коротко прокоментую бланк
name: CI # название скрипта: будет отображаться во вкладке Actions
on: [push] # действие, по которому запускается данный скрипт
jobs: # роботы, которые будут выполняться
build: # сборка, которая..
runs-on: ubuntu-latest # ..будет запущена на основе этого образа
steps: # шаги которые будут проделаны после запуска образа
- uses: actions/checkout@v1 # переход в самую актуальную ветку
- name: Run a one-line script # имя работы номер 1
run: echo Hello, world! # суть работы номер 1 (bash-команда записана в одну строку)
- name: Run a multi-line script # имя работы номер 2
run: | # суть работы номер 2 (многострочная)
echo Add other actions to build,
echo test, and deploy your project.
Напишемо свій на основі шаблону:
-
Ім’я можна залишити й “CI”. Тут справа смаку.
-
Далі необхідно вибрати ту дію/тригер, яка призведе до запуску скрипту, у нашому випадку це звичайний push нового коміту в репозиторій.
on:
push
- Образ, на основі якого запускатиметься скрипт, також залишимо з прикладу, оскільки Ubuntu цілком влаштовує за необхідною функціональністю. Дивлячись на доступні інструменти, стає зрозуміло, що це може бути будь-який потрібний або просто зручний образ (чи Docker-контейнер на його основі).
build:
runs-on: ubuntu-latest
- У кроках спочатку налаштуємо середовище для підготовки до основної роботи.
3.1) переходимо в потрібну нам гілку (стандартний крок checkout):
- uses: actions/checkout@v1
3.2) встановлюємо Python:
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.7
3.3) встановлюємо залежності нашого генератора:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
3.4) створюємо директорію, у яку генеруватимуться сторінки сайту:
- name: Make output folder
run: mkdir output
- Щоб робота над сайтом була послідовною, а саме не видаляла попередні зміни й можна було без конфліктів додавати зміни в репозиторій сайту, наступним кроком будемо щоразу клонувати репозиторій сайту:
- name: Clone master branch
run: git clone "https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git" --branch master --single-branch ./output
Цей крок звертається до системних змінних:
-
змінну
GITHUB_ACTORGitHub установлює сам, і це ім’я користувача, з вини якого запустився цей скрипт; -
змінна
secrets.ACCESS_TOKEN— це згенерований токен для керування GitHub, його у вигляді змінної середовища ми можемо передати, встановивши у вкладціSecretsналаштувань нашого репозиторію. Прошу зауважити: під час генерації токен буде надано нам лише раз, більше доступу до нього не буде. Так само, як і до значень пунктів Secrets.
- Переходимо до генерації наших сторінок:
- name: Generate static pages
run: pelican content -o output -s publishconf.py
Параметри, передані генератору, відповідають за директорію, куди буде надіслано згенеровані файли (-o output), і конфігураційний файл, який використовуємо для генерації (-s publishconf.py; про підхід до поділу локального конфігу та конфігу для публікації можна почитати в документації Pelican).
Нагадаю, що в нас у папку output уже склоновано репозиторій сайту.
- Налаштуємо git і проіндексуємо наші змінені файли:
- name: Set git config and add changes
run: |
git config --global user.email "${GITHUB_ACTOR}@https://users.noreply.github.com/"
git config --global user.name "${GITHUB_ACTOR}"
git add --all
working-directory: ./output
У цьому пункті використовується вже відома змінна й указується робоча директорія, у якій відбуватиметься запуск команд із цього кроку. Команда переходу в робочу директорію інакше виглядала б як — cd output.
- Згенеруємо повідомлення коміту, закомітимо зміни й відправимо їх у репозиторій. Щоб коміт не був порожнім і, відповідно, не видав помилку в bash (результат на виході не
0), — спочатку перевіримо, чи взагалі потрібно щось комітити й відправляти. Для цього використаємо командуgit diff-index --quiet --cached HEAD --, яка на виході в термінал видасть0, якщо немає змін відносно попередньої версії сайту, і1, якщо такі зміни є. Після чого обробляємо результат цієї команди. Таким чином ми в інформації про виконання скрипту запишемо корисну інформацію про стан сайту на цьому етапі замість автоматичного падіння й надсилання нам звіту про падіння скрипту.
Ці дії також проводимо в нашій директорії з готовими сторінками.
- name: Push and send notification
run: |
COMMIT_MESSAGE="Update pages on $(date +'%Y-%m-%d %H:%M:%S')"
git diff-index --quiet --cached HEAD -- && echo "No changes!" && exit 0 || echo $COMMIT_MESSAGE
# Only if repo have changes
git commit -m "${COMMIT_MESSAGE}"
git push https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git master
working-directory: ./output
Результат
У підсумку такий скрипт дає змогу не думати про створення статичних сторінок. Додаючи зміни безпосередньо в приватний репозиторій — чи то роботою з git з-під будь-якої системи, чи то створенням файлу через вебінтерфейс GitHub, — Actions зроблять усе самі. У разі несподіваного падіння скрипту на пошту прийде сповіщення.
Повний код
Залишу свій робочий варіант, у ньому в останній крок додано надсилання сповіщення про те, що коміт було відправлено в основний репозиторій.
Використовуються описані вище Secrets, куди додано токен бота й ідентифікатор користувача, якому потрібно надіслати повідомлення.
name: Push content to the user's GitHub pages repository
on:
push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Make output folder
run: mkdir output
- name: Clone master branch
run: git clone "https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git" --branch master --single-branch ./output
- name: Generate static pages
run: pelican content -o output -s publishconf.py
- name: Set git config and add changes
run: |
git config --global user.email "${GITHUB_ACTOR}@https://users.noreply.github.com/"
git config --global user.name "${GITHUB_ACTOR}"
git add --all
working-directory: ./output
- name: Push and send notification
run: |
COMMIT_MESSAGE="Update pages on $(date +'%Y-%m-%d %H:%M:%S')"
git diff-index --quiet --cached HEAD -- && echo "No changes!" && exit 0 || echo $COMMIT_MESSAGE
git commit -m "${COMMIT_MESSAGE}"
git push https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git master
curl "https://api.telegram.org/bot${{ secrets.BOT_TOKEN }}/sendMessage?text=$COMMIT_MESSAGE %0ALook at ${GITHUB_ACTOR}.github.io %0ARepository%3A github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io&chat_id=${{ secrets.ADMIN_ID }}"
working-directory: ./output
Скриншоти

Результат одного із запусків, відображений у вкладці Actions репозиторію з вихідними файлами

Повідомлення від бота про завершення роботи скрипту
Корисні посилання
Загальні відомості про Actions
Синтаксис Actions
Список тригерів
Варіанти віртуальних середовищ
Github Pages
Static Generator list