Este repositorio contiene una versión de código abierto de BCMS, un CMS moderno y sin cabeza que le permite administrar fácilmente contenido con una estructura flexible. Interfaz fácil de usar y opciones de implementación rápidas: lo hacen perfecto para desarrolladores y equipos que buscan una solución CMS personalizable.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
Una vez que tenga un servidor basado en Debian, puede ingresar mediante SSH y seguir los pasos a continuación.
Instale dependencias si aún no las tiene en el servidor:
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
Dado que estamos usando paquetes de GitHub, deberá agregar configuración a ~/.npmrc
para extraer los paquetes. Agregue las dos líneas siguientes:
//npm.pkg.github.com/:_authToken=<GITHUB_TOKEN>
@bcms:registry=https://npm.pkg.github.com
Para generar un GITHUB_TOKEN
, sigue este tutorial. El único permiso necesario para este token es read:packages
.
npm i -g @bcms/selfhosted-cli
selfbcms --deploy debian
Una vez que tenga un servidor basado en Debian, puede ingresar mediante SSH y seguir los pasos a continuación.
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
Si no tienes MongoDB, puedes ejecutarlo dentro de un contenedor Docker en el mismo 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
Con esta configuración, la base de datos MongoDB se almacenará en ~/bcms/db
y se podrá acceder a ella desde bcms-net
en el puerto 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
Si MongoDB está configurado en el mismo servidor, DB_URL
será mongodb://<DB_ADMIN_USERNAME>:<DB_ADMIN_PASSWORD>@my-bcms-db:27017/admin
.
Para manejar las solicitudes entrantes, debe configurar un proxy inverso de 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 configuración utiliza el host virtual Nginx predeterminado. Para utilizar un dominio personalizado, ajuste la configuración según sea necesario.
# 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
¡Agradecemos las contribuciones!
En septiembre de 2024, hicimos un gran cambio: BCMS Cloud pasó a ser de código cerrado (como BCMS Pro) y la versión de código abierto se volvió completamente independiente.
¿Por qué hicimos esto?
Cuando creamos BCMS por primera vez, centralizamos el sistema de autenticación en BCMS Cloud. Incluso si fuera autohospedador, aún tenía que iniciar sesión a través de nuestro sistema. Pensamos que esto simplificaría cosas como invitar a usuarios, enviar correos electrónicos y gestionar la incorporación. Pero luego la gente en Reddit nos destrozó por eso. Y tenían razón. Entonces escuchamos.
Otro problema era mantener actualizado BCMS. Lo mejoramos constantemente, pero asegurarnos de que su versión autohospedada pueda actualizarse fácilmente siempre fue un dolor de cabeza técnico.
La forma en que configuramos originalmente BCMS Cloud también creó problemas. Cada instancia tenía que ejecutarse en su propio VPS aislado, lo que ralentizó las cosas e hizo que los costos de infraestructura se dispararan.
Nuestro objetivo siempre ha sido crear una experiencia de edición de contenido rápida y obstinada. Pero tratar de mantener las versiones de código abierto y de nube avanzando comenzó a parecer insostenible. Entonces, hicimos el llamado para dividirlos. La comunidad de código abierto ahora tiene exactamente lo que pidió: un BCMS totalmente autónomo y autohospedable, completamente gratuito.
Mientras tanto, seguiremos avanzando en todas las novedades: BCMS Pro, ahora de código cerrado, rediseñado desde cero y optimizado para aquellos que necesitan una experiencia administrada premium.
El equipo central de BCMS es muy pequeño: sólo tres ingenieros, un diseñador, un director de proyecto y un redactor de contenidos. Por lo tanto, estamos centrando la mayor parte de nuestra energía en BCMS Pro, pero estamos emocionados de ver a dónde lleva la comunidad la versión de código abierto.
¡Feliz codificación!
Si tiene alguna pregunta o necesita ayuda, no dude en abrir un problema o comunicarse con nosotros en Discord.
Sigue en X (Twitter)
Seguir en LinkedIn
Únase a nosotros en Discord
Hay 4 formas principales en las que puede ampliar su BCMS y son Eventos, Funciones, Trabajos y Complementos. Los primeros 3 son solo para ampliar la funcionalidad del backend y realizar tareas de backend personalizadas, mientras que los complementos son aplicaciones que tienen su propio backend y UI además del núcleo de BCMS.
Los eventos BCMS son archivos JavaScript personalizados (función JS) que se ejecutan cuando se activa un evento BCMS interno, por ejemplo, cuando se crea, actualiza o elimina una entrada o cuando se crea, actualiza o elimina un widget. Lista de todos los tipos de eventos
Crear un controlador de eventos es tan simple como agregar un directorio raíz de archivos /backend/events
. Este directorio se encuentra dentro del contenedor BCMS. Puede montarlo como un volumen cuando ejecuta el contenedor y se vería así: -v <path_to_my_events_dir>:/app/backend/events
.
Si está ejecutando BCMS localmente desde este repositorio, puede agregar un archivo de evento a /backend/events/<my_event>.{js|ts}
.
Aquí hay un evento de ejemplo simple:
// 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 archivo será cargado por el backend de BCMS y ejecutado, lo que significa que se ejecuta en el mismo ámbito y puede usar utilidades internas como Repo.*
para acceder a la base de datos. La ejecución de este archivo no tiene restricciones, todos los permisos que tiene el backend de BCMS, su evento también los tendrá. ¿No es esto peligroso? Sí y no, un gran poder conlleva una gran responsabilidad. Esto significa que nunca debes ejecutar código que no sea de confianza dentro del evento.
Ahora, ¿qué sucede si desea importar algún módulo personalizado, por ejemplo @paralleldrive/cuid2? Puedes hacer esto agregándolo a /backend/custom-package.json
, que debería tener una estructura como esta:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Los paquetes personalizados se inicializarán cuando se inicie el backend de BCMS.
Hay una cosa más, ¿qué pasa si tienes alguna lógica compartida entre tus Eventos, Trabajos y Funciones? A esos archivos los llamamos adicionales, pero puede verlos como sus utilidades personalizadas. Dentro de /backend/additional
puedes agregar cualquier archivo JS e importarlo en el Evento.
De manera similar a los eventos BCMS, las funciones BCMS son archivos JavaScript (función JS) que se ejecutan cuando se llama al punto final de la función: POST: /api/v4/function/<my_function_name>
. Puede verlos como su código personalizado que se ejecutará en el backend de BCMS cuando se realice una solicitud HTTP para funcionar como punto final de ejecución.
Crear una función es tan simple como agregar un archivo al /backend/functions
. Este directorio se encuentra dentro del contenedor BCMS. Puede montarlo como un volumen cuando ejecuta el contenedor y se vería así: -v <path_to_my_functions_dir>:/app/backend/functions
.
Si está ejecutando BCMS localmente desde este repositorio, puede agregar un archivo de función a /backend/functions/<my_function>.{js|ts}
.
Aquí hay una función de ejemplo simple que hará eco de una solicitud:
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 llamar correctamente a esta función, deberá crear una clave Api ( administración/administrador de claves ) y permitirle llamar a la función. Después de esto, puedes crearle una solicitud 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"
// }
// }
// }
Este archivo será cargado por el backend de BCMS y ejecutado, lo que significa que se ejecuta en el mismo ámbito y puede usar utilidades internas como Repo.*
para acceder a la base de datos. La ejecución de este archivo no tiene restricciones, todos los permisos que tiene el backend de BCMS, su función también los tendrá. ¿No es esto peligroso? Sí y no, un gran poder conlleva una gran responsabilidad. Esto significa que nunca debes ejecutar código que no sea de confianza dentro de la función.
Ahora, ¿qué sucede si desea importar algún módulo personalizado, por ejemplo @paralleldrive/cuid2? Puedes hacer esto agregándolo a /backend/custom-package.json
, que debería tener una estructura como esta:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Los paquetes personalizados se inicializarán cuando se inicie el backend de BCMS.
Hay una cosa más, ¿qué pasa si tienes alguna lógica compartida entre tus Eventos, Trabajos y Funciones? A esos archivos los llamamos adicionales, pero puede verlos como sus utilidades personalizadas. Dentro de /backend/additional
puede agregar cualquier archivo JS e importarlo en la Función.
De manera similar a los eventos y funciones de BCMS, los trabajos de BCMS son archivos JavaScript (función JS) que se ejecutan en un intervalo CRON específico. Puede verlos como su código personalizado que se ejecutará en el backend de BCMS en un intervalo específico.
Crear un trabajo es tan simple como agregar un archivo al /backend/jobs
. Este directorio se encuentra dentro del contenedor BCMS. Puede montarlo como un volumen cuando ejecuta el contenedor y se vería así: -v <path_to_my_jobs_dir>:/app/backend/jobs
.
Si está ejecutando BCMS localmente desde este repositorio, puede agregar un archivo de trabajo a /backend/jobs/<my_job>.{js|ts}
.
A continuación se muestra una función de ejemplo sencilla que registrará en la consola la hora actual 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 archivo será cargado por el backend de BCMS y ejecutado, lo que significa que se ejecuta en el mismo ámbito y puede usar utilidades internas como Repo.*
para acceder a la base de datos. La ejecución de este archivo no tiene restricciones, todos los permisos que tiene el backend de BCMS, su trabajo también los tendrá. ¿No es esto peligroso? Sí y no, un gran poder conlleva una gran responsabilidad. Esto significa que nunca debes ejecutar código que no sea de confianza dentro del trabajo.
Ahora, ¿qué sucede si desea importar algún módulo personalizado, por ejemplo @paralleldrive/cuid2? Puedes hacer esto agregándolo a /backend/custom-package.json
, que debería tener una estructura como esta:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Los paquetes personalizados se inicializarán cuando se inicie el backend de BCMS.
Hay una cosa más, ¿qué pasa si tienes alguna lógica compartida entre tus Eventos, Trabajos y Funciones? A esos archivos los llamamos adicionales, pero puede verlos como sus utilidades personalizadas. Dentro de /backend/additional
puede agregar cualquier archivo JS e importarlo en el Trabajo.
Puede considerar el complemento BCMS como una aplicación con su propio backend y frontend que es atendido por el backend de BCMS y tiene acceso a todas las funciones de backend y frontend. Para el backend del complemento, debe seguir algunos patrones, pero para la interfaz de usuario del complemento puede crear cualquier aplicación SPA (puede usar React o VanillaJS, pero recomendamos VueJS porque podrá usar los componentes de la interfaz de usuario de BCMS).
La mejor manera de explicar esto es darle un ejemplo simple. Esta será la estructura del Plugin:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
Para que este ejemplo sea lo más simple posible, el código completo del frontend estará contenido en _ui/index.html
mientras que el backend contendrá 1 controlador que saludará al usuario.
configuración.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: explicar los complementos con más detalle y crear complementos de ejemplo