Dieses Repository enthält eine Open-Source-Version von BCMS, einem modernen Headless-CMS, das Ihnen die einfache Verwaltung von Inhalten mit einer flexiblen Struktur ermöglicht. Benutzerfreundliche Oberfläche, schnelle Bereitstellungsoptionen – ideal für Entwickler und Teams, die eine anpassbare CMS-Lösung suchen.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
Nachdem Sie einen Debian-basierten Server haben, können Sie eine SSH-Verbindung herstellen und die folgenden Schritte ausführen.
Installieren Sie Abhängigkeiten, falls Sie diese noch nicht auf dem Server haben:
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
Da wir GitHub-Pakete verwenden, müssen Sie die Konfiguration zu ~/.npmrc
hinzufügen, um Pakete abzurufen. Fügen Sie die folgenden zwei Zeilen hinzu:
//npm.pkg.github.com/:_authToken=<GITHUB_TOKEN>
@bcms:registry=https://npm.pkg.github.com
Befolgen Sie dieses Tutorial, um ein GITHUB_TOKEN
zu generieren. Die einzige für dieses Token erforderliche Berechtigung ist read:packages
.
npm i -g @bcms/selfhosted-cli
selfbcms --deploy debian
Nachdem Sie einen Debian-basierten Server haben, können Sie eine SSH-Verbindung herstellen und die folgenden Schritte ausführen.
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
Wenn Sie nicht über MongoDB verfügen, können Sie es in einem Docker-Container auf demselben Server ausführen:
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
Mit diesem Setup wird die MongoDB-Datenbank in ~/bcms/db
gespeichert und ist über bcms-net
auf Port 27017 zugänglich.
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
Wenn MongoDB auf demselben Server eingerichtet ist, lautet die DB_URL
mongodb://<DB_ADMIN_USERNAME>:<DB_ADMIN_PASSWORD>@my-bcms-db:27017/admin
.
Um eingehende Anfragen zu verarbeiten, müssen Sie einen Nginx-Reverse-Proxy einrichten.
# 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;
}
}
}
Diese Konfiguration verwendet den standardmäßigen virtuellen Nginx-Host. Um eine benutzerdefinierte Domäne zu verwenden, passen Sie die Konfiguration nach Bedarf an.
# 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
Wir freuen uns über Beiträge!
Im September 2024 haben wir einen großen Wandel vollzogen: BCMS Cloud wurde auf Closed-Source umgestellt (als BCMS Pro) und die Open-Source-Version wurde völlig eigenständig.
Warum haben wir das getan?
Als wir BCMS zum ersten Mal entwickelten, zentralisierten wir das Authentifizierungssystem in der BCMS Cloud. Selbst wenn Sie selbst hosten würden, müssten Sie sich dennoch über unser System anmelden. Wir dachten, dass dies Dinge wie das Einladen von Benutzern, das Versenden von E-Mails und die Verwaltung des Onboardings vereinfachen würde. Aber dann haben uns die Leute auf Reddit dafür zerrissen. Und sie hatten Recht. Also haben wir zugehört.
Ein weiteres Problem bestand darin, BCMS auf dem neuesten Stand zu halten. Wir verbessern es ständig, aber sicherzustellen, dass Ihre selbst gehostete Version problemlos aktualisiert werden kann, war immer ein technisches Problem.
Auch die Art und Weise, wie wir BCMS Cloud ursprünglich eingerichtet haben, verursachte Probleme. Jede Instanz musste auf ihrem eigenen isolierten VPS laufen, was die Arbeit verlangsamte und die Infrastrukturkosten in die Höhe schnellen ließ.
Unser Ziel war es immer, ein schnelles, einfühlsames Erlebnis bei der Bearbeitung von Inhalten zu schaffen. Doch der Versuch, sowohl die Open-Source- als auch die Cloud-Versionen weiter voranzutreiben, schien nicht mehr tragbar zu sein. Also haben wir beschlossen, sie aufzuteilen. Die Open-Source-Community hat jetzt genau das, was sie gesucht hat: ein völlig eigenständiges, selbsthostbares BCMS, völlig kostenlos.
In der Zwischenzeit werden wir weiterhin alles Neue vorantreiben – BCMS Pro, jetzt Closed-Source, von Grund auf neu entwickelt und für diejenigen optimiert, die ein erstklassiges, verwaltetes Erlebnis benötigen.
Das BCMS-Kernteam ist sehr klein – nur drei Ingenieure, ein Designer, ein Projektmanager und ein Content-Autor. Deshalb konzentrieren wir den Großteil unserer Energie auf BCMS Pro, sind aber gespannt, wie die Community die Open-Source-Version annimmt.
Viel Spaß beim Codieren!
Wenn Sie Fragen haben oder Hilfe benötigen, können Sie gerne ein Problem eröffnen oder sich bei Discord an uns wenden.
Folgen Sie auf X (Twitter)
Folgen Sie auf LinkedIn
Begleiten Sie uns auf Discord
Es gibt im Wesentlichen vier Möglichkeiten, wie Sie Ihr BCMS erweitern können: Ereignisse, Funktionen, Jobs und Plugins. Die ersten drei dienen nur der Erweiterung der Backend-Funktionalität und der Durchführung benutzerdefinierter Backend-Aufgaben, während Plugins Apps sind, die über ein eigenes Backend und eine eigene Benutzeroberfläche zusätzlich zum BCMS-Kern verfügen.
BCMS-Ereignisse sind benutzerdefinierte JavaScript-Dateien (JS-Funktion), die ausgeführt werden, wenn ein internes BCMS-Ereignis ausgelöst wird, beispielsweise wenn ein Eintrag erstellt, aktualisiert oder gelöscht wird oder wenn ein Widget erstellt, aktualisiert oder gelöscht wird. Liste aller Veranstaltungstypen
Das Erstellen eines Event-Handlers ist genauso einfach wie das Hinzufügen eines Dateistammverzeichnisses /backend/events
. Dieses Verzeichnis befindet sich im BCMS-Container. Sie können es beim Ausführen des Containers als Volume bereitstellen. Dies würde dann etwa so aussehen: -v <path_to_my_events_dir>:/app/backend/events
.
Wenn Sie BCMS lokal aus diesem Repository ausführen, können Sie eine Ereignisdatei zu /backend/events/<my_event>.{js|ts}
hinzufügen.
Hier ist ein einfaches Beispielereignis:
// 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 } ) ;
} ,
} ;
} ) ;
Diese Datei wird vom BCMS-Backend geladen und ausgeführt, was bedeutet, dass sie im selben Bereich ausgeführt wird und Sie interne Dienstprogramme wie Repo.*
verwenden können, um auf die Datenbank zuzugreifen. Die Ausführung dieser Datei ist uneingeschränkt, alle Berechtigungen, die das BCMS-Backend hat, hat Ihr Event auch. Ist das nicht gefährlich? Ja und nein, mit großer Macht geht große Verantwortung einher. Das bedeutet, dass Sie innerhalb des Events niemals nicht vertrauenswürdigen Code ausführen sollten.
Was ist nun, wenn Sie ein benutzerdefiniertes Modul importieren möchten, zum Beispiel @paralleldrive/cuid2? Sie können dies tun, indem Sie es zu /backend/custom-package.json
hinzufügen, das eine Struktur wie diese haben sollte:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Benutzerdefinierte Pakete werden initialisiert, wenn das BCMS-Backend gestartet wird.
Es gibt noch eine weitere Sache: Was wäre, wenn Sie eine gemeinsame Logik zwischen Ihren Ereignissen, Jobs und Funktionen hätten? Wir nennen diese zusätzlichen Dateien, aber Sie können sie als Ihre benutzerdefinierten Dienstprogramme betrachten. Innerhalb von /backend/additional
können Sie eine beliebige JS-Datei hinzufügen und in das Event importieren.
Ähnlich wie BCMS-Ereignisse sind BCMS-Funktionen JavaScript-Dateien (JS-Funktion), die ausgeführt werden, wenn der Funktionsendpunkt aufgerufen wird: POST: /api/v4/function/<my_function_name>
. Sie können sie wie Ihren benutzerdefinierten Code betrachten, der auf dem BCMS-Backend ausgeführt wird, wenn eine HTTP-Anfrage an den Funktionsausführungsendpunkt gestellt wird.
Das Erstellen einer Funktion ist so einfach wie das Hinzufügen einer Datei zu /backend/functions
. Dieses Verzeichnis befindet sich im BCMS-Container. Sie können es beim Ausführen des Containers als Volume bereitstellen. Dies würde dann etwa so aussehen: -v <path_to_my_functions_dir>:/app/backend/functions
.
Wenn Sie BCMS lokal aus diesem Repository ausführen, können Sie eine Funktionsdatei zu /backend/functions/<my_function>.{js|ts}
hinzufügen.
Hier ist eine einfache Beispielfunktion, die eine Anfrage widerspiegelt:
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 ,
} ;
} ,
} ;
} ) ;
Um diese Funktion erfolgreich aufzurufen, müssen Sie einen API-Schlüssel ( Administration/Schlüsselmanager ) erstellen und ihm erlauben, die Funktion aufzurufen. Anschließend können Sie eine HTTP-Anfrage daran erstellen:
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"
// }
// }
// }
Diese Datei wird vom BCMS-Backend geladen und ausgeführt, was bedeutet, dass sie im selben Bereich ausgeführt wird und Sie interne Dienstprogramme wie Repo.*
verwenden können, um auf die Datenbank zuzugreifen. Die Ausführung dieser Datei ist uneingeschränkt. Alle Berechtigungen, die das BCMS-Backend hat, hat auch Ihre Funktion. Ist das nicht gefährlich? Ja und nein, mit großer Macht geht große Verantwortung einher. Das bedeutet, dass Sie niemals nicht vertrauenswürdigen Code innerhalb der Funktion ausführen sollten.
Was ist nun, wenn Sie ein benutzerdefiniertes Modul importieren möchten, zum Beispiel @paralleldrive/cuid2? Sie können dies tun, indem Sie es zu /backend/custom-package.json
hinzufügen, das eine Struktur wie diese haben sollte:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Benutzerdefinierte Pakete werden initialisiert, wenn das BCMS-Backend gestartet wird.
Es gibt noch eine weitere Sache: Was wäre, wenn Sie eine gemeinsame Logik zwischen Ihren Ereignissen, Jobs und Funktionen hätten? Wir nennen diese zusätzlichen Dateien, aber Sie können sie als Ihre benutzerdefinierten Dienstprogramme betrachten. Innerhalb von /backend/additional
können Sie jede JS-Datei hinzufügen und in die Funktion importieren.
Ähnlich wie BCMS-Ereignisse und -Funktionen sind BCMS-Jobs JavaScript-Dateien (JS-Funktion), die im angegebenen CRON-Intervall ausgeführt werden. Sie können sie wie Ihren benutzerdefinierten Code betrachten, der in bestimmten Intervallen auf dem BCMS-Backend ausgeführt wird.
Das Erstellen eines Jobs ist so einfach wie das Hinzufügen einer Datei zu /backend/jobs
. Dieses Verzeichnis befindet sich im BCMS-Container. Sie können es beim Ausführen des Containers als Volume bereitstellen. Dies würde dann etwa so aussehen: -v <path_to_my_jobs_dir>:/app/backend/jobs
.
Wenn Sie BCMS lokal aus diesem Repository ausführen, können Sie eine Jobdatei zu /backend/jobs/<my_job>.{js|ts}
hinzufügen.
Hier ist eine einfache Beispielfunktion, die jede Minute die aktuelle Uhrzeit protokolliert:
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 ( ) ) ;
} ,
} ;
} ) ;
Diese Datei wird vom BCMS-Backend geladen und ausgeführt, was bedeutet, dass sie im selben Bereich ausgeführt wird und Sie interne Dienstprogramme wie Repo.*
verwenden können, um auf die Datenbank zuzugreifen. Die Ausführung dieser Datei ist uneingeschränkt. Alle Berechtigungen, die das BCMS-Backend hat, hat auch Ihr Job. Ist das nicht gefährlich? Ja und nein, mit großer Macht geht große Verantwortung einher. Das bedeutet, dass Sie niemals nicht vertrauenswürdigen Code innerhalb des Jobs ausführen sollten.
Was ist nun, wenn Sie ein benutzerdefiniertes Modul importieren möchten, zum Beispiel @paralleldrive/cuid2? Sie können dies tun, indem Sie es zu /backend/custom-package.json
hinzufügen, das eine Struktur wie diese haben sollte:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Benutzerdefinierte Pakete werden initialisiert, wenn das BCMS-Backend gestartet wird.
Es gibt noch eine weitere Sache: Was wäre, wenn Sie eine gemeinsame Logik zwischen Ihren Ereignissen, Jobs und Funktionen hätten? Wir nennen diese zusätzlichen Dateien, aber Sie können sie als Ihre benutzerdefinierten Dienstprogramme betrachten. Innerhalb von /backend/additional
können Sie eine beliebige JS-Datei hinzufügen und in den Job importieren.
Sie können das BCMS-Plugin als eine Anwendung mit eigenem Backend und Frontend betrachten, die vom BCMS-Backend bedient wird und Zugriff auf alle Backend- und Frontend-Funktionen hat. Für das Plugin-Backend müssen Sie einigen Mustern folgen, aber für die Plugin-Benutzeroberfläche können Sie jede beliebige SPA-Anwendung erstellen (Sie können React oder VanillaJS verwenden, wir empfehlen jedoch VueJS, da Sie BCMS-UI-Komponenten verwenden können).
Der beste Weg, dies zu erklären, besteht darin, Ihnen ein einfaches Beispiel zu geben. Dies wird die Struktur des Plugins sein:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
Um dieses Beispiel so einfach wie möglich zu gestalten, wird der gesamte Frontend-Code in _ui/index.html
enthalten sein, während das Backend einen Controller enthält, der einen Benutzer begrüßt.
config.json
{
"version" : " 1 " ,
"dependencies" : {}
}
controller.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: Plugins genauer erklären und Beispiel-Plugins erstellen