站台轉移

本頁內容

站台套件是 EmDash 站台的內容模型、內容、編輯歷程、站台呈現、設定與媒體檔案的可攜式複本。匯入站台套件可將站台移至另一個 EmDash 部署,包括使用不同資料庫的部署:SQLite、PostgreSQL 或 Cloudflare D1。

匯入會寫入內容區域為空的新站台。EmDash 在寫入任何內容之前會檢查整個套件,以可恢復的小步驟執行匯入,回讀已匯入的站台,並在結果與套件一致時簽發回條。

站台套件不包含使用者、憑證或機密。但它包含站台上的每筆條目與留言,包括作者與留言者的電子郵件地址。請像對待資料庫備份一樣謹慎儲存與傳送。

選擇合適的複製方式

機制用途可匯入媒體檔案使用者與機密
種子檔案初始化內容模型與範例內容是,採用種子語意否否
預覽快照填入隔離的預覽算繪僅限預覽否否
JSON 備份檢查選定的資料庫形態狀態否否否
原始資料庫與媒體備份復原一個部署還原到同一類資料庫需單獨複製是
站台套件將站台移至另一個 EmDash 站台是,匯入到空站台是否。僅作者姓名與電子郵件

在資料遺失後復原部署時,請使用原始資料庫備份。要在其他位置建立站台的新複本時,請使用站台套件。

站台套件包含的內容

站台套件包含:

  • 集合、欄位、包含每個版本的區塊類型、分類法定義、關係定義以及署名欄位定義;
  • 每個語言環境下的每筆內容條目,包括草稿、排程條目、回收筒條目、修訂歷程與翻譯群組;
  • 分類法術語與術語指派、署名與署名記錄、內容參照與 SEO 記錄;
  • 選單與選單項目、小工具區域與小工具、區段與重新導向;
  • 留言與留言反應(除非匯出關閉留言);
  • 媒體資料夾、媒體中繼資料,以及每個就緒媒體檔案的位元組;以及
  • 下文列出的可攜式站台設定。

套件以物件鍵排序的方式儲存 JSON 值(例如 JSON 欄位與 Portable Text)。因此,匯入後的值可能以與來源站不同的鍵順序列出。除此之外,值保持不變。

可攜式設定

僅匯出以下設定:site:title、site:tagline、site:logo、site:favicon、site:postsPerPage、site:dateFormat、site:timezone、site:social、site:seo、emdash:site_title、emdash:site_tagline 與 emdash:locale。

目標站台保留自己的站台 URL(site:url 與 emdash:site_url)、站台 ID、設定狀態與備份設定。匯入絕不會覆寫它們。

匯入計畫會詢問是保留設定精靈寫入的目標站標題與標語,還是使用套件中的值。預設使用套件中的值。

主體(principal)

使用者帳戶絕不會隨套件遷移。對於內容、修訂、媒體、署名或留言所參照的每個來源站使用者,套件會攜帶一個主體:使用者 ID、顯示名稱與電子郵件地址。主體沒有角色、密碼、通行金鑰、工作階段或權杖。

匯入期間,你可將每個主體對應到目標站使用者,或保持未對應。參見將作者對應到目標使用者。

留言

留言包含作者姓名與電子郵件、內文、狀態、討論串、時間戳記與審核中繼資料。不匯出 IP 位址雜湊與使用者代理。

反應保留其計數。匯出會把每個投票者雜湊取代為新的隨機值,因此目標站無法將反應與做出反應的訪客對應起來。

站台套件不包含的內容

站台套件絕不包含:

  • 使用者、工作階段、通行金鑰、OAuth 帳戶、允許的網域、API 權杖、OAuth 用戶端、授權碼或裝置碼;
  • 外掛儲存、外掛狀態或外掛設定(包括外掛機密);
  • 可攜式設定以外的設定,例如預覽簽署機密;
  • 稽核日誌、速率限制、編輯鎖定、排程任務狀態、404 日誌或遷移歷程;
  • 媒體使用記錄與搜尋索引(匯入會重建它們);
  • 來源站儲存鍵、儲存桶名稱、資料庫名稱或繫結名稱;或
  • 未就緒的媒體,例如未完成的上傳。

來自外部媒體提供者的媒體仍保持外部。套件保留參照,但不複製提供者的檔案。

準備目標站台

請匯入到滿足以下全部要求的站台。當目標站的內容、語言環境、上傳限制或支援的格式與套件不相符時,分析會回報阻斷項。

  • 管理員帳戶。 匯入以已登入管理員或 API 權杖身分執行。請在設定過程中建立目標站管理員。
  • 儲存後端。 來源站與目標站都需要已設定的儲存。EmDash 會在其中暫存套件檔案。
  • 無內容。 目標站不得包含條目(含回收筒)、修訂、媒體或媒體資料夾、署名或署名欄位、留言、重新導向、術語指派、關係、SEO 記錄、在管理後台建立的區段,或設定完成後建立的集合或區塊類型。從任意官方範本設定的站台都符合要求。設定過程建立的是設定鷹架:種子集合與區塊類型、分類法定義及其未指派術語、選單及其項目、小工具區域及其小工具,以及主題區段。計畫會列出鷹架,你確認計畫後匯入會將其刪除。
  • 套件使用的每個語言環境。 將套件中的每個語言環境加入目標站的 i18n 設定。沒有 i18n 設定的站台僅接受 en。語言環境比對不區分大小寫,匯入會以目標站設定的大小寫寫入每個語言環境,並宣告為 locale_recased。
  • 足夠大的上傳限制。 每個媒體檔案都必須符合目標站的 maxUploadSize,預設值為 50 MiB。
  • 格式版本 1。 目標站必須支援套件的格式版本以及所有必要功能。

以下請求回傳支援的格式版本、功能與限制。其 portableDomain 物件回報站台是否可接收匯入,以及不可接收時的原因。

curl https://new.example.com/_emdash/api/admin/transfer/capabilities \
  -H "Authorization: Bearer $EMDASH_TOKEN"

匯出站台

匯出會以有界步驟讀取站台,並將套件寫入站台的儲存。匯出完成前,匯出器會以與匯入相同的方式驗證成品套件。若匯出期間對站台的寫入成功,匯出器會重新開始。取得或續期條目編輯鎖定不算寫入。三次嘗試後會以 TRANSFER_EXPORT_CONCURRENT_WRITES 失敗。

匯出檔案在匯出建立後七天內可用。之後下載會回傳 TRANSFER_EXPIRED。

在管理後台匯出

  1. 開啟 Settings → Transfer。該頁面僅對管理員可用。

  2. 在 Export 區段關閉 Include comments,以排除留言與反應。

  3. 選取 Export site。頁面顯示匯出進度。請保持頁面開啟;若離開,返回後匯出會繼續。

  4. 出現 Export ready 後,選取 Download package 並選擇儲存 .emdash 檔案的位置。頁面顯示已下載的檔案數與位元組數,Stop 可取消下載。

該區段還會顯示套件摘要、各類記錄數量,以及站台最近的匯出(在過期前各自有下載按鈕)。

Download package 會逐個檔案取得匯出,對照資訊清單檢查每個檔案的大小與 SHA-256 摘要,並在瀏覽器中建立 .emdash 檔案,因此在 Cloudflare Workers 上對任意大小的站台都可用。若檔案不相符,下載會以錯誤停止。Chrome、Edge 及其他基於 Chromium 的瀏覽器會將檔案直接寫入磁碟。其他瀏覽器會在下載完成前將整個套件保存在記憶體中;對於約超過 500 MB 的匯出,頁面建議使用基於 Chromium 的瀏覽器或 CLI。

Download as one file 改為向伺服器請求單次回應中的封存。適合小型站台。在 Cloudflare Workers 上,大型站台可能超出單次請求限制。

使用 CLI 匯出

登入來源站台,然後匯出為套件檔案:

npx emdash login --url https://example.com
npx emdash site export --url https://example.com --output site.emdash

該指令會將匯出推進到完成,逐檔案下載套件,檢查每個檔案的大小與摘要,並寫入 site.emdash。加入 --no-comments 可排除留言與反應。若指令中斷,使用相同選項再次執行即可恢復同一次匯出。參見 emdash site export 參考。

使用 REST API 匯出

每次呼叫 advance 會執行一步,並回傳 nextRequestInMs(下次呼叫前的延遲)。當 nextRequestInMs 為 null 時匯出完成。

這些範例使用具有 transfer:export 範圍的個人存取權杖。參見權杖範圍。

  1. 開始匯出。若要排除留言與反應,請將 { "comments": false } 作為本文傳送。Idempotency-Key 標頭會使重試請求回傳同一匯出,而不是開始新的匯出。以不同選項重用同一金鑰會以 409 TRANSFER_IDEMPOTENCY_CONFLICT 失敗。

    curl -X POST https://example.com/_emdash/api/admin/transfer/exports \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Idempotency-Key: move-to-new-host"
  2. 推進匯出,直到 nextRequestInMs 為 null。在呼叫之間等待回傳的毫秒數。operation.progress 回報已完成與總步驟數(done 與 total)、迄今寫入的 records,以及在已知套件大小後的 bytesDone 與 bytesTotal。

    curl -X POST https://example.com/_emdash/api/admin/transfer/exports/$EXPORT_ID/advance \
      -H "Authorization: Bearer $EMDASH_TOKEN"
  3. 確認 operation.state 為 complete。失敗的匯出在 operation.errorCode 中攜帶原因。

  4. 將套件下載為單一 .emdash 檔案:

    curl -o site.emdash \
      https://example.com/_emdash/api/admin/transfer/exports/$EXPORT_ID/archive \
      -H "Authorization: Bearer $EMDASH_TOKEN"

.emdash 檔案是以 manifest.json 為首個項目的未壓縮 tar 封存。封存在一次回應中串流傳輸所有檔案。在 Cloudflare Workers 上,大型站台可能超出單次請求限制。請改為從 exports/{id}/manifest 下載 manifest.json,並從 exports/{id}/files/{path} 下載每個檔案。每個下載的檔案在串流傳輸時都會對照其記錄的摘要檢查。若儲存的位元組在匯出後發生變化,下載會以錯誤結束而不是完成。

匯入站台

匯入由套件建立,分析成計畫,且僅在你透過其摘要確認該計畫後才會執行。尚未開始執行的匯入會在建立後 24 小時過期。

管理後台、CLI 與 REST API 可執行每一步。AI 代理可透過 MCP 工具分析並啟動已上傳的匯入。

在管理後台匯入

  1. 在目標站開啟 Settings → Transfer。當站台可接收匯入時會出現 Import 區段;否則會列出站台已有的、妨礙匯入的內容。

  2. 選取 Choose package file 並選擇 .emdash 檔案。瀏覽器會檢查套件並分片上傳。上傳期間站台不會發生變化。若上傳中斷,再次選擇同一檔案即可從斷點繼續。

  3. 上傳完成後,站台會分析套件。你可以離開頁面稍後再回來。

  4. 審閱匯入:來源站台、匯出日期與 EmDash 版本、大小、套件摘要,以及各類記錄數量。閱讀 Blockers 與 Warnings、列出計畫轉換的 Differences from the source site,以及依類型分組的 Starter content that will be removed。參見審閱匯入計畫。

  5. 在 Authors 下,為每位作者的內容選擇本站使用者作為擁有者,或選擇 Don’t map。與使用者電子郵件相符的作者會標記為 Matched by email。參見將作者對應到目標使用者。

  6. 在 Site identity 下,選擇使用套件中的站台標題與標語,還是保留本站的。

  7. 選取 Start import 並確認。計畫存在阻斷項時按鈕會停用。站台編輯會暫停,直到匯入完成。

  8. 跟進進度。匯入完成後,頁面會顯示帶有 Verified 徽章的回條,以及回條、套件、計畫與內容摘要。選取 Copy receipt 可儲存回條 JSON 複本。

該頁面還提供從上傳到匯入完成期間的 Cancel import,以及在已開始寫入的匯入失敗或取消後的 Abandon import。兩者都需要確認。參見取消匯入與放棄未完成的匯入。

使用 CLI 匯入

登入目標站,然後分析套件:

npx emdash login --url https://new.example.com
npx emdash site import site.emdash --url https://new.example.com --analyze

該指令會在本機檢查整個套件檔案、上傳、分析,並列印帶有計畫摘要的計畫。當計畫有阻斷項時以結束代碼 2 結束。按審閱匯入計畫所述審閱計畫。

要變更計畫的決定,請再次執行帶決策旗標的 --analyze。--map-principal 將主體(依 ID 或電子郵件)對應到目標使用者(依 ID 或電子郵件),或對應到 none。--use-target-title 與 --use-target-tagline 會保留目標站的標題與標語:

npx emdash site import site.emdash --url https://new.example.com --analyze \
  --map-principal editor@example.com=editor@example.com \
  --map-principal 01J8ZQ4Y6T2N0D3VJ5R7K9M1PX=none \
  --use-target-title

傳入你審閱過的計畫摘要以執行:

npx emdash site import site.emdash --url https://new.example.com \
  --plan sha256:3f1c… --confirm

該指令會將匯入執行到完成並列印回條。若中斷,使用 emdash site import resume <operation-id> 繼續。emdash site import status <operation-id> 列印匯入狀態,emdash site import receipt <operation-id> 再次列印回條。參見 emdash site import 參考。

使用 REST API 匯入

伺服器處理套件內的檔案,而不是 .emdash 封存。請先解包。其中包含 manifest.json、index/ 下的索引檔案、records/ 下的記錄檔案,以及 media/ 下的媒體檔案。資訊清單固定每個檔案的大小與 SHA-256 摘要,因此套件摘要可識別整個套件。

這些範例使用具有 transfer:analyze 與 transfer:execute 範圍的權杖。

  1. 建立匯入。將 manifest.json 的未變更位元組作為請求本文傳送。回應包含操作以及伺服器仍需要的檔案的第一頁。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Idempotency-Key: move-to-new-host" \
      --data-binary @site/manifest.json
  2. 將每個缺失檔案上傳到 imports/{id}/files/{path}。Content-Length 標頭必須等於檔案宣告的大小,位元組必須符合其宣告的摘要。

    curl -X PUT \
      https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/files/index/000000.ndjson \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      --data-binary @site/index/000000.ndjson

    上傳索引檔案會宣告其列出的記錄與媒體檔案。每批上傳後再次請求 imports/{id}/missing,直到它不再回傳任何項目。

    上傳已儲存的檔案會再次檢查它。若已儲存複本不再相符,上傳會取代它,回應回報 alreadyVerified: false。

  3. 分析套件。呼叫 imports/{id}/analyze,直到 nextRequestInMs 為 null。最終回應包含 plan 及其 planDigest。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/analyze \
      -H "Authorization: Bearer $EMDASH_TOKEN"
  4. 審閱計畫,閱讀每個阻斷項、警告與轉換。參見審閱匯入計畫。

  5. 若預設值不是你想要的,提交決定。每次提交都會回傳新的計畫與計畫摘要。一旦請求執行,計畫會被凍結,再提交決定會以 409 TRANSFER_INVALID_STATE 失敗。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/analyze \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{ "decisions": { "principalMappings": { "01J8ZQ4Y6T2N0D3VJ5R7K9M1PX": null }, "siteTitle": "target" } }'
  6. 使用你審閱過的摘要開始匯入:

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/execute \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{ "packageDigest": "sha256:…", "planDigest": "sha256:…" }'
  7. 推進匯入,直到 nextRequestInMs 為 null,在呼叫之間等待回傳的延遲。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/advance \
      -H "Authorization: Bearer $EMDASH_TOKEN"
  8. 確認 operation.state 為 complete,然後從 imports/{id}/receipt 讀取回條。

當任一摘要與暫存套件或目前計畫不同時,執行會以 TRANSFER_PACKAGE_DIGEST_MISMATCH 或 TRANSFER_PLAN_DIGEST_MISMATCH 失敗。從 imports/{id}/plan 讀取目前計畫,再次審閱,並用其摘要重試。

將作者對應到目標使用者

分析會列出每個主體及其顯示名稱、電子郵件地址,以及參照它的套件記錄數量。當恰好有一個目標使用者具有相同電子郵件地址(比對不區分大小寫)時,計畫會建議該使用者,並預設將主體對應到該使用者。沒有建議的主體以未對應開始。

要變更對應,向 emdash site import --analyze 傳遞 --map-principal,或向 analyze 端點提交 principalMappings。每個對應指定一個目標使用者,或保持主體未對應(CLI 中為 none,API 中為 null)。EmDash 會將每個對應套用到條目作者、修訂作者、媒體上傳者、署名使用者連結與留言作者。

未對應主體的參照會被移除。當未對應作者還擁有關聯到其帳戶的署名時,匯入器會在該作者每筆沒有明確署名記錄、且其語言環境具有作者署名的條目上,明確署上該署名。因此作者署名會保留在頁面上。

以下兩種對應會產生 principal_conflict 阻斷項:

  • 在同一語言環境中有多個署名的主體被對應到一個使用者;或
  • 在同一語言環境中都有署名的兩個主體被對應到同一使用者。

目標使用者每個語言環境只能有一個署名。請將一個主體保持未對應,或將主體對應到不同使用者。

審閱匯入計畫

計畫列出匯入將建立的內容、將套用的決定,以及三類發現:

  • 阻斷項會阻止執行。在計畫沒有阻斷項之前,執行會回傳 TRANSFER_PLAN_BLOCKED。變更主體對應以解決 principal_conflict。任何其他阻斷項都需要對套件或目標站進行變更:取消匯入,完成變更,然後建立新的匯入。
  • 警告描述不會阻止匯入的套件問題。它們會被複製到回條中。
  • 轉換是來源站與匯入站台之間精確、已宣告的差異。先列出匯出器的變更,再列出匯入的變更。驗證在比較匯入站台與套件時會套用匯入的轉換。

計畫最多列出 500 個阻斷項與警告。issues_truncated 警告會回報另外還發現了多少。

阻斷項

代碼含義
package_invalid套件檔案或路徑未通過驗證。
unsupported_format目標站不支援套件的格式或格式版本。
unsupported_feature套件需要目標站不支援的功能。
limit_exceeded套件檔案或記錄超出限制。
file_missing已宣告的套件檔案尚未上傳。
file_mismatch套件檔案的大小或摘要與其宣告不相符。
record_invalid記錄格式錯誤或不是規範 JSON。
record_count_mismatch某類記錄數量與資訊清單不同。
record_order_invalid記錄順序錯誤,或父項出現在其子項之後。
duplicate_id同類的兩筆記錄共用一個 ID。
dangling_reference記錄參照了套件中不存在的記錄。包括 blocks 欄位命名了套件中缺少的區塊類型,以及目前版本在套件中缺失的區塊類型。
reference_cycle術語、留言或選單項目是其自身的父項。
media_ref_invalid內容參照了套件中不存在的媒體記錄。
media_blob_missing媒體記錄的檔案不在套件中。
media_blob_too_large媒體檔案大於目標站的 maxUploadSize。
target_not_empty目標站已有內容。阻斷項的 detail 會指出發現了什麼。
locale_not_configured套件使用了目標站 i18n 設定未包含的語言環境。
field_type_unknown欄位或署名欄位使用了目標站不支援的類型。
principal_conflict主體對應會使一個使用者在同一語言環境中擁有兩個署名。
integer_out_of_range整數超出目標資料庫的整數範圍。PostgreSQL 以 32 位元儲存整數。
value_constraint_violation管理後台 API 會拒絕的值。參見下方清單。
unique_violation記錄會在目標站上重複另一筆記錄的唯一鍵。

匯入器直接寫入記錄,因此分析會套用管理後台 API 在儲存這些記錄時套用的相同檢查。以下每類值都是 value_constraint_violation:

  • 不符合其欄位欄的條目值、必填欄位沒有值,或集合沒有的欄位的值;
  • 來源或目標不是站台路徑、類型不受支援、來源模式無效,或目標使用了來源未擷取的參數的重新導向;
  • 不是 http 或 https URL 的署名網站、不符合其欄位類型或選項的署名欄位值,或選項數超過站台支援上限的署名欄位;
  • 無效的集合 URL 模式;
  • 具有保留 slug、標籤為空或超過 200 個字元,或區塊類型編輯器會拒絕的欄位定義的區塊類型;
  • 既不是 http/https URL 也不是站台路徑的 SEO 正規 URL;以及
  • 具有選單不允許的配置的選單項目 URL。

警告

代碼含義
media_provider_external內容使用外部提供者的媒體。保留參照;不複製檔案。
media_row_missing設定參照了套件中不存在的媒體。
soft_reference_dangling選用參照無法解析為套件中的記錄。
redirect_loops_unchecked套件中的重新導向過多,無法在匯入前檢查迴圈。會關閉迴圈的重新導向將以停用狀態匯入。
issues_truncated發現的阻斷項或警告多於計畫列出的數量。

轉換

匯出器宣告其對來源站資料所做的變更。以下每個轉換都攜帶記錄類型與計數:

代碼含義
orphan_dropped父項在來源站上已不存在的記錄被省略,例如已刪除條目的修訂。
soft_orphan_dropped指向缺失記錄的連結被省略,例如指向已刪除術語的術語指派,或指向已刪除條目的選單項目。
orphan_reference_nulled對缺失記錄的參照被移除,例如媒體檔案已刪除的資料夾。
avatar_nulled署名頭像或區段預覽圖參照了套件中不存在的媒體,因而被移除。
media_not_ready_dropped未就緒的媒體(例如未完成的上傳)被省略。
media_ref_unlinked對套件中不存在的媒體的參照已從內容中移除。
media_url_relativized指向來源站自身媒體檔案的絕對 URL 被轉換為可在目標站解析的站台相對 URL。
redirect_duplicate_dropped同一來源路徑的重複重新導向被省略。每個來源路徑保留一個重新導向。
unknown_storage_key記錄仍參照來源站沒有的媒體檔案。它們被原樣匯出。

匯入宣告其自身的變更:

代碼含義
principal_mapped主體參照被改寫為對應的目標使用者。
principal_unmapped對未對應主體的參照被移除。
seeded_scaffold_removed在匯入寫入之前刪除目標站上的設定鷹架。計畫會列出每一項。
redirect_loop_disabled形成迴圈的重新導向以停用狀態匯入。
search_unsupported由於目標站使用 PostgreSQL,所列集合的搜尋被關閉。
float4_rounded小數值被捨入到目標站 PostgreSQL real 欄的精確度。
locale_recased語言環境以目標站設定的大小寫寫入,例如將 pt-br 寫為 pt-BR。

執行匯入

執行依以下階段依序進行:

  1. 預留目標站並再次確認其為空。
  2. 移除計畫中列出的設定鷹架。
  3. 建立區塊類型、集合、欄位、分類法定義、關係定義與署名欄位。
  4. 將媒體檔案複製到目標站儲存並建立媒體記錄。
  5. 寫入術語與署名。
  6. 寫入修訂與條目。
  7. 寫入術語指派、署名記錄、內容參照與 SEO 記錄。
  8. 寫入選單、小工具、區段、重新導向、留言、反應與設定。
  9. 重建搜尋索引與快取,並將媒體使用重新索引加入佇列。
  10. 驗證結果。

每次 advance 呼叫執行一個有界步驟,以適應 D1 上 Cloudflare Workers 的請求限制。進度保存在伺服器上。中斷的請求最多只會遺失進行中的步驟,且每次寫入都是冪等的,因此再次執行某步不會重複記錄。

當另一請求正在執行某步,或另一請求在某步期間接管操作時,advance 會回傳帶有較短 nextRequestInMs 的操作。儲存或資料庫錯誤會重試:操作記錄錯誤,且 nextRequestInMs 隨連續失敗增加。在沒有進展的情況下反覆失敗後,匯入會失敗。

匯入期間阻止寫入

從第一個執行步驟到匯入完成,EmDash 會以 503 TRANSFER_IMPORT_IN_PROGRESS 拒絕對其 API 的寫入請求。這涵蓋管理後台、REST API、外掛路由、公開留言提交、排程發佈以及外掛內容寫入。登入、使用者與 API 權杖管理、條目編輯鎖定,以及轉移 API 本身仍可用。讀取請求不會被阻止。

MCP 寫入工具(包括外掛 MCP 工具)會在通常的工具錯誤中以 TRANSFER_IMPORT_IN_PROGRESS 失敗。唯讀 MCP 工具與 site_* 轉移工具仍可運作,因此透過 MCP 啟動的匯入可透過 MCP 恢復、檢查並完成。

中斷後恢復

管理後台頁面僅在開啟時推進匯入。要恢復,請重新開啟 Settings → Transfer,執行 emdash site import resume <operation-id>,或再次對該操作呼叫 advance。伺服器從最後一個已完成步驟繼續。若中斷的請求仍持有該操作,下一次呼叫會等待該持有過期,最多五分鐘。

失敗或已取消的匯入無法恢復。

取消匯入

在 Settings → Transfer 中選取 Cancel import,執行 emdash site import cancel <operation-id>,或傳送 POST imports/{id}/cancel。進行中的步驟會在目前批次後停止。取消不會移除已寫入的記錄。

放棄未完成的匯入

已開始寫入的失敗或已取消匯入會繼續阻止寫入,以免未完成的站台被誤編輯。要解除阻止,在 Settings → Transfer 中選取 Abandon import,執行 emdash site import abandon <operation-id>,或傳送 POST imports/{id}/abandon。放棄會保留已匯入的資料。

放棄後,站台不再為空,因此無法再接收另一次匯入。請改為匯入到新建完成的站台。

從未開始寫入的失敗或已取消匯入不會阻止寫入,也無需放棄。

驗證結果

驗證使用與匯出器相同的程式碼回讀每筆已匯入記錄,將計畫宣告的轉換套用到套件的記錄,然後比較兩者。它還會檢查每類記錄的數量,並再次下載每個已匯入媒體檔案以檢查其摘要。任何差異都會使匯入以 TRANSFER_VERIFICATION_FAILED 失敗。操作的 errorDetail 最多列出 50 處差異。

成功的匯入會產生回條:

{
	"operationId": "01J8ZR2C4S6D8F0G2H4J6K8M0N",
	"packageDigest": "sha256:…",
	"planDigest": "sha256:…",
	"targetSiteId": "01J8ZR0A2B4C6D8E0F2G4H6J8K",
	"originSiteId": "01J1A3C5E7G9J1L3N5Q7S9U1W3",
	"formatVersion": "1",
	"importerEmDashVersion": "0.38.0",
	"completedAt": "2026-09-23T10:15:00.000Z",
	"logicalDigest": "sha256:…",
	"counts": { "entry": 412, "media": 96 },
	"warnings": [],
	"verification": "verified",
	"receiptDigest": "sha256:…"
}

回條記錄:在驗證完成時,由 targetSiteId 識別的目標站在套用由 planDigest 識別的計畫後,恰好持有由 packageDigest 識別的套件的內容。logicalDigest 彙總已驗證的記錄。

receiptDigest 是移除 receiptDigest 屬性後回條規範 JSON 的 SHA-256 摘要。它可偵測簽發後被變更的回條。回條未簽署,因此不能證明由哪台伺服器簽發。若這一點很重要,請透過已驗證連線從目標站取得回條。

回條描述驗證完成那一刻的站台。它不說明之後的編輯。

在資料庫之間遷移

套件不依賴來源站的資料庫。可從 SQLite、PostgreSQL 或 D1 匯出並匯入到其中任意一種。當目標站使用 PostgreSQL 時,請規劃以下差異:

  • PostgreSQL 以 32 位元儲存整數。超出該範圍的整數是 integer_out_of_range 阻斷項。
  • PostgreSQL 將 number 欄位與媒體焦點存為 32 位元浮點值。發生變化的值會宣告為 float4_rounded,驗證會比較捨入後的值。
  • 全文搜尋僅在 SQLite 與 D1 上可用。啟用了搜尋的集合會在關閉搜尋的情況下匯入,並宣告為 search_unsupported。

匯入器會將媒體寫入目標站儲存後端的新儲存鍵下,並改寫內容、設定與 SEO 記錄中的媒體參照以相符。對來源站沒有的媒體檔案的參照會原樣匯出,並宣告為 unknown_storage_key。

安全性

  • 將套件視為敏感資料。 它包含所有內容(包括草稿與回收筒),以及作者與留言者的電子郵件。請勿放在公用儲存桶或共用資料夾中,並刪除不再需要的複本。
  • 將套件視為不可信輸入。 匯入在寫入前會檢查路徑、大小、摘要、記錄結構描述、參照與限制。它絕不會執行套件中的程式碼或 SQL,也絕不會從套件中取得 URL。
  • 有意地授予轉移存取權限。 轉移需要管理員角色。具有 admin 範圍的權杖可執行所有轉移操作,因此請只給代理權杖它所需要的轉移範圍。
  • 審閱稽核日誌。 EmDash 會在站台的稽核日誌中記錄轉移操作:transfer_export_create、transfer_import_create、transfer_import_execute、transfer_import_cancel、transfer_import_abandon、transfer_import_complete、transfer_import_fail、transfer_approval_approve 與 transfer_approval_deny。每筆記錄會指出操作用者以及操作或核准(資源類型為 transfer_operation 或 transfer_approval)。其詳情僅包含 ID、摘要、記錄計數與錯誤代碼,絕不包含套件內容。轉移錯誤詳情同樣絕不包含套件內容。
  • 保持暫存私人。 EmDash 在儲存桶的 transfers/ 前綴下暫存套件檔案,並拒絕透過其媒體路由提供該前綴。若儲存桶有公用網域,請像備份一樣將其範圍限定為媒體。操作結束或過期後會刪除暫存檔案。

權杖範圍

轉移使用三種 API 權杖範圍:

範圍允許
transfer:export開始、推進和下載匯出。
transfer:analyze建立匯入、上傳套件檔案、分析和讀取計畫。
transfer:execute開始、推進、取消和放棄匯入。

admin 範圍包含全部三種,因此 emdash login 儲存的權杖可執行所有轉移。每種轉移範圍僅授予其自身操作,且只有管理員可以簽發。用它們為權杖提供比 admin 更窄的存取權限,例如只能分析套件但不能匯出或匯入的代理。參見範圍參考。

代理核准

AI 代理透過 site_* MCP 工具驅動轉移。這些工具開始、推進並回報操作。它們從不攜帶套件位元組,因此代理的使用者需用 CLI 或 REST API 下載匯出並上傳套件。每個工具都需要 Admin 角色。

權杖既沒有 admin 也沒有相符轉移範圍的 MCP 用戶端(例如僅被授予 transfer:analyze 的代理)無法單獨開始匯出或匯入。其 site_export_start 或 site_import_start 呼叫會建立待處理的核准請求,並以 TRANSFER_APPROVAL_REQUIRED 與核准 ID 失敗。管理員在 Settings → Transfer 的 Approval requests 下核准或拒絕請求,其中會列出每個待處理請求的請求者、操作與過期時間。僅限工作階段的 POST /_emdash/api/admin/transfer/approvals/{id}/approve 與 …/deny 端點作用相同。API 權杖不能核准請求。用戶端隨後使用核准 ID 重複呼叫。核准僅適用於這些 MCP 工具;REST API 沒有核准參數。

一次核准向請求它的使用者授予一次呼叫,須來自同一權杖、相同參數。匯出核准繫結到匯出選項。匯入核准繫結到操作與兩個摘要,因此變更後的計畫需要新的核准。待處理請求 15 分鐘後過期,已核准的在核准後 15 分鐘過期。啟動操作的重試會消耗它;若操作未能啟動,可在過期前用同一核准重試。同一使用者與權杖隨後可在沒有該範圍的情況下檢查並推進那一次操作。

僅當代理必須在無人逐次核准的情況下執行轉移時,才向代理權杖授予 transfer:export、transfer:execute 或 admin。

限制

限制值
manifest.json8 MiB
一筆記錄1,900,000 位元組
一個記錄或索引檔案4 MiB 與 1,000 筆記錄
每個套件的記錄數5,000,000
每個套件的檔案數1,000,000
JSON 巢狀深度64
一個媒體檔案目標站的 maxUploadSize,預設 50 MiB

capabilities 端點回報站台強制執行的值。

面向託管提供者

託管控制平面可以僅用 REST API 將客戶站台遷移到正式環境:

  1. 佈建具有其儲存、語言環境與 maxUploadSize 的新 EmDash 站台,並完成設定。確認 capabilities 將 portableDomain.empty 回報為 true。

  2. 為控制平面簽發具有 transfer:analyze 與 transfer:execute 的權杖。不要將其放入任何代理或建站工具。

  3. 執行匯入,並在執行前對計畫的警告強制執行你自己的原則。拒絕任何有阻斷項的計畫。

  4. 取得回條並在推廣站台前檢查:

    • verification 為 verified;
    • packageDigest 是你打算發佈的套件的摘要;
    • planDigest 是你接受的計畫;
    • targetSiteId 是你即將推廣的站台;並且
    • receiptDigest 與回條的規範 JSON 相符。
  5. 推廣站台,例如將其網域路由到該站台。

在第 4 步成功之前,請保持目標站不可達。EmDash 不會對訪客隱藏部分匯入的站台。

疑難排解

轉移錯誤使用穩定代碼。HTTP 狀態與每個代碼一併出現。

代碼狀態如何處理
TRANSFER_TARGET_NOT_EMPTY409目標站已有內容。請匯入到新建完成的站台。Settings → Transfer 與 capabilities 會列出使站台不合格的內容。
TRANSFER_IMPORT_IN_PROGRESS503此站台上有匯入正在執行,或未完成的匯入仍在阻止寫入。等待其完成,或放棄失敗或已取消的匯入。
TRANSFER_FENCE_CHECK_FAILED503EmDash 無法檢查是否有匯入正在執行。請重試寫入。
TRANSFER_EXPORT_CONCURRENT_WRITES409匯出執行期間站台持續變化。請在編輯較靜默時再次匯出。
TRANSFER_EXPIRED410匯出檔案在七天後被刪除,或匯入未在 24 小時內執行。請重新開始。
TRANSFER_FILE_MISSING422部分已宣告檔案未上傳。請上傳 imports/{id}/missing 列出的全部內容。
TRANSFER_FILE_NOT_DECLARED422上傳路徑不在套件中。請僅上傳列出的路徑。
TRANSFER_FILE_SIZE_MISMATCH422Content-Length 或上傳的位元組與宣告的大小不同。請原樣上傳檔案。
TRANSFER_FILE_DIGEST_MISMATCH422上傳的位元組與宣告的摘要不同,或匯出檔案在匯出後發生了變化。請上傳原始檔案,或再次匯出。
TRANSFER_LIMIT_EXCEEDED413檔案超出限制。對於媒體,請提高目標站的 maxUploadSize。
TRANSFER_MANIFEST_INVALID422請求本文不是有效資訊清單。請逐位元組傳送 manifest.json。
TRANSFER_UNSUPPORTED_FORMAT422升級目標站上的 EmDash。
TRANSFER_UNSUPPORTED_FEATURE422升級目標站上的 EmDash。
TRANSFER_CONTAINER_INVALID422.emdash 檔案不是有效的套件封存。請重新下載。
TRANSFER_PLAN_BLOCKED409計畫有阻斷項。參見審閱匯入計畫。
TRANSFER_PACKAGE_DIGEST_MISMATCH409摘要與暫存套件不相符。請使用操作的 packageDigest。
TRANSFER_PLAN_DIGEST_MISMATCH409自你審閱以來計畫已變更。請讀取目前計畫並再次審閱。
TRANSFER_DECISIONS_INVALID422決定命名了未知主體,或不存在的目標使用者。請更正對應。
TRANSFER_INVALID_STATE409操作不處於允許該請求的狀態。請讀取操作並遵循其 state。
TRANSFER_LEASE_ACTIVE409另一請求正在執行某步。請等待並重試。
TRANSFER_IDEMPOTENCY_CONFLICT409Idempotency-Key 已用於其他選項的匯出,或另一套件的匯入。請使用新金鑰。
TRANSFER_RUNTIME_MISMATCH409不相容的 EmDash 版本啟動了該操作。請用啟動它的版本完成,或開始新的操作。
TRANSFER_VERIFICATION_FAILED422已匯入站台與套件不相符。請閱讀 errorDetail 中的差異,放棄匯入,並匯入到新站台。
TRANSFER_APPROVAL_REQUIRED403管理員必須核准該請求。參見代理核准。
TRANSFER_APPROVAL_INVALID403核准未知、已拒絕、已過期、已使用,或繫結到其他參數。請請求新的核准。
TRANSFER_SCHEMA_UNCLASSIFIED500資料庫有匯出器無法識別的資料表或欄。請執行與資料庫遷移相符的 EmDash 版本。
INSUFFICIENT_SCOPE403權杖既沒有 admin,也沒有請求所需的轉移範圍。請簽發具有該範圍的權杖。