Este repositório contém uma versão de código aberto do BCMS, um CMS moderno e headless que permite gerenciar facilmente o conteúdo com uma estrutura flexível. Interface amigável e opções de implantação rápidas - o tornam perfeito para desenvolvedores e equipes que procuram uma solução CMS personalizável.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
Depois de ter um servidor baseado em Debian, você pode fazer SSH nele e seguir as etapas abaixo.
Instale dependências se ainda não as tiver no servidor:
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
Como estamos usando pacotes GitHub, você precisará adicionar configuração ao ~/.npmrc
para extrair pacotes. Adicione as duas linhas a seguir:
//npm.pkg.github.com/:_authToken=<GITHUB_TOKEN>
@bcms:registry=https://npm.pkg.github.com
Para gerar um GITHUB_TOKEN
, siga este tutorial. A única permissão necessária para este token é read:packages
.
npm i -g @bcms/selfhosted-cli
selfbcms --deploy debian
Depois de ter um servidor baseado em Debian, você pode fazer SSH nele e seguir as etapas abaixo.
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
Se você não possui o MongoDB, pode executá-lo dentro de um contêiner Docker no mesmo servidor:
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
Com esta configuração, o banco de dados MongoDB será armazenado em ~/bcms/db
e acessível a partir do bcms-net
na porta 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
Se o MongoDB estiver configurado no mesmo servidor, o DB_URL
será mongodb://<DB_ADMIN_USERNAME>:<DB_ADMIN_PASSWORD>@my-bcms-db:27017/admin
.
Para lidar com solicitações recebidas, você precisa configurar um proxy reverso 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;
}
}
}
Esta configuração usa o host virtual Nginx padrão. Para usar um domínio personalizado, ajuste a configuração conforme necessário.
# 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
Aceitamos contribuições!
Em setembro de 2024, fizemos uma grande mudança: o BCMS Cloud passou a ser de código fechado (como BCMS Pro) e a versão de código aberto tornou-se completamente independente.
Por que fizemos isso?
Quando construímos o BCMS pela primeira vez, centralizamos o sistema de autenticação no BCMS Cloud. Mesmo se você fosse auto-hospedado, ainda teria que fazer login em nosso sistema. Achamos que isso simplificaria coisas como convidar usuários, enviar e-mails e gerenciar a integração. Mas então as pessoas no Reddit nos separaram por isso. E eles estavam certos. Então, nós ouvimos.
Outra questão foi manter o BCMS atualizado. Estamos constantemente melhorando isso, mas garantir que sua versão auto-hospedada possa ser atualizada facilmente sempre foi uma dor de cabeça técnica.
A forma como originalmente configuramos o BCMS Cloud também criou problemas. Cada instância teve que ser executada em seu próprio VPS isolado, o que desacelerou as coisas e fez com que os custos de infraestrutura disparassem.
Nosso objetivo sempre foi criar uma experiência de edição de conteúdo rápida e opinativa. Mas tentar manter as versões de código aberto e em nuvem avançando começou a parecer insustentável. Então, tomamos a decisão de separá-los. A comunidade de código aberto agora tem exatamente o que pediu: um BCMS totalmente independente, auto-hospedável e totalmente gratuito.
Enquanto isso, continuaremos avançando em todos os novos recursos - BCMS Pro, agora de código fechado, redesenvolvido do zero e otimizado para aqueles que precisam de uma experiência gerenciada premium.
A equipe principal do BCMS é muito pequena – apenas três engenheiros, um designer, um gerente de projeto e um redator de conteúdo. Portanto, estamos concentrando a maior parte de nossa energia no BCMS Pro, mas estamos entusiasmados em ver até onde a comunidade levará a versão de código aberto.
Boa codificação!
Se você tiver alguma dúvida ou precisar de ajuda, sinta-se à vontade para abrir um problema ou entrar em contato conosco no Discord.
Siga no X (Twitter)
Siga no LinkedIn
Junte-se a nós no Discord
Existem 4 maneiras principais pelas quais você pode estender seu BCMS: Eventos, Funções, Trabalhos e Plugins. Os três primeiros servem apenas para estender a funcionalidade de back-end e executar tarefas de back-end personalizadas, enquanto os plug-ins são aplicativos que possuem seu próprio back-end e UI no topo do núcleo do BCMS.
Eventos BCMS são arquivos JavaScript personalizados (função JS) que são executados quando um evento interno do BCMS é acionado, por exemplo, quando a Entrada é criada, atualizada ou excluída ou quando o Widget é criado, atualizado ou excluído. Lista de todos os tipos de eventos
Criar um manipulador de eventos é tão simples quanto adicionar um diretório root /backend/events
de arquivo. Este diretório está localizado dentro do contêiner do BCMS. Você pode montá-lo como um volume ao executar o contêiner e isso seria parecido com isto: -v <path_to_my_events_dir>:/app/backend/events
.
Se você estiver executando o BCMS localmente a partir deste repositório, poderá adicionar um arquivo de evento a /backend/events/<my_event>.{js|ts}
.
Aqui está um exemplo simples de evento:
// 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 } ) ;
} ,
} ;
} ) ;
Este arquivo será carregado pelo backend do BCMS e executado, o que significa que ele roda no mesmo escopo e você pode usar utilitários internos como Repo.*
para acessar o banco de dados. A execução deste arquivo é irrestrita, todas as permissões que o backend do BCMS possui, seu Evento também terá. Isso não é perigoso? Sim e não, com grande poder vem uma grande responsabilidade. Isso significa que você nunca deve executar código não confiável dentro do Evento.
Agora, e se você quiser importar algum módulo customizado, por exemplo @paralleldrive/cuid2? Você pode fazer isso adicionando-o ao /backend/custom-package.json
que deve ter uma estrutura como esta:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Os pacotes personalizados serão inicializados quando o backend do BCMS for iniciado.
Tem mais uma coisa, e se você tiver alguma lógica compartilhada entre seus Eventos, Jobs e Funções? Estamos chamando esses arquivos adicionais, mas você pode considerá-los seus utilitários personalizados. Dentro de /backend/additional
você pode adicionar qualquer arquivo JS e importá-lo no Evento.
Semelhante aos Eventos BCMS, as Funções BCMS são arquivos JavaScript (função JS) que são executados quando o endpoint da função é chamado: POST: /api/v4/function/<my_function_name>
. Você pode considerá-los como seu código personalizado que será executado no backend do BCMS quando a solicitação HTTP for feita para o terminal de execução da função.
Criar uma função é tão simples quanto adicionar um arquivo ao /backend/functions
. Este diretório está localizado dentro do contêiner do BCMS. Você pode montá-lo como um volume ao executar o contêiner e isso seria parecido com isto: -v <path_to_my_functions_dir>:/app/backend/functions
.
Se você estiver executando o BCMS localmente a partir deste repositório, poderá adicionar um arquivo de função a /backend/functions/<my_function>.{js|ts}
.
Aqui está um exemplo simples de função que ecoará uma solicitação:
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 ,
} ;
} ,
} ;
} ) ;
Para chamar esta Função com sucesso, você precisará criar uma Chave API ( Administração/Gerenciador de Chaves ) e permitir que ela chame a Função. Depois disso, você pode criar uma solicitação HTTP para ele:
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"
// }
// }
// }
Este arquivo será carregado pelo backend do BCMS e executado, o que significa que ele roda no mesmo escopo e você pode usar utilitários internos como Repo.*
para acessar o banco de dados. A execução deste arquivo é irrestrita, todas as permissões que o backend do BCMS possui, sua Função também terá. Isso não é perigoso? Sim e não, com grande poder vem uma grande responsabilidade. Isso significa que você nunca deve executar código não confiável dentro da Função.
Agora, e se você quiser importar algum módulo customizado, por exemplo @paralleldrive/cuid2? Você pode fazer isso adicionando-o ao /backend/custom-package.json
que deve ter uma estrutura como esta:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Os pacotes personalizados serão inicializados quando o backend do BCMS for iniciado.
Tem mais uma coisa, e se você tiver alguma lógica compartilhada entre seus Eventos, Jobs e Funções? Estamos chamando esses arquivos adicionais, mas você pode considerá-los seus utilitários personalizados. Dentro de /backend/additional
você pode adicionar qualquer arquivo JS e importá-lo na Função.
Semelhante aos eventos e funções do BCMS, os trabalhos do BCMS são arquivos JavaScript (função JS) que são executados em um intervalo CRON especificado. Você pode considerá-los como seu código personalizado que será executado no backend do BCMS em um intervalo especificado.
Criar um Job é tão simples quanto adicionar um arquivo ao /backend/jobs
. Este diretório está localizado dentro do contêiner do BCMS. Você pode montá-lo como um volume ao executar o contêiner e isso seria parecido com isto: -v <path_to_my_jobs_dir>:/app/backend/jobs
.
Se você estiver executando o BCMS localmente a partir deste repositório, poderá adicionar um arquivo Job a /backend/jobs/<my_job>.{js|ts}
.
Aqui está um exemplo simples de função que consolará o registro da hora atual a cada minuto:
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 ( ) ) ;
} ,
} ;
} ) ;
Este arquivo será carregado pelo backend do BCMS e executado, o que significa que ele roda no mesmo escopo e você pode usar utilitários internos como Repo.*
para acessar o banco de dados. A execução deste arquivo é irrestrita, todas as permissões que o backend do BCMS possui, seu Job também terá. Isso não é perigoso? Sim e não, com grande poder vem uma grande responsabilidade. Isso significa que você nunca deve executar código não confiável dentro do Job.
Agora, e se você quiser importar algum módulo customizado, por exemplo @paralleldrive/cuid2? Você pode fazer isso adicionando-o ao /backend/custom-package.json
que deve ter uma estrutura como esta:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Os pacotes personalizados serão inicializados quando o backend do BCMS for iniciado.
Tem mais uma coisa, e se você tiver alguma lógica compartilhada entre seus Eventos, Jobs e Funções? Estamos chamando esses arquivos adicionais, mas você pode considerá-los seus utilitários personalizados. Dentro de /backend/additional
você pode adicionar qualquer arquivo JS e importá-lo no Job.
Você pode considerar o Plugin BCMS como um aplicativo com backend e frontend próprios que é servido pelo backend do BCMS e ter acesso a todos os recursos de backend e frontend. Para o backend do Plugin você precisa seguir alguns padrões, mas para a UI do Plugin você pode construir qualquer aplicativo SPA (você pode usar React ou VanillaJS, mas recomendamos VueJS porque você poderá usar componentes de UI do BCMS).
A melhor maneira de explicar isso é dar um exemplo simples. Esta será a estrutura do Plugin:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
Para tornar este exemplo o mais simples possível, todo o código do frontend estará contido em _ui/index.html
enquanto o backend conterá 1 controlador que cumprimentará o usuário.
config.json
{
"version" : " 1 " ,
"dependencies" : {}
}
controlador.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 } !` ,
} ;
} ,
} ) ,
} ;
} ,
} ) ;
principal.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: Explique os plug-ins com mais detalhes e crie plug-ins de exemplo