У вас есть готовый пирог. Нужно восстановить рецепт — без записей, по вкусу.
Примерно так я себя почувствовал, когда обнаружил, что исходников блога blog.graywrk.ru больше нет. Локальный каталог, где жил Hugo-проект, пуст. Бэкапов не было. На сервере осталась только собранная статика — готовые HTML, CSS, ассеты.
Хорошая новость: в 2026 году потеря исходников статического сайта — это не катастрофа, а интересный рабочий день. Рассказываю, как за одну сессию с ZCode я развернул блог обратно, заодно переоформив его по своему брендбуку.
Разведка: где что лежит
Сначала — понять, что вообще уцелело. Блог жил на выделенной виртуалке в Selectel и отдавался через Caddy. Все детали инфраструктуры я аккуратно веду в репозитории homelab — он-то и стал точкой опоры. Документация подсказала адрес сервера и что сайт — статическая сборка на Hugo.
Подключаюсь по SSH, осматриваю /var/www/blog.graywrk.ru. Внутри — классическая картина Hugo-вывода: index.html, каталог posts/ с подкаталогами-слагами, index.xml (RSS), sitemap.xml, пагинация в page/. И никаких markdown-исходников, темы, hugo.toml — вообще ничего, из чего это собиралось.
Зато HTML много говорит о происхождении. В футере честно написано: «Hugo & PaperMod». Заголовок поста несёт мета-теги Open Graph и Twitter Cards с заголовком, датой публикации, описанием, тегами. В <head> — canonical-ссылки на каждый пост. То есть собранная статика — это не тупой набор файлов, а структурированный документ с метаданными.
Ищу следы исходников по всему серверу: домашние каталоги пусты, git-репозиториев нет, .bash_history не содержит ничего про деплой. Отчётливый вывод — исходник деплоился снаружи (с моей рабочей машины) и при потере исчез бесследно. Восстанавливать предстояло из того, что лежит на проде.
Реконструкция: контент из готовой статики
Главное наблюдение: PaperMod рендерит страницу поста так, что почти всё нужное для markdown лежит прямо в HTML.
- Заголовок — в
<title>(с суффиксом сайта, который я отрезаю). - Дата публикации — в
<meta property="article:published_time">. - Описание — в
<meta name="description">и OG-описании. - Теги — в
<meta property="article:tag">(по одному на тег, PaperMod так и кладёт). - Тело поста — внутри
<article class="post-single">, точнее в<div class="post-content">до<footer class="post-footer">.
Остаётся вытащить метаданные в Hugo front matter, а тело прогнать через HTML→markdown-конвертер. На Python это markdownify — маленькая, толковая библиотека на BeautifulSoup.
Конвертер получился простой: обойти posts/*/index.html, вытащить мета-теги в YAML-front-matter, отдать .post-content в markdownify. Результат — content/posts/<slug>/index.md для каждого из 16 постов. Slug’и я брал как есть из имён каталогов: my-first-post, processor-200-mhz, docker-image-optimization — все совпали с тем, как PaperMod их сгенерил изначально.
Честные оговорки. Два поста оказались без тегов — и в исходном HTML их тоже не было, конвертер тут ни при чём. Язык код-блоков (какой именно ```bash или ```yaml) PaperMod в <pre><code> не сохраняет — это не восстановимо без ручной проверки, и я оставил блоки без подсветки как факт. Inline-код, списки, таблицы, эмодзи и русские кавычки-«ёлочки» перенеслись честно.
Итог этапа: 16 файлов, около 180 КБ чистого контента. Сравнил список slug’ов с сервером — один в один. Контент снова стал текстом, который можно редактировать и пересобирать.
Параллельный акт: рескин по брендбуку
Пока контент восстанавливался, возникла мысль — зачем собирать блог обратно в том же виде? У меня уже есть брендбук: дизайн-система с тёмной палитрой (#0A0A0B canvas), янтарным акцентом (#F59E0B), шрифтами Golos Text и JetBrains Mono. Блог и брендбук принадлежат одному автору — мне. Логично привести их к одному визуальному языку.
PaperMod управляется через CSS-переменные: --theme, --entry, --primary, --border, --link и так далее. Это значит, что для рескина не нужно переписывать шаблоны темы — достаточно переопределить переменные значениями из брендбука. Я собрал всё в один assets/css/brand.css: токены, override CSS-переменных PaperMod под тёмную и светлую темы, плюс стили для брендовых компонентов — SVG-марка «СД» в хедере, eyebrow-меток над заголовками, lede-абзацев из description.
Шрифты — отдельный разговор. В брендбуке они подключены через Google Fonts CDN, но для блога я решил сделать self-hosting: скачал woff2 (Golos Text и JetBrains Mono оказались variable-шрифтами, так что уложился в 4 файла — по два subset’а, кириллица и латиница, на каждое семейство) и прописал @font-face с font-display: swap. Блог теперь самодостаточен: деплоится без внешних зависимостей, работает за файрволами, не звонит в Google при каждом заходе.
Тут же полезла первая заноза. После деплоя читатель сообщил: на странице /posts/ стили слетели. Смотрю вычисленные значения — переменные брендбука не применяются нигде, сайт выглядит как дефолтный PaperMod. Причина — в специфичности CSS-селекторов. Тема объявляет переменные через :root[data-theme="dark"] (специфичность 0,1,1), а мой override был написан через [data-theme="dark"] (всего 0,1,0). Браузер честно выбирал более конкретное правило — то есть из темы. Лечится одной строкой: поменять селектор на :root[data-theme="dark"]. После этого каскад снова на стороне брендбука.
Урок банальный, но полезный: проверять надо не то, что CSS-файл подключён, а то, какие значения переменных resolved на живой странице. Я раньше смотрел в исходник — там всё было на месте. Заглянул в DevTools — увидел, что побеждает тема. Это не бага Hugo или PaperMod, это классический каскад.
Деплой и verification
С деплоем всё просто: Hugo собирает статикой, rsync перетирает файлы на сервере, Caddy их раздаёт. Один bash-скрипт, никаких CI/CD ради личного блога.
Перед деплоем — обязательный бэкап: копия /var/www/blog.graywrk.ru в .bak.<дата>. Если новая сборка что-то сломает, откат в одну команду.
После rsync — проверки curl’ом. Главная отдаёт 200. Пост доступен по старому URL — это критично, потому что на посты где-то могут вести внешние ссылки, и сломать slug’и значит сломать SEO и чужие закладки. Слава богу, ничего не сломалось: 16 slug’ов на сервере и в новой сборке совпали один в один. Шрифты и favicon отдаются. Метаданные в HTML корректные.
Бэкап удаляется. Блог снова живой.
Что значит владеть контентом
Когда-то потеря исходников сайта означала катастрофу. Сегодня AI-агент за одну сессию делает то, что раньше заняло бы мне пару вечеров с кофе и матом. Это не магия — это просто хорошо структурированная задача: статика предсказуема, PaperMod предсказуем, брендбук уже существует. ZCode не делал ничего, чего я бы не смог сделать сам — он делал это быстрее и не путался в опечатках.
Но из этого не следует, что бэкапы не нужны. Скорость восстановления не отменяет того факта, что несколько часов сессии ушли на работу, которой вообще не должно было быть. Бэкап исходников в git — это минута против часов.
Что реально ценно в блоге — не движок и не тема (их можно заменить за вечер), а тексты. 16 постов с моим опытом, наблюдениями, иронией. Восстановить их из готового HTML получилось потому, что контент был отделён от оформления — и слава богу, что я выбрал статический генератор, а не Wix. Готовая статика навсегда остаётся на сервере; markdown-исходник восстанавливается.
Ну и главный парадокс истории. Потеря стала поводом не просто вернуть блог, а привести его в соответствие с брендом, который я тем временем построил. До этого дня блог жил сам по себе, брендбук — сам по себе. Теперь они наконец-то разговаривают на одном визуальном языке. Исходники лежат в приватном репозитории на GitHub, деплой — одной командой, контент снова мой в полном смысле этого слова.
Если у вас статический сайт и его исходники где-то в одном экземпляре — закоммитьте их прямо сегодня. А если уже потеряли — что ж, кое-что восстановить всё-таки можно.