このリポジトリには、柔軟な構造でコンテンツを簡単に管理できる最新のヘッドレス 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
に設定を追加する必要があります。次の 2 行を追加します。
//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 クラウドは (BCMS Pro として) クローズドソースになり、オープンソース バージョンは完全にスタンドアロンになりました。
なぜこんなことをしたのでしょうか?
最初に BCMS を構築したとき、認証システムを BCMS クラウドに一元化しました。セルフホスティングを行っている場合でも、当社のシステムを通じてログインする必要がありました。これにより、ユーザーの招待、メールの送信、オンボーディングの管理などが簡素化されると考えました。しかしその後、レディットの人々はそれを理由に私たちを引き裂きました。そして彼らは正しかった。そこで、私たちは話を聞きました。
もう 1 つの問題は、BCMS を最新の状態に保つことでした。私たちは常に改善を続けていますが、セルフホスト型バージョンを簡単に更新できるようにすることは、常に技術的な悩みの種でした。
BCMS クラウドを最初にセットアップした方法でも問題が発生しました。各インスタンスは独自の分離された VPS 上で実行する必要があったため、処理速度が遅くなり、インフラストラクチャのコストが法外に高くなりました。
私たちの目標は常に、高速で独自のコンテンツ編集エクスペリエンスを作成することでした。しかし、オープンソース バージョンとクラウド バージョンの両方を前進させ続けることは持続不可能だと感じ始めました。そこで、私たちはそれらを分割するよう電話をかけました。オープンソース コミュニティは、まさに彼らが求めていたもの、つまり完全に自己完結型で自己ホスト可能な BCMS を完全に無料で手に入れました。
その間、私たちはすべての新しいもの、つまりクローズドソース化され、ゼロから再開発され、プレミアムな管理エクスペリエンスを必要とするユーザー向けに最適化された BCMS Pro を推進し続けます。
BCMS の中核チームは非常に小規模で、エンジニアが 3 名、デザイナーが 1 名、プロジェクト マネージャーが 1 名、コンテンツ ライターが 1 名のみです。そのため、私たちは BCMS Pro にほとんどのエネルギーを集中していますが、コミュニティがオープンソース バージョンをどのような方向に導くかを見るのが楽しみです。
コーディングを楽しんでください!
ご質問がある場合やサポートが必要な場合は、お気軽に問題を開くか、@ Discord までご連絡ください。
X(Twitter)でフォローしてください
LinkedIn でフォローする
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 バックエンドの起動時に初期化されます。
もう 1 つあります。イベント、ジョブ、関数の間に共有ロジックがある場合はどうなるでしょうか?これらを追加ファイルと呼んでいますが、カスタム ユーティリティとみなすことができます。 /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 バックエンドの起動時に初期化されます。
もう 1 つあります。イベント、ジョブ、関数の間に共有ロジックがある場合はどうなるでしょうか?これらを追加ファイルと呼んでいますが、カスタム ユーティリティとみなすことができます。 /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 バックエンドの起動時に初期化されます。
もう 1 つあります。イベント、ジョブ、関数の間に共有ロジックがある場合はどうなるでしょうか?これらを追加ファイルと呼んでいますが、カスタム ユーティリティとみなすことができます。 /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 つのコントローラーが含まれます。
config.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 } !` ,
} ;
} ,
} ) ,
} ;
} ,
} ) ;
メイン.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: プラグインをさらに詳しく説明し、サンプル プラグインを作成する