이 저장소에는 유연한 구조로 콘텐츠를 쉽게 관리할 수 있는 최신 헤드리스 CMS인 BCMS의 오픈 소스 버전이 포함되어 있습니다. 사용자 친화적인 인터페이스, 빠른 배포 옵션 - 맞춤형 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 패키지를 사용하고 있으므로 패키지를 가져오려면 ~/.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 Cloud에 중앙 집중화했습니다. 자체 호스팅을 하더라도 여전히 우리 시스템을 통해 로그인해야 했습니다. 우리는 이것이 사용자 초대, 이메일 전송, 온보딩 관리와 같은 작업을 단순화할 것이라고 생각했습니다. 그런데 Reddit의 사람들이 우리를 갈라놓았습니다. 그리고 그들이 옳았습니다. 그래서 우리는 들었습니다.
또 다른 문제는 BCMS를 최신 상태로 유지하는 것이었습니다. 우리는 지속적으로 이를 개선하고 있지만 자체 호스팅 버전을 쉽게 업데이트할 수 있는지 확인하는 것은 항상 기술적인 골칫거리였습니다.
원래 BCMS Cloud를 설정한 방식에도 문제가 발생했습니다. 각 인스턴스는 자체 격리된 VPS에서 실행되어야 했고, 이로 인해 작업 속도가 느려지고 인프라 비용이 폭등했습니다.
우리의 목표는 항상 빠르고 독창적인 콘텐츠 편집 환경을 만드는 것이었습니다. 하지만 오픈 소스와 클라우드 버전을 모두 유지하려는 노력은 지속 불가능하다고 느껴지기 시작했습니다. 그래서 우리는 그들을 분할하기 위해 전화를 걸었습니다. 이제 오픈 소스 커뮤니티는 그들이 요구했던 바로 그 기능을 갖추게 되었습니다. 완전 독립형, 자체 호스팅 가능 BCMS, 완전 무료입니다.
동시에 우리는 이제 비공개 소스로 처음부터 다시 개발되어 프리미엄 관리형 경험이 필요한 사람들을 위해 최적화된 새로운 BCMS Pro를 계속해서 추진할 것입니다.
핵심 BCMS 팀은 엔지니어 3명, 디자이너 1명, 프로젝트 관리자, 콘텐츠 작성자 1명으로 매우 작습니다. 따라서 우리는 BCMS Pro에 대부분의 에너지를 집중하고 있지만, 커뮤니티가 오픈 소스 버전을 어디로 활용하는지 보고 싶습니다.
즐거운 코딩하세요!
질문이 있거나 도움이 필요한 경우 언제든지 문제를 열거나 @Discord로 문의해 주세요.
X 팔로우(트위터)
링크드인에서 팔로우하기
Discord에서 우리와 함께하세요
BCMS를 확장할 수 있는 주요 방법에는 이벤트, 기능, 작업 및 플러그인 4가지가 있습니다. 처음 3개는 백엔드 기능을 확장하고 사용자 지정 백엔드 작업을 수행하는 데만 사용되는 반면 플러그인은 BCMS 코어 위에 자체 백엔드와 UI가 있는 앱입니다.
BCMS 이벤트는 내부 BCMS 이벤트가 트리거될 때(예: 항목이 생성, 업데이트 또는 삭제되거나 위젯이 생성, 업데이트 또는 삭제될 때) 실행되는 사용자 정의 JavaScript 파일(JS 함수)입니다. 모든 이벤트 유형 목록
이벤트 핸들러를 생성하는 것은 파일 루트 /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>
. 함수 실행 엔드포인트에 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 백엔드가 갖고 있는 모든 권한과 함수도 갖게 됩니다. 이거 위험하지 않아? 예, 아니오, 큰 힘에는 큰 책임이 따릅니다. 이는 함수 내에서 신뢰할 수 없는 코드를 실행해서는 안 된다는 것을 의미합니다.
이제 @paralleldrive/cuid2와 같은 일부 사용자 정의 모듈을 가져오려면 어떻게 해야 합니까? 다음과 같은 구조를 갖는 /backend/custom-package.json
에 추가하여 이를 수행할 수 있습니다.
{
"dependencies" : {
// Your custom packages
"@paralleldrive/cuid2" : " ^2.2.2 "
}
}
BCMS 백엔드가 시작되면 사용자 지정 패키지가 초기화됩니다.
한 가지가 더 있습니다. 이벤트, 작업 및 기능 간에 공유 논리가 있다면 어떻게 될까요? 우리는 이러한 추가 파일을 호출하고 있지만 이를 사용자 정의 유틸리티로 볼 수 있습니다. /backend/additional
내에서 JS 파일을 추가하고 함수로 가져올 수 있습니다.
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를 사용할 수 있지만 BCMS UI 구성 요소를 사용할 수 있으므로 VueJS를 권장합니다).
이를 설명하는 가장 좋은 방법은 간단한 예를 제시하는 것입니다. 이는 플러그인의 구조입니다:
/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: 플러그인을 더 자세히 설명하고 예제 플러그인을 만듭니다.