Ce référentiel contient la version open source de BCMS, un CMS moderne sans tête qui vous permet de gérer facilement le contenu avec une structure flexible. Une interface conviviale et des options de déploiement rapides le rendent parfait pour les développeurs et les équipes à la recherche d'une solution CMS personnalisable.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
Une fois que vous disposez d'un serveur basé sur Debian, vous pouvez y accéder via SSH et suivre les étapes ci-dessous.
Installez les dépendances si vous ne les avez pas déjà sur le serveur :
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
Puisque nous utilisons des packages GitHub, vous devrez ajouter une configuration au ~/.npmrc
pour extraire les packages. Ajoutez les deux lignes suivantes :
//npm.pkg.github.com/:_authToken=<GITHUB_TOKEN>
@bcms:registry=https://npm.pkg.github.com
Pour générer un GITHUB_TOKEN
, suivez ce tutoriel. La seule autorisation requise pour ce jeton est read:packages
.
npm i -g @bcms/selfhosted-cli
selfbcms --deploy debian
Une fois que vous disposez d'un serveur basé sur Debian, vous pouvez y accéder via SSH et suivre les étapes ci-dessous.
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 vous n'avez pas MongoDB, vous pouvez l'exécuter dans un conteneur Docker sur le même serveur :
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
Avec cette configuration, la base de données MongoDB sera stockée dans ~/bcms/db
et accessible depuis bcms-net
sur le port 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 configuré sur le même serveur, l' DB_URL
sera mongodb://<DB_ADMIN_USERNAME>:<DB_ADMIN_PASSWORD>@my-bcms-db:27017/admin
.
Pour gérer les demandes entrantes, vous devez configurer un proxy inverse 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;
}
}
}
Cette configuration utilise l'hôte virtuel Nginx par défaut. Pour utiliser un domaine personnalisé, ajustez la configuration selon vos besoins.
# 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
Nous apprécions les contributions !
En septembre 2024, nous avons opéré un grand changement : BCMS Cloud est devenu une version fermée (sous le nom de BCMS Pro) et la version open source est devenue complètement autonome.
Pourquoi avons-nous fait cela ?
Lorsque nous avons créé BCMS pour la première fois, nous avons centralisé le système d'authentification dans BCMS Cloud. Même si vous étiez auto-hébergé, vous deviez toujours vous connecter via notre système. Nous pensions que cela simplifierait des choses comme inviter des utilisateurs, envoyer des e-mails et gérer l'intégration. Mais ensuite, les gens sur Reddit nous ont déchirés pour ça. Et ils avaient raison. Alors, nous avons écouté.
Un autre problème consistait à maintenir BCMS à jour. Nous l'améliorons constamment, mais s'assurer que votre version auto-hébergée puisse se mettre à jour facilement a toujours été un casse-tête technique.
La façon dont nous avons initialement configuré BCMS Cloud a également créé des problèmes. Chaque instance devait fonctionner sur son propre VPS isolé, ce qui ralentissait les choses et faisait exploser les coûts d'infrastructure.
Notre objectif a toujours été de créer une expérience d'édition de contenu rapide et avisée. Mais essayer de maintenir à la fois les versions open source et Cloud a commencé à sembler insoutenable. Nous avons donc décidé de les séparer. La communauté open source a désormais exactement ce qu'elle demandait : un BCMS entièrement autonome et auto-hébergable, entièrement gratuit.
En attendant, nous continuerons à développer de toutes nouvelles nouveautés : BCMS Pro, désormais à code source fermé, entièrement redéveloppé et optimisé pour ceux qui ont besoin d'une expérience gérée haut de gamme.
L'équipe principale de BCMS est très petite : seulement trois ingénieurs, un concepteur, un chef de projet et un rédacteur de contenu. Nous concentrons donc l'essentiel de notre énergie sur BCMS Pro, mais nous sommes impatients de voir où la communauté mènera la version open source.
Bon codage !
Si vous avez des questions ou avez besoin d'aide, n'hésitez pas à ouvrir un problème ou à nous contacter @ Discord.
Suivre sur X (Twitter)
Suivre sur LinkedIn
Rejoignez-nous sur Discord
Il existe 4 manières principales d'étendre votre BCMS : les événements, les fonctions, les tâches et les plugins. Les 3 premiers servent uniquement à étendre les fonctionnalités du backend et à effectuer des tâches backend personnalisées, tandis que les plugins sont des applications qui ont leur propre backend et leur propre interface utilisateur au-dessus du noyau BCMS.
Les événements BCMS sont des fichiers JavaScript personnalisés (fonction JS) qui sont exécutés lorsqu'un événement BCMS interne est déclenché, par exemple lorsque l'entrée est créée, mise à jour ou supprimée ou lorsque le widget est créé, mis à jour ou supprimé. Liste de tous les types d'événements
Créer un gestionnaire d'événements est aussi simple que d'ajouter un répertoire racine de fichier /backend/events
. Ce répertoire est situé à l'intérieur du conteneur BCMS. Vous pouvez le monter en tant que volume lors de l'exécution du conteneur et cela ressemblerait à ceci : -v <path_to_my_events_dir>:/app/backend/events
.
Si vous exécutez BCMS localement à partir de ce référentiel, vous pouvez ajouter un fichier d'événement à /backend/events/<my_event>.{js|ts}
.
Voici un exemple simple d'événement :
// 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 } ) ;
} ,
} ;
} ) ;
Ce fichier sera chargé par le backend BCMS et exécuté, ce qui signifie qu'il s'exécute dans la même portée et que vous pouvez utiliser des utilitaires internes comme Repo.*
pour accéder à la base de données. L'exécution de ce fichier est illimitée, toutes les autorisations dont dispose le backend BCMS, votre événement les aura également. N'est-ce pas dangereux ? Oui et non, un grand pouvoir implique de grandes responsabilités. Cela signifie que vous ne devez jamais exécuter de code non fiable à l'intérieur de l'événement.
Maintenant, que se passe-t-il si vous souhaitez importer un module personnalisé, par exemple @paralleldrive/cuid2 ? Vous pouvez le faire en l'ajoutant à /backend/custom-package.json
qui devrait avoir une structure comme celle-ci :
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Les packages personnalisés seront initialisés au démarrage du backend BCMS.
Il y a encore une chose : et si vous aviez une logique partagée entre vos événements, tâches et fonctions ? Nous appelons ces fichiers supplémentaires, mais vous pouvez les considérer comme vos utilitaires personnalisés. Dans le /backend/additional
vous pouvez ajouter n'importe quel fichier JS et l'importer dans l'événement.
Semblables aux événements BCMS, les fonctions BCMS sont des fichiers JavaScript (fonction JS) qui sont exécutés lorsque le point de terminaison de la fonction est appelé : POST: /api/v4/function/<my_function_name>
. Vous pouvez les considérer comme votre code personnalisé qui s'exécutera sur le backend BCMS lorsqu'une requête HTTP est adressée au point de terminaison d'exécution de la fonction.
Créer une fonction est aussi simple que d'ajouter un fichier au /backend/functions
. Ce répertoire est situé à l'intérieur du conteneur BCMS. Vous pouvez le monter en tant que volume lors de l'exécution du conteneur et cela ressemblerait à ceci : -v <path_to_my_functions_dir>:/app/backend/functions
.
Si vous exécutez BCMS localement à partir de ce référentiel, vous pouvez ajouter un fichier de fonction à /backend/functions/<my_function>.{js|ts}
.
Voici un exemple simple de fonction qui fera écho à une requête :
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 ,
} ;
} ,
} ;
} ) ;
Pour appeler avec succès cette fonction, vous devrez créer une clé API ( Administration/Gestionnaire de clés ) et lui permettre d'appeler la fonction. Après cela, vous pouvez créer une requête 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"
// }
// }
// }
Ce fichier sera chargé par le backend BCMS et exécuté, ce qui signifie qu'il s'exécute dans la même portée et que vous pouvez utiliser des utilitaires internes comme Repo.*
pour accéder à la base de données. L'exécution de ce fichier est illimitée, toutes les autorisations dont dispose le backend BCMS, votre fonction les aura également. N'est-ce pas dangereux ? Oui et non, un grand pouvoir implique de grandes responsabilités. Cela signifie que vous ne devez jamais exécuter de code non fiable dans la fonction.
Maintenant, que se passe-t-il si vous souhaitez importer un module personnalisé, par exemple @paralleldrive/cuid2 ? Vous pouvez le faire en l'ajoutant à /backend/custom-package.json
qui devrait avoir une structure comme celle-ci :
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Les packages personnalisés seront initialisés au démarrage du backend BCMS.
Il y a encore une chose : et si vous aviez une logique partagée entre vos événements, tâches et fonctions ? Nous appelons ces fichiers supplémentaires, mais vous pouvez les considérer comme vos utilitaires personnalisés. Dans le /backend/additional
vous pouvez ajouter n'importe quel fichier JS et l'importer dans la fonction.
Semblables aux événements et fonctions BCMS, les tâches BCMS sont des fichiers JavaScript (fonction JS) qui sont exécutés dans un intervalle CRON spécifié. Vous pouvez les considérer comme votre code personnalisé qui s'exécutera sur le backend BCMS dans un intervalle spécifié.
Créer un travail est aussi simple que d'ajouter un fichier au /backend/jobs
. Ce répertoire est situé à l'intérieur du conteneur BCMS. Vous pouvez le monter en tant que volume lors de l'exécution du conteneur et cela ressemblerait à ceci : -v <path_to_my_jobs_dir>:/app/backend/jobs
.
Si vous exécutez BCMS localement à partir de ce référentiel, vous pouvez ajouter un fichier de travail à /backend/jobs/<my_job>.{js|ts}
.
Voici un exemple simple de fonction qui permettra à la console d'enregistrer l'heure actuelle toutes les minutes :
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 ( ) ) ;
} ,
} ;
} ) ;
Ce fichier sera chargé par le backend BCMS et exécuté, ce qui signifie qu'il s'exécute dans la même portée et que vous pouvez utiliser des utilitaires internes comme Repo.*
pour accéder à la base de données. L'exécution de ce fichier est illimitée, votre Job disposera également de toutes les autorisations dont dispose le backend BCMS. N'est-ce pas dangereux ? Oui et non, un grand pouvoir implique de grandes responsabilités. Cela signifie que vous ne devez jamais exécuter de code non fiable dans le Job.
Maintenant, que se passe-t-il si vous souhaitez importer un module personnalisé, par exemple @paralleldrive/cuid2 ? Vous pouvez le faire en l'ajoutant à /backend/custom-package.json
qui devrait avoir une structure comme celle-ci :
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Les packages personnalisés seront initialisés au démarrage du backend BCMS.
Il y a encore une chose : et si vous aviez une logique partagée entre vos événements, tâches et fonctions ? Nous appelons ces fichiers supplémentaires, mais vous pouvez les considérer comme vos utilitaires personnalisés. Dans /backend/additional
vous pouvez ajouter n'importe quel fichier JS et l'importer dans le Job.
Vous pouvez considérer le plugin BCMS comme une application avec son propre backend et son propre frontend qui sont servis par le backend BCMS et avoir accès à toutes les fonctionnalités backend et frontend. Pour le backend du plugin, vous devez suivre certains modèles, mais pour l'interface utilisateur du plugin, vous pouvez créer n'importe quelle application SPA (vous pouvez utiliser React ou VanillaJS, mais nous recommandons VueJS car vous pourrez utiliser les composants de l'interface utilisateur BCMS).
La meilleure façon d’expliquer cela est de vous donner un exemple simple. Ce sera la structure du Plugin :
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
Pour rendre cet exemple aussi simple que possible, tout le code du frontend sera contenu dans _ui/index.html
tandis que le backend contiendra 1 contrôleur qui accueillera un utilisateur.
config.json
{
"version" : " 1 " ,
"dependencies" : {}
}
contrôleur.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 >
À FAIRE : Expliquez les plugins plus en détail et créez des exemples de plugins