يحتوي هذا المستودع على إصدار مفتوح المصدر من BCMS، وهو نظام إدارة محتوى حديث مقطوع الرأس يسمح لك بإدارة المحتوى بسهولة من خلال بنية مرنة. واجهة سهلة الاستخدام وخيارات نشر سريعة - تجعلها مثالية للمطورين والفرق التي تبحث عن حل CMS قابل للتخصيص.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
بعد أن يكون لديك خادم يستند إلى دبيان، يمكنك استخدام 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
بعد أن يكون لديك خادم يستند إلى دبيان، يمكنك استخدام 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 Cloud. حتى لو كنت تستضيف نفسك بنفسك، فلا يزال يتعين عليك تسجيل الدخول من خلال نظامنا. لقد اعتقدنا أن هذا من شأنه تبسيط الأمور مثل دعوة المستخدمين، وإرسال رسائل البريد الإلكتروني، وإدارة عملية الإعداد. ولكن بعد ذلك قام الناس على موقع Reddit بتمزيقنا بسبب ذلك. وكانوا على حق. لذلك، استمعنا.
هناك مشكلة أخرى وهي تحديث نظام إدارة استمرارية الأعمال (BCMS). نحن نعمل على تحسينه باستمرار، ولكن التأكد من إمكانية تحديث الإصدار المستضاف ذاتيًا بسهولة كان يمثل دائمًا مشكلة فنية.
الطريقة التي قمنا بها في الأصل بإعداد BCMS Cloud خلقت مشاكل أيضًا. كان يجب تشغيل كل مثيل على خادم VPS المعزول الخاص به، مما أدى إلى إبطاء الأمور وارتفاع تكاليف البنية التحتية إلى أعلى المستويات.
لقد كان هدفنا دائمًا هو إنشاء تجربة تحرير محتوى سريعة ومحددة. لكن محاولة الحفاظ على تقدم كلا الإصدارين مفتوح المصدر والسحابي بدأ يبدو غير مستدام. لذلك، وجهنا الدعوة لتقسيمهم. أصبح لدى مجتمع المصادر المفتوحة الآن بالضبط ما طلبوه: نظام إدارة استمرارية الأعمال (BCMS) مستقل تمامًا وقابل للاستضافة الذاتية، ومجاني تمامًا.
وفي الوقت نفسه، سنواصل المضي قدمًا في كل ما هو جديد - BCMS Pro، الآن مغلق المصدر، أعيد تطويره من الألف إلى الياء، وتم تحسينه لأولئك الذين يحتاجون إلى الخبرة المتميزة والمدارة.
فريق BCMS الأساسي صغير جدًا - ثلاثة مهندسين فقط، ومصمم واحد، ومدير مشروع، وكاتب محتوى. لذلك، نحن نركز معظم طاقتنا على BCMS Pro، ولكننا متحمسون لرؤية أين سيأخذ المجتمع النسخة مفتوحة المصدر.
ترميز سعيد!
إذا كانت لديك أي أسئلة أو كنت بحاجة إلى مساعدة، فلا تتردد في فتح مشكلة أو التواصل معنا على Discord.
اتبع على X (تويتر)
اتبع على لينكد إن
انضم إلينا على الديسكورد
هناك 4 طرق رئيسية يمكنك من خلالها توسيع نظام إدارة استمرارية الأعمال الخاص بك وهي الأحداث والوظائف والوظائف والمكونات الإضافية. أول 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 واستيراده في الحدث.
على غرار أحداث 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 UI Components).
أفضل طريقة لشرح ذلك هي أن أقدم لك مثالاً بسيطًا. سيكون هذا هو هيكل البرنامج المساعد:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
لجعل هذا المثال بسيطًا قدر الإمكان، سيتم تضمين كود الواجهة الأمامية بالكامل في _ui/index.html
بينما ستحتوي الواجهة الخلفية على وحدة تحكم واحدة سترحب بالمستخدم.
التكوين.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 >
المهام المطلوبة: اشرح المكونات الإضافية بمزيد من التفاصيل وقم بإنشاء أمثلة للمكونات الإضافية