Костыли ИИ Велосипеды

Аргус: простой движок для блога v0.1.0

Что это такое?

Движок: Python, FastAPI
Ведение записей: Obsidian11
База данных: SQLite
Комментарии: не предусмотрены
Откуда скачать: Argus Blog Engine
Лицензия: MIT
Как работает блог: владелец блога создает записи в Obsidian, затем командой из командной строки публикует сайт.

Зачем мне еще один движок?

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

Я начал с отличного движка WordPress. Превосходная вещь, заточена под любые нужды, но есть проблема. Wordpress — это универсальная платформа. Для того чтобы универсальная платформа удовлетворяла нужды всех пользователей, в ней должно быть много настроек, много кастомизации. И здесь оказалась первая проблема. Мне нужно было не добавлять какие-то функции а наоборот выключать уже готовые.

Вторая вещь: темы. Их очень много, под любые нужды, но универсальность ведет к тому, что я не нашел ту, которая понравится мне. Сделать свою необходимо изучить движок. Я не захотел. Так и родился очень простой движок на FAST API.

Третья вещь: ведение контента. Мне очень нравится Obsidian, превосходная вещь для записей для того, чтобы делать ссылки из одного файла на другой и формировать связанный граф знаний. И мне приходилось периодически копировать текст из Obsidian, вставлять его в WordPress, менять стили, разбивать дополнительно на абзацы. Все это занимало время.

Почему Аргус?

Аргус — имя пса из Одиссеи Гомера. История об Аргусе — это, пожалуй, самое первое в западной литературе упоминание о верности собак. Гомер запечатлел здесь архетипический образ преданного пса, который ждёт хозяина и умирает, дождавшись его возвращения. Этот мотив потом будет повторяться бесчисленное количество раз — от средневековых легенд до современных фильмов и книг, но исток его именно в «Одиссее».

Что реализовано по визуалу и темизации

В стандарте тема реализована приблизительно такая, которая вы видите на сайте.

Чтобы добавить новую тему либо кастомизировать текущую, достаточно просто скопировать её из папки default в другую папку и назначить новую переменную в env-файле.

# Тема оформления — папка app/themes/<THEME>/ с шаблонами и статикой.  
THEME=datacycle

Всего 10 HTML-файлов с Jinja-templates внутри.
CSS взят базовый, без каких-либо библиотек, но ничего не мешает подключить новую библиотеку.

Как публиковать заметку

Главное условие publish: true. И это единственное обязательное поле.

yaml
---  
title: Как я настроил блог  
date: 2026-08-03  
tags:  
  - obsidian  - pythondescription: Текст, который Google покажет под ссылкой в выдаче.  
publish: true  
---  
Поле Если не задано
publish заметка игнорируется
title первый # в тексте, иначе имя файла
date дата создания файла
tags пост без тегов
description первый абзац, обрезанный до ~160 символов
slug генерируется и дописывается в файл
updated дата, когда содержание менялось в последний раз
cover первая картинка в посте (путь — как к вложению в тексте)
aliases используется только для разрешения [[ссылок]]

В Obsidian всё это удобно заполнять через панель свойств (Ctrl+;),
а publish завести как свойство-галочку.

Теги берутся только из frontmatter и записываются списком —
по строке на тег, как их пишет панель свойств Obsidian. Запись в строку
(tags: [obsidian, python]) тоже читается, переписывать старые заметки
не нужно. Инлайновые #теги в тексте не обрабатываются — так предсказуемее.

Порядок тегов значим. Первый в списке считается основным: он попадает
в хлебные крошки поста (Главная → obsidian → Заголовок) и стоит первым
в плашках под заголовком. Хотите другую рубрику — переставьте теги местами.

Дата обновления хранится в самой заметке. Заметив, что вы поправили текст,
синхронизация впишет в свойства updated: 2026-08-09 — так же, как вписывает
slug. Дальше эта дата и уходит на сайт, в sitemap и в RSS.

Время изменения файла для этого не годится: Obsidian переписывает заметку при
закрытии, синхронизация и git трогают её при выгрузке — дата ползла бы вверх
без единой правки. Поэтому она меняется, только если изменились текст,
заголовок, описание, теги или обложка. Перестановка свойств в панели, новый
алиас или служебное поле плагина её не трогают.

Раз дата лежит в заметке, пересборка базы (--rebuild) и даже её потеря
ничего не теряют. Обратная сторона: правку, сделанную в отсутствие базы,
синхронизация не заметит — дата обновится при следующей.

У новых заметок поля нет, и это не забывчивость: пока правок не было, датой
обновления считается дата публикации. Проставите updated руками — будет
ваше значение, пока вы снова не измените текст.

Что понимается из синтаксиса Obsidian

  • [[Заметка]], [[Заметка|подпись]], [[Заметка#Раздел]], [[папка/Заметка]], алиасы
  • ![[картинка.png]] — вложения копируются в media/
  • > [!warning] Заголовок — коллауты
  • `` — вырезаются
  • ```python — подсветка кода с номерами строк и кнопкой копирования
  • одиночный перенос строки — виден на сайте, как в режиме чтения Obsidian

Mermaid-диаграммы не поддерживаются: блок ```mermaid отрисуется как код.

Правило приватности

Всё, что не помечено publish: true, не существует. Ссылка на такую
заметку превращается в обычный текст, эмбед выбрасывается. Наружу не уходит
ни адрес, ни название. Синхронизация отчитывается о каждом таком случае:

Ссылки:  
  ! Как я настроил блог: [[Личные финансы]] не опубликована — оставлена текстом

Про slug

Slug генерируется из заголовка один раз и записывается в файл. Дальше
переименование заголовка не меняет URL. Если поправить слаг руками,
синхронизация запишет 301 со старого адреса на новый и схлопнёт цепочки
предыдущих редиректов.

Запись в vault — единственное, что скрипт делает с вашими заметками, и только
с помеченными к публикации. Полей всего два: slug и updated. Меняется одна
строка в существующем frontmatter, YAML не пересобирается: порядок полей,
кавычки и комментарии остаются как были.

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

SEO

  • уникальные title и description, canonical, og:*, Twitter Cards
  • JSON-LD: WebSite, BlogPosting, BreadcrumbList
  • sitemap.xml с lastmod, robots.txt, RSS с content:encoded
  • ЧПУ с транслитерацией кириллицы, 301 при смене слага
  • ровно один <h1> на странице, <article>, <time datetime>
  • noindex на поиске, честный 404 вместо страниц-пустышек с кодом 200

Скорость (Core Web Vitals — фактор ранжирования):

  • всё рендерится на сервере, готовый HTML лежит в базе
  • шрифты самохостятся, запросов к сторонним доменам нет вообще
  • у каждой картинки проставлены width и height — страница не прыгает
  • единственный скрипт ~300 байт инлайном

Развёртывание на VPS

  • VPS — 1 vCPU и 1 ГБ памяти хватает с запасом: два воркера uvicorn едят
    порядка 120 МБ, остальное берёт на себя Caddy.
  • Домен с доступом к DNS-записям.
  • SSH-ключ. Деплой-скрипт делает три подключения подряд и один раз
    запускает sudo без терминала — с парольным входом это не работает.
  • Локально — свой скрипт на каждую систему. В Linux и macOS выкладывает
    deploy/deploy.sh, ему нужны rsync и ssh. В Windows выкладывает
    deploy/deploy.ps1 из PowerShell: rsync там нет ни в системе, ни в Git Bash,
    поэтому скрипт обходится ssh, scp и tar — они входят в Windows 10/11.
    Ни WSL, ни Git Bash для деплоя не нужны.

Подробная инструкция в файле DEPLOY.md