попереднього разу я спробував нашвидкоруч переналаштувати генерацію блогу з архіву дописів за допомогою двигунця jekyll, але з першого разу не подужав і покинув аж на два місяці. сьогодні, маючи трохи більше часу, повернувся до цієї задачі, так само невдало: свіжовстановлена версія jekyll потребувала цілої купи модулів (gems), від яких залежить і сам jekyll, і деякі функції теми. вивчення ruby лише задля підтримки блогу в робочому стані аж ніяк не входить в мої плани, тож я здався і повернувся до думки перезібрати значно простішу конфігурацію з hugo.
скелет сайту на hugo
встановлення hugo (pacman бо arch):
> sudo pacman -S hugo
майданчик для експериментів:
> tree ~/blog/ -L2
~/blog/
├── blog.content # вміст щоденника
│ ├── blog # ...дописи
│ │ ├── 2001 № ...за роками
│ │ ├── ...
│ │ ├── 2024
│ ├── drafts # ...робочі чернетки
│ ├── images # ...ілюстрації
│ │ ├── 2013 # ...за роками
│ │ ├── ...
│ │ ├── 2024
│ └── pages # ...копії сторінок поза щоденником
├── blog.jekyll # старі файли для jekyll (тема, шаблони тощо)
важливо. теку blog/
довелося перейменувати на posts/
через дивний глюк codeberg pages (див. далі).
цього разу не хочу експериментувати з конвертуванням сайту jekyll: вже пробував, проблем чи не більше, ніж якщо починати з нуля. окрім того, я таки хочу почати з нуля, щоби спробувати зробити щонаймінімальніший сайт, без зовнішніх залежностей, без зовнішніх css-фреймворків та js-скриптів.
> hugo new site ~/blog/blog.test
...
> tree ~/blog/blog.test/ -L2
~/blog/blog.test/
├── archetypes
│ └── default.md
├── assets
├── content
├── data
├── hugo.toml
├── i18n
├── layouts
├── static
└── themes
додавання теми
створився порожній скелет для сайту. далі підказка радить вибрати тему оформлення на themes.gohugo.io; найменше, що знайшов з того, що не виїдає очі — hugo xmin (підказка з використаня):
> cd ~/blog/blog.test/themes/
> git clone https://github.com/yihui/hugo-xmin.git
...
> tree ~/blog/blog.test/themes/ -L2
~/blog/blog.test/themes/
└── hugo-xmin
├── archetypes
├── exampleSite
├── hugo-xmin.Rproj
├── images
├── layouts
├── LICENSE.md
├── README.md
├── static
└── theme.toml
не розумію, навіщо майже всі нові теми вихваляються здатністю відмальовувати складні математичні формули: сайтів, де це справді потрібно, дуже мало. менше з тим… themes/hugo-xmin/exampleSite/
містить файли для швидкої випроби:
> tree ~/blog/blog.test/themes/hugo-xmin/exampleSite/ -L2
~/blog/blog.test/themes/hugo-xmin/exampleSite/
├── content
│ ├── about.md
│ ├── _index.markdown
│ ├── _index.Rmarkdown
│ ├── note
│ └── post
├── hugo.yaml
└── layouts
└── partials
випроба:
> cd ~/blog/blog.test
> cp -r themes/hugo-xmin/exampleSite/content/* content/
> cp -r themes/hugo-xmin/exampleSite/layouts/* layouts/
> cp -r themes/hugo-xmin/exampleSite/hugo.yaml ./
> hugo server
...
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).
конвертування дописів
спершу я гадав, що hugo має суттєво інший формат заголовків файлів markdown, тому почав шукати дієвий спосіб конвертувати свої більш як 2 тис. дописів; офіційна підказка згадує три; скрипти на pythonʼі не спрацювали, але штатна команда hugo import jekyll ...
, що я нею вже скористався попереднього разу — так, хоч і з прикрим нюансом: вона конвертує ввесь сайт, а не лише дописи markdown, і відмовляється працювати, поки не знайде всю структуру тек сайту:
> cd ~/blog/
# затягнув копію сайту з сервера
> mkdir blog.jekyll
> scp tivasyk@server.lan:/home/jekyll/blog/\* blog.jekyll/
> cp blog.test/hugo.yaml blog.test/hugo.yaml.backup # hugo import переписує!
> hugo import jekyll --force ./blog.jekyll/ ./blog.test/
> cp blog.test/hugo.yaml.backup blog.test/hugo.yaml # відновлення налаштувань
> hugo server --source ~/blog/blog.test # локальний сервер з теки
...
працює. порівнюю файли дописів до і після конвертування… рукалице: як вчитатися, зрозуміло, що тут hugo конвертував лише формат дати (і поігнорував час) і попереставляв місцями блоки в заголовку.
> cd ~/blog/
> diff -y --suppress-common-lines blog.content/blog/2024/2024-05-29-freecad-beginning.markdown blog.test/content/post/2024/2024-05-29-freecad-beginning.markdown
layout: post <
title: "freecad: ресурси для вивчення" <
date: 2024-05-29 17:30 <
tags: | date: "2024-05-29T00:00:00Z"
- щоденник | description: підказка з початків використання…
- комп'ютери | моделей для друку на 3d принтері.
- 3d друк | tags:
- підказки | - щоденник
description: "підказка з початків використання… | - комп'ютери
> - 3d друк
> - підказки
> title: 'freecad: ресурси для вивчення'
гірше, що не конвертовано внутрішні посилання на інші сторінки блогу {% blog_post 2024/2024-05-25-tasco %}
— це формат jekyll, і hugo його не розуміє (див. документацію). конветор hugo перекладає ілюстрації до static/assets/images/
; це незручно, я хочу тримати їх поруч дописів, у content/images/
; це потребуватиме ще однієї зміни за допомогою awk до всіх посилань на зображення.
все це можна відносно легко робити старим як світ awk — три рядки, не рахуючи моїх коментарів:
# скрипт awk для конвертування файлів markdown у форматі jekyll до формату hugo
# використання: awk -f j2h.awk <path-to-Jekyll-post(s)>
{
# перетворення дати
# 2024-11-30 01:23 -> "2024-11-30T01:23:00"
$0 = gensub(/^date:(\s+)([0-9-]{10})\s+([0-9:]{5})/, "date:\\1\"\\2T\\3:00\"", "g", $0)
# перетворення крос-посилань
# {% post_url post/2024/2024-11-30-test_1 %} -> {{< ref "blog/2024/2024-11-30-test-1" >}}
$0 = gensub(/\{%\s+post_url\s+([^ ]+)\s+%\}/, "{{< ref \"blog/\\1\" >}}", "g", $0)
# перетворення посилань на зображення
# ![назва](/images/2024/2024-11-30-image.jpg) -> ![назва](images/2024/2024-11-30-image.jpg)
$0 = gensub(/!\[(.+)\]\(\/assets(.+)\)/, "![\\1](\\2)", "g", $0)
print $0
}
awk
не вміє рекурсивно обробляти цілі теки, але ж є find
; випробування на blog.test:
> find ~/blog/blog.test/ -path '*/blog/*' -name '*.markdown' -exec awk -i inplace -f j2h.awk {} +
працює. ну от хто для такого розчохляє цілий python? ех, молодь…
резервна копія blog.content
, і можна конвертувати:
> cd ~/blog/
> mkdir backup
> tar --exclude='.git' -zcvf backup/backup_blog.content_20241201.tar.gz blog.content
> find ~/blog/blog.content/ -path '*/blog/*' -name '*.markdown' -exec awk -i inplace -f j2h.awk {} +
збирання сайту з двох репозиторіїв
час спробувати «зібрати» все разом з двох окремих репозиторіїв. тестова тека blog.test
стане порожньою основою для hugo; треба спорожнити content/
та public/
:
> cd ~/blog/
> mv blog.test blog.hugo
> rm -rf blog.hugo/content/*
> rm -rf blog.hugo/public/*
> tree ~/blog/blog.hugo -L3
blog.hugo/
├── archetypes
│ └── default.md
├── content # <- сюди підключатиметься blog.content/
├── data
├── hugo.yaml
├── i18n
├── layouts
│ └── ...
├── public # <- сюди hugo генерує статичний сайт
├── static
│ └── assets
│ └── ...
└── themes
└── hugo-xmin
└── ...
локальна випроба, підключеня конвертованого вмісту (blog.content) до основи (blog.hugo); скористаюся mount --bind
, бо канонічно і правильно:
> cd ~/blog/
> (sudo) mount --bind /home/tivasyk/blog/blog.content/ ./blog/blog.hugo/content/
> hugo server --source ~/blog/blog.hugo
it’s alive! відключаю…
> cd ~/blog/
> (sudo) umount ./blog.hugo/content/
тепер для blog.content
та blog.hugo
потрібно створити репозиторії; в моєму випадку — на codeberg.org. шпаргалка з налаштування й виштовхування (push), на прикладі blog.hugo
:
> cd ~/blog/blog.hugo
> git init
> git remote add origin git@codeberg.org:tivasyk/blog.hugo.git
> git checkout -b master
> git add .
> git commit -m "add: перший коміт шаблону для сайту на hugo /ti"
> git push -u origin master
вивантаження згенерованого сайту на codeberg
локальний тестовий сервер запускається; залишилося навчитися автоматично виштовхувати згененований статичний сайт (вміст blog.test/pulic/
) до третього репозиторію, з якого codeberg сам роздаватиме його. цю частину я вже випробував, але не автоматизував.
можу помилятися, але git автоматично розрізняє вкладені репозиторії як субмодулі. отже, якщо підключити вже існуючий репозиторій pages до blog.test/public/…
> git clone git@codeberg.org:tivasyk/pages.git ~/blog/blog.test/public/
git затягнув увесь старий (jekyll) сайт з codeberg’а, він більше не потрібен (я маю копію); видалити (опційно), перегенерувати й виштовхнути до master на codeberg:
> rm -rf ~/blog/blog.test/public/*
> hugo --source ~/blog/blog.test
> cd ~/blog/blog.test/public/
> git add .
> git commit -m "add: видалення старого вмісту, перегенерування з hugo /ti"
> git push
щось працює! перестворений на hugo щоденник тепер доступний з codeberg – хоч і страшний, як смертний гріх, бо я не торкався теми hugo-xmin, а вона, виявляється, зовсім не підходить для блогів з великою кількістю дописів. але це вже інша задача. проявилася інша проблема…
глюк codeberg pages
codeberg відмовляється роздавати внутрішні сторіки блогу з теки blog/
. чому? схоже на глюк codeberg, повʼязаний з одним із форматів urlʼу: https://<username>.codeberg.page/<repo>/...
— тут <repo>
може вказувати не репозиторій в профілі username
ʼу, якщо такий репозиторій існує. в мене такого немає, але посилання виду https://tivasyk.codeberg.page/blog/2024/2024-11-29-hugo-from-zero
не працюють; і сторінками поза підтекою blog/
проблем немає.
довелося знову перейменувати blog/
на posts/
, і прогнати ще один скрипт awk, щоби виправити всі внутрішні посилання; після цього всі сторінки відображаються.
автоматичне поновлення
можна спробувати автоматизувати поновлення сайту. найпростіший скрипт міг би працювати приблизно так:
- створити тимчасову робочу теку, клонувати туди blog.hugo;
- створити робочій теці /content/, клонувати туди blog.content;
- створити робочій теці /public/, клонувати туди pages (бо це простіше, ніж налаштовувати репозиторій);
- перестворити сайт;
- виштовхнути /public/ на codeberg;
- видалити тимчасову робочу теку.
зрозуміло, що для скриптування роботи з codeberg треба, щоби безпарольні ssh-ключі було зареєстровано на сервері. скрипт може бути якийсь такий:
#!/usr/bin/env bash
# tivasyk <tivasyk@gmail.com>
# мінімалістичний скрипт для перегенерування щоденника tivasyk@home і публікації на codeberg pages.
# безпечні опції: вихід за найменшої помилки
set -o errexit
set -o pipefail
set -o nounset
# репозиторії для збирання сайту
git_content="git@codeberg.org:tivasyk/blog.content.git"
git_hugo="git@codeberg.org:tivasyk/blog.hugo.git"
git_pages="git@codeberg.org:tivasyk/pages.git"
# локальні теки для збирання
work_dir=""
content_dir="content"
target_dir="public"
# константи
lock_file="/tmp/.republish_lock"
marker="◆"
# прибрати робочу теку й лок-файл на виході
function cleanup() {
if [[ -d ${work_dir} ]]; then
printf "${marker} %s\n" "Removing work dir (${work_dir})..."
rm -rf "${work_dir}"
fi
if [[ -f ${lock_file} ]]; then
printf "${marker} %s\n" "Removing lock file (${lock_file})..."
rm "${lock_file}"
fi
}
# уникати запуску другої копії, якщо лок-файл вже існує
if [[ -s ${lock_file} ]]; then
printf "${marker} %s\n" "A lock file (${lock_file}) exists, is another copy running ($(cat ${lock_file}))?"
exit 0
fi
# зареєструвати cleanup() (важливо: після перевірки лок-файлу)
trap cleanup EXIT ERR
# створити лок-файл
printf "${marker} %s\n" "Creating lock file (${lock_file})..."
date -Iseconds > ${lock_file}
# тимчасова робоча тека для збрання
work_dir="$(mktemp -d)"
# клонувати шаблон
printf "${marker} %s\n" "Cloning template repo (${git_hugo}) into work dir (${work_dir})..."
git clone --depth 1 ${git_hugo} ${work_dir}
# клонувати вміст content
printf "${marker} %s\n" "Cloning content repo (${git_content}) into content dir (${work_dir}/${content_dir})..."
git clone --depth 1 ${git_content} ${work_dir}/${content_dir}
# клонувати цільову теку public
printf "${marker} %s\n" "Cloning target repo (${git_pages}) into target dir (${work_dir}/${target_dir})..."
git clone --depth 1 ${git_pages} ${work_dir}/${target_dir}
# перегенерувати сайт
printf "${marker} %s\n" "Running $(which hugo) to regenerate the site in ${work_dir}..."
hugo --source ${work_dir} || exit 3
# виштовхнути перегенерований сайт
cd ${work_dir}/${target_dir}
printf "${marker} %s\n" "Pushing the regenerated site to $(git remote get-url --all origin) from ${work_dir}/${target_dir}"
git add . \
&& git commit -m "mod: перегенеровано автоматично $(date -Iseconds)" \
&& git push
цей франкенштайн працює.
підсумки
чому так довго? я витратив досить багато часу на ці експерименти, щоби краще зрозуміти, що я роблю; коли вже знаєш — створити сайт на codeberg pages з нуля, чи конвертувати старі дописи — питання може на пів години.
чи можна без мороки з git і codeberg? так, я міг би відтворити зараз свій старий підхід, коли на постійно включеному серверочку лежала тека з цілим сайтом, туди з owncloud затягувалась копія лише теки з дописами (cron + rsync), генератор (hugo) детектував би зміни й перестворював public, а легенький веб-сервер (lighttpd) роздавав би сайт.
тоді навіщо?.. тому що git — це складно і непотрібно лише для людини, котра ще не зрозуміла життя. коли зрозуміє — це буде лише складно =)
чи можна максимально просто, і без самохосту? найпростіше: тримати на своєму компʼютері теку з сайтом (шаблон, дописи), але згенероване хостити на codeberg pages; знадобиться лише один репозиторій і спрощення скрипта — від нього знадобиться лише запускати hugo і виштовхувати public на codeberg; тоді створив новий допис в теці content, прогнав скрипт, все.
що далі?
- справді автоматизувати поновлення (cron на постійно ввімкненій віртуалці?);
- допиляти (або поміняти) тему оформлення.