Mosaica


Описание приложения

mosaica

Небольшое приложение на Node.js + Express для просмотра и редактирования AsciiDoc-документов, хранящихся в файлах.

Что умеет

  • открывает AsciiDoc-документы из папки content/

  • показывает дерево файлов

  • создает, удаляет и перемещает файлы/папки прямо из компонента дерева

  • редактирует AsciiDoc в браузере

  • сохраняет изменения обратно в файлы

  • поддерживает авторизацию пользователей без базы данных

  • скрывает и показывает части документов по правам доступа пользователя

  • защищает редактор и файловый API ключами доступа

  • хранит пользователей в SECURITY.JSON, а серверные сессии в SESSIONS.JSON

  • поддерживает внутренние компоненты через синтаксис webcomp::component-name[…​]

  • поддерживает файловое меню сайта и меню разделов через _menu.json

Структура проекта

  • content/ — пользовательский контент в формате .adoc

  • SECURITY.JSON — пользователи, группы и ключи доступа

  • SESSIONS.JSON — файловое хранилище активных серверных сессий

  • src/ — сервер и клиентские исходники

  • public/ — собранные браузерные бандлы

  • dist/ — собранный серверный код

  • test/ — автоматические smoke/regression тесты

Как это работает

  • сайт стартует с content/index.adoc

  • страница редактора находится в content/editor.adoc

  • дерево файлов показывает содержимое content/

  • все пути в ссылках и редакторе указываются относительно content/

  • префикс content/ в ссылках писать не нужно

  • директивы с путями к файлам (например include::) должны быть относительными к текущему документу: include::./temp.adoc[]

  • заголовок вкладки браузера можно задать атрибутом документа :title:

  • пользователи, группы и права доступа хранятся в SECURITY.JSON

  • после входа сервер сохраняет сессию в SESSIONS.JSON и выдает cookie

  • блоки доступа рендерятся только для пользователей с нужным ключом доступа

  • внутри webcomp::access[…​] директивы include::…​[] обрабатываются как отдельный блок (включая случай inline-записи)

  • редактор, дерево и файловый API доступны только авторизованным пользователям с одним из ключей: edit, content.edit, editor, admin

  • при открытии документа сервер ищет шаблон с именем из .env переменной template_name

  • шаблон ищется в папке документа и далее вверх по дереву до корня content/

  • content:: внутри шаблона заменяется содержимым открываемого документа до финального рендера AsciiDoc

  • кнопки редактирования (иконка карандаша) добавляются на этапе серверного рендера отдельно для шаблона и отдельно для текущей страницы

  • меню:

  • глобальное меню берется из content/_menu.json

  • меню раздела ищется как ближайший _menu.json вверх от текущего документа до корня content/

  • пункты меню можно ограничивать по ключу доступа через accessKey

Компоненты

Авторизация

webcomp::auth[]

Дерево файлов

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`
]

Шаблонный слот

Шапка

content::

Подвал

Титул вкладки

Чтобы управлять названием вкладки браузера, задайте атрибут :title: в документе:

= Заголовок документа
:title: Мое название вкладки

Формат _menu.json

Файл может быть массивом пунктов или объектом вида { "items": […​] }.

Поддерживаемые поля пункта:

  • title — заголовок пункта

  • path — путь к документу (если не указан, пункт будет без ссылки)

  • accessKey — ключ доступа для ограничения видимости пункта

  • children — массив дочерних пунктов

Правила для path:

  • ./…​ и ../…​ считаются относительно папки, где лежит текущий _menu.json

  • остальные пути считаются относительно корня content/

Требования

  • Node.js >= 22

Установка

npm install

Опционально можно создать .env из .env.example.

Запуск

Режим разработки

npm run dev

Сборка

npm run build

Запуск production-сборки

npm start

Тесты

npm test

Docker

Сборка образа

docker build -t mosaica:local .

Локальный запуск контейнера

docker run --rm -p 3000:3000 mosaica:local

Приложение хранит контент, пользователей и сессии в файлах. Для production-контейнера эти данные лучше монтировать снаружи через volume:

  • TREE_BASE_DIR — директория с content/

  • SECURITY_FILE — путь к SECURITY.JSON

  • SESSION_FILE — путь к SESSIONS.JSON

Kubernetes и Helm

В репозитории добавлен chart: chart/mosaica.

По умолчанию chart:

  • поднимает 1 реплику

  • создает PersistentVolumeClaim

  • хранит content, SECURITY.JSON и SESSIONS.JSON в одном persistent volume

  • при первом старте копирует стартовые content/ и SECURITY.JSON из образа в volume

  • публикует ingress для mosaica.silinmo.ru

Пример ручного деплоя:

helm upgrade --install mosaica ./chart/mosaica \
  --namespace mosaica \
  --create-namespace \
  --set image.repository=registry.example.com/mosaica \
  --set image.tag=latest \
  --set imagePullSecrets[0].name=regcred

Если у кластера есть свой ingress class или cert-manager, это настраивается через chart/mosaica/values.yaml, например:

ingress:
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
  tls:
    - hosts:
        - mosaica.silinmo.ru
      secretName: mosaica-tls

GitHub Actions deploy

Добавлен workflow .github/workflows/deploy.yml для self-hosted runner.

Он делает следующее:

  • собирает Docker-образ

  • пушит его в ваш registry

  • подключается к Kubernetes через KUBE_CONFIG

  • создает или обновляет imagePullSecret

  • выполняет helm upgrade --install

Нужные GitHub Secrets:

  • REGISTRY_URL

  • REGISTRY_USERNAME

  • REGISTRY_PASSWORD

  • KUBE_CONFIG

KUBE_CONFIG можно хранить либо как обычный текст kubeconfig, либо как base64 от kubeconfig: workflow поддерживает оба варианта.

По умолчанию workflow создает namespace mosaica и imagePullSecret с именем regcred.

Если в кластере нужен нестандартный ingress class, storage class или TLS, это лучше сразу прописать в chart/mosaica/values.yaml.

Основные маршруты

  • / — открывает content/index.adoc

  • /editor — открывает страницу редактора и требует права доступа

  • /health — health check

  • /api/tree — дерево файлов, требует права доступа

  • /api/tree/create — создать файл/папку, требует права доступа

  • /api/tree/delete — удалить файл/папку, требует права доступа

  • /api/tree/move — переместить/переименовать файл/папку, требует права доступа

  • /api/file — чтение и сохранение файлов, требует права доступа

  • /api/asciidoc/render — рендер AsciiDoc в HTML

  • /api/menu — отдает меню сайта/раздела (type=site|section)

  • /api/auth/session — текущая сессия пользователя

  • /api/auth/register — регистрация

  • /api/auth/login — вход

  • /api/auth/logout — выход

  • /api/auth/profile — обновление профиля

Ограничения

  • приложение работает только с файлами внутри content/

  • выход за пределы базовой директории запрещен

  • бинарные файлы не поддерживаются

  • большие файлы могут быть отклонены по лимиту размера

  • данные пользователей и прав хранятся только в SECURITY.JSON

  • активные сессии хранятся только в SESSIONS.JSON

Пример ссылки внутри контента

link:editor.adoc[Открыть редактор]

или

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"]
    }
  ]
}

© Mijail O Silin aka captGreen, 2026