Mosaica |
Описание приложения
Тестовые новости
Карусель изображений
Mosaica (0.3.3)
Небольшое приложение на Node.js + Express для просмотра и редактирования AsciiDoc-документов, хранящихся в файлах.
Боевой контент, медиа и mosaica.site.json теперь живут только в отдельном site-repo. Сам движок больше не разворачивается как самостоятельный сайт: в корне этого репозитория нет папки content/, а пример полного site-repo вынесен в example_website/.
Что умеет
-
открывает AsciiDoc-документы из папки
content/ -
показывает дерево файлов
-
создает, удаляет и перемещает файлы/папки прямо из компонента дерева
-
редактирует AsciiDoc в браузере
-
сохраняет изменения обратно в файлы
-
поддерживает авторизацию пользователей без базы данных
-
скрывает и показывает части документов по правам доступа пользователя
-
защищает редактор и файловый API ключами доступа
-
позволяет управлять пользователями, группами и ключами доступа через встроенную админ-страницу
-
хранит пользователей в
SECURITY.JSON, а серверные сессии вSESSIONS.JSON -
поддерживает внутренние компоненты через синтаксис
webcomp::component-name[…] -
поддерживает файловое меню сайта и меню разделов через
_menu.json -
читает метаданные документов
:title:,:author:,:published:,:tags:и:summary: -
индексирует новостные разделы через
news.config.jsonиnews-tags.json -
отдает JSON API новостей с фильтрацией по иерархическим тегам и сортировкой по дате
-
рендерит управляемую ленту новостей через
webcomp::news-feed[…][]с пагинацией или бесконечной подгрузкой -
позволяет администратору подключать и обслуживать git-репозиторий сайта из интерфейса
-
позволяет редактировать и переключать темы оформления сайта из интерфейса
Структура проекта
-
example_website/— пример отдельного site-repo сcontent/,content/media/,chart/,mosaica.site.jsonи workflow развертывания -
docs/text-style-system.adoc— спецификация текстовых стилей, их наследования и соответствия AsciiDoc -
SECURITY.JSON— пользователи, группы и ключи доступа -
SESSIONS.JSON— файловое хранилище активных серверных сессий -
THEMES.JSON— наборы тем оформления сайта и активная тема -
src/— сервер и клиентские исходники -
public/— собранные браузерные бандлы -
dist/— собранный серверный код -
test/— автоматические smoke/regression тесты
Как это работает
-
в развернутом site-repo сайт стартует с
content/index.adoc -
служебные страницы движка больше не лежат в
content/; используются внутренние URL/editor,/access,/site-repository,/themes -
дерево файлов показывает содержимое
content/ -
все пути в ссылках и редакторе указываются относительно
content/ -
префикс
content/в ссылках писать не нужно -
content/section/index.adocоткрывается по красивому адресу/section -
отдельные AsciiDoc-страницы вроде
content/page.adocоткрываются как/page.adoc -
вложенные AsciiDoc-страницы вроде
content/news/release-0-3-0.adocоткрываются как/news/release-0-3-0 -
html/css/js/svgи другие не-.adocфайлы внутриcontent/отдаются напрямую по URL, напримерcontent/vibeCoding/chess.htmlбудет доступен как/vibeCoding/chess.html -
медиа сайта рекомендуется хранить в
content/media/и использовать по пути/media/… -
директивы с путями к файлам (например
include::) должны быть относительными к текущему документу:include::./temp.adoc[] -
заголовок вкладки браузера можно задать атрибутом документа
:title: -
документы могут задавать автора, дату публикации, теги и summary через
:author:,:published:,:tags:и:summary: -
если у новости не задан
:published:, движок берет время изменения файла из файловой системы -
новостной раздел описывается через
news.config.json, а таксономия тегов хранится вnews-tags.json -
публикации внутри новостного раздела можно хранить как файлы
news/*/.adocлюбой глубины, включая каталоги с точками в имени, напримерnews/1.1/release.adoc -
также сохраняется совместимость с прежней схемой
news/**/index.adoc -
компонент
webcomp::news-feed[…][]рендерит ленту новостей сервером и добавляет клиентские фильтры по тегам, сортировку, постраничную навигацию или infinite scroll -
шаблон карточки новости в ленте и обертка всей ленты задаются отдельными
.adoc-шаблонами -
пользователи, группы и права доступа хранятся в
SECURITY.JSON -
каталог ключей доступа также хранится в
SECURITY.JSON -
после входа сервер сохраняет сессию в
SESSIONS.JSONи выдает cookie -
блоки доступа рендерятся только для пользователей с нужным ключом доступа
-
внутри
webcomp::access[…]директивыinclude::…[]обрабатываются как отдельный блок (включая случай inline-записи) -
редактор, дерево и файловый API доступны только авторизованным пользователям с одним из ключей:
edit,content.edit,editor,admin -
управление доступом доступно авторизованным пользователям с одним из ключей:
admin.users,admin.groups,admin.keys,admin -
управление git-репозиторием сайта доступно авторизованным пользователям с одним из ключей:
admin.git,admin -
управление темами сайта доступно авторизованным пользователям с одним из ключей:
admin.themes,admin -
при открытии документа сервер ищет шаблон с именем из
.envпеременнойtemplate_name -
шаблон ищется в папке документа и далее вверх по дереву до корня
content/ -
content::внутри шаблона заменяется содержимым открываемого документа до финального рендера AsciiDoc -
ближайший шаблон новости может использовать слоты
news-title::,news-author::,news-published::,news-tags::,news-summary::иnews-link::[Текст] -
карусель изображений задаётся блоком
carousel:: … [thumbs=true, captions=true]и рендерится вmosaica-carousel -
кнопки редактирования (иконка карандаша) добавляются на этапе серверного рендера отдельно для шаблона и отдельно для текущей страницы
-
меню:
-
глобальное меню берется из
content/_menu.json -
меню раздела ищется как ближайший
_menu.jsonвверх от текущего документа до корняcontent/ -
пункты меню можно ограничивать по ключу доступа через
accessKey -
сам движок ожидает, что
content/иmosaica.site.jsonбудут наложены поверх workspace на этапе site-deploy; в этом репозитории они сохранены только внутриexample_website/как образец
Компоненты
Авторизация
webcomp::auth[]
Только вход, без регистрации:
webcomp::auth[mode=login-only]
Дерево файлов
webcomp::file-tree[]
AsciiDoc-редактор
webcomp::asciidoc-editor[]
Меню
Вертикальное (по умолчанию):
webcomp::menu[type=site][]
Горизонтальное:
webcomp::menu[type=site,layout=horizontal][]
Меню раздела:
webcomp::menu[type=section][]
Меню раздела (горизонтальное):
webcomp::menu[type=section,layout=horizontal][]
Блок доступа
webcomp::access[key=demo][
Содержимое увидят только пользователи с ключом `demo`
]
Лента новостей
webcomp::news-feed[
path=news,
item-template=./news-item.adoc,
template=./news-feed.adoc,
sort=desc,
mode=paged,
page-size=10
][]
Поддерживаемые параметры:
-
path— путь к новостному разделу сnews.config.json -
item-template— шаблон одной новости в ленте, путь относительно текущей страницы -
template— шаблон всей ленты, путь относительно текущей страницы -
tags— стартовый фильтр тегов через запятую -
match—anyилиall -
sort—descилиasc -
mode—pagedилиinfinite -
page-size— размер страницы или пачки подгрузки -
controls— видимые элементы управления через|, напримерsort|tags|page-size
Особенности отображения:
-
поле режима показа в интерфейсе не выводится
-
в
mode=infiniteнастройкаpage-sizeне показывается, даже если указана вcontrols
Шаблон элемента ленты может использовать слоты:
-
news-title:: -
news-author:: -
news-published:: -
news-tags:: -
news-summary:: -
news-link::[Текст ссылки]
Шаблон всей ленты может использовать слоты:
-
news-items:: -
news-total:: -
news-count:: -
news-offset:: -
news-limit:: -
news-active-tags:: -
news-sort:: -
news-match:: -
news-path::
Управление доступом
Служебная страница движка: /access
На ней доступны компоненты:
webcomp::security-admin[]
и
webcomp::site-repo-admin[]
Репозиторий сайта
webcomp::site-repo-admin[]
Компонент показывает:
-
настройки репозитория сайта
-
trackedPathsдля sparse checkout сайта -
статус локального git-рабочего дерева
-
синхронизацию контента из git в рабочую копию сайта
-
создание commit и push в origin
Управление репозиторием ограничено путями из trackedPaths, то есть используется только для контента и настроек сайта. Медиа хранится внутри content/media/.
Для push по HTTPS в окружении сайта должен быть настроен SITE_REPO_GIT_TOKEN. При необходимости можно также задать SITE_REPO_GIT_USERNAME.
Дополнительно движок держит локальный untracked-кэш .mosaica-site-repo.local.json, чтобы sync не терял repoUrl и служебные поля, если tracked mosaica.site.json в git содержит только deploy-настройки.
Встроенная служебная страница движка: /site-repository
Темы сайта
webcomp::theme-admin[]
Компонент показывает:
-
список тем
-
выбор активной темы
-
редактирование стилевых свойств по сущностям верстки
-
сохранение текущей темы или новой копии
Темы хранятся отдельно в THEMES.JSON. Активная тема применяется ко всем серверно рендеримым страницам сайта.
Модель сущностей верстки и JSON-схема темы описаны в docs/theme-style-system.adoc.
Встроенная служебная страница движка: /themes
Карусель изображений
carousel::
./media/carousel/slide-1.svg|Подпись 1
./media/carousel/slide-2.svg|Подпись 2
./media/carousel/slide-3.svg|Подпись 3
[thumbs=true, captions=true, fit=contain, autoplay=true, interval=5000, lightbox=true]
Поддерживаемые параметры:
-
thumbs— показывать миниатюры -
captions— показывать подпись активного кадра -
fit—containилиcover -
autoplay— автопрокрутка кадров -
interval— интервал автопрокрутки в миллисекундах -
lightbox— открывать увеличенный просмотр по клику на изображение
Пример mosaica.site.json:
{
"repoUrl": "https://github.com/TulaArchery/mosaica_content.git",
"branch": "main",
"trackedPaths": ["content", "mosaica.site.json"],
"engineRepoUrl": "https://github.com/TulaArchery/mosaica.git",
"engineRef": "dockerfile-helm",
"releaseName": "mosaica",
"imageName": "mosaica",
"domain": "mosaica.silinmo.ru",
"namespace": "mosaica",
"templateName": "template.adoc",
"ingressClassName": "nginx",
"clusterIssuer": "letsencrypt-prod",
"tlsSecretName": "mosaica-tls",
"contentSyncMode": "overlay",
"commitAuthorName": "Mosaica Admin",
"commitAuthorEmail": "admin@example.com"
}
Шаблонный слот
Шапка
content::
Подвал
Новости и метаданные
Базовые атрибуты документа:
= Заголовок новости
:title: Заголовок вкладки
:author: Имя автора
:published: 2026-06-14 10:30
:tags: 1.1, 2.a
:summary: Короткий анонс новости
Если :published: не задан, для новости используется время изменения файла.
Новостной раздел можно описать так:
{
"tagsFile": "./news-tags.json",
"defaultSort": "desc",
"defaultPageSize": 10
}
Таксономия тегов хранится в news-tags.json. Теги можно объединять в дерево: если фильтровать по 1, в выборку попадут материалы с 1, 1.1, 1.2 и любыми другими потомками.
Пример шаблона страницы новости:
[.news-page-title]
news-title::
[.news-page-meta]
Автор: news-author::
[.news-page-meta]
Дата публикации: news-published::
[.news-page-meta]
Теги: news-tags::
content::
Пример шаблона элемента ленты:
[.news-feed-item-title]
news-title::
[.news-feed-item-meta]
Автор: news-author::
[.news-feed-item-meta]
Дата: news-published::
news-summary::
news-link::[Читать]
Пример обертки всей ленты:
[.news-feed-heading]
Лента новостей
[.news-feed-meta]
Всего публикаций: news-total::
news-items::
JSON API новостей:
-
GET /api/news?path=news -
GET /api/news?path=news&tags=1 -
GET /api/news?path=news&tags=1,2&match=all -
GET /api/news?path=news&sort=asc&limit=5&offset=10 -
GET /api/news/render?doc=news/index.adoc&path=news&template=./news-feed.adoc&itemTemplate=./news-item.adoc
Титул вкладки
Чтобы управлять названием вкладки браузера, задайте атрибут :title: в документе:
= Заголовок документа
:title: Мое название вкладки
Формат _menu.json
Файл может быть массивом пунктов или объектом вида { "items": […] }.
Поддерживаемые поля пункта:
-
title— заголовок пункта -
path— путь к документу (если не указан, пункт будет без ссылки) -
accessKey— ключ доступа для ограничения видимости пункта -
children— массив дочерних пунктов
Правила для path:
-
./…и../…считаются относительно папки, где лежит текущий_menu.json -
остальные пути считаются относительно корня
content/
Требования
-
Node.js >= 22
Установка
npm install
Опционально можно создать .env из .env.example.
Важно: этот репозиторий больше не содержит deployable content/ в корне. Для локального запуска нужно передать TREE_BASE_DIR, SECURITY_FILE и SESSION_FILE, указывающие на отдельный site-repo или на копию example_website/, развернутую вне корня движка.
Запуск
Режим разработки
npm run dev
Сборка
npm run build
Запуск production-сборки
npm start
Тесты
npm test
Развертывание
Схема развертывания была разработана Игорем Васильевым aka igorjum.
Docker
Репозиторий движка сам по себе больше не собирается в deployable image. Dockerfile ожидает, что поверх workspace уже наложены content/ и mosaica.site.json из site-repo. Это делает workflow site-repo перед docker build.
Приложение хранит контент, пользователей и сессии в файлах. Для runtime-контейнера используются внешние пути:
-
TREE_BASE_DIR— директория сcontent/ -
SECURITY_FILE— путь кSECURITY.JSON -
SESSION_FILE— путь кSESSIONS.JSON
Kubernetes и Helm
Helm chart теперь живет в site-repo. В этом репозитории он сохранен только как часть примера example_website/chart/mosaica.
Chart сайта:
-
поднимает
1реплику -
создает
PersistentVolumeClaim -
хранит
content,SECURITY.JSONиSESSIONS.JSONв одном persistent volume -
при старте берет
content/иmosaica.site.jsonиз образа, уже собранного site-repo workflow -
публикует ingress для домена, заданного в
mosaica.site.json
Если у кластера есть свой ingress class или cert-manager, это настраивается через chart/mosaica/values.yaml внутри site-repo, например:
ingress:
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt
tls:
- hosts:
- mosaica.silinmo.ru
secretName: mosaica-tls
GitHub Actions deploy
У движка больше нет собственного deploy workflow. Развертывание выполняется только из site-repo, пример которого лежит в example_website/.github/workflows/deploy.yml.
Site-repo workflow делает следующее:
-
читает
mosaica.site.json -
забирает движок из
engineRepoUrl -
накладывает поверх него
content/иmosaica.site.json -
собирает Docker-образ
-
использует локальный
chart/mosaicaиз site-repo -
пушит его в registry
-
выполняет
helm upgrade --install
Нужные GitHub Secrets в site-repo:
-
REGISTRY_URL -
REGISTRY_USERNAME -
REGISTRY_PASSWORD -
ENGINE_REPO_TOKEN -
SITE_REPO_GIT_TOKEN— чтобы кнопкаPushвнутри сайта могла отправлять изменения обратно в GitHub -
SITE_REPO_GIT_USERNAME— опционально, если для HTTPS-аутентификации нужен явный username
ENGINE_REPO_TOKEN должен иметь как минимум Contents: Read-only к приватному репозиторию движка. Доступ к Kubernetes workflow берет с self-hosted runner через локальный /home/captgreen/.kube/config.
При наличии SITE_REPO_GIT_TOKEN workflow создает в namespace secret site-repo-git-auth, а chart автоматически прокидывает его в контейнер как SITE_REPO_GIT_TOKEN и SITE_REPO_GIT_USERNAME.
Отдельный repo сайта
Поддерживается схема из двух репозиториев:
-
mosaica.git— движок -
mosaica_content.git— контент,content/media/,chart/,mosaica.site.jsonи workflow развертывания
В example_website/ лежит готовый пример такого site-repo, который можно скопировать в новый репозиторий почти без изменений.
В отдельном site-repo workflow:
-
читает
mosaica.site.json -
клонирует движок из
engineRepoUrl -
накладывает поверх него
content/иmosaica.site.json -
собирает образ сайта
-
использует локальный
chart/mosaicaиз site-repo -
публикует образ и делает
helm upgrade --install
Для site-repo рекомендуется режим contentSyncMode = overlay, чтобы файлы из git обновлялись на сайте, а новые локальные файлы, которых нет в репозитории, не удалялись.
Пользовательский сценарий
Что делает владелец сайта от создания repo до появления домена:
-
Создает новый репозиторий сайта на основе
example_website/. -
Заполняет
content/своими страницами, шаблонами и файлами вcontent/media/. -
Настраивает
mosaica.site.json:repoUrl,domain,namespace, при необходимостиengineRef. -
Добавляет в Secrets нового repo:
-
REGISTRY_URL -
REGISTRY_USERNAME -
REGISTRY_PASSWORD -
ENGINE_REPO_TOKEN
-
-
Подключает self-hosted runner с доступом к Docker,
kubectl,helmи локальному/home/captgreen/.kube/config. -
Делает
pushвmain. -
Workflow подтягивает движок
mosaica, накладывает контент сайта, собирает образ и выполняетhelm upgrade --installчерез локальныйchart/mosaicaэтого site-repo. -
После завершения workflow сайт открывается по домену из
domain.
Основные маршруты
-
/— открываетcontent/index.adoc -
/section— открываетcontent/section/index.adoc -
/page.adoc— открываетcontent/page.adoc -
/news/release-0-3-0— открываетcontent/news/release-0-3-0.adocилиcontent/news/release-0-3-0/index.adoc -
/editor— открывает страницу редактора и требует права доступа -
/health— health check, возвращаетstatus,name,versionиgitSha -
/api/tree— дерево файлов, требует права доступа -
/api/tree/create— создать файл/папку, требует права доступа -
/api/tree/delete— удалить файл/папку, требует права доступа -
/api/tree/move— переместить/переименовать файл/папку, требует права доступа -
/api/file— чтение и сохранение файлов, требует права доступа -
/api/asciidoc/render— рендер AsciiDoc в HTML -
/api/news— индекс новостей в JSON (path,tags,match,sort,limit,offset) -
/api/news/render— готовый HTML ленты новостей (doc,path,itemTemplate,template,tags,match,sort,limit,offset,mode) -
/api/menu— отдает меню сайта/раздела (type=site|section) -
/api/auth/session— текущая сессия пользователя -
/api/auth/register— регистрация -
/api/auth/login— вход -
/api/auth/logout— выход -
/api/auth/profile— обновление профиля -
/api/security/store— текущее состояние пользователей, групп и ключей, требует admin-доступ -
/api/security/users— создание пользователя, требует admin-доступ -
/api/security/users/:email— изменение или удаление пользователя, требует admin-доступ -
/api/security/groups— создание группы, требует admin-доступ -
/api/security/groups/:name— изменение или удаление группы, требует admin-доступ -
/api/security/keys— создание ключа доступа, требует admin-доступ -
/api/security/keys/:name— изменение или удаление ключа доступа, требует admin-доступ -
/api/site-repo— настройки и статус git-репозитория сайта, требует admin.git -
/api/site-repo/settings— сохранить настройкиmosaica.site.json, требует admin.git -
/api/site-repo/connect— инициализировать локальное git-подключение, требует admin.git -
/api/site-repo/sync— подтянуть контент из git в сайт, требует admin.git -
/api/site-repo/commit— создать commit из изменений сайта, требует admin.git -
/api/site-repo/push— отправить локальные коммиты в origin, требует admin.git -
/api/themes— список тем и активная тема -
/api/themes/css— CSS активной темы -
/api/themes/save— сохранить тему и при необходимости сделать её активной, требует admin.themes
Ограничения
-
приложение работает только с файлами внутри
content/ -
выход за пределы базовой директории запрещен
-
бинарные файлы не поддерживаются
-
большие файлы могут быть отклонены по лимиту размера
-
данные пользователей и прав хранятся только в
SECURITY.JSON -
активные сессии хранятся только в
SESSIONS.JSON -
при удалении группы или ключа сервер не даст сломать связанные записи: сначала нужно снять зависимости
Пример ссылки внутри контента
link:/editor[Открыть редактор]
или
link:index.adoc[На главную]
Пример содержимого SECURITY.JSON
{
"users": [
{
"email": "user@example.com",
"username": "user",
"passwordHash": "scrypt:...",
"groups": ["demo", "Editor"]
}
],
"groups": [
{
"name": "Editor",
"keys": ["edit"]
},
{
"name": "demo",
"keys": ["demo"]
},
{
"name": "Admin",
"keys": ["admin.git", "admin.users", "admin.groups", "admin.keys", "edit"]
}
],
"keys": [
{
"name": "admin.git",
"title": "Git-репозиторий сайта",
"description": "Подключение, синхронизация и отправка изменений сайта в git"
},
{
"name": "edit",
"title": "Редактирование",
"description": "Доступ к редактору и файловым операциям"
},
{
"name": "admin.users",
"title": "Пользователи",
"description": "Управление пользователями"
}
]
}
© Michail O Silin aka captGreen, 2026