
pacod-cms — система управления контентом с открытым исходным кодом для редакционных сайтов. Одна инсталляция обслуживает множество сайтов, у каждого из которых своя база данных, файлы и кэш; редакторы работают в многоязычной студии, а читатели получают быстрые публичные сайты с маршрутизацией по хосту.
Управление контентом
- Лицензия: Apache-2.0
- Версия: 0.0.1 · Предварительная
- Языки: Java
- Технологии: Java 25, Spring Boot 4.1, Thymeleaf, MongoDB, Maven, Caddy, Gotenberg, Docker
- Открыть на GitHub · Создать issue
Возможности
Редакционное ядро
Статьи, рубрики, авторы, теги и постоянные страницы, а также общая библиотека изображений для всего сайта.
Мультисайтовость изначально
У каждого сайта своя база данных MongoDB, файлы и кэш, и он отвечает на тех хостах, которые за ним закреплены.
Реклама и статистика
Размещение рекламы с конструктором HTML5-баннеров, страница link-in-bio для профилей в соцсетях и статистика по читателям, рекламе и краулерам, чтобы редакторы видели, что читают и что индексируют. Размещение по отдельным рубрикам и иконка GitHub для страницы ссылок на подходе (ещё не выпущено, в ветке blog-site).
Многоязычность
Контент на многих языках с настраиваемым языком оригинала и AI-перевод статьи или целого сайта в виде задачи, где любой ответ отклоняется, если в нём не сохранена в точности разметка статьи; сама студия — на английском, болгарском, немецком и русском. Русский язык для собственных надписей публичного сайта на подходе (ещё не выпущено, в ветке blog-site).
AI-помощь в написании
Заголовки, целая статья по заданию, фрагмент в позиции курсора и текст, свёрстанный стилизованными блоками без потери хотя бы одного слова автора.
AI-дизайн
Тема, макет страницы или дизайн всего сайта по нескольким словам, как доработка текущего черновика; только черновики, с одной отменой.
Организации и роли
Организации, сайты и приглашения, с ролями на уровне организации и сайта, которые хранятся в pacod-id.
PDF и выпуски журнала
PDF статей в виде фоновой задачи, в печатном оформлении сайта или в научном формате в две колонки, с аннотацией и ключевыми словами из статьи или написанными AI; выпуск журнала собирает статьи в один PDF на каждый язык с обложкой, редакционной статьёй, содержанием и нумерацией страниц и публикуется в архиве сайта.
Экспорт и восстановление
Сайт или целая организация скачивается одним ZIP-файлом и восстанавливается как новый — в этой инсталляции или в другой.

Скриншоты





Один стек, множество сайтов
pacod-cms обслуживает множество сайтов множества организаций из одного стека. У каждого сайта своя база данных MongoDB, своя папка с файлами и свои записи в кэше, и он отвечает на тех хостах, которые за ним закреплены. Любой зарегистрировавшийся может создать организацию, стать её администратором, создавать в ней сайты и приглашать людей для работы над ними; лимиты задаёт администратор платформы.
Архитектура исходит из того, что владелец сайта может быть враждебен ко всем остальным в инсталляции. Сайты выполняют скрипты, написанные их владельцами, — HTML5-баннеры, менеджеры тегов, тексты статей, — и платформа должна держать эти скрипты и эти данные там, где им место.
Как устроены части
| Часть | Что делает |
|---|---|
pacod-cms-core |
Библиотека: документы, сервисы, мультиарендность, управляющий слой, начальное содержимое, модель дизайна и порт движка правил pacod-id на Java. |
pacod-cms-site |
Публичный сайт сразу для всех сайтов: страницы, JSON только для чтения под /web/v1/**, медиафайлы, счётчики трафика. |
pacod-cms-studio |
Студия: портал для организаций, сайтов и участников, экраны редактирования, фоновые задачи, AI. |
| Caddy | Входная дверь, маршрутизирует по хосту: хост студии — в студию, хост pacod-id — в pacod-id, любой другой хост — на сайт. |
| Gotenberg | Создаёт PDF статей и выпусков журнала из версий сайта для печати. |
Два приложения никогда не обращаются друг к другу. Оба работают с MongoDB напрямую, поэтому правка, сделанная в студии, доходит до публичного сайта через базу данных; кэши сайта истекают через 60 секунд, а не сбрасываются, так что правка появляется на сайте в течение минуты.
От хоста до страницы
Каждая единица работы относится ровно к одному сайту, который привязан к ней на всё время её выполнения.
- Caddy передаёт запрос сайту. В продакшене Caddy запрашивает сертификат сайта при первом посещении, но только после того, как сайт подтвердит, что хост принадлежит активному сайту.
- Сайт ищет хост в управляющем слое раньше, чем выполняется что-либо ещё. Хост, который не закреплён ни за одним активным сайтом, получает простой 404, а не сайт по умолчанию.
- Когда сайт привязан, каждая операция с MongoDB идёт в базу данных этого сайта. Если сайт не привязан, слой доступа к данным выбрасывает ошибку, а не угадывает.
- Макет типа страницы — области с упорядоченными секциями — разрешается относительно набора шаблонов сайта, а данные каждой секции загружаются заранее. Шаблоны никогда не делают запросов; секция, данные которой не загрузились, пропускается и записывается в лог, а страница всё равно отображается.
В студии сайт определяется по пути (/sites/<id>/mgmt/…); фоновая работа привязывает тот сайт,
для которого выполняется.
Где хранятся данные
| Где | Что |
|---|---|
pacod_cms |
Управляющий слой: организации, сайты, домены, настройки платформы, очередь задач. |
site_<id> |
Отдельная база данных для каждого сайта: статьи, страницы, изображения, баннеры, меню, настройки, макеты, тема, статистика. |
sites/<id>/ |
Файлы сайта: изображения к статьям, рекламные креативы, PDF. PDF черновика выпуска лежат в закрытой папке, которую отдаёт только студия. |
| pacod-id | Люди, сессии и назначенные роли. CMS не хранит учётных записей. |
Идентичность сайта — название, логотипы, контактные данные, идентификаторы сторонних сервисов —
это данные, а не разметка. Сторонний идентификатор выводится, только если он задан и имеет формат
своего поставщика, поэтому новый сайт не загружает никакого отслеживания. Исходный язык
содержимого сайта — последний запасной вариант при отсутствии перевода и юридически определяющий
текст. Канонический адрес, hreflang и метаданные статьи в head каждой страницы для читателей, а также
Atom-ленты для каждого сайта и каждой рубрики на подходе (ещё не выпущено, в ветке blog-site).
Дизайн без кода
В разделе Дизайн студии каждый сайт выбирает один из трёх наборов шаблонов — editorial,
главная страница из сеток статей с колонкой рядом с содержимым; minimal, одна спокойная колонка;
или magazine, построенный на изображениях, с главным материалом во всю ширину, фотографиями
мозаикой и статьями, которые открываются своим снимком, — и составляет макет для каждого типа
страницы: главная, статья, рубрика, последние, постоянная страница и поиск. Среди секций — списки
статей (последние, из рубрики или самые читаемые), форматированный текст, призыв к действию, поле
поиска и ссылки на соцсети; секция, показывающая текст постоянной страницы на языке читателя, на
подходе (ещё не выпущено, в ветке blog-site). Тема задаёт цвета со значениями для ночного режима,
шрифты и ширины.
Текст статьи может содержать стилизованные блоки из закрытого списка: вводный абзац, врезку с
примечанием, вынесенную цитату, врезку с главными фактами, две колонки и иллюстрацию с подписью —
обычную или прижатую влево или вправо. Сайты оформляют их по теме, в том числе в версиях для печати,
а значит, и в PDF, а редактор показывает их так, как увидят читатели. Пять из них есть в меню
Блоки редактора; AI-вёрстка может использовать все шесть. Примеры кода, вставляемые из
редактора и подсвечиваемые на сайтах с помощью Prism, на подходе (ещё не выпущено, в ветке blog-site).
Смена набора сначала показывает, что будет потеряно, и ничего не удаляется: секции, которые новый набор не может показать, сохраняются и возвращаются при обратной смене. Макеты и тема остаются черновиками, пока их не опубликует тот, у кого есть на это право, — кто бы их ни написал, человек или AI. Помощник по дизайну набрасывает тему, макет или дизайн всего сайта по нескольким словам; подробности — в разделе об AI ниже. Предпросмотр показывает черновики с собственного адреса сайта, в ширине компьютера, планшета или телефона, по подписанному токену, действующему 30 минут, и ничего не учитывает в статистике.
Новый набор шаблонов — это код, который устанавливает оператор, а не загружаемый файл: шаблон Thymeleaf может всё, что может сам сайт.
PDF и выпуски журнала
Долгая работа выполняется в студии как задача, не более одной на организацию одновременно, с живым логом и кнопкой отмены. Экспорт в PDF — одна из таких задач: Gotenberg превращает каждую статью в отдельный PDF на каждом языке, на котором она написана, а статья ссылается на свой PDF, как только он появится.
- Два формата. Стандартный — это версия сайта для печати. Научный — макет научной статьи: шапка на всю ширину с заголовком, авторами, аннотацией и ключевыми словами, затем текст в две колонки.
- Два способа создания. Только код берёт аннотацию и ключевые слова из описания и тегов статьи. С AI поручает pacod-gw написать аннотацию, ключевые слова и короткий колонтитул для каждой статьи и каждого языка. Текст статьи никогда не меняется.
- Настройки PDF хранят значения по умолчанию для каждого сайта: формат и способ, бумагу (A4, Letter или A5) и поля, название журнала на каждом языке и его ISSN, колонтитул и номера страниц. Любой запуск может выбрать другой формат и способ.
Выпуск журнала собирает статьи в один PDF на каждый язык. У него есть том, номер и месяц,
заголовок и редакционная статья на каждом языке и, по желанию, изображение для обложки; статьи
выбираются по одной или подбираются по его месяцу, а затем упорядочиваются. При генерации создаются
обложка, редакционная статья, содержание с номерами страниц, а затем статьи, с закладкой для каждой
статьи и сквозной нумерацией страниц. PDF черновика выпуска скачиваются только из студии. После
публикации выпуск и его PDF появляются на сайте по адресу /issues и в карте сайта.
Модель безопасности
Кто что может
Учётные записи и роли принадлежат pacod-id. Токены не несут ролей: студия следит за потоком
назначенных ролей pacod-id и сама решает каждое разрешение по манифесту CMS (authz/cms.yaml). Роли
таковы: администратор платформы, провижнер, администратор организации, администратор сайта,
дизайнер, редактор, автор и аналитик — на уровне платформы, организации или сайта. Автор пишет;
редактор ещё и публикует.
- Каждый обработчик студии объявляет нужное ему разрешение; обработчик, который ничего не объявил, отклоняется. Неявных ролей нет.
- Если поток pacod-id не отвечал минуту, студия отвечает 503, а не принимает решение по ролям, которые, возможно, уже отозваны. Публичные сайты это не затрагивает.
Сайты изолированы друг от друга
- Студия и pacod-id находятся на другом регистрируемом домене, чем сайты, иначе студия не
запускается. В противном случае скрипт сайта мог бы устанавливать для них cookie и обойти их
защиту от CSRF. Инсталляция, все сайты которой доверенные, например демо на одном домене, может
принять этот риск с
PACOD_SITE_DOMAIN_SHARED=true: студия тогда запускается с предупреждением, а сайт всё равно никогда не получит собственный хост студии или pacod-id. - Студия никогда не отдаёт HTML или скрипты сайта. Предпросмотр черновиков и HTML5-баннеров встраивается с собственного origin сайта.
- Gotenberg изолирован: он может обращаться только к внутреннему адресу сайта и не выполняет JavaScript.
- До читателя доходят только опубликованные статьи — на каждом маршруте, в каждом списке, карте сайта и PDF. Черновик выпуска журнала никогда не бывает публичным.
AI, который не трогает ваши ключи
Каждый вызов AI проходит через pacod-gw; студия никогда не хранит ключ поставщика. Для новых организаций AI по умолчанию выключен: администратор платформы включает его для организации, а администратор сайта может отключить его для своего сайта.
Тексты
В редакторе статей помощь в написании работает с текстом в его текущем виде, сохранён он или нет:
- Переформулировать выделенный текст шестью способами: яснее, короче, подробнее, без ошибок, официальнее или дружелюбнее.
- Заголовки: до трёх вариантов заголовка статьи.
- Черновик целой статьи — заголовок, описание и текст — по заданию, короткий, средний или длинный (около 300, 600 или 1200 слов), на исходном языке сайта. Используются только факты из задания; там, где факта не хватает, ставится заполнитель в квадратных скобках, который заполнит автор, и ничего не выдумывается.
- Написать в позиции курсора: фрагмент по заданию, на языке и в манере окружающего текста, по тому же правилу о фактах.
- Сверстать выделенный фрагмент или весь текст: заголовки, списки, выделения и стилизованные блоки. Каждое слово автора должно остаться на своём месте по порядку, иначе ответ отклоняется. Текст с фреймом, видео, скриптом или формой отклоняется ещё до вызова, потому что вёрстка бы их потеряла.
- Анонс и теги для статьи и альтернативный текст на каждом языке, который модель компьютерного зрения пишет по самому изображению.
Ответы очищаются до короткого списка тегов и схем ссылок (http, https, mailto, относительные). Класс
сохраняется, только если он называет стилизованный блок на его собственном элементе; никаких
style, скриптов, изображений, которых не было в тексте автора, и ссылок на адреса, которых не
давали текст автора, задание или статья. Числа, имена и вынесенные цитаты, которые есть в ответе, но
нет в его материале, перечисляются рядом с ним, чтобы автор их проверил, — это эвристика, а не
доказательство. Переформулировка, фрагмент или
вёрстка сначала показываются в предпросмотре и вставляются одним шагом отмены; черновик заполняет
форму. Ничего не сохраняется, пока автор не сохранит.
Дизайн
- Помощник по дизайну набрасывает тему или макет одного типа страницы по нескольким словам. По умолчанию он дорабатывает текущий черновик, который видит модель, и меняет только то, о чём просит задание; по запросу начинает заново. Каждый ответ проверяется так, будто его ввёл оператор, а один щелчок отменяет изменение или возвращает его.
- Дизайн всего сайта — тема и макет каждого типа страницы — составляется по одному заданию в виде задачи. Каждый шаг самостоятелен, так что неудавшийся шаг не мешает остальным; задачу можно безопасно запустить заново, отменить, и она учитывает свою стоимость. У сайта одновременно бывает не больше одной задачи дизайна.
В обоих случаях меняются только черновики; публиковать по-прежнему должен тот, у кого есть право.
Перевод и PDF
- Перевод статей выполняется как задача. Переведённый текст должен в точности сохранить
разметку оригинала — меняться могут только
alt,title,aria-labelиplaceholder, — иначе он отклоняется. - Перевод сайта охватывает постоянные страницы, названия разделов, меню, страницу ссылок и, если отмечено, теги, не перезаписывая правки, сделанные во время выполнения; не более 2000 вызовов за запуск.
- Шапка PDF: для PDF, созданных с AI, аннотация, ключевые слова и колонтитул для каждой статьи и каждого языка, проверенные как обычный текст. Они используются повторно, пока статью не отредактируют.
Что должен пройти каждый ответ
- Каждый ответ должен соответствовать строгой JSON-схеме. Оборванный ответ или отказ модели считается ошибкой, и ничего из него не записывается.
- Текст человека передаётся как материал, и модели сказано, что он никогда не является инструкцией, даже если звучит как она.
- Шлюз видит только непрозрачные идентификаторы, никогда не имена и не адреса электронной почты.
Три окружения, один Makefile
| dev | stage | prod | |
|---|---|---|---|
| Входная дверь | HTTP, плюс HTTPS от локального CA Caddy | HTTPS (Let's Encrypt) | HTTPS (Let's Encrypt или собственная пара из сертификата и ключа) |
| Почта pacod-id | перехватывает mailpit | перехватывает mailpit | ваш релей |
| AI | мок-шлюз | мок-шлюз | ваш pacod-gw или выключен |
| Демонстрационные сайты | да | да | нет |
Stage — это конфигурация продакшена с перехваченной почтой и имитацией AI. make check не даёт
запуститься, пока не хватает секрета или конфигурация небезопасна. Делайте резервные копии .env,
баз данных MongoDB, файлов сайтов и PostgreSQL pacod-id. /mgmt-health студии проверяет только, что
она жива: студия, которая не может достучаться до pacod-id, остаётся «здоровой», пока её экраны
отвечают 503, и сообщает об этом в своём логе. Сайт и студия пишут логи в общем формате pacod, в
виде JSON или текста (APP_LOG_FORMAT).
Экспорт и восстановление переносят сайт из одной инсталляции в другую. Сайт или целая организация скачивается из студии одним ZIP-файлом с содержимым и файлами, без статистики посещений, участников и хостов. Восстановление проверяет весь архив, прежде чем что-либо создать, и всегда создаёт новый сайт или новую организацию — в этой инсталляции или в другой; загрузки до 2 GiB принимаются только по этим двум путям. Резервные копии всей платформы — по-прежнему дампы MongoDB.
Новая инсталляция в dev или stage создаёт три демонстрационных сайта: Demo Journal (editorial),
Demo Notes (minimal) и Starward (magazine) — журнал о космических путешествиях по вымышленной
звёздной системе: 24 объёмные статьи, в которых использованы все стилизованные блоки, пять авторов,
собственные страницы, меню, макеты, тема и два опубликованных выпуска журнала, 37 фотографий NASA с
указанием авторства и пять вымышленных рекламодателей с HTML5-креативами.
make site-create … DEMO=space или make seed SITE=<id> DEMO=space
даёт другому сайту содержимое Starward. Наполнение сайта из собственной папки (SEED_DIR=<folder>),
которая целиком проверяется до того, как что-либо будет записано, на подходе (ещё не выпущено, в
ветке blog-site).
Каждое окружение работает в pacod-net, Docker-сети, общей для всех проектов pacod. Адреса, к
которым обращаются только серверы, например внутренний адрес pacod-id, находятся во второй,
внутренней сети pacod-admin-net, к которой подключается студия. Собственные
сервисы CMS — сайт, студия, MongoDB и Gotenberg — описаны в одном compose-файле; её собственные копии
общих компонентов — Caddy, pacod-id со своим PostgreSQL, шлюз для разработки и перехватчик почты — во
втором, который Makefile загружает вместе с первым. На одной машине помещается только один стек с
общими компонентами; работа за Caddy и pacod-id, которые уже запущены на хосте
(PROXY_MODE=external), на подходе (ещё не выпущено, в ветке blog-site).
pacod-demo загружает только первый файл
и запускает CMS вместе с остальными проектами за одним Caddy, одним pacod-id, одним PostgreSQL и
одним шлюзом; он доступен по адресу demo.pacod.io.
Что протестировано и каковы ограничения
- Модульные тесты запускаются с болгарским в качестве языка оригинала, чтобы проверялась именно конфигурация не по умолчанию.
- Интеграционные тесты запускают два сайта в одной настоящей MongoDB и проверяют, что они остаются изолированными: маршрутизация, определение хоста, кэши, хранилище и статистика.
make e2eпроверяет вход и портал на запущенном стеке.- Новые обращения к AI — карточка черновика, список заголовков, доработка и отмена в помощнике по дизайну, задача дизайна и список фактов для проверки — на запущенном стеке ещё не проверены.
Ограничения и что почитать
Известные ограничения v1:
- У идентичности сайта (
siteSettings) пока нет экрана в студии; она редактируется в базе данных. - Демонстрационное содержимое написано на английском, поэтому его может получить только сайт, язык содержимого которого — английский.
- Список участников не показывает роли уровня платформы.
Дорожная карта перечисляет, что v1 откладывает, в том числе API для записи, ключ шлюза и бюджет на AI для каждой организации, отдельный обработчик, хранилище S3 и импорт содержимого.
В репозитории:
- Архитектура — как запрос находит свой сайт
- Безопасность — что разделяет сайты и студию
- Наборы шаблонов — как написать собственный набор
- Запуск и Развёртывание — окружения, TLS, хосты, настройки
- Дорожная карта — что входит в v1 и что отложено
Быстрый старт
Машина разработчика
make init
make up
open http://studio.localhost:8000/
Ещё один сайт из командной строки
make site-create SLUG=acme NAME="Acme News" HOSTS=acme.localhost DEMO=1
Работает с
- pacod-id: Студия выполняет вход пользователей через pacod-id, где также хранятся их роли на уровне организации и сайта.
- pacod-gw: AI-функции работают через шлюз — студия никогда не хранит ключ провайдера.
- pacod-demo: Запускает CMS вместе с остальными проектами за одним Caddy, одним pacod-id и одним шлюзом — живая студия и три демонстрационных сайта.
Собственный код проекта распространяется под Apache-2.0. Студия включает редактор TinyMCE (GPL-2.0-or-later), поэтому собранная студия в целом распространяется на условиях GPL-3.0. Публичный сайт его не содержит.