設定外掛沙箱

本頁內容

沙箱化外掛除了外掛宣告之外還需要一個平台運行器。Marketplace 和 registry 安裝始終使用該運行器,sandboxed: [] 下列出的外掛也是如此。plugins: [] 下的原生外掛在 EmDash 伺服器程序中執行,不會取得沙箱隔離。

運行器取決於部署平台。在 Cloudflare Workers 上,每個外掛作為透過 Worker Loader 繫結建立的 Dynamic Worker 執行。在 Node.js 上,伺服器啟動開源 Workers 執行環境 workerd 作為子程序,並將每個外掛作為其中的服務執行。emdash() 的 sandboxRunner 選項選擇運行器並啟用託管登錄檔目錄。沒有它,sandboxed: [] 下的外掛不會被載入。明確設定的登錄檔仍可瀏覽,但安裝或更新沙箱化外掛會以 SANDBOX_NOT_AVAILABLE 失敗。

下表總結了每個運行器需要和強制執行的內容。

Cloudflare WorkersNode.js
sandboxRunner@emdash-cms/cloudflare 的 sandbox()"@emdash-cms/sandbox-workerd/sandbox"
要求Workers 付費方案、worker_loaders 繫結、從 Worker 進入點匯出 PluginBridgeworkerd 套件
資料庫存取DB D1 繫結(與設定的配接器無關)設定的資料庫
強制限制CPU 時間、子請求、掛鐘時間掛鐘時間

Cloudflare Workers

Dynamic Workers 在 Workers 付費方案中可用。*-cloudflare 範本包含以下進入點匯出,但將繫結註解掉了,因此新專案在 Workers 免費方案上部署,除非你在鷹架期間啟用沙箱化外掛。

  1. 在 wrangler.jsonc 中啟用 Worker Loader 繫結。運行器以 LOADER 名稱讀取它,僅在此繫結存在時選擇 Cloudflare 沙箱:

    {
    	"worker_loaders": [
    		{
    			"binding": "LOADER",
    		},
    	],
    }

    如果 Wrangler 設定使用命名環境,請在 Astro 建置期間設定 CLOUDFLARE_ENV。Cloudflare Vite 外掛和 sandbox() 將讀取相同的環境。繫結不會被繼承,因此請將 LOADER 新增到執行沙箱化外掛的每個命名環境中。

  2. 從 Worker 進入點匯出 PluginBridge,並將 main 指向該檔案。PluginBridge 是沙箱化外掛存取內容、媒體、儲存和電子郵件的進入點;運行器在進入模組的匯出中查找它:

    import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker";
    
    export { PluginBridge };
    
    export default {
    	...handler,
    	scheduled: createScheduledHandler(),
    } satisfies ExportedHandler;
    {
    	"main": "./src/worker.ts",
    }
  3. 在 emdash() 整合中選擇運行器:

    import { d1, r2, sandbox } from "@emdash-cms/cloudflare";
    
    emdash({
    	database: d1({ binding: "DB" }),
    	storage: r2({ binding: "MEDIA" }),
    	sandboxRunner: sandbox(),
    });

Node.js

  1. 將運行器與作為 peer 依賴的 workerd 一起安裝:

    npm install @emdash-cms/sandbox-workerd workerd

    workerd 套件透過可選依賴安裝目前平台的二進位檔(x64 上的 Linux、macOS 和 Windows;arm64 上的 Linux 和 macOS)。在伺服器執行的平台上啟用可選依賴進行安裝。在多階段 Docker 建置中,在與執行階段相同平台的階段中執行安裝。

  2. 在 emdash() 整合中選擇運行器:

    import { sqlite } from "emdash/db";
    
    emdash({
    	database: sqlite({ url: "file:./data/emdash.db" }),
    	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
    });

運行器將 Miniflare 宣告為可選依賴。套件管理器預設安裝它。當 NODE_ENV 為 development(astro dev 設定的值)時,運行器將外掛交給 Miniflare,由其管理自己的 workerd 程序;下面的崩潰策略不適用。如果省略了可選依賴,運行器改用 workerd。astro preview 將 NODE_ENV 設為 production,node ./dist/server/entry.mjs 不設定它;兩者都使用 workerd。

workerd 程序如何執行

EmDash 在沙箱化外掛載入後、站點首次請求的初始化期間啟動 workerd,並等待最多 10 秒讓外掛服務回應。從管理面板安裝或更新外掛會重新啟動它。workerd 寫入 stdout 或 stderr 的所有內容都以 [emdash:workerd] 前綴出現在伺服器輸出中。

外掛服務在 127.0.0.1 上監聽,返回伺服器的通道是 Unix 網域通訊端(Windows 上為 127.0.0.1 TCP 連接埠)。不需要開啟入站連接埠。

子程序僅從伺服器環境接收 PATH、HOME、TMPDIR、TMP、TEMP、LANG 和 LC_ALL,因此伺服器環境中的密鑰不會進入沙箱。要傳遞更多變數,請將 EMDASH_WORKERD_PASSTHROUGH_ENV 設定為逗號分隔的變數名稱清單。

如果 workerd 意外退出,運行器會記錄 [emdash:workerd] workerd exited with <reason> 並在下次呼叫時重新啟動它,延遲從 1 秒開始,每次加倍直到 30 秒。當 workerd 在 60 秒內崩潰超過五次時,運行器停止重新啟動並記錄 [emdash:workerd] workerd crashed 5 times in 60 seconds, giving up。此後,每個沙箱化外掛鉤子和路由都會以 Plugin sandbox unavailable for <plugin>: workerd crashed 5 times in 60 seconds and the runner stopped retrying; restart the server 失敗。重新啟動伺服器會再次啟動 workerd,從管理面板安裝或更新外掛也會如此。向伺服器傳送 SIGTERM 會同時終止 workerd。

資源限制

每個運行器對每次外掛呼叫應用相同的限制集。限制是固定的;emdash() 整合沒有相關選項。

限制值Cloudflare WorkersNode.js
CPU 時間50 ms由 Worker Loader 強制執行;外掛達到限制時拋出例外不強制執行
子請求10由 Worker Loader 強制執行;外掛達到限制時拋出例外不強制執行
記憶體128 MB不按外掛強制執行;適用平台的 isolate 記憶體上限不強制執行
掛鐘時間30 秒由運行器強制執行由運行器強制執行

當鉤子或路由超過掛鐘時間限制時,呼叫以 Plugin <id> exceeded wall-time limit of 30000ms during hook:<name>(或 route:<name>)失敗。對於鉤子,EmDash 以 EmDash: Sandboxed plugin <id> 前綴記錄失敗,並在沒有該外掛結果的情況下繼續請求。超過限制的外掛路由對其呼叫者失敗。

運行器不可用時

在 Cloudflare Workers 上,sandbox() 在建置時檢查 wrangler.jsonc。沒有名為 LOADER 的 worker_loaders 繫結時,它將運行器留為未設定並記錄以下警告:

[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding. Worker Loader requires a Workers paid plan.

選定的運行器在執行時仍可能不可用:在 Cloudflare Workers 上,當部署的 LOADER 繫結或 PluginBridge 匯出缺失時;在 Node.js 上,當 workerd 未安裝或其二進位檔無法執行時。EmDash 會記錄一條警告,其中包含運行器在冒號後報告的原因。以下警告在繫結缺失時在 Cloudflare Workers 上記錄:

EmDash: Plugin sandbox is configured but not available on this platform: the worker has no worker_loaders binding named LOADER. Sandboxed plugins will not be loaded.

sandboxed: [] 下的外掛不會被載入,已安裝的 marketplace 和 registry 外掛不會執行,管理面板的新安裝會以錯誤碼 SANDBOX_NOT_AVAILABLE 失敗。站點的其餘部分不受影響。

在程序內執行沙箱化外掛

在 emdash() 中設定 sandbox: false,以在沒有隔離或限制的情況下在伺服器程序中執行 sandboxed: [] 下的外掛和已安裝的 marketplace 外掛。這是一個除錯選項,用於區分外掛中的錯誤和沙箱中的錯誤。以下設定在 Node.js 站點上關閉沙箱:

emdash({
	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
	sandbox: false,
});

在 Cloudflare Workers 上,執行環境會以 sandbox: false is not supported in Cloudflare Workers 拒絕啟動。

疑難排解

每個條目以伺服器記錄的訊息或管理面板傳回的錯誤碼為標題。

「[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding」

Cloudflare 配接器未選擇沙箱運行器,因為建置時的 Wrangler 設定沒有名為 LOADER 的 worker_loaders 繫結。這是 Workers 免費方案上的預期設定。在 Workers 付費方案上,在 wrangler.jsonc 中啟用繫結並重新建置站點。

「Plugin sandbox is configured but not available on this platform」

冒號後的文字指出了原因。在 Cloudflare Workers 上,the worker has no worker_loaders binding named LOADER 表示 wrangler.jsonc 需要一個名為 LOADER 的 worker_loaders 繫結,the worker entrypoint does not export PluginBridge 表示 main 指向的檔案必須匯出 PluginBridge。部署繫結需要 Workers 付費方案。

在 Node.js 上,workerd is missing or its binary does not run on this platform 表示運行器無法執行 workerd。直接執行已安裝的二進位檔,以防止檢查下載缺失的套件:

./node_modules/.bin/workerd --version

在 Windows 上,執行 node_modules\\.bin\\workerd.cmd --version。如果命令失敗,說明 workerd 在 node_modules 中缺失,或安裝的二進位檔無法在此平台上執行。在目標平台上啟用可選依賴重新安裝。

「workerd failed to start within 10 seconds」

子程序已啟動,但其外掛服務未在 10 秒內回應。此訊息前帶有 [emdash:workerd] 前綴的行包含 workerd 本身的輸出,包括設定和啟動錯誤。運行器將在下次呼叫時重試。

「workerd crashed 5 times in 60 seconds, giving up」

運行器已停止重新啟動 workerd。此訊息前的 [emdash:workerd] workerd exited with <reason> 行指出了每次崩潰的退出碼或訊號。修復原因後重新啟動伺服器。

安裝外掛時出現 SANDBOX_NOT_AVAILABLE

管理面板的安裝請求被拒絕,因為運行器缺失或不可用。當運行器已設定時,錯誤訊息以與上述啟動警告相同的原因結尾。為平台設定運行器,或修復該原因,然後重新部署。