Перейти к основному содержимому

Как создать свой блог на Hugo

·1781 слово·9 минут
Гайды
Юрий Семеньков
Автор
Юрий Семеньков
DevOps, tech, geek, mentor
Оглавление

Мне кажется, что DevOps — это про творчество. У нас есть огромное количество способов достичь одной цели, и зачастую не будет единственного правильного решения. Что-то более оптимальное, что-то сложнее, что-то удобнее поддерживать.

Мы можем собрать бинарник и положить в контейнер, а можем собрать и закинуть файл по ssh. О, а как запустить — можем просто docker run сделать, а можем завернуть в compose. Или обернуть всё в systemd-сервис, или может в init.d… Ой, а если еще затронуть Kubernetes и Helm…

Мне захотелось на досуге попробовать поднять блог на Hugo. Я раньше это делал, но так нормально и не разобрался с порядком работы. Поднимать буду прямо на виртуалке, сделаю CI/CD пайплайн с разными окружениями и покажу как тестировать все локально.

Что такое Hugo и почему именно он?
#

Hugo — это так называемый генератор статических вебсайтов. Это приложение на языке Go, которое из определенного набора файлов сгенерирует нам статику, а ее мы будем раздавать пользователям с помощью вебсервера.

Классная особенность статических страниц в том, что мы можем раздавать их хоть с s3-бакета, хоть с GitHub Pages.

Почему же именно Hugo? На самом деле генераторов статики несколько: Jekyll, Hugo, Gatsby… Hugo — бесплатный, посты мы готовим в знакомым многим Markdown формате, есть встроенный веб-сервер в не-продакшн режиме… ну и другие я не пробовал.

Выбираем стек
#

Чтобы было повеселее сегодня я сделаю следующее:

  • Покажу как установить Hugo на виртуальную машину, локально и как запускать его
  • Настрою Nginx для раздачи статики, добавлю бесплатный SSL сертификат
  • Установлю self-hosted GitHub Actions Runner на виртуальную машину
  • Настрою пайплайн со сборкой и доставкой статики на наш сервер

Устанавливаем Hugo на виртуальную машину
#

Тут официальная документация предлагает нам несколько вариантов, вплоть до установки из стандартных репозиториев Ubuntu (apt install hugo), но там устанавливается очень старая версия.

Мы же скачаем .deb пакет и установим его. Идем в репозиторий на GitHub и открываем страницу с последним релизом. Качаем .deb файл с amd64 архитектурой.

# Скачиваем пакет
wget -P /tmp https://github.com/gohugoio/hugo/releases/download/v0.117.0/hugo_extended_0.117.0_linux-amd64.deb

# Устанавливаем пакет
dpkg -i /tmp/hugo_extended_0.117.0_linux-amd64.deb

# Проверяем установку
hugo version

Устанавливаем Hugo локально
#

Параллельно с этим я устанавливаю Hugo себе локально, чтобы было удобнее отлаживать код, а так же чтобы не мучиться с иницализацией репозитория.

Так как у меня MacOS я могу установить Hugo через менеджер пакетов brew:

brew install hugo

Разбираемся как оно работает
#

Эти действия я предлагаю выполнять на своей локальной машине

Для начала создадим структуру для нового сайта. Предположим, что мы у себя в домашней директории:

❯ hugo new site superblog

Congratulations! Your new Hugo site is created in /Users/admin/superblog.

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/ or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

Переходим в созданную директорию и смотрим, что лежит внутри:

❯ cd superblog
❯ tree .
.
├── archetypes
│   └── default.md
├── assets
├── content  <~~ здесь все посты
├── data
├── hugo.toml  <~~ конфигурационный файл сайта
├── layouts
├── static
└── themes  <~~ темы

Теперь нужно установить тему. Повыбирать можно тут, а в качестве примера воспользуемся темой Stack.

Документация предлагает два варианта — установить через git submodules, либо зашить тему вглубь директории layouts (установка через модули Hugo). Первый вариант мне кажется более подходящим — он делает простым смену тем.

Сначала инициализируем git-репозиторий, а затем добавим туда тему как submodule.

git init
git submodule add https://github.com/CaiJimmy/hugo-theme-stack.git themes/stack

И добавим нашу тему в конфигурационный файл:

echo "theme = 'stack'" >> hugo.toml

На этом моменте мы можем уже попробовать запустить Hugo в не-продакшен режиме: в этом случае нам не нужно настраивать Nginx — это очень удобно для локальной отладки нашего сайта и этим мы будем пользоваться постоянно. К тому же оно поддерживает горячее изменение файлов без перезапуска.

❯ hugo server

Watching for changes in /Users/admin/superblog/{archetypes,assets,content,data,layouts,static,themes}
Watching for config changes in /Users/admin/superblog/hugo.toml, /Users/admin/superblog/themes/stack/config.yaml
Start building sites …
hugo v0.117.0-b2f0696cad918fb61420a6aff173eb36662b406e+extended darwin/amd64 BuildDate=2023-08-07T12:49:48Z VendorInfo=brew


                   | EN
-------------------+-----
  Pages            |  7
  Paginator pages  |  0
  Non-page files   |  0
  Static files     |  0
  Processed images |  3
  Aliases          |  3
  Sitemaps         |  1
  Cleaned          |  0

Built in 56 ms
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

Ого, оно что-то сделало и предлагает нам зайти на http://127.0.0.1:1313:

Ну, можно выйти и продолжить: Ctrl-C.

Будем предполагать, что именно таким образом мы будем локально тестировать наш блог: отредактировали, запустили hugo server, посмотрели локально, а затем пушим в репозиторий

Создаем первый пост
#

Общая структура файлов с постами будет следующей:

❯ tree content
content
└── posts <~ общая категория posts, может быть любой другой
    ├── first-post <~ директория под файлы одной статьи
    │   ├── image1.png <~ картинка-аттачмент для первого поста
    │   └── index.md <~ markdown файл первого поста
    └── second-post <~ директория под второй пост
        └── index.md

Перезапустим наш веб-сервер hugo server:

Сейчас нашу статику раздает встроенный веб-сервер hugo. Это так называемый “не-продакшн” режим.

С одной стороны мы можем написать systemd-сервис, который будет запускать команду hugo server. Затем мы можем либо опубликовать этот веб-сервер на 0.0.0.0, либо настроить наш Nginx на обратной проксирование на 127.0.0.1:1313. Но так не было задумано разработчиками.

👉 С помощью команды hugo, приложение сгенерирует статическую версию нашего сайта из всех файлов и положит ее в поддиректорию public.

Установим Github Actions Runner
#

У нас есть возможность хостить наши статические файлы даже в GitHub Pages, и это совершенно несложно. Но не так интересно.

Я буду хостить сайт прямо на виртуальной машине, и Github Actions Runner тоже установлю как self-hosted runner туда же.

Предположим, что мы уже создали отдельный пустой репозиторий в GitHub, к которому мы потом привяжем тот локальный, что мы создавали командой git init.

Переходим в Settings, затем слева Actions - Runners, New self-hosted runner:

Дальше после выбора нужной платформы нам предоставят список команд для установки и инициализации раннера. В конце я рекомендую установить раннер как сервис, для этого можно обратиться к документации, где скажут запустить его командой:

sudo ./svc.sh install

Вот такая картина говорит нам о том, что наш раннер успешно подключился к Github Actions и ждет работы:

Создаем CI/CD пайплайн
#

Документация для референса: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions

Возвращаемся в наш репозиторий, создаем директорию .github, а в ней поддиректорию workflows. Workflows это отдельные пайплайны в Github Actions. Поэтому создаем файл dev-cicd.yml и начинаем творчество. Я сделал так:

name: dev-etogeek cicd
on:
  workflow_dispatch:
  push:
    branches:
      - dev
jobs:
  deploy-dev:
    runs-on: self-hosted
    environment: 
      name: dev
      url: https://dev.mysite.tech
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
        with:
          submodules: recursive
      - name: Deploy Dev
        run: |
          hugo
          sudo rm -rf /opt/dev-etogeek
          sudo mkdir -p /opt/dev-etogeek/static
          sudo cp -r public/* /opt/dev-etogeek/static/          

Теперь по порядку о том, что здесь происходит:

  • on: workflow_dispatch: — позволяет нам запустить пайплайн вручную, через Run Workflow
  • on: push: branches: -dev — пайплайн будет запущен автоматически при пуше кода в dev-ветку
  • runs-on: self-hosted — указывает, где будет запущен наш пайплайн; здесь указываются теги self-hosted раннеров, либо название github-actions образа
  • environment: — позволит более-менее удобно следить деплоями, не самая обязательная красивость
  • Этап checkout — склонирует наш репозиторий в рабочую директорию runner-а, а параметр with: submodules: recursive скачает дополнительно нашу тему, которую мы установили как git-submodule. По умолчанию рабочая директория очищается при каждом деплое.

Этап деплоя я сделал таким образом:

  • Команда hugo генерирует в директорию public статику из нашего hugo-репозитория. Команда, конечно же, требует установленного hugo на виртуальной машине, что мы сделали выше.
  • Далее мы удаляем директорию с нашей статикой, чтобы каждый раз копировать туда актуальные файлы. Сразу после удаления мы создаем ее заново.
  • Копируем с ключем -r (recursive) все файлы из директории public в нашу директорию, на которую в будущем будет ссылаться Nginx

Делаем полностью аналогичный файл, только для production-версии нашего блога. Меняем названия, путь к директории и ветку для триггера на main.

Обязательно добавляем в файл .gitignore нашу директорию public:

❯ cat .gitignore
/public/
.hugo_build.lock

Коммитим наш код. Первый раз, думаю, проще будет задеплоиться из основной ветки, чтобы все проверить. А про общий воркфлоу я напишу ниже. Смотрим, что наш пайплайн будет зеленым. Идем на виртуальную машину, проверяем, что все файлы на месте в нужной директории и мы можем приступить к настройке Nginx.

Настройка Nginx
#

На виртуальной машине создаем файл с конфигурацией Nginx /etc/nginx/sites-available/prod.conf:

server {
    server_name mysite.tech;

    root /opt/prod-etogeek/static/;
    index index.html;

    location / {
            try_files $uri $uri/ =404;
    }

    listen 80;

}

Затем создаем symlink в директорию sites-enabled с конфигурациями, которая считывается Nginx-ом:

ln -s /etc/nginx/sites-available/prod.conf /etc/nginx/sites-enabled/prod.conf

Проверяем конфигурацию и перезапускаем Nginx:

nginx -t
systemctl restart nginx
# или systemctl reload nginx 

Уже сейчас можем зайти по доменному имени dev.mysite.tech, которое мы конечно же поменяли на свое реальное, и увидим наш сайт. Но, добавить на сайт SSL-сертификат. Нет ничего проще, чем обратиться за помощью к Let’s Encrypt и скрипту certbot:

apt install certbot
certbot

Выбираем наш конфигурационный файл в интерактивном меню, а в конце выбираем Redirect. Можно будет увидеть, что certbot добавил в нашу nginx-конфигурацию несколько новых строк и переадресацию с http на https.

Если и пока у тебя нет доменного имени, можно схитрить и добавить адрес виртуальной машины в локальный /etc/hosts — тогда Nginx будет нормально обрабатывать имя из адресной строки

Процесс работы
#

Основная часть работы позади, автоматизированная доставка кода настроена, стоит подытожить планируемый процесс работы:

  • Разработку веду в локальном репозитории. Создаю статьи как отдельные .md файлы.
  • Тестирую работоспособность сайта, визуальную составляющую с помощью встроенного веб-сервера командой hugo server.
  • Вливаю свою код в dev ветку, спустя минуту-другую наблюдаю изменения на dev-версии блога
  • Делаю Pull Request из dev-ветки в main-ветку, вливаю его, наблюдаю изменения на production версии блога

Что можно усовершенствовать?
#

Немного размышлений: это сложно назвать прямо идеальным девопсерским ci/cd пайплайном как минимум потому что у нас никак не отделены этапы сборки и выкатки кода.

Мы можем выполнять команду hugo в отдельном этапе, хоть даже на отдельной виртуальной машине или в контейнере. Затем собирать статику в архив, помечать его некой версией, например хэшом коммита и сохранять как артефакт. Затем этот артефакт уносить на dev-машину, и таким же образом — на prod-инстанс.

Но я не уверен, что на текущем этапе мне нужны эти усложнения — я все делаю на одной и той же виртуальной машине, и даже dev и prod окружения у меня лежат просто в соседних директориях. Зачем большее для блога?

Так же мы можем собирать это все не в self-hosted раннере, а в cloud-режиме, который предоставляет GitHub. В конце концов нужно будет просто скопировать собранную статику на нашу виртуалку.

Я писал однажды небольшой пост «Не усложняй» как раз про использование более сложных технологий, чем требует продукт.


🙋‍♂️С комментариями можно смело приходить в наш телеграм-чат https://t.me/etogeekchat

🌀Так же рекомендую подписаться на телеграм-канал, чтобы не пропускать классные посты и анонсы новых статей — https://t.me/etogeek

Related

Бест-практисы Ansible
·1052 слов·5 минут
Гайды
Мониторинг SSL сертификатов в Zabbix
·843 слов·4 минут
Гайды
Сканер открытых портов в Raycast
·417 слов·2 минут
Софт Гайды