Repositori ini menyimpan BCMS versi sumber terbuka, CMS modern tanpa kepala yang memungkinkan Anda mengelola konten dengan mudah dengan struktur fleksibel. Antarmuka yang ramah pengguna, opsi penerapan cepat - menjadikannya sempurna untuk pengembang dan tim yang mencari solusi CMS yang dapat disesuaikan.
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
Setelah Anda memiliki server berbasis Debian, Anda dapat melakukan SSH ke dalamnya dan ikuti langkah-langkah di bawah ini.
Instal dependensi jika Anda belum memilikinya di server:
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
Karena kami menggunakan Paket GitHub, Anda perlu menambahkan konfigurasi ke ~/.npmrc
untuk menarik paket. Tambahkan dua baris berikut:
//npm.pkg.github.com/:_authToken=<GITHUB_TOKEN>
@bcms:registry=https://npm.pkg.github.com
Untuk menghasilkan GITHUB_TOKEN
, ikuti tutorial ini. Satu-satunya izin yang diperlukan untuk token ini adalah read:packages
.
npm i -g @bcms/selfhosted-cli
selfbcms --deploy debian
Setelah Anda memiliki server berbasis Debian, Anda dapat melakukan SSH ke dalamnya dan ikuti langkah-langkah di bawah ini.
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
Jika Anda tidak memiliki MongoDB, Anda dapat menjalankannya di dalam container Docker di server yang sama:
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
Dengan pengaturan ini, database MongoDB akan disimpan di ~/bcms/db
dan dapat diakses dari bcms-net
pada 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
Jika MongoDB diatur di server yang sama, DB_URL
nya adalah mongodb://<DB_ADMIN_USERNAME>:<DB_ADMIN_PASSWORD>@my-bcms-db:27017/admin
.
Untuk menangani permintaan masuk, Anda perlu menyiapkan reverse proxy 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;
}
}
}
Konfigurasi ini menggunakan host virtual Nginx default. Untuk menggunakan domain kustom, sesuaikan konfigurasi sesuai kebutuhan.
# 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
Kami menyambut kontribusi!
Pada bulan September 2024, kami melakukan perubahan besar: BCMS Cloud menjadi sumber tertutup (seperti BCMS Pro), dan versi sumber terbuka menjadi sepenuhnya mandiri.
Mengapa kami melakukan ini?
Saat pertama kali kami membangun BCMS, kami memusatkan sistem autentikasi di BCMS Cloud. Meskipun Anda menghosting sendiri, Anda tetap harus masuk melalui sistem kami. Kami pikir ini akan menyederhanakan berbagai hal seperti mengundang pengguna, mengirim email, dan mengelola orientasi. Tapi kemudian orang-orang di Reddit mencabik-cabik kami karenanya. Dan mereka benar. Jadi, kami mendengarkan.
Masalah lainnya adalah memperbarui BCMS. Kami terus menyempurnakannya, namun memastikan versi yang dihosting sendiri dapat diperbarui dengan mudah selalu menjadi masalah teknis.
Cara kami menyiapkan BCMS Cloud pada awalnya juga menimbulkan masalah. Setiap instance harus berjalan pada VPS tersendiri yang terisolasi, sehingga memperlambat segalanya dan membuat biaya infrastruktur melambung tinggi.
Tujuan kami selalu menciptakan pengalaman pengeditan konten yang cepat dan berpendirian keras. Namun upaya untuk mempertahankan versi open-source dan Cloud mulai terasa tidak berkelanjutan. Jadi, kami memutuskan untuk membaginya. Komunitas sumber terbuka kini memiliki apa yang mereka minta: BCMS yang sepenuhnya mandiri dan dapat dihosting sendiri, sepenuhnya gratis.
Sementara itu, kami akan terus mengembangkan semua yang baru - BCMS Pro, kini bersumber tertutup, dikembangkan kembali dari awal, dan dioptimalkan bagi mereka yang membutuhkan pengalaman premium dan terkelola.
Tim inti BCMS sangat kecil - hanya tiga insinyur, satu desainer, seorang manajer proyek, dan seorang penulis konten. Jadi, kami memfokuskan sebagian besar energi kami pada BCMS Pro, namun kami sangat antusias melihat ke mana komunitas akan memanfaatkan versi sumber terbukanya.
Selamat membuat kode!
Jika Anda memiliki pertanyaan atau butuh bantuan, jangan ragu untuk membuka masalah atau menghubungi kami @ Discord.
Ikuti di X (Twitter)
Ikuti di LinkedIn
Bergabunglah dengan kami di Perselisihan
Ada 4 cara utama untuk memperluas BCMS Anda, yaitu Acara, Fungsi, Pekerjaan, dan Plugin. 3 yang pertama hanya untuk memperluas fungsionalitas backend dan melakukan tugas backend khusus, sedangkan Plugin adalah aplikasi yang memiliki backend dan UI sendiri di atas inti BCMS.
Peristiwa BCMS adalah file JavaScript khusus (fungsi JS) yang dijalankan ketika peristiwa BCMS internal dipicu, misalnya ketika Entri dibuat, diperbarui atau dihapus atau ketika Widget dibuat, diperbarui atau dihapus. Daftar semua jenis Acara
Membuat Event handler semudah menambahkan direktori root file /backend/events
. Direktori ini terletak di dalam wadah BCMS. Anda dapat memasangnya sebagai volume saat menjalankan wadah dan ini akan terlihat seperti ini: -v <path_to_my_events_dir>:/app/backend/events
.
Jika Anda menjalankan BCMS secara lokal dari repositori ini, Anda dapat menambahkan file Acara ke /backend/events/<my_event>.{js|ts}
.
Berikut adalah contoh sederhana Acara:
// 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 } ) ;
} ,
} ;
} ) ;
File ini akan dimuat oleh backend BCMS dan dieksekusi yang berarti berjalan dalam cakupan yang sama dan Anda dapat menggunakan utilitas internal seperti Repo.*
untuk mengakses database. Eksekusi file ini tidak dibatasi, semua izin yang dimiliki backend BCMS, Acara Anda juga akan dimiliki. Bukankah ini berbahaya? Ya dan tidak, dengan kekuatan besar datang pula tanggung jawab yang besar. Ini berarti Anda tidak boleh menjalankan kode yang tidak tepercaya di dalam Acara.
Sekarang, bagaimana jika Anda ingin mengimpor beberapa modul khusus, misalnya @paralleldrive/cuid2? Anda dapat melakukan ini dengan menambahkannya ke /backend/custom-package.json
yang seharusnya memiliki struktur seperti ini:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Paket khusus akan diinisialisasi ketika backend BCMS dimulai.
Ada 1 hal lagi, bagaimana jika Anda memiliki logika yang sama antara Acara, Pekerjaan, dan Fungsi Anda? Kami menyebut file tambahan tersebut, tetapi Anda dapat melihatnya sebagai utilitas khusus Anda. Di dalam /backend/additional
Anda dapat menambahkan file JS apa pun dan mengimpornya ke Acara.
Mirip dengan Acara BCMS, Fungsi BCMS adalah file JavaScript (fungsi JS) yang dieksekusi ketika titik akhir fungsi dipanggil: POST: /api/v4/function/<my_function_name>
. Anda dapat melihatnya seperti kode khusus Anda yang akan berjalan di backend BCMS ketika permintaan HTTP dibuat ke titik akhir eksekusi fungsi.
Membuat Fungsi semudah menambahkan file ke /backend/functions
. Direktori ini terletak di dalam wadah BCMS. Anda dapat memasangnya sebagai volume saat menjalankan wadah dan ini akan terlihat seperti ini: -v <path_to_my_functions_dir>:/app/backend/functions
.
Jika Anda menjalankan BCMS secara lokal dari repositori ini, Anda dapat menambahkan file Fungsi ke /backend/functions/<my_function>.{js|ts}
.
Berikut adalah contoh sederhana Fungsi yang akan menggemakan permintaan:
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 ,
} ;
} ,
} ;
} ) ;
Agar berhasil memanggil Fungsi ini, Anda perlu membuat Kunci Api ( Administrasi/Manajer Kunci ) dan mengizinkannya memanggil Fungsi tersebut. Setelah ini, Anda dapat membuat permintaan HTTP ke sana:
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"
// }
// }
// }
File ini akan dimuat oleh backend BCMS dan dieksekusi yang berarti berjalan dalam cakupan yang sama dan Anda dapat menggunakan utilitas internal seperti Repo.*
untuk mengakses database. Eksekusi file ini tidak dibatasi, semua izin yang dimiliki backend BCMS, Fungsi Anda juga akan memilikinya. Bukankah ini berbahaya? Ya dan tidak, dengan kekuatan besar datang pula tanggung jawab yang besar. Ini berarti Anda tidak boleh menjalankan kode yang tidak tepercaya di dalam Fungsi.
Sekarang, bagaimana jika Anda ingin mengimpor beberapa modul khusus, misalnya @paralleldrive/cuid2? Anda dapat melakukan ini dengan menambahkannya ke /backend/custom-package.json
yang seharusnya memiliki struktur seperti ini:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Paket khusus akan diinisialisasi ketika backend BCMS dimulai.
Ada 1 hal lagi, bagaimana jika Anda memiliki logika yang sama antara Acara, Pekerjaan, dan Fungsi Anda? Kami menyebut file tambahan tersebut, tetapi Anda dapat melihatnya sebagai utilitas khusus Anda. Di dalam /backend/additional
Anda dapat menambahkan file JS apa pun dan mengimpornya ke Fungsi.
Mirip dengan Acara dan Fungsi BCMS, Pekerjaan BCMS adalah file JavaScript (fungsi JS) yang dijalankan dalam interval CRON tertentu. Anda dapat melihatnya seperti kode khusus Anda yang akan berjalan di backend BCMS dalam interval tertentu.
Membuat Pekerjaan semudah menambahkan file ke /backend/jobs
. Direktori ini terletak di dalam wadah BCMS. Anda dapat memasangnya sebagai volume saat menjalankan wadah dan ini akan terlihat seperti ini: -v <path_to_my_jobs_dir>:/app/backend/jobs
.
Jika Anda menjalankan BCMS secara lokal dari repositori ini, Anda dapat menambahkan file Pekerjaan ke /backend/jobs/<my_job>.{js|ts}
.
Berikut adalah contoh sederhana Fungsi yang akan konsol mencatat waktu saat ini setiap menit:
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 ( ) ) ;
} ,
} ;
} ) ;
File ini akan dimuat oleh backend BCMS dan dieksekusi yang berarti berjalan dalam cakupan yang sama dan Anda dapat menggunakan utilitas internal seperti Repo.*
untuk mengakses database. Eksekusi file ini tidak dibatasi, semua izin yang dimiliki backend BCMS, Pekerjaan Anda juga akan dimiliki. Bukankah ini berbahaya? Ya dan tidak, dengan kekuatan besar datang pula tanggung jawab yang besar. Ini berarti Anda tidak boleh menjalankan kode yang tidak tepercaya di dalam Job.
Sekarang, bagaimana jika Anda ingin mengimpor beberapa modul khusus, misalnya @paralleldrive/cuid2? Anda dapat melakukan ini dengan menambahkannya ke /backend/custom-package.json
yang seharusnya memiliki struktur seperti ini:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
Paket khusus akan diinisialisasi ketika backend BCMS dimulai.
Ada 1 hal lagi, bagaimana jika Anda memiliki logika yang sama antara Acara, Pekerjaan, dan Fungsi Anda? Kami menyebut file tambahan tersebut, tetapi Anda dapat melihatnya sebagai utilitas khusus Anda. Di dalam /backend/additional
Anda dapat menambahkan file JS apa pun dan mengimpornya ke dalam Job.
Anda dapat melihat Plugin BCMS sebagai aplikasi dengan backend dan frontendnya sendiri yang dilayani oleh backend BCMS dan memiliki akses ke semua fitur backend dan frontend. Untuk backend Plugin Anda perlu mengikuti beberapa pola tetapi untuk UI Plugin Anda dapat membangun aplikasi SPA apa pun (Anda dapat menggunakan React atau VanillaJS, namun kami merekomendasikan VueJS karena Anda akan dapat menggunakan Komponen UI BCMS).
Cara terbaik untuk menjelaskan hal ini adalah dengan memberi Anda contoh sederhana. Ini akan menjadi struktur Plugin:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
Untuk membuat contoh ini sesederhana mungkin, seluruh kode frontend akan dimuat dalam _ui/index.html
sedangkan backend akan berisi 1 pengontrol yang akan menyambut pengguna.
config.json
{
"version" : " 1 " ,
"dependencies" : {}
}
pengontrol.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: Jelaskan plugin lebih detail dan buat contoh plugin