該儲存庫包含 BCMS 的開源版本,這是一種現代無頭 CMS,可讓您以靈活的結構輕鬆管理內容。使用者友好的介面、快速的部署選項 - 使其成為尋求可自訂 CMS 解決方案的開發人員和團隊的完美選擇。
git clone https://github.com/bcms/cms
npm i
docker compose up
http://localhost:8080
擁有基於 Debian 的伺服器後,您可以透過 SSH 連接到該伺服器並按照以下步驟操作。
如果伺服器上尚未安裝依賴項,請安裝它們:
sudo apt update && sudo apt install docker.io git nodejs npm
npm i -g n && n 20
由於我們使用的是 GitHub Packages,因此您需要將設定新增至~/.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
擁有基於 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
中,並可透過連接埠 27017 上的bcms-net
進行存取。
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 年 9 月,我們做出了重大轉變:BCMS Cloud 變成閉源(作為 BCMS Pro),開源版本變得完全獨立。
我們為什麼要這樣做?
當我們第一次建置 BCMS 時,我們將身份驗證系統集中在 BCMS 雲端。即使您是自架,您仍然必須透過我們的系統登入。我們認為這會簡化邀請用戶、發送電子郵件和管理入職等工作。但後來 Reddit 上的人卻因此拆散了我們。他們是對的。所以,我們聽了。
另一個問題是保持 BCMS 保持最新狀態。我們不斷改進它,但確保您的自架版本可以輕鬆更新始終是一個技術難題。
我們最初設定 BCMS 雲端的方式也產生了問題。每個實例都必須在自己獨立的 VPS 上運行,這會減慢速度並使基礎設施成本飆升。
我們的目標始終是創造快速、個人化的內容編輯體驗。但試圖同時保持開源和雲端版本的發展開始讓人感覺不可持續。因此,我們呼籲將他們分開。開源社群現在完全滿足了他們的要求:完全獨立、可自架、完全免費的 BCMS。
同時,我們將繼續推進所有新功能 - BCMS Pro,現在是閉源的,從頭開始重新開發,並針對需要優質託管體驗的人進行了最佳化。
核心 BCMS 團隊非常小 - 只有三名工程師、一名設計師、一名專案經理和一名內容作家。因此,我們將大部分精力集中在 BCMS Pro 上,但我們很高興看到社群對開源版本的態度。
快樂編碼!
如果您有任何疑問或需要協助,請隨時提出問題或透過@Discord 與我們聯繫。
關注 X(推特)
在 LinkedIn 上關注
加入我們的 Discord
您可以透過 4 種主要方式來擴充 BCMS,即事件、功能、作業和外掛程式。前 3 個僅用於擴展後端功能和執行自訂後端任務,而插件是在 BCMS 核心之上擁有自己的後端和 UI 的應用程式。
BCMS 事件是自訂 JavaScript 檔案(JS 函數),當觸發內部 BCMS 事件時執行,例如建立、更新或刪除 Entry 時或建立、更新或刪除 Widget 時。所有事件類型的列表
建立事件處理程序就像添加文件根/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後端擁有的所有權限,您的Event也將擁有。這不是很危險嗎?是的,也不是,權力越大,責任越大。這意味著您永遠不應該在事件內執行不受信任的程式碼。
現在,如果您想匯入一些自訂模組(例如@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>
。您可以將它們視為自訂程式碼,當向函數執行端點發出 HTTP 請求時,自訂程式碼將在 BCMS 後端執行。
建立函數就像將檔案加入/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後端擁有的所有權限,您的Function也將擁有。這不是很危險嗎?是的,也不是,權力越大,責任越大。這意味著您永遠不應該在函數內運行不受信任的程式碼。
現在,如果您想匯入一些自訂模組(例如@paralleldrive/cuid2)怎麼辦?您可以透過將其新增至/backend/custom-package.json
來做到這一點,該檔案的結構應如下所示:
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
自訂包將在 BCMS 後端啟動時初始化。
還有一件事,如果您的事件、作業和函數之間有一些共享邏輯怎麼辦?我們正在呼叫這些附加文件,但您可以將它們視為您的自訂實用程式。在/backend/additional
中,您可以新增任何 JS 檔案並將其匯入到 Function 中。
與 BCMS 事件和函數類似,BCMS 作業是在指定 CRON 間隔內執行的 JavaScript 檔案(JS 函數)。您可以將它們視為自訂程式碼,它將按指定的時間間隔在 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 檔案並將其匯入作業中。
您可以將 BCMS 外掛視為具有自己的後端和前端的應用程序,該應用程式由 BCMS 後端提供服務,並且可以存取所有後端和前端功能。對於插件後端,您需要遵循一些模式,但對於插件 UI,您可以建立任何 SPA 應用程式(您可以使用 React 或 VanillaJS,但我們建議使用 VueJS,因為您將能夠使用 BCMS UI 元件)。
解釋這一點的最佳方法是給您一個簡單的例子。這將是插件的結構:
/backend/plugins
- /hello-world
- /_ui
- index.html
- config.json
- controller.js
- main.js
為了讓這個範例盡可能簡單,整個前端程式碼將包含在_ui/index.html
中,而後端將包含 1 個用於迎接使用者的控制器。
配置.json
{
"version" : " 1 " ,
"dependencies" : {}
}
控制器.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:更詳細地解釋插件並建立範例插件