Hugo, Tor и Gemini

2021-12-01 · Nacht · hugo, tor, gemini, blog

Дееевоньки… вы даже не представляете, какой мне сегодня рецептик на форуме для мамочек посоветовали!

Кхем. Вернее…

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

В поисках вдохновения и референсов я прошлась по паре десятков сайтов, так или иначе указывающих на свою легковесность. Часто можно встретить одни и те же идеи: сосредоточенность на текстовых данных, минимальное количество CSS и отсутствие JavaScript, общий минимализм в оформлении и стилистике, помимо синдикации в RSS, также бывают зеркала в Tor и Gemini, элементы интерактивности с помощью IndieWeb. Рассмотрим небольшие хитрости для зеркалирования с помощью Hugo.

Референсы:

- https://seirdy.one/

- https://drewdevault.com/

- https://sylvaindurand.org/

- https://muto.ca/

и много других. Сами найдёте, если захотите — это не такое редкое явление.

Tor

Не требует каких-либо дополнительных действий, кроме поднятия скрытого сервиса и настройки веб-сервера (например, Caddy).

Tor Browser и Brave Browser поддерживают HTTP-заголовок `Onion-Location`, отправляемый нашим веб-сервером для чистой сети, и будут автоматически предлагать пользователю перейти по Onion-адресу. Также существует фоллбек с помощью meta-тега.

Конфиг Tor:

HiddenServiceDir /etc/tor/droom.vision/
HiddenServicePort 80 127.0.0.1:8180

Конфиг Caddy:

droom.vision {
	root * /srv/http/droom.vision
	file_server

	header Onion-Location http://wjbwa5f5klig6szvtm7zsv7xk55r7yi2aaqo5vgtz7syxxdvy2m2skyd.onion{path}

	tls
	encode gzip
}

http://wjbwa5f5klig6szvtm7zsv7xk55r7yi2aaqo5vgtz7syxxdvy2m2skyd.onion:8180 {
	root * /srv/http/droom.vision
	file_server

	encode gzip
}

Meta-тег для добавления в условный шаблон Hugo `layouts/partials/head.html`:

<meta http-equiv="onion-location" content="http://wjbwa5f5klig6szvtm7zsv7xk55r7yi2aaqo5vgtz7syxxdvy2m2skyd.onion{{.RelPermalink}}" />

Не забудьте сменить Onion-адрес на свой. Возможно, этот meta-тег можно автоматически генерировать с помощью `[outputFormats]` в конфиге Hugo по аналогии с Gemini.

Gemini

Самое, пожалуй, нетривиальное в теории, но как оказалось, достаточно простое на практике.

Для удобства дебага капсулы рекомендую этот малюсенький Gemini-сервер.

- https://github.com/n0x1m/gmifs

Автоматика

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

- https://github.com/tdemin/gmnhg

- https://github.com/n0x1m/hugoext

Полуавтоматика

Вся магия происходит в `config.toml` Hugo. Поправив его, можно работать с `.gmi`-файлами как с обычными шаблонами.

uglyurls = true

[permalinks]
  blog = '/blog/:filename'
  notes = '/notes/:year/:month/:day/:filename'

[outputs]
  home = ['HTML', 'RSS', 'GEMTEXT']
  section = ['HTML', 'RSS', 'GEMTEXT']
  page = ['HTML', 'GEMTEXT']

[mediaTypes]
  [mediaTypes.'text/gemini']
    suffixes = ['gmi']

[outputFormats]
  [outputFormats.GEMINI]
    name = 'GEMTEXT'
    baseName = 'index'
    isPlainText = true
    isHTML = false
    mediaType = 'text/gemini'
    protocol = 'gemini://'
    permalinkable = true
    path = 'gemini/'

Что здесь происходит:

- `uglyurls` заставляет Hugo генерировать страницы в виде `pagename.html` вместо `pagename/index.html`, что хоть и добавляет дополнительное требование по настройке веб-сервера, но взамен нормализует использование не-HTML разметок;

- `[permalinks]` задаёт явный вид генерируемой структуре файловой системы, опять же, с использованием имени страницы вместо стандарной директории с файлом индекса, что требуется для корректного сосуществования HTML и не-HTML разметок;

- `[outputs]` сообщает движку, какие форматы нужно генерировать для каждого типа страниц;

- `[mediaTypes]` регистрирует MIME type для нашего нового произвольного формата;

- `[outputFormats]` описывает наш новый произвольный формат.

Вот, кстати, та самая настройка веб-сервера для работы с некрасивыми ссылками:

try_files {path}.html {path} {path}/ =404
uri strip_suffix /

За подробностями о каждом отдельном ключе рекомендую обратиться в справочную.

Далее, вы можете создавать такие же шаблоны в `layouts/_default/`, `layouts/sectionname/` и `layouts/pagename.gmi`, как и в случае с HTML. Вам доступны все данные из front matter ваших Markdown-файлов, параметры сайта из `config.toml` и Go-функции.

Например, `layouts/index.gmi` может быть как наполненным информацией, так и брать информацию из `_index.md`. У меня сосуществуют два разных файла индекса, то есть первый вариант, потому что я (пока что) не занимаюсь конвертацией.

`layouts/_default/list.gmi` уже требует заполнения некоторых директив:

{{ range .Pages }}
=> {{ replace .RelPermalink "/gemini" "" 1}} {{ .Title | safeHTML }}
{{.Date.Format "2006-01-02"}} · {{.Params.categories}} · {{ if eq .Kind "page" }} {{ delimit .Params.tags ", " }} {{end}}
{{ end }}

Что здесь происходит:

- скорее всего, вы уже знаете про императивную итерацию по данным страниц с помощью `range`;

- мы генерируем относительную перманентную ссылку на нашу страницу, вырезая из неё `/gemini`, которое вставляется Hugo, потому что генерируемые страницы складываются в `public/gemini`, то есть поддиректорию относительно HTML-страниц;

- этой ссылке задаётся имя, которое с помощью `safeHTML` превращается в юникод вместо HTML-экранирования кракозябрами;

- далее обычный текст с датой, категорией и тегами — оформляйте по-своему, в общем.

И самое интересное в `layots/_default/single.gmi`. До сих пор мы ничего не делали со страницами, записанными в Markdown. В принципе, если вы не злоупотребляете разметкой, можно ничего не делать: заголовки и списки в Markdown и Gemini одинаковые, остальная разметка просто не рендерится и выглядит вполне уместно. Именно это я делаю для постов, содержащих исключительно текст. Но что насчёт ссылок, альтернативных списков, Hugo-шорткодов?

# {{ .Title | safeHTML }}

{{if fileExists (replace $.File.Path ".md" ".gmi") }}
{{readFile (replace (replace $.File.Path ".md" ".gmi") ".html" ".gmi") | safeHTML}}
{{else}}
{{.RawContent | safeHTML}}
{{end}}

Что здесь происходит:

- вписывается заголовок страницы;

- если существует одноимённый файл страницы с расширением `.gmi`, мы используем его содержимое вместо `.md`;

- в противном случае используется содержимое `.md`.

Вот, в принципе, и всё. Как генерировать исправленные `.gmi`-файлы уже решать вам: можно как я и другие — переписывать вручную; можно генерировать автоматически с помощью условного `md2gmn` и альтернатив — опять же, вручную, с помощью Git pre-commit hook или в процессе CI. Логика сего действа проста и тупа до смерти.

Отдельные герои умудряются конвертировать содержимое из Markdown в Gemtext с помощью встроенных Go-регулярок, но это некрасивое с виду решение. Настолько, что я предпочту попрактиковаться в клацаньи клавиш для достижения результата.

Для полной красоты ещё можете генерировать meta-тег в HTML, указывающий на существование Gemini-версии страницы. Что, впрочем, бесполезно, потому что URL-то всё равно неправильный, а как задать свой я пока что ещё не знаю.

{{ range .AlternativeOutputFormats -}}
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
{{ end -}}

Если я ничего не упустила, после выполнения `hugo` у вас внутри `public/` будут как HTML-файлы, так и `gemini/`.

Но не всё так радужно.

Баги

Hugo не был бы Hugo, не сломав мне мозг в совершенно неожиданных местах;

- если вам когда-нибудь понадобится получить контент страниц в шаблоне `list.gmi`, смените `$.File.Path` на `.File.Path`;

- ожидаете файлы с индексом секций где-нибудь в `public/gemini/sectionname.gmi`? Хуй вам, однако. Они лежат в `public/sectionname/gemini.gmi` (???);

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

Итог

Смотрите исходники моего сайта и ~~не~~ делайте так же. Смотрите исходники других сайтов — там-то люди умнее постарались. Городите новые велосипеды и помогайте с починкой существующих (обратите внимание на gmnhg, например). ~~Воруй, убивай, еби гусей.~~