Правишь сайт в отдельной ветке, открываешь PR, мержишь в main — GitHub Actions сам выкладывает новую версию на htmlkin.ru. Адрес сайта остаётся прежним, откат делается одним git revert.
Так работает тренажёр устного счёта math.htmlkin.ru. Его репозиторий открыт — github.com/seliseev/math, весь код ниже взят оттуда.
Устройство такое: GitHub Actions упаковывает репозиторий в ZIP и отправляет его в REST API htmlkin запросом PUT /api/v1/pages/{id}. Сайт заменяется целиком.
Что понадобится
- Тариф «Оптимальный» или «Бизнес» — на них открыт REST API.
- Репозиторий на GitHub, в корне которого лежит
index.html.
Шаг 1. Создай API-ключ
Личный кабинет → «API-ключи» → создай ключ. Ключ показывается один раз — скопируй его сразу.
Шаг 2. Скопируй ID сайта
Workflow обновляет сайт по его ID. ID показан в карточке сайта в личном кабинете, в разделе «Мои сайты», — рядом с ним кнопка копирования.
Сайт уже опубликован — бери его ID. Адрес сохранится. Первый же прогон заменит содержимое сайта файлами из репозитория.
Сайта ещё нет — создай его: «Мои сайты» → «Опубликовать сайт» → перетащи index.html из репозитория. Остальные файлы выложит первый прогон workflow. ID появится в карточке сайта.
Из терминала сайт создаётся запросом POST /api/v1/pages, ID придёт в поле id ответа — пример в документации REST API.
Шаг 3. Положи ключ и ID в настройки репозитория
Ключ храни только в секретах репозитория. С ним можно обновить или удалить любой твой сайт, а файлы репозитория видит каждый, у кого есть к нему доступ.
gh secret set HTMLKIN_API_KEY --repo owner/repo
gh variable set HTMLKIN_PAGE_ID --repo owner/repo --body 'abc123'
gh secret set спросит значение — вставь ключ. Через интерфейс то же самое делается в Settings → Secrets and variables → Actions: ключ — на вкладке Secrets, ID — на вкладке Variables.
ID без ключа ничего не даёт, поэтому он хранится в обычной переменной. Её значение видно в настройках и логах.
Шаг 4. Добавь workflow
Создай файл .github/workflows/deploy.yml:
name: Deploy to htmlkin.ru
on:
push:
branches: [main]
workflow_dispatch:
# htmlkin заменяет сайт целиком, поэтому два прогона подряд
# могут выложить старую версию поверх новой.
concurrency:
group: htmlkin-deploy
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Собрать ZIP сайта
run: |
zip -r site.zip . \
-x '.git/*' '.github/*' '.gitignore' 'README.md' '*.zip'
unzip -l site.zip
- name: Обновить сайт на htmlkin.ru
env:
HTMLKIN_API_KEY: ${{ secrets.HTMLKIN_API_KEY }}
PAGE_ID: ${{ vars.HTMLKIN_PAGE_ID }}
run: |
set -euo pipefail
if [ -z "${HTMLKIN_API_KEY}" ]; then
echo "::error::Не задан секрет HTMLKIN_API_KEY (Settings → Secrets and variables → Actions → Secrets)"
exit 1
fi
if [ -z "${PAGE_ID}" ]; then
echo "::error::Не задана переменная HTMLKIN_PAGE_ID (Settings → Secrets and variables → Actions → Variables)"
exit 1
fi
code=$(curl -sS -o response.json -w '%{http_code}' \
-X PUT "https://htmlkin.ru/api/v1/pages/${PAGE_ID}" \
-H "Authorization: Bearer ${HTMLKIN_API_KEY}" \
-F "site_archive=@site.zip")
echo "HTTP ${code}"
cat response.json
echo
if [ "${code}" -lt 200 ] || [ "${code}" -ge 300 ]; then
echo "::error::htmlkin.ru вернул ${code}: $(jq -r '.message // .error // "неизвестная ошибка"' response.json)"
exit 1
fi
url=$(jq -r '.url' response.json)
files=$(jq -r '.file_count' response.json)
echo "Сайт обновлён: ${url} (файлов: ${files})"
{
echo "### Сайт обновлён"
echo ""
echo "- Адрес: ${url}"
echo "- Файлов: ${files}"
echo "- Коммит: \`${GITHUB_SHA}\`"
} >> "${GITHUB_STEP_SUMMARY}"
Что он делает:
- Запускается на каждый push в
mainи вручную — кнопкой Run workflow на вкладке Actions. - Упаковывает в ZIP всё содержимое репозитория, кроме перечисленного после
-x. Новые страницы и картинки попадут в архив сами. Служебные файлы, которым не место на сайте, допиши в этот список. - Отправляет архив в API, и htmlkin заменяет сайт целиком: файл, удалённый из репозитория, исчезнет и с сайта.
- Отменяет прогон, который ещё идёт, если пришёл новый push. Так старая версия не ляжет поверх новой.
- Падает с текстом ошибки, если API ответил не 2xx. При успехе пишет в Summary прогона адрес, число файлов и коммит.
Шаг 5. Запушь и проверь
git add .github/workflows/deploy.yml
git commit -m "Публикация на htmlkin.ru"
git push origin main
Открой вкладку Actions → прогон Deploy to htmlkin.ru. В примере прогон занимает 8–12 секунд. В Summary будет адрес — открой его и проверь, что правка на месте.
Как работать дальше: ветка → PR → main
Правило одно: что в main, то и на сайте. Пуши в другие ветки workflow не запускают, поэтому незаконченные правки держи в ветках.
git switch -c page-40
# добавляешь 40.html, правишь ссылки
git add .
git commit -m "Счёт до 40"
git push -u origin page-40
gh pr create --fill
PR открывается и кнопкой Compare & pull request на GitHub. В нём видна каждая изменённая строка — проверь сам или отдай на ревью. Нажимаешь Merge pull request, GitHub делает push в main, и workflow публикует сайт.
Если над сайтом работают несколько человек, включи защиту ветки main в настройках репозитория, в разделе Branches. Тогда в main попадает только то, что прошло через PR.
Как откатить неудачную версию
Сайт повторяет main, поэтому откат — обычный revert. На странице вмерженного PR нажми Revert: GitHub создаст PR, который отменяет изменения. Смержи его — после прогона на сайте окажется прежняя версия.
Из терминала:
git revert -m 1 <sha мерж-коммита>
git push origin main
Ключ -m 1 нужен для мерж-коммита. Обычный коммит откатывается командой git revert <sha>.
Если сайт собирается или лежит в подпапке
Пример публикует репозиторий как есть. Если сайт собирается сборщиком вроде Vite или Astro, упаковывай папку с результатом сборки. Замени шаг «Собрать ZIP сайта»:
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci && npm run build
- name: Собрать ZIP сайта
run: cd dist && zip -r ../site.zip .
dist — папка, куда сборщик кладёт готовый сайт. Для подпапки без сборки хватит последней строки с её именем: cd site && zip -r ../site.zip ..
Если прогон упал
Текст ошибки — в логе шага «Обновить сайт на htmlkin.ru».
| HTTP | error |
Что делать |
|---|---|---|
| 401 | invalid_api_key |
Секрет пустой или ключ скопирован с ошибкой. Создай новый ключ и перезапиши секрет |
| 403 | api_not_allowed |
API работает на «Оптимальном» и «Бизнесе» — перейди на один из них |
| 404 | not_found |
Сверь HTMLKIN_PAGE_ID с ID в карточке сайта в «Мои сайты» |
| 400 | missing_entry_file |
Положи index.html в корень архива |
| 400 | too_many_files |
В сайте больше 500 файлов |
| 413 | file_too_large |
Сайт больше лимита тарифа: 75 МБ на «Оптимальном», 200 МБ на «Бизнесе» |
| 429 | rate_limit_exceeded |
Лимит — 60 запросов в минуту на «Оптимальном», 120 на «Бизнесе». Перезапусти прогон через время из заголовка Retry-After |
Все коды и поля запросов — в документации REST API.
Начни с шагов 1–3, затем скопируй workflow из шага 4 и запушь его в main. Следующий merge уже обновит сайт.



