В этом репозитории хранится версия BCMS с открытым исходным кодом, современная автономная CMS, которая позволяет легко управлять контентом с помощью гибкой структуры. Удобный интерфейс, возможности быстрого развертывания — делают его идеальным для разработчиков и команд, которым требуется настраиваемое решение CMS.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
После того, как у вас есть сервер на базе Debian, вы можете подключиться к нему по SSH и выполнить следующие шаги.
Установите зависимости, если их еще нет на сервере:
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
Поскольку мы используем пакеты GitHub, вам нужно будет добавить конфигурацию в ~/.npmrc
для извлечения пакетов. Добавьте следующие две строки:
//npm.pkg.github.com/:_authToken=<GITHUB_TOKEN>
@bcms:registry=https://npm.pkg.github.com
Чтобы создать GITHUB_TOKEN
, следуйте этому руководству. Единственное разрешение, необходимое для этого токена, — read:packages
.
npm i -g @bcms/selfhosted-cli
selfbcms --deploy debian
После того, как у вас есть сервер на базе Debian, вы можете подключиться к нему по SSH и выполнить следующие шаги.
sudo apt update && sudo apt install docker.io git
mkdir ~ /bcms
mkdir ~ /bcms/db ~ /bcms/uploads ~ /bcms/backups
git clone https://github.com/bcms/cms
docker build . -t my-bcms
docker network create -d bridge --subnet 10.20.0.0/16 --ip-range 10.20.30.0/24 --gateway 10.20.30.1 bcms-net
Если у вас нет MongoDB, вы можете запустить ее внутри контейнера Docker на том же сервере:
docker run -d --name my-bcms-db -v ~ /bcms/db:/data/db -e MONGO_INITDB_ROOT_USERNAME= < DB_ADMIN_USERNAME > -e MONGO_INITDB_ROOT_PASSWORD= < DB_ADMIN_PASSWORD > --network bcms-net mongo:7
При такой настройке база данных MongoDB будет храниться в ~/bcms/db
и доступна из bcms-net
через порт 27017.
docker run -d --name my-bcms -v ~ /bcms/uploads:/app/backend/uploads -v ~ /bcms/backups:/app/backend/backups -e " DB_URL=<MONGODB_CONNECTION_URL> " --network bcms-net my-bcms
Если MongoDB настроен на том же сервере, DB_URL
будет mongodb://<DB_ADMIN_USERNAME>:<DB_ADMIN_PASSWORD>@my-bcms-db:27017/admin
.
Для обработки входящих запросов вам необходимо настроить обратный прокси-сервер Nginx.
# File location: ~/bcms/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768 ;
}
http {
sendfile on ;
tcp_nopush on ;
tcp_nodelay on ;
keepalive_timeout 65 ;
types_hash_max_size 2048 ;
server_tokens off ;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on ;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' blob: data:" ;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "no-referrer" ;
server {
listen 80 default_server ;
listen [::]:80 default_server ;
server_name _;
client_max_body_size 105G ;
location /api/v4/socket {
proxy_http_version 1.1 ;
proxy_set_header Upgrade $http_upgrade ;
proxy_set_header Connection "upgrade" ;
proxy_pass http://my-bcms:8080/api/v4/socket;
}
location /__plugin {
proxy_read_timeout 60 ;
proxy_connect_timeout 60 ;
proxy_send_timeout 60 ;
proxy_pass http://my-bcms:8080/__plugin;
}
location / {
proxy_read_timeout 60 ;
proxy_connect_timeout 60 ;
proxy_send_timeout 60 ;
proxy_pass http://my-bcms:8080;
}
}
}
В этой конфигурации используется виртуальный хост Nginx по умолчанию. Чтобы использовать личный домен, измените конфигурацию по мере необходимости.
# File location: ~/bcms/proxy.Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
docker build . -f proxy.Dockerfile -t my-bcms-proxy
docker run -d -p 80:80 --name my-bcms-proxy --network bcms-net my-bcms-proxy
Мы приветствуем вклады!
В сентябре 2024 года мы совершили большой сдвиг: BCMS Cloud стал закрытым исходным кодом (как BCMS Pro), а версия с открытым исходным кодом стала полностью автономной.
Почему мы это сделали?
Когда мы впервые создали BCMS, мы централизовали систему аутентификации в облаке BCMS. Даже если у вас был собственный хостинг, вам все равно пришлось войти в систему через нашу систему. Мы думали, что это упростит такие вещи, как приглашение пользователей, отправка электронных писем и управление онбордингом. Но потом люди на Reddit разорвали нас за это. И они были правы. Итак, мы прислушались.
Еще одной проблемой было поддержание актуальности BCMS. Мы постоянно совершенствуем его, но обеспечение возможности легкого обновления вашей локальной версии всегда было технической головной болью.
То, как мы изначально настроили BCMS Cloud, также создало проблемы. Каждый экземпляр должен был работать на собственном изолированном VPS, что замедляло работу и увеличивало затраты на инфраструктуру.
Нашей целью всегда было создание быстрого и качественного процесса редактирования контента. Но попытки сохранить как версию с открытым исходным кодом, так и облачную версию начали казаться неустойчивыми. Итак, мы призвали их разделить. Сообщество открытого исходного кода теперь имеет именно то, что они просили: полностью автономную, самостоятельно размещаемую BCMS, совершенно бесплатную.
Тем временем мы будем продолжать продвигать все новые возможности - BCMS Pro, теперь с закрытым исходным кодом, переработанный с нуля и оптимизированный для тех, кому нужен управляемый опыт премиум-класса.
Основная команда BCMS очень маленькая — всего три инженера, один дизайнер, менеджер проекта и автор контента. Итак, мы сосредотачиваем большую часть нашей энергии на BCMS Pro, но нам очень интересно видеть, как сообщество воспримет версию с открытым исходным кодом.
Приятного кодирования!
Если у вас есть какие-либо вопросы или вам нужна помощь, не стесняйтесь открыть вопрос или свяжитесь с нами @ Discord.
Следите за X (Twitter)
Следуйте в LinkedIn
Присоединяйтесь к нам в Discord
Существует 4 основных способа расширения вашей BCMS: события, функции, задания и плагины. Первые 3 предназначены только для расширения функциональности серверной части и выполнения пользовательских задач серверной части, а плагины — это приложения, которые имеют собственную серверную часть и пользовательский интерфейс поверх ядра BCMS.
События BCMS — это пользовательские файлы JavaScript (функция JS), которые выполняются при запуске внутреннего события BCMS, например, когда запись создается, обновляется или удаляется или когда виджет создается, обновляется или удаляется. Список всех типов событий
Создать обработчик событий так же просто, как добавить корневой каталог файла /backend/events
. Этот каталог находится внутри контейнера BCMS. Вы можете смонтировать его как том при запуске контейнера, и это будет выглядеть примерно так: -v <path_to_my_events_dir>:/app/backend/events
.
Если вы используете BCMS локально из этого репозитория, вы можете добавить файл событий в /backend/events/<my_event>.{js|ts}
.
Вот простой пример события:
// Is used locally in /backend/events/test.js
const { createEvent } = require ( '@bcms/selfhosted-backend/event' ) ;
module . exports = createEvent ( async ( ) => {
return {
config : {
// Trigger Event handler only for
// Entry changes
scope : 'entry' ,
// Listen for all Event types:
// (create, update, delete)
type : 'all' ,
} ,
// This method will be executed when
// Event is triggered
async handler ( type , scope , data ) {
// Do something with the event
console . log ( { type , scope , data } ) ;
} ,
} ;
} ) ;
Этот файл будет загружен серверной частью BCMS и выполнен, что означает, что он выполняется в той же области, и вы можете использовать внутренние утилиты, такие как Repo.*
, для доступа к базе данных. Выполнение этого файла не ограничено, все разрешения, которые есть у бэкэнда BCMS, будут иметь и ваше мероприятие. Разве это не опасно? Да и нет, с большой силой приходит и большая ответственность. Это означает, что вам никогда не следует запускать ненадежный код внутри события.
А что, если вы захотите импортировать какой-нибудь собственный модуль, например @paralleldrive/cuid2? Вы можете сделать это, добавив его в /backend/custom-package.json
, который должен иметь такую структуру:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Пользовательские пакеты будут инициализированы при запуске серверной части BCMS.
Есть еще одна вещь: что, если у вас есть общая логика между вашими событиями, заданиями и функциями? Мы называем эти дополнительные файлы, но вы можете рассматривать их как свои собственные утилиты. Внутри /backend/additional
вы можете добавить любой JS-файл и импортировать его в Event.
Подобно событиям BCMS, функции BCMS представляют собой файлы JavaScript (функция JS), которые выполняются при вызове конечной точки функции: POST: /api/v4/function/<my_function_name>
. Вы можете рассматривать их как собственный код, который будет выполняться на серверной части BCMS, когда HTTP-запрос делается к конечной точке выполнения функции.
Создать функцию так же просто, как добавить файл в /backend/functions
. Этот каталог находится внутри контейнера BCMS. Вы можете смонтировать его как том при запуске контейнера, и это будет выглядеть примерно так: -v <path_to_my_functions_dir>:/app/backend/functions
.
Если вы используете BCMS локально из этого репозитория, вы можете добавить файл функции в /backend/functions/<my_function>.{js|ts}
.
Вот простой пример функции, которая будет отображать запрос:
const { createFunction } = require ( '@bcms/selfhosted-backend/function' ) ;
module . exports = createFunction ( async ( ) => {
return {
config : {
name : 'echo' ,
} ,
async handler ( { request } ) {
return {
message : 'Function echo' ,
data : request . body ,
} ;
} ,
} ;
} ) ;
Для успешного вызова этой функции вам необходимо создать ключ API ( администрирование/менеджер ключей ) и разрешить ему вызывать функцию. После этого вы можете создать к нему HTTP-запрос:
POST http://localhost:8080/api/v4/function/echo
Content-Type: application/json
Authorization: ApiKey <key_id>.<key_secret>
{
"fromClient" : " Hey from client "
}
// Response
// {
// "success": true,
// "result": {
// "message": "Function echo",
// "data": {
// "fromClient": "Hey from client"
// }
// }
// }
Этот файл будет загружен серверной частью BCMS и выполнен, что означает, что он выполняется в той же области, и вы можете использовать внутренние утилиты, такие как Repo.*
, для доступа к базе данных. Выполнение этого файла не ограничено, все разрешения, которые есть у бэкэнда BCMS, также будут иметь ваша функция. Разве это не опасно? Да и нет, с большой силой приходит и большая ответственность. Это означает, что вам никогда не следует запускать ненадежный код внутри функции.
А что, если вы захотите импортировать какой-нибудь собственный модуль, например @paralleldrive/cuid2? Вы можете сделать это, добавив его в /backend/custom-package.json
, который должен иметь такую структуру:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Пользовательские пакеты будут инициализированы при запуске серверной части BCMS.
Есть еще одна вещь: что, если у вас есть общая логика между вашими событиями, заданиями и функциями? Мы называем эти дополнительные файлы, но вы можете рассматривать их как свои собственные утилиты. Внутри /backend/additional
вы можете добавить любой файл JS и импортировать его в функцию.
Подобно событиям и функциям BCMS, задания BCMS представляют собой файлы JavaScript (функция JS), которые выполняются с указанным интервалом CRON. Вы можете рассматривать их как собственный код, который будет выполняться на сервере BCMS через заданный интервал.
Создать задание так же просто, как добавить файл в /backend/jobs
. Этот каталог находится внутри контейнера BCMS. Вы можете смонтировать его как том при запуске контейнера, и это будет выглядеть примерно так: -v <path_to_my_jobs_dir>:/app/backend/jobs
.
Если вы используете BCMS локально из этого репозитория, вы можете добавить файл задания в /backend/jobs/<my_job>.{js|ts}
.
Вот простой пример функции, которая каждую минуту будет регистрировать текущее время:
const { createJob } = require ( '@bcms/selfhosted-backend/job' ) ;
module . exports = createJob ( async ( ) => {
return {
cronTime : '* * * * *' , // You can use: https://crontab.guru/
async handler ( ) {
console . log ( new Date ( ) . toISOString ( ) ) ;
} ,
} ;
} ) ;
Этот файл будет загружен серверной частью BCMS и выполнен, что означает, что он выполняется в той же области, и вы можете использовать внутренние утилиты, такие как Repo.*
, для доступа к базе данных. Выполнение этого файла не ограничено, все разрешения, которые есть у бэкэнда BCMS, будет иметь и ваше задание. Разве это не опасно? Да и нет, с большой силой приходит и большая ответственность. Это означает, что вам никогда не следует запускать ненадежный код внутри задания.
А что, если вы захотите импортировать какой-нибудь собственный модуль, например @paralleldrive/cuid2? Вы можете сделать это, добавив его в /backend/custom-package.json
, который должен иметь такую структуру:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Пользовательские пакеты будут инициализированы при запуске серверной части BCMS.
Есть еще одна вещь: что, если у вас есть общая логика между вашими событиями, заданиями и функциями? Мы называем эти дополнительные файлы, но вы можете рассматривать их как свои собственные утилиты. Внутри /backend/additional
вы можете добавить любой JS-файл и импортировать его в Job.
Вы можете рассматривать плагин BCMS как приложение с собственным серверным и внешним интерфейсом, которое обслуживается серверной частью BCMS и имеет доступ ко всем функциям внутреннего и внешнего интерфейса. Для серверной части плагина вам необходимо следовать некоторым шаблонам, но для пользовательского интерфейса плагина вы можете создать любое SPA-приложение (вы можете использовать React или VanillaJS, но мы рекомендуем VueJS, поскольку вы сможете использовать компоненты пользовательского интерфейса BCMS).
Лучший способ объяснить это — привести простой пример. Это будет структура плагина:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
Чтобы максимально упростить этот пример, весь код внешнего интерфейса будет содержаться в _ui/index.html
а внутренний код будет содержать 1 контроллер, который будет приветствовать пользователя.
config.json
{
"version" : " 1 " ,
"dependencies" : {}
}
контроллер.js
const { createPluginController } = require ( '@bcms/selfhosted-backend/plugin' ) ;
const { createControllerMethod } = require ( '@bcms/selfhosted-backend/_server' ) ;
const {
RP ,
} = require ( '@bcms/selfhosted-backend/security/route-protection/main' ) ;
exports . HelloWorldController = createPluginController ( {
name : 'NameGreet' ,
path : '/greet' ,
methods ( ) {
return {
greet : createControllerMethod ( {
path : '/:name' ,
type : 'get' ,
preRequestHandler : RP . createJwtCheck ( ) ,
async handler ( { request } ) {
const params = request . params ;
return {
greet : `Hello ${ params . name } !` ,
} ;
} ,
} ) ,
} ;
} ,
} ) ;
main.js
const { createPlugin } = require ( '@bcms/selfhosted-backend/plugin' ) ;
const { HelloWorldController } = require ( './controller' ) ;
module . exports = createPlugin ( async ( ) => {
return {
id : 'hello-world' ,
name : 'Hello World' ,
controllers : [ HelloWorldController ] ,
middleware : [ ] ,
async policy ( ) {
return [
{
name : 'Full access' ,
} ,
] ;
} ,
} ;
} ) ;
_ui/index.html
<!DOCTYPE html >
< html lang =" en " >
< head >
< meta charset =" UTF-8 " />
< title > Hello world </ title >
< style >
body {
color: white;
}
</ style >
</ head >
< body >
< h1 > Hello world </ h1 >
< div >
< input id =" greet-input " placeholder =" Greet " type =" text " />
< button onclick =" greet(this) " > Send </ button >
< div id =" greet-result " > </ div >
</ div >
< h2 > List of templates </ h2 >
< div id =" templates " > </ div >
< script >
async function onLoad ( ) {
const templates =
await window . parent . bcms . sdk . template . getAll ( ) ;
const el = document . getElementById ( 'templates' ) ;
if ( el ) {
el . innerHTML = `<pre> ${ JSON . stringify (
templates ,
null ,
4 ,
) } </pre>` ;
}
window . removeEventListener ( 'load' , onLoad ) ;
}
window . addEventListener ( 'load' , onLoad ) ;
async function greet ( ) {
const inputEl = document . getElementById ( 'greet-input' ) ;
const value = inputEl . value ;
const result = await window . parent . bcms . sdk . send ( {
url : `/api/v4/plugin/hello-world/greet/ ${ value } ` ,
} ) ;
const el = document . getElementById ( 'greet-result' ) ;
el . innerHTML = `<pre> ${ JSON . stringify ( result , null , 4 ) } </pre>` ;
}
</ script >
</ body >
</ html >
TODO: Более подробно объяснить плагины и создать примеры плагинов.