ProHomelab: автоматизация публикации статей - подробная инструкция#
Эта заметка описывает, как настроить автопубликацию статей: вы пишете в Obsidian → статья сама уходит в Forgejo → это запускает сборку сайта на Hugo (тема Blowfish) → готовый сайт сам заливается на хостинг. Ручной hugo -D и FileZilla больше не нужны.
Когда я начинал ее писать, это реально был актуальный для меня workflow. В настоящий момент я таким способом публикации уже не пользуюсь. Хотя описанный в данной статье вариант более чем рабочий, он не оптимальный, а статья служит описанием моего пути по автоматизации публикаций сайта.
Инструкция написана по итогам реального прохождения всего пути от начала до конца - включая все ошибки, с которыми я столкнулся, и их готовые решения. Если пройти её по шагам с нуля, то теоретически на эти грабли вы наступить не должны.
Статья рассчитана на новичка. В примерах используются собственные имена репозиториев, сервера и логин - у вас они будут свои, но структура команд останется той же.
Как это выглядит на схеме#
Прежде чем переходить к шагам, которые будем делать, вот общая картина того, что мы соберём. Дальше в инструкции разберём каждый блок отдельно, но сначала - откуда куда что движется.
Коротко, своими словами, по человечески:
- Вы сохраняете заметку в Obsidian.
- Она сама улетает в репозиторий со статьями на Forgejo (это просто хранилище текста, ничего больше).
- Этот репозиторий «будит» репозиторий сайта - говорит ему «появилось что-то новое, пора пересобрать сайт».
- Раннер (фоновая программа на вашем сервере) видит этот сигнал, забирает свежие статьи, запускает Hugo и собирает из них готовые HTML-страницы.
- Готовый сайт автоматически заливается на хостинг - именно то, что видят посетители.
Всё, что происходит между «сохранил в Obsidian» и «статья на сайте» - это и есть автоматизация, которую мы дальше настроим по шагам.
0. Термины, которые встретятся ниже#
- git - система контроля версий. Отслеживает изменения файлов и умеет их отправлять на сервер.
- репозиторий (repo) - папка, за которой следит git.
- remote - адрес удалённого сервера (в нашем случае Forgejo), куда репозиторий отправляет изменения.
- commit - «снимок» изменений с комментарием, сохранённый в истории git.
- push - отправка коммитов на remote-сервер.
- pull / clone - скачивание репозитория (или его изменений) с сервера к себе.
- submodule - репозиторий внутри репозитория (у вас так подключена тема Blowfish).
- CI/CD, Forgejo Actions - механизм «если в репозитории что-то поменялось - автоматически выполни набор команд» (у GitHub это называется GitHub Actions, у Forgejo - Forgejo Actions).
- раннер (runner) - программа, которая физически выполняет эти автоматические команды.
- workflow - YAML-файл, описывающий, какие шаги и когда выполнять.
- YAML - текстовый формат файлов конфигурации (отступы важны, табы использовать нельзя, только пробелы).
- secrets - зашифрованные переменные (пароли, токены), которые Forgejo Actions подставляет в скрипт, не показывая их в логах.
- токен доступа (Personal Access Token) - длинная строка-пароль для программного доступа к Forgejo вместо обычного пароля аккаунта.
- SSH - протокол для подключения к серверу через терминал.
- systemd-сервис - способ в Linux запускать программу в фоне и держать её постоянно включённой, даже после перезагрузки.
1. Архитектура#
Два репозитория в Forgejo:
- prohomelab-content - только статьи (у меня это папка
01-Projects\Prohomelabв Obsidian). Без файлов темы Hugo, без служебных файлов Obsidian. - prohomelab-site - весь Hugo-проект: тема Blowfish, конфиги, layouts, скрипты сборки.
Почему два, а не один: Obsidian Git может версионировать только одну конкретную подпапку, и мешать в неё Hugo-специфичные файлы незачем. Может я конечно и ошибаюсь, но пока вот так.
Порядок событий при публикации статьи:
- Сохранили заметку в Obsidian.
- Плагин Obsidian Git сам коммитит и пушит изменение в prohomelab-content.
- Push в prohomelab-content запускает свой workflow, который через Forgejo API «будит» второй репозиторий.
- На сервере раннер: скачивает свежий контент → чинит обсидиановский синтаксис картинок → кладёт статьи в Hugo-проект →
hugo -D --minify→ заливаетpublic/на хостинг по FTPS.
Важное отличие от того, как это делает GitHub: в Forgejo (по крайней мере в версии, использованной здесь, v13) нет API-события repository_dispatch с произвольным именем события, как в GitHub Actions. Есть только workflow_dispatch - запуск заранее известного конкретного workflow-файла по имени через POST /repos/{owner}/{repo}/actions/workflows/{имя-файла}.yml/dispatches. Вся схема ниже сразу построена на этом. Я в этом не особо разбирался и сначала получал ошибку 404, все волосы с головы повыдергивал и в монитор кинул. В итоге все решил, в разделе Troubleshooting в конце статьи расписано, как эту ошибку опознать и починить.
Глава 1. Приводим в порядок Hugo-репозиторий на сервере#
Шаг 1.1. Подключитесь к серверу по SSH#
ssh root@IP_ВАШЕГО_СЕРВЕРАДальше все команды этого раздела - внутри этой SSH-сессии.
Шаг 1.2. Перейдите в папку проекта#
cd /home/prohomelab
pwdДолжно вывести /home/prohomelab (или ваш путь, если Hugo-проект лежит в другом месте).
Шаг 1.3. Укажите git, кто вы#
git config user.name "Ваше имя"
git config user.email "you@example.com"Без --global - настройка применится только к этому репозиторию.
Шаг 1.4. Создайте файл .gitignore#
cat > .gitignore <<'EOF'
public/
resources/_gen/
.hugo_build.lock
EOF
cat .gitignorepublic/ (результат сборки Hugo) и resources/_gen (кэш) пересобираются каждый раз заново - хранить их в git не нужно, поэтому мы их исключаем из синхронизации.
Шаг 1.5. Уберите public/ и resources/ из git, если они уже оказались в репозитории.#
git rm -r --cached public resources 2>/dev/null || trueШаг 1.6. Первый коммит#
git add .
git statusПроверьте список - public/ и resources/ не должны быть в списке коммита, остальное (content/, themes/, hugo.toml, .gitmodules и т.д.) - должно быть отмечено зелёным.
git commit -m "Initial commit: Hugo + Blowfish site"Шаг 1.7. Создайте пустой репозиторий в Forgejo#
В браузере откройте свой Forgejo (у меня это https://forgejo.ваш-домен.ru) → “+” → New Repository → имя prohomelab-site → НЕ ставьте галочки инициализации (README/.gitignore/лицензия - репозиторий должен остаться пустым) → Create Repository.
Шаг 1.8. Токен доступа вместо пароля (создаем сразу, чтобы потом не спотыкаться на этапе push)#
Push по HTTPS в Forgejo требует не обычный пароль аккаунта, а Personal Access Token. Создайте один универсальный токен сразу для всех задач этого проекта:
Settings → Applications → Generate New Token → имя, например, prohomelab-ci → права: write:repository (repository access - All) → Generate. Сохраните значение токена - оно показывается только один раз.
Шаг 1.9. Привяжите remote (удаленнный репозиторий) с токеном в URL#
git remote add origin https://ваш-логин:ВАШ_ТОКЕН@forgejo.ваш-домен.ru/ваш-логин/prohomelab-site.git
git branch -M main
git push -u origin mainЗамените ваш-логин на ваш логин Forgejo, forgejo.ваш-домен.ru - на адрес вашего инстанса Forgejo, а ВАШ_ТОКЕН - на токен из шага 1.8. Так push пройдёт сразу без интерактивных запросов пароля.
Шаг 1.10. Проверяем результат#
Откройте https://forgejo.ваш-домен.ru/ваш-логин/prohomelab-site - должны быть видны hugo.toml, content/, themes/ и т.д.
Как итог, того что мы с вами только что сделали: репозиторий Hugo-сайта теперь находится и версионируется в Forgejo.
Глава 2. Настраиваем Obsidian так, чтобы статьи сами уходили в Forgejo#
Шаг 2.1. Создайте второй пустой репозиторий в Forgejo#
Делаем все как в п. 1.7, но с именем prohomelab-content.
Шаг 2.2. Инициализируйте git прямо в папке со статьями (не во всём vault!)#
На Windows, в PowerShell:
cd "C:\Users\ваш-пользователь\Documents\Obsidian\ваш-vault\01-Projects\Prohomelab"
git init
git config user.name "Ваше имя"
git config user.email "you@example.com"Если PowerShell не знает команду git, значит вы его ещё не установили. Установите Git for Windows (https://git-scm.com/download/win), настройки по умолчанию, перезапустите PowerShell.
Шаг 2.3. Исключите служебные файлы Obsidian#
@"
Prohomelab.base
_Prohomelab-Index.md
"@ | Out-File -Encoding utf8 .gitignore
Get-Content .gitignoreДопишите туда любые другие черновики/служебные заметки, которые не должны публиковаться. Пример выше касается моих настроек, у вас может быть другая структура хранилища, поэтому не надо копировать команду бездумно.
Шаг 2.4. Первый коммит и push#
git add .
git status
git commit -m "Initial content"
git remote add origin https://ваш-логин:ВАШ_ТОКЕН@forgejo.ваш-домен.ru/ваш-логин/prohomelab-content.git
git branch -M main
git push -u origin mainИспользуем тот же токен из Шага 1.8 - он выдан с правами на все репозитории аккаунта, если вы ещё не забыли.
Шаг 2.5. Настройте плагин Obsidian Git на эту конкретную папку#
Так как плагин достаточно активно обновляется, то наименование полей к сожалению может поменяться, в таком случае, придется искать по смыслу.
Открыть Obsidian → Settings → Git → раздел Advanced → поле Custom base path:
01-Projects/Prohomelab(со слэшем /, даже на Windows). Это ключевая настройка - без неё плагин работает с корнем всего vault.
После сохранения плагин может попросить перезагрузить Obsidian - соглашаемся, выбора-то у нас все равно нет.
После перезагрузки, там же в настройках Git (названия полей могут отличаться версии от версии - у современных версий это объединённая формулировка):
- Auto commit and sync interval (minutes) - например
10. Раз в N минут: если есть несохранённые изменения - коммит и сразу push. (В более старых версиях плагина это могло быть двумя отдельными полями - Vault backup interval и Auto push interval - тогда ставьте туда те же значения, если по каким-то причинам у вас столь древняя версия плагина.) - Commit message on auto commit and sync - можно оставить по умолчанию.
- Pull on startup - включите, чтобы подтягивать изменения при открытии Obsidian (полезно при работе с нескольких устройств).
Главное не путайте с настройкой Automatically refresh Source Control View on file changes - это просто обновление окна интерфейса, к автокоммиту/автопушу никакого отношения не имеет.
Шаг 2.6. Проверьте автокоммит#
Откройте любую статью, допишите тестовый символ (я обычно пишу, stilicho, ты балбес, но было бы странно и крайне тревожно, если бы и вы написали тоже самое), сохраните. Через палитру команд (Ctrl+P) выполните команду коммита/синка вручную (не дожидаясь таймера), затем проверьте https://forgejo.ваш-домен.ru/ваш-логин/prohomelab-content - должен появиться новый коммит.
Как итог действий, перечисленных в настоящей главе - сохранение заметки в Obsidian долетает до Forgejo без вашего участия.
Глава 3. Ставим раннер на сервер#
Раннер - фоновая программа, которая ждёт сигнала от Forgejo «в репозитории что-то поменялось, выполни вот эти команды, да побыстрее».
Шаг 3.1. Проверьте, что Forgejo Actions включены#
Начиная с Forgejo v1.21 Actions включены по умолчанию - специально что-то включать почти наверняка не придётся. Самое простое сразу проверить в браузере, откройте любой репозиторий (prohomelab-site) - сверху должна быть вкладка Actions. Если она уже есть - смело переходите к Шагу 3.2, редактировать app.ini не нужно.
Если вкладки нет, то это все правится это через [actions] в app.ini - тем же способом, что и в статье про GitOps.
Если Forgejo у вас в Docker Compose (самый частый случай в хоумлабе): найдите на хосте, где лежит docker-compose.yml, папку данных - обычно она указана в volumes: как ./forgejo:/data. Тогда конфиг лежит по пути:
<папка-с-docker-compose.yml>/forgejo/gitea/conf/app.iniОтредактируйте его прямо на хосте (не заходя внутрь контейнера):
nano ./forgejo/gitea/conf/app.iniДопишите в конец файла:
[actions]
ENABLED = trueСохраните (Ctrl+O, Enter, Ctrl+X) и перезапустите контейнер:
docker restart forgejo(имя контейнера смотрите в своём docker-compose.yml, поле container_name).
Проверьте, что настройка применилась:
docker exec forgejo cat /data/gitea/conf/app.ini | grep -A2 "\[actions\]"Должно показать ENABLED = true, и вкладка Actions появится в репозитории.
Шаг 3.2. Скачайте и проверьте бинарник раннера#
У code.forgejo.org (в отличие от GitHub) нет alias /latest/download/... - зато есть API, который одной командой отдаёт номер актуального релиза, без похода в браузер и ручного копирования ссылок:
export ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
export RUNNER_VERSION=$(curl -s https://data.forgejo.org/api/v1/repos/forgejo/runner/releases/latest | jq -r .name | cut -c2-)
export FORGEJO_URL="https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-${ARCH}"
wget -O forgejo-runner "${FORGEJO_URL}"
wget -O forgejo-runner.asc "${FORGEJO_URL}.asc"Бинарники Forgejo подписаны GPG-ключом проекта - стоит проверить подпись перед тем, как запускать что-то от имени системного пользователя с доступом к вашему серверу:
gpg --keyserver hkps://keys.openpgp.org --recv EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
gpg --verify forgejo-runner.asc forgejo-runnerВ ответ должно быть Good signature from "Forgejo <contact@forgejo.org>". Если вместо этого BAD signature - файл повреждён или подменён при скачивании, удаляйте и качайте заново, дальше не продолжайте.
Строка WARNING: This key is not certified with a trusted signature! ниже - это нормально и не имеет отношения к целостности файла. Она означает только то, что вы лично не подписывали этот ключ в своей цепочке доверия GPG (никто этого не делает ради разовой проверки бинарника), а не то, что с подписью что-то не так. Значение имеет строка Good signature from и совпадение fingerprint’а с тем, что вы запрашивали у keyserver (EB11 4F5E ... C592 3710).
Шаг 3.3. Установите бинарник и создайте отдельного пользователя#
chmod +x forgejo-runner
sudo mv forgejo-runner /usr/local/bin/forgejo-runner
forgejo-runner -v
sudo useradd --create-home runnerРаннер для сборки сайта работает в режиме host - выполняет задания напрямую в системе, без Docker. Поэтому, в отличие от раннеров, которым нужен доступ к Docker-сокету, добавлять пользователя runner в группу docker здесь не нужно.
Шаг 3.4. Зарегистрируйте раннер#
В статьях и видео про Forgejo Actions часто встречается команда forgejo-runner register --instance ... --token ... --name ... - интерактивная регистрация с одноразовым токеном из веб-интерфейса. Эта команда официально помечена как deprecated прямо в документации Forgejo - пока работает, но это уже не рекомендуемый путь. Актуальный способ - декларативный, без интерактивного диалога, в два шага на двух разных машинах.
Получить пару UUID + секрет можно двумя равноценными способами - выберите тот, что удобнее.
Вариант А: через CLI на стороне Forgejo. Заходим на хост, где крутится сам Forgejo (если он у вас в Docker Compose - через docker exec в его контейнер):
docker exec -it forgejo forgejo forgejo-cli actions register \
--name site-runner \
--scope ваш-логин \
--secret $(openssl rand -hex 20)В отличие от сценария из статьи про Renovate (там раннер обслуживал только один репозиторий и --scope указывался как владелец/репозиторий), здесь один и тот же раннер с меткой host должен быть виден сразу обоим вашим репозиториям - prohomelab-content (там на него завязан notify.yml) и prohomelab-site (там - deploy.yml). Поэтому --scope указываем не на конкретный репозиторий, а на уровне владельца - просто ваш логин Forgejo, без слэша и имени репо. Тогда раннер увидят все ваши репозитории сразу, а не только один.
Команда выведет UUID - его нужно скопировать вместе с секретом на следующий шаг.
Вариант Б: через веб-интерфейс, без консоли вообще. Для нашего сценария (раннер нужен сразу нескольким репозиториям) даже удобнее - открываем страницу личного аккаунта:
https://forgejo.ваш-домен.ru/user/settings/actions/runners- Заходите под своим обычным аккаунтом.
- Жмёте Create new runner, в поле имени - что-то понятное, например
site-runner. - Жмёте Create - Forgejo сразу показывает страницу с готовыми UUID и секретом (сгенерированным автоматически).
- Копируете оба значения - они уходят в
config.yamlна следующем шаге.
Секрет показывается один раз и больше нигде не хранится в открытом виде - если закрыли страницу, не скопировав его, придётся удалить эту регистрацию раннера и создать новую.
Шаг 3.5. Сгенерируйте и заполните config.yaml#
sudo -u runner forgejo-runner generate-config | sudo -u runner tee /home/runner/config.yaml > /dev/nullrunner - отдельный системный пользователь, которого мы завели на шаге 3.3, и его домашняя директория /home/runner не открывается вашим обычным логином просто так - права на неё есть только у root и у самого runner. Если попробовать nano /home/runner/config.yaml под своим логином, получите Permission denied. Поэтому дальше для чтения и редактирования файлов в /home/runner используем sudo.
Файл, который получился - это не пустой шаблон с парой строк, а полный пример конфига на несколько сотен строк со всеми секциями и подробным комментарием к каждому полю. Разбираться в нём целиком не нужно - правим только два конкретных места, остальное оставляем как сгенерировалось:
sudo nano /home/runner/config.yamlПервое место - секция runner:, поле labels. По умолчанию оно пустое:
runner:
...
labels: []Заменяем на список с одной меткой - именно по ней оба workflow (deploy.yml и notify.yml, у обоих в примерах ниже стоит runs-on: host) найдут этот раннер:
runner:
...
labels:
- "host:host"Второе место - секция server:, поле connections. В сгенерированном файле она в самом конце секции server: и по умолчанию пустая - всё, что выше неё (# example:, # codeberg:), закомментировано и просто для примера, трогать не нужно:
server:
connections:Прямо под этой строкой, с отступом на два пробела больше, чем у connections:, дописываем свой блок - forgejo здесь просто ключ для этой связки (можно назвать иначе), а дальше три значения с предыдущего шага:
server:
connections:
forgejo:
url: https://forgejo.ваш-домен.ru/
uuid: <UUID_ИЗ_ШАГА_3.4>
token: <ТОТ_ЖЕ_СЕКРЕТ_ЧТО_В___SECRET_ВЫШЕ>В отличие от раннера, который настраивается под Renovate, здесь не нужно трогать секцию container:/поле docker_host - она отвечает за проброс Docker-сокета в контейнер джобы, а наш раннер типа host вообще не запускает задания внутри контейнеров, ему просто нечего пробрасывать.
Сохраните (Ctrl+O, Enter) и выйдите (Ctrl+X).
Шаг 3.6. Создайте systemd-сервис#
Актуальный unit-файл готов в официальном репозитории раннера - не нужно писать его руками, только скачать и поправить путь до своего конфига:
sudo wget -O /etc/systemd/system/forgejo-runner.service \
https://code.forgejo.org/forgejo/runner/raw/branch/main/contrib/forgejo-runner.service
sudo sed -i 's|/home/runner/runner-config.yml|/home/runner/config.yaml|' /etc/systemd/system/forgejo-runner.serviceПо умолчанию в юните прописан путь /home/runner/runner-config.yml - sed выше подменяет его на файл, который мы сгенерировали на шаге 3.5 (config.yaml). Дальше как с любым новым unit-файлом - перечитать конфиги systemd и включить сервис:
sudo systemctl daemon-reload
sudo systemctl enable --now forgejo-runner
sudo systemctl status forgejo-runnerДолжно быть active (running).
Проверьте логами, что раннер реально запустился и подключился к Forgejo:
journalctl -u forgejo-runner -fИ в веб-интерфейсе (там же, где регистрировали в Шаге 3.4) - раннер должен появиться со статусом Idle (может занять минуту).
Шаг 3.7. Установите зависимости для сборки и деплоя#
apt update
apt install -y git lftp locales
locale-gen en_US.UTF-8
update-locale LANG=en_US.UTF-8Пакет locales и генерация en_US.UTF-8 нужны, чтобы lftp (и некоторые другие утилиты) корректно работали со строками - без этого будут случаться труднообъяснимые ошибки вида “could not convert string to UTF-8”.
Hugo у вас уже установлен, если проходили предыдущую статью про установку Hugo + Blowfish - проверьте на всякий случай:
hugo versionИтог настоящей главы - у Forgejo есть рабочая лошадка на вашем сервере, готовая выполнять сборку.
Глава 4. Скрипт сборки и деплоя#
Шаг 4.1. Токен для чтения контент-репозитория#
Тот же способ, что в Шаге 1.8 - можно использовать тот же токен prohomelab-ci (если давали ему write:repository на все репозитории - этого достаточно, он имеет право и на чтение). Если создаете отдельный токен - прав read:repository достаточно для этой задачи.
Шаг 4.2. Скрипт синхронизации контента#
На сервере:
cd /home/prohomelab
mkdir -p scripts
cat > scripts/sync-content.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
export GIT_TERMINAL_PROMPT=0
CONTENT_REPO_HOST="forgejo.ваш-домен.ru/ваш-логин/prohomelab-content.git"
CONTENT_DIR="/tmp/prohomelab-content"
TARGET_DIR="content/posts"
if [ -n "${CONTENT_REPO_TOKEN:-}" ]; then
CONTENT_REPO_URL="https://ваш-логин:${CONTENT_REPO_TOKEN}@${CONTENT_REPO_HOST}"
else
echo "ERROR: CONTENT_REPO_TOKEN is not set" >&2
exit 1
fi
rm -rf "$CONTENT_DIR"
git clone --depth 1 "$CONTENT_REPO_URL" "$CONTENT_DIR"
rsync -a --delete \
--exclude='.git' \
--exclude='Prohomelab.base' \
--exclude='_Prohomelab-Index.md' \
"$CONTENT_DIR/" "$TARGET_DIR/"
find "$TARGET_DIR" -type f -name '*.md' -print0 | xargs -0 sed -i -E 's/!\[\[([^]]+)\]\]//g'
echo "Content synced and converted."
EOF
chmod +x scripts/sync-content.sh(В строке CONTENT_REPO_HOST подставьте свой домен Forgejo и логин вместо forgejo.ваш-домен.ru/ваш-логин.)
Разбор ключевых моментов:
export GIT_TERMINAL_PROMPT=0- запрещает git пытаться интерактивно спросить логин/пароль. Без этой строки, если токен по какой-то причине не пришёл, git может зависнуть намертво на командеclone, ожидая ввод с несуществующего терминала, вместо того чтобы сразу выдать ошибку. Мы словили это на практике - зависание длилось много минут, пока не отменили вручную.- Явная проверка
if [ -n "${CONTENT_REPO_TOKEN:-}" ]- если секрет не пришёл, скрипт сразу падает с понятной ошибкойERROR: CONTENT_REPO_TOKEN is not set, а не тратит время на попытку подключения без авторизации. git clone --depth 1- скачивает только последнее состояние без истории, быстрее.rsync -a --delete- синхронизирует содержимое, удаляя на стороне сайта то, чего больше нет в контенте (иначе удалённые в Obsidian статьи продолжали бы висеть на сайте).sedв конце - конвертирует обсидиановский синтаксис вставки картинокв обычный markdown, который Hugo понимает. Если позже найдёте другие непонятные Hugo конструкции (коллбауты> [!note], внутренние wiki-ссылки[[Заметка]]между статьями) - добавляйте сюда ещё по одной строкеsedпо тому же принципу.
Проверьте скрипт вручную перед тем, как доверить его автоматике:
export CONTENT_REPO_TOKEN=ВАШ_ТОКЕН
bash scripts/sync-content.sh
unset CONTENT_REPO_TOKEN
ls content/postsДолжно все отработать без вопросов о пароле и показать структуру папок со статьями.
Закоммитьте скрипт сразу, не откладывая - это частая причина ошибки “No such file or directory” при первом запуске через Actions: раннер всегда работает с чистым checkout из git, а не с тем, что физически лежит на диске:
git add scripts/sync-content.sh
git commit -m "Add content sync script"
git pushШаг 4.3. Файл workflow сборки и деплоя#
mkdir -p .forgejo/workflows
cat > .forgejo/workflows/deploy.yml <<'EOF'
name: Build and Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: host
steps:
- name: Checkout site repo
uses: actions/checkout@v4
with:
submodules: recursive
- name: Sync content from Obsidian repo
env:
CONTENT_REPO_TOKEN: ${{ secrets.CONTENT_REPO_TOKEN }}
run: bash scripts/sync-content.sh
- name: Build with Hugo
run: hugo -D --minify
- name: Deploy via FTP
env:
FTP_HOST: ${{ secrets.FTP_HOST }}
FTP_USER: ${{ secrets.FTP_USER }}
FTP_PASS: ${{ secrets.FTP_PASS }}
run: |
lftp -u "$FTP_USER,$FTP_PASS" "$FTP_HOST" <<'INNEREOF'
set ssl:verify-certificate no
mirror -R --delete --verbose public/ /www/ВАШ_ДОМЕН
bye
INNEREOF
EOFРазбор:
on:- толькоpush(на ваш собственный push в сайт, например правку темы) иworkflow_dispatch(можно запустить вручную из UI, и именно так его будет дёргать второй репозиторий - см. Шаг 4.5). Мы намеренно не используемrepository_dispatch- такого API-события в Forgejo нет, попытка его использовать даёт404 page not found.runs-on: host- выполнять на раннере с меткойhost(левая часть лейбла из Шага 3.5).submodules: recursive- подтягивает тему Blowfish при checkout.hugo -D --minify--Dвключает черновики (если не хотите публиковать черновики на боевом сайте - уберите флаг, когда будете готовы к полностью боевому режиму),--minifyсжимает вывод.set ssl:verify-certificate no- многие shared-хостинги используют для FTPS сертификат, который не проходит строгую проверку доверия, хотя соединение всё равно шифруется. Без этой строкиlftpупадёт сCertificate verification: The certificate is NOT trusted. Если у вашего хостинга сертификат нормальный - эту строку можно убрать.- Путь
/www/ВАШ_ДОМЕН- не угадывайте этот путь, проверьте его вручную (Шаг 4.4), прежде чем вписывать в workflow - от него зависит, куда--deleteбудет удалять файлы.
Шаг 4.4. Проверьте реальный путь на хостинге, прежде чем гонять --delete вслепую#
Зайдите в панель управления вашим хостингом и посмотрите поле «Корневая директория» для нужного сайта - часто оно выглядит как www/ваш-домен.ru, а не просто ваш-домен.ru или голое имя проекта без домена. Не полагайтесь на память о том, что было в FileZilla - проверьте заново, ошибка в пути с --delete может стереть не то.
Подключитесь вручную прямо с сервера:
lftp -u ВАШ_FTP_ЛОГИН ftp://АДРЕС_FTP_СЕРВЕРАЕсли при первой же команде (ls) получите:
Fatal error: Certificate verification: The certificate is NOT trusted.- выполните внутри той же сессии:
set ssl:verify-certificate noи повторите ls.
Если после этого 530 Login incorrect - перепроверьте пароль (без отображения на экране легко ошибиться), выйдите (exit) и зайдите заново.
Когда зашли - пройдите по структуре и убедитесь, что видите реальные файлы Hugo-сайта (index.html, posts/, sitemap.xml):
ls
cd www
ls
cd ВАШ_ДОМЕН
lsЗапомните точный путь (например /www/prohomelab.com) и подставьте его в mirror -R --delete --verbose public/ ЭТОТ_ПУТЬ в Шаге 4.3 вместо плейсхолдера. Выйдите: exit.
Закоммитьте deploy.yml:
cd /home/prohomelab
git add .forgejo/workflows/deploy.yml
git commit -m "Add deploy workflow"
git pushШаг 4.5. Второй workflow - «будильник» в репозитории контента#
Push в prohomelab-content сам по себе не запускает workflow в prohomelab-site - это два независимых репозитория. Нужен маленький workflow, который через API Forgejo запускает конкретный workflow-файл (deploy.yml) во втором репозитории.
На Windows, в PowerShell, в папке контента:
cd "C:\Users\ваш-пользователь\Documents\Obsidian\ваш-vault\01-Projects\Prohomelab"
mkdir .forgejo\workflows -Force
@"
name: Notify site repo
on:
push:
branches: [main]
jobs:
notify:
runs-on: host
steps:
- name: Trigger site build
env:
TOKEN: `${{ secrets.FORGEJO_DISPATCH_TOKEN }}
run: |
curl -X POST \
-H "Authorization: token `$TOKEN" \
-H "Content-Type: application/json" \
"https://forgejo.ваш-домен.ru/api/v1/repos/ваш-логин/prohomelab-site/actions/workflows/deploy.yml/dispatches" \
-d '{"ref":"main"}'
"@ | Out-File -Encoding utf8 .forgejo\workflows\notify.yml
Get-Content .forgejo\workflows\notify.ymlОбратите внимание на бэктики ` перед ${{ и $TOKEN - в PowerShell это экранирование, чтобы символы попали в файл буквально, а не были подставлены самим PowerShell. Проверьте вывод Get-Content - там должно быть ровно ${{ secrets.FORGEJO_DISPATCH_TOKEN }} и $TOKEN, без искажений.
Важно про сам эндпоинт: используется /actions/workflows/deploy.yml/dispatches (запуск конкретного workflow по имени файла) с телом {"ref":"main"} - это правильный, рабочий способ в Forgejo. Путь /repos/{owner}/{repo}/dispatches с телом {"event_type": "..."}" (аналог GitHub repository_dispatch) в Forgejo не существует и даст 404 - не используйте его, даже если увидите в примерах для GitHub Actions.
Закоммитьте и запушьте:
git add .forgejo/workflows/notify.yml
git commit -m "Add dispatch workflow"
git pushШаг 4.6. Секреты#
В prohomelab-content (Settings → Actions → Secrets):
FORGEJO_DISPATCH_TOKEN- токен из Шага 1.8/4.1 (нужны права как минимум на запуск workflow в целевом репозитории - токен сwrite:repositoryна все репозитории подходит).
В prohomelab-site:
CONTENT_REPO_TOKEN- тот же токен, для чтения контент-репозитория.FTP_HOST- адрес FTP-сервера вашего хостинга.FTP_USER- логин FTP.FTP_PASS- пароль FTP.
Чек-пойнт Главы 4: оба workflow-файла, скрипт и все секреты на месте. ✅
Глава 5. Сквозная проверка#
Шаг 5.1. Тестовое изменение#
В Obsidian отредактируйте статью, сохраните, выполните коммит/синк вручную (палитра команд, Ctrl+P), не дожидаясь таймера.
Шаг 5.2. Проверьте notify.yml#
https://forgejo.ваш-домен.ru/ваш-логин/prohomelab-content/actions → должен быть новый прогон notify.yml со статусом Success.
Шаг 5.3. Проверьте deploy.yml#
https://forgejo.ваш-домен.ru/ваш-логин/prohomelab-site/actions → должен появиться новый прогон deploy.yml, запущенный через workflow_dispatch (не через push - обратите внимание на пометку триггера). Раскройте все шаги:
- Checkout site repo - просто отрабатывает.
- Sync content from Obsidian repo - должно занять секунды (не минуты - если висит долго, смотрите Troubleshooting ниже).
- Build with Hugo - вывод Hugo, сколько страниц собрано.
- Deploy via FTP - список файлов, которые
lftpзаливает.
Итоговый статус job - Success.
Шаг 5.4. Проверка вживую#
Откройте ваш сайт в браузере, убедитесь, что правка на месте.
Раздел «если что-то не работает» (по нашему реальному опыту)#
git pushпросит пароль и не принимает обычный пароль аккаунта. Используйте Personal Access Token вместо пароля (Шаг 1.8), либо сразу пропишите его прямо в URL remote:git remote set-url origin https://ваш-логин:ВАШ_ТОКЕН@forgejo.ваш-домен.ru/....docker exec forgejo cat /data/gitea/conf/app.iniне показывает вашу правку[actions]. Значит редактировали не тот файл на хосте - сверьте путьvolumes:вdocker-compose.yml, конфиг лежит по<volume-host-path>/gitea/conf/app.ini.export RUNNER_VERSION=$(...)в Шаге 3.2 возвращает пустоту или ошибкуjq: command not found. На свежем сервереjqчасто не установлен по умолчанию - поставьте его (apt install -y jq) и повторите команду. Еслиjqна месте, а строка всё равно пустая - проверьте вручную, чтоcurl -s https://data.forgejo.org/api/v1/repos/forgejo/runner/releases/latestвообще отвечает (не заблокирован ли исходящий доступ файрволом).gpg --recv EB114F5E...виснет или падает с ошибкой соединения. Keyserverhkps://keys.openpgp.orgиногда недоступен из вашей сети - попробуйте ещё раз через минуту, либо укажите другой keyserver тем же флагом--keyserver. Пропускать саму проверку подписи не стоит - именно она страхует от подменённого при скачивании бинарника.Раннер зарегистрирован, но задания в workflow вечно “Waiting for a runner with the following label: host”. Откройте
/home/runner/config.yaml(sudo cat /home/runner/config.yaml | grep -A3 "labels") - в секцииrunner:полеlabelsдолжно быть списком с"host:host"(с двоеточием), а не пустым[]и не просто"host". Если поправили - перезапустите сервис:sudo systemctl restart forgejo-runner.Раннер в веб-интерфейсе показывает статус offline, хотя
systemctl status forgejo-runnerпишетactive (running). Почти всегда это опечатка или устаревшее значение в секцииserver: connections:в/home/runner/config.yaml-uuid/tokenдолжны точно совпадать с теми, что показала Forgejo при регистрации в Шаге 3.4 (не с секретом, который вы, возможно, вводили сами в старом--tokenварианте - здесь секрет генерируется автоматически или черезopenssl, руками не придумывается).git cloneв скрипте синхронизации виснет на много минут вместо секунд. Добавьте в скриптexport GIT_TERMINAL_PROMPT=0и явную проверку токена перед clone (см. Шаг 4.2) - тогда вместо зависания будет мгновенная понятная ошибка, если что-то не так с токеном.bash: scripts/sync-content.sh: No such file or directoryпри первом запуске через Actions, хотя скрипт точно есть на диске. Раннер при каждом запуске делает чистый checkout из git - если скрипт не был закоммичен и запушен, в свежей копии его физически нет. Коммитьте и пушьте скрипт сразу после создания, не откладывайте.curlв notify.yml возвращает404 page not found. Скорее всего используется несуществующий в Forgejo эндпоинт/repos/{owner}/{repo}/dispatchesсevent_type(аналог GitHubrepository_dispatch). В Forgejo используйте/repos/{owner}/{repo}/actions/workflows/{имя}.yml/dispatchesс телом{"ref":"main"}(см. Шаг 4.5), и уберите изdeploy.ymlнеработающий триггерrepository_dispatch, оставив толькоpushиworkflow_dispatch.lftp: command not foundна шаге деплоя. Забыли установитьlftpна сервере -apt install -y lftp(Шаг 3.7).lftpвыдаётcould not convert string to UTF-8. На системе не сгенерирована локаль, хотяLANGна неё ссылается. Выполнитеapt install -y locales && locale-gen en_US.UTF-8 && update-locale LANG=en_US.UTF-8.lftp: Certificate verification: The certificate is NOT trusted. Обычное дело для shared-хостинга с самоподписанным/недоверенным сертификатом FTPS. Добавьтеset ssl:verify-certificate noпервой строкой в блоке командlftp- как в workflow, так и при ручной проверке.530 Login incorrectв lftp. Обычно просто опечатка в пароле при ручном вводе (пароль не отображается на экране). Выйдите (exit) и зайдите заново, вводя осторожно, или подставьте пароль из менеджера паролей копированием.Деплой прошёл успешно, но файлы легли не в ту папку на хостинге / затёрли что-то не то. Прежде чем доверять
--deleteавтоматике, всегда вручную проверяйте реальный путь черезlftp(Шаг 4.4) и сверяйте с «Корневой директорией» в панели управления хостингом - она может отличаться от того, что вы помните по работе в FileZilla.Hugo падает с
ERROR the "published" front matter field is not a parsable date. В конкретной статье во фронтматтере в полеpublishedстоит не дата, а, например,true/false(если у вас в конфиге сайтаpublishedнастроено как алиас даты публикации, а не флаг черновика). Исправьте на настоящую дату вида2026-08-09без кавычек. Отдельно, предупреждение “has both draft and published settings… Using draft” - не ошибка, а просто уведомление о приоритетеdraftнадpublished, когда оба поля заданы одновременно.Картинки на сайте не отображаются. Почти наверняка необработанный обсидиановский синтаксис вставки картинок. Проверьте markdown статьи - возможно, картинка вставлена не как
(это правило уже покрыто скриптом), а каким-то другим способом - тогда нужно добавить ещё одноsed-правило вscripts/sync-content.shпо аналогии.Раннер выполняет только одно задание за раз, а вы отменили не тот прогон. В
config.yamlраннера по умолчаниюcapacity: 1- задания выполняются строго по очереди. Если запустили несколько прогонов подряд (например, тестируя), лишние встанут в очередь “Waiting” - это нормально, не пытайтесь запускать параллельно, дождитесь текущего или явно отмените (кнопка Cancel на странице прогона).
Что делать дальше, когда всё настроено#
Обычный рабочий цикл: открыли Obsidian → написали или отредактировали статью → сохранили → (по желанию) сразу выполнили коммит-и-синк, если не хотите ждать автопуш по таймеру. Дальше всё происходит само: Forgejo → раннер → сборка → заливка на хостинг. FileZilla и ручной hugo -D из этого процесса полностью исключены.
Если статья ещё не готова к публикации, но вы хотите закоммитить прогресс - держите draft: true во фронтматтере; при hugo -D она всё равно попадёт на сайт (это удобно для предпросмотра, но помните об этом - если хотите публиковать только готовое, уберите флаг -D из команды hugo в deploy.yml, тогда черновики не будут собираться вовсе).




