5998 字
30 分鐘

roim-picx 安裝與舊版升級:Cloudflare R2、D1 設定教學

roim-picx 新版不只使用 Cloudflare Pages、R2 和 KV,還加入 D1 儲存圖片 metadata、使用者、相簿與統計資料。第一次安裝只要把三種資源綁定正確;從舊版升級則多了一步:把原本只存在 R2 的圖片資料登記到 D1。

我這次實際處理的是舊版升級。過程中遇到三個容易誤判的狀況:Wrangler 找不到設定檔、登入畫面顯示 Token login failed,以及升級後舊圖片全部消失。最後確認圖片檔沒有遺失,而是新版清單改從 D1 讀取,必須另外執行 R2 到 D1 的同步。

這篇以 2026-07-15 的 roim-picx main 分支與 Wrangler 4.110.0 實測結果整理。若你還不熟悉 R2 bucket、自訂網域與快取,可以先看Cloudflare R2 圖片託管最新版

roim-picx 是什麼?#

roim-picx 是一套可以部署在自己 Cloudflare 帳號下的圖片託管應用。它不只提供存放圖片的空間,還補上瀏覽器上傳、圖片列表、資料夾搜尋、重新命名、分享與管理介面,讓 R2 不必只靠 Dashboard 或 CLI 操作。

它把不同類型的資料拆到 Cloudflare 對應的服務:

服務在 roim-picx 裡的工作
Cloudflare Pages部署 Vue 前端與靜態資源
Pages Functions處理登入、上傳、查詢、刪除與管理 API
R2保存實際圖片檔案
D1保存圖片 metadata、使用者、相簿、分享與統計資料
KV保存工作階段等鍵值資料

這個拆法也是新版升級時容易困惑的地方:R2 裡有圖片,不代表 D1 裡已經有圖片紀錄。檔案與管理資料分開保存,所以舊版升級後,還要將 R2 物件的 metadata 同步到 D1,新版列表才看得到舊圖。

roim-picx 可以做什麼?#

如果只是偶爾上傳一張圖片,直接使用 R2 Dashboard 也能完成。但圖片開始變多後,真正需要的通常是「怎麼找到、整理和管理」,這正是 roim-picx 補上的部分。

目前專案提供的主要功能包括:

  • 單張與批次圖片上傳,可選擇資料夾並保留原始檔名。
  • 圖片列表、分頁、前綴搜尋、重新命名與刪除。
  • 圖片到期時間與自動清理機制。
  • 使用者、權限、上傳統計與稽核紀錄。
  • 相簿建立、公開或密碼分享,以及分享項目管理。
  • Token、GitHub、Google、Steam 等登入方式,其中 OAuth 屬於選配。
  • 使用獨立 API Key 上傳、列出與刪除圖片,方便串接腳本或產文流程。
  • R2 與 Hugging Face 儲存選項,以及 NSFW 偵測、文字浮水印等進階功能。
  • 將舊 R2 圖片 metadata 分批同步到 D1,保留既有圖片資產。

這篇聚焦在最小可用的 R2 架構:Token 登入、Pages、KV、R2 與 D1。其他 OAuth、Hugging Face、NSFW 偵測與浮水印,建議等基本上傳流程穩定後再逐項開啟。

為什麼我會選 roim-picx?#

我原本已經在 Cloudflare 部署舊版,圖片也持續保存在自己的 R2 bucket。升級新版時,與其換一套圖床並重新搬移所有圖片,我更希望沿用現有 R2,再補上新版的 D1 管理能力。roim-picx 剛好保留這條升級路徑。

實際考量主要有這幾點:

圖片資源留在自己的 Cloudflare 帳號#

Pages、R2、D1 與 KV 都由自己建立。即使 roim-picx 是第三方開源專案,實際圖片與資料庫仍放在自己的 Cloudflare 資源中,也能自行決定網域、Token 與存取設定。

這不等於資料會自動備份或永遠免費。R2 容量、操作次數與其他 Cloudflare 服務仍要依帳號用量計算,正式使用前應先了解Cloudflare R2 的免費額度與請求成本

不需要另外維護一台圖床伺服器#

整套應用使用 Pages 與 Pages Functions 執行,不需要為了圖片管理介面另外準備 VPS、作業系統與常駐 Node.js 程序。需要維護的重點會轉成 Cloudflare Bindings、環境變數、D1 migration 與專案版本。

比直接操作 R2 多一層日常管理介面#

R2 適合保存 object,但原生 Dashboard 不是為日常圖床工作流程設計。roim-picx 把上傳、搜尋、相簿、分享、權限與統計集中在同一個網頁介面,對經常管理文章圖片或專案素材的人會比較順手。

舊版圖片可以原地納入新系統#

升級時不需要重新下載再上傳圖片。專案內建同步 API,會掃描原本的 R2 object,再把 key、大小、格式、資料夾與建立時間等 metadata 寫入 D1。圖片檔仍留在原 bucket,降低搬移時改壞既有圖片網址的風險。

API 上傳後可以直接取得圖片 URL#

我不只想在瀏覽器上傳圖片,也希望讓圖片生成或排程產文流程直接呼叫 API,拿到 URL 後寫進 Astro Markdown。新版 roim-picx 已有獨立 API Key,不需要把管理員 Token 放進自動化腳本;API Key 的權限也限制在上傳、列表與刪除。

這項功能讓 roim-picx 不只是圖片後台,也能當成自動化流程中的圖片儲存端。後面會提供可直接測試的 curl 與 Node.js 範例。

為什麼這次沒有改用 CloudFlare-ImgBed?#

設定 D1 卡住時,我也考慮過是否乾脆換成 CloudFlare-ImgBed。但比較之後,我的結論是繼續使用 roim-picx。

這不是因為 roim-picx 的整體功能更多,而是兩個專案的定位不同。CloudFlare-ImgBed 不只處理圖片,還支援一般檔案、RESTful API、WebDAV、Docker 與 Serverless 部署,以及 Telegram、Discord、R2、S3、Hugging Face、WebDAV 等多種儲存後端。這些功能適合想建立完整檔案平台的人,對只想維護個人圖床的情境則會增加設定與排錯範圍。

面向roim-picxCloudFlare-ImgBed
主要定位圖床、圖片管理與相簿圖床、檔案託管與個人雲端空間
日常操作上傳、搜尋、目錄、相簿、圖片分享檔案生命週期、目錄、權限、API、WebDAV
儲存後端R2、Hugging FaceR2、S3、WebDAV、Telegram、Discord 等
部署範圍Pages、Functions、R2、KV、D1Serverless 或 Docker,依功能搭配不同服務
設定複雜度中等,仍要處理 D1 migration功能面較廣,要理解的設定與整合更多
比較適合自用圖床、文章圖片、相簿管理一般檔案、跨工具掛載、多後端儲存

我的需求是自己使用、以圖片上傳與管理為主、已經有 R2 圖片,而且不打算把它變成完整網路硬碟。這種條件下,換專案不會讓「不熟悉 D1」自動消失,只是把問題換成另一套部署、權限與儲存設定。

反過來說,如果你需要 WebDAV、一般檔案管理、多種外部儲存後端,或希望把服務當成個人雲端硬碟,CloudFlare-ImgBed 的功能範圍會比較符合需求。若你只需要把 Astro 文章圖片上傳到 R2,完全不在乎網頁管理後台,直接使用桌面上傳工具、CLI 或 S3 相容工具反而更單純。

繼續使用 roim-picx 前要知道的風險#

roim-picx 的介面與功能比較貼近我的需求,但不代表後續可以完全不維護。

目前沒有正式 GitHub Releases#

截至 2026-07-15,專案 GitHub 頁面仍顯示沒有正式 Releases。若 Cloudflare Pages 直接追蹤上游 main,一次更新可能同時帶入新功能、migration、環境變數或不相容調整。

比較保守的做法是:

  1. Fork 到自己的 GitHub 帳號。
  2. 保留目前已驗證可運作的 commit。
  3. 不要自動把上游 main 直接推進正式環境。
  4. 更新前先檢查 migrations/.env.example、README 與程式差異。
  5. 重要更新前備份 D1,並確認 R2 圖片有可恢復來源。

架構已經不只是簡單圖床#

新版需要 R2、D1、KV、Pages Functions 與登入設定。D1 平常不需要反覆建立,但每次更新只要出現新的 migration,就要先判斷執行順序與正式資料庫狀態,再重新部署驗證。

文件可能落後目前程式碼#

這次實際版本已有 00010011,但專案內仍可看到只執行 0001_init.sql 的舊說明。這也是本文選擇直接檢查 migration 目錄與程式碼,而不是完全照著單一 README 指令操作的原因。

哪些情況不一定適合?#

roim-picx 的代價是設定項目比一般註冊即用的圖床多。第一次安裝要處理 Pages、KV、R2、D1、環境變數與 migration;專案更新後也要留意資料庫結構是否有新 migration。

需求是否適合 roim-picx
想把圖片放在自己的 Cloudflare 帳號適合
已有舊版與 R2 圖片,希望原地升級適合
需要網頁管理、搜尋、相簿與分享適合
只想臨時貼幾張圖,不想維護設定不一定需要
不熟悉 Cloudflare Bindings,且不想碰 CLI學習成本會比較高
需要完整商用 SLA、客服與代管備份應另外評估託管服務

如果這些取捨可以接受,接下來再依自己的狀況選擇「第一次安裝」或「舊版升級」。

先判斷你是哪一種安裝情境#

情境R2 內有舊圖片要建立 D1要同步舊圖 metadata
第一次安裝
舊版升級

兩條路徑最後都使用相同的 Pages Functions Binding:

Cloudflare 平台資源Variable name資源名稱範例
KV namespaceXKimgs
R2 bucketPICXmedia
D1 databaseDBroim-picx

XKPICXDB 是程式使用的 Binding 名稱,不能自行改成資源名稱。右側的 imgsmediaroim-picx 才是你在 Cloudflare 建立的實際資源,名稱可以不同。

第一次安裝 roim-picx#

第一次安裝沒有舊 R2 資料,重點是先建立 Pages 專案與三種儲存資源,再把 D1 migration 完整套用到正式資料庫。

Step 1:Fork 專案並建立 Cloudflare Pages#

先 Fork roimdev/roim-picx,再把自己的 Fork clone 到電腦:

Terminal window
git clone https://github.com/<你的 GitHub 帳號>/roim-picx.git
cd roim-picx
npm install

到 Cloudflare Dashboard 建立 Pages 專案,連接剛才的 GitHub repository。建置設定可使用:

Build command: npm run build
Build output directory: dist

先讓 Pages 完成第一次建置。資源 Binding 與環境變數可以在專案建立後補上。

Step 2:建立 KV、R2 與 D1#

在 Cloudflare Dashboard 建立:

  1. 一個 KV namespace,保存工作階段等鍵值資料。
  2. 一個 R2 bucket,保存實際圖片檔案。
  3. 一個 D1 database,保存圖片 metadata 與其他結構化資料。

名稱可以自行決定。本文後續以這組名稱示範:

KV namespace: imgs
R2 bucket: media
D1 database: roim-picx

Step 3:設定 Production Bindings#

回到 Pages 專案,進入 Settings → Bindings。有些 Dashboard 版本會把入口放在 Settings → Functions

新增三個 Production Binding:

KV namespace binding
Variable name: XK
KV namespace: imgs
R2 bucket binding
Variable name: PICX
R2 bucket: media
D1 database binding
Variable name: DB
D1 database: roim-picx

如果 D1 資料庫就叫 roim-picx,Variable name 仍然要填 DB,不能也填成 roim-picx。Cloudflare 的 Binding 是 Pages Functions 存取資源時使用的程式介面。

Step 4:加入最小環境變數#

只使用 Token 登入與 R2 儲存時,先設定以下 Production 變數:

變數用途
BASE_URLhttps://你的圖床網域產生完整網址
PICX_AUTH_TOKEN自行產生的長隨機字串管理員登入 Token
ALLOW_TOKEN_LOGINtrue開放 Token 登入
STORAGE_TYPER2使用 R2 儲存圖片

PICX_AUTH_TOKEN 應存成 Cloudflare Secret,不要寫進 Git、Wrangler 設定檔或文章。GitHub、Google、Steam OAuth 與 Hugging Face 儲存都是選配,先完成基本部署再加比較容易排錯。

Step 5:建立本機 Wrangler 設定#

這次直接在沒有 Wrangler 設定檔的專案執行 migration 時,Wrangler 4.110.0 回傳:

No configuration file found. Create a wrangler.jsonc file to define your D1 database.

先登入 Cloudflare 並查出 D1 的 database ID:

Terminal window
npx wrangler login
npx wrangler whoami
npx wrangler d1 list

在專案根目錄新增 wrangler.local.jsonc

{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "roim-picx",
"compatibility_date": "2026-07-15",
"d1_databases": [
{
"binding": "DB",
"database_name": "roim-picx",
"database_id": "填入 d1 list 顯示的實際 ID",
"migrations_dir": "migrations"
}
]
}

這份檔案只用來讓本機 Wrangler 找到正式 D1,不需要放 Token 或 OAuth Secret。若不打算提交它,可以加入本機 repository 的 exclude:

Terminal window
echo 'wrangler.local.jsonc' >> .git/info/exclude

Step 6:一次套用所有 D1 migrations#

目前專案有 00010011 共 11 個 migration。不要只執行 0001_init.sql,否則相簿、系統設定、NSFW、API Key 等後續資料結構不會建立。

請在專案根目錄執行:

Terminal window
npx wrangler d1 migrations apply roim-picx \
--remote \
--config wrangler.local.jsonc

--remote 不能省略。Cloudflare 現行 Wrangler 的 D1 指令預設使用本機資料庫,沒有加上 --remote 不會初始化 Pages 正式環境使用的 D1。

完成後檢查是否還有未套用的 migration:

Terminal window
npx wrangler d1 migrations list roim-picx \
--remote \
--config wrangler.local.jsonc

正常應顯示沒有 migration 等待執行。也可以查詢遠端資料表:

Terminal window
npx wrangler d1 execute roim-picx \
--remote \
--config wrangler.local.jsonc \
--command="SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"

Step 7:重新部署並驗證#

新增 Binding 或修改環境變數後,舊 deployment 不會自動取得新設定。到 Pages 的 Deployments 對最新 Production 執行 Retry deployment,或推送一個新 commit 觸發部署。

部署列表出現新的 Production deployment 後,再測試:

  1. 使用 PICX_AUTH_TOKEN 登入。
  2. 上傳一張測試圖片。
  3. 重新整理圖片列表。
  4. 確認管理頁能讀取統計資料。

第一次安裝到這裡就完成,不需要執行歷史圖片同步。

從沒有 D1 的舊版升級#

舊版升級不能把部署成功當成結束。圖片檔雖然還在 R2,但新版圖片列表會查 D1 的 images 表;D1 沒有 metadata 時,畫面看起來就像圖片全部消失。

Step 1:保留舊 R2 與 KV#

不要刪除或重建原本的 R2 bucket。舊圖片檔仍然保存在裡面,升級只是在既有架構旁新增 D1。

先把 Fork 或本機部署分支更新到新版,再確認 Cloudflare 仍綁定原有資源:

KV:XK → 原本的 KV namespace
R2:PICX → 原本的 R2 bucket

接著建立新的 D1 database,並補上:

D1:DB → roim-picx

Step 2:初始化 D1 並重新部署#

照前一節建立 wrangler.local.jsonc,再對遠端 D1 套用全部 migration:

Terminal window
npx wrangler d1 migrations apply roim-picx \
--remote \
--config wrangler.local.jsonc

確認 migration 已全部套用後,回到 Pages 重新部署 Production。這一步不能省略,因為已經在執行的舊 deployment 不會自動取得剛新增的 DB Binding。

Step 3:遇到 Token login failed 先檢查 D1#

這次升級時,登入畫面一直顯示 Token login failed,但 Token 本身沒有填錯。從專案程式碼來看,流程是先確認 Token 與 PICX_AUTH_TOKEN 相符,再檢查 ALLOW_TOKEN_LOGIN,接著才讀寫 D1、建立 system 使用者並簽發 JWT。

因此三種訊息要分開判斷:

訊息優先檢查
Token 無效輸入值是否與 Production 的 PICX_AUTH_TOKEN 相同
Token login is disabledALLOW_TOKEN_LOGIN 是否為字串 true
Token login failedD1 migration、Production 的 DB Binding、是否已重新部署

本次真正的原因是 Production 仍在執行兩個月前的舊 deployment,新增的 D1 Binding 還沒進入正式環境。重新部署後即可登入,不需要重設 Token。這個現象也與專案的 Issue #30 相符。

Step 4:確認新版 D1 還沒有舊圖資料#

登入成功但舊圖片沒出現時,先查 images 筆數:

Terminal window
npx wrangler d1 execute roim-picx \
--remote \
--config wrangler.local.jsonc \
--command="SELECT COUNT(*) AS image_count FROM images;"

如果 R2 有圖片、結果卻是 0,代表圖片檔仍在 R2,只是尚未把 metadata 寫入 D1,不是前端顯示錯誤。

Step 5:先 dry run R2 到 D1 同步#

不要把 Token 直接貼進 shell history。先暫存在目前終端機工作階段:

Terminal window
read -s "PICX_TOKEN?請輸入登入 Token:"
echo

先執行 dry run,不寫入 D1:

Terminal window
curl -sS \
-H "Authorization: Bearer $PICX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"limit":100,"dryRun":true}' \
"https://你的圖床網域/rest/admin/sync-r2-to-d1"

確認回傳 code: 200 後,再正式同步第一批:

Terminal window
curl -sS \
-H "Authorization: Bearer $PICX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"limit":100}' \
"https://你的圖床網域/rest/admin/sync-r2-to-d1"

Step 6:依 nextCursor 同步所有批次#

每次最多處理指定的 limit。回傳內容要看兩個欄位:

{
"nextCursor": "實際回傳的一長串 cursor",
"hasMore": true
}

只要 hasMoretrue,就把實際的 nextCursor 放進下一次請求:

Terminal window
curl -sS \
-H "Authorization: Bearer $PICX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"limit":100,"cursor":"貼入實際 nextCursor"}' \
"https://你的圖床網域/rest/admin/sync-r2-to-d1"

不要把 貼入實際 nextCursor 這類範例文字直接送出。那不是合法的 R2 continuation token,會收到:

Continuation token is not valid. (10023)

如果已經找不到上一個 cursor,可以重新執行第一批請求。同步 API 會用圖片 key 檢查 D1,已經存在的圖片會計入 skipped,不會重複新增,並會再次提供下一個 cursor。

重複執行到回傳:

{
"hasMore": false,
"errors": 0
}

最後清除目前 shell 暫存的 Token:

Terminal window
unset PICX_TOKEN

unset 只會清除目前終端機的環境變數,不會刪除 Cloudflare Secret、D1、R2 或剛完成的同步資料。

安裝完成後:用 API 上傳並取得 URL#

完成第一次安裝或舊版升級後,可以建立 roim-picx API Key,讓腳本直接呼叫:

POST https://你的圖床網域/rest/upload

API Key 可放在其中一種 Header:

X-API-Key: px_xxxxxxxx.xxxxxxxxxxxxx

或:

Authorization: Bearer px_xxxxxxxx.xxxxxxxxxxxxx

API Key 只允許呼叫上傳、列表與刪除相關路由。這比在自動化腳本中直接保存完整管理員 Token 更容易控制影響範圍;若 Key 外洩,也能從管理介面單獨撤銷。

建立 API Key#

登入 roim-picx 後,到管理頁的 API Keys 區塊建立新 Key,例如命名為:

Astro uploader

完整 Key 只在建立時回傳一次,格式如下:

px_前綴.秘密字串

請立即存進密碼管理器或自動化平台的 Secret。D1 的 api_keys 表只保存秘密字串的 SHA-256 hash,之後無法從資料庫還原完整 Key。

如果目前介面暫時找不到 API Keys 頁面,也可以先登入取得 JWT,再呼叫建立 API:

Terminal window
curl -sS -X POST \
"https://你的圖床網域/rest/api-keys" \
-H "Authorization: Bearer $ROIM_LOGIN_JWT" \
-H "Content-Type: application/json" \
-d '{"name":"Astro uploader"}'

也可以設定 ISO 8601 格式的到期時間:

{
"name": "Astro uploader",
"expires_at": "2027-01-01T00:00:00.000Z"
}
API Key 只會完整顯示一次

不要把 API Key 寫進 Git、Markdown 或公開的 shell 指令。請用環境變數或 Secret 管理;如果遺失,就撤銷舊 Key 並重新建立。

用 curl 上傳一張圖片#

先把 API Key 暫存在目前終端機:

Terminal window
read -s "ROIM_PICX_API_KEY?請輸入 roim-picx API Key:"
echo

接著上傳圖片:

Terminal window
curl -sS -X POST \
"https://你的圖床網域/rest/upload" \
-H "X-API-Key: $ROIM_PICX_API_KEY" \
-F "files=@./image.webp"

上傳 API 使用 multipart/form-data,檔案欄位名稱一定要是:

files

不是常見的 fileimage。批次上傳時,重複提供多個 files

Terminal window
curl -sS -X POST \
"https://你的圖床網域/rest/upload" \
-H "X-API-Key: $ROIM_PICX_API_KEY" \
-F "files=@./cover.webp" \
-F "files=@./content-01.png"

可傳入的 FormData 欄位#

欄位用途範例
files圖片檔,可重複多次@image.webp
pathR2 儲存目錄blog/2026/
keepName保留原始檔名true
expireAt過期時間,Unix 毫秒1780000000000
tags標籤,以逗號分隔blog,cover
albumId加入指定相簿12
storageType儲存後端R2HF
nsfwNSFW 標記true
nsfwScoreNSFW 分數0.85

例如上傳到文章封面目錄、保留原始檔名並加入標籤:

Terminal window
curl -sS -X POST \
"https://你的圖床網域/rest/upload" \
-H "X-API-Key: $ROIM_PICX_API_KEY" \
-F "files=@./文章封面.webp" \
-F "path=blog/covers" \
-F "keepName=true" \
-F "tags=blog,cover"

path 不需要自己補齊前後斜線,後端會移除開頭的 /,並在結尾補上 /。啟用 keepName 時,英文、數字、中文以及 ._- 可以保留,其他不支援的字元會被替換成底線。

從回傳資料取得 URL#

上傳成功後,回應中的 data 是圖片陣列:

{
"code": 200,
"msg": "",
"data": [
{
"key": "blog/covers/example.webp",
"size": 123456,
"url": "https://你的圖床網域/blog/covers/example.webp",
"filename": "example.webp",
"delToken": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"storageType": "R2",
"nsfw": false,
"nsfwScore": 0
}
]
}

單張上傳的圖片網址位於:

data[0].url

取得這個值後,就能直接寫入 Astro 文章的 image frontmatter,或插入 Markdown 圖片語法。

Node.js/TypeScript 串接範例#

以下函式會上傳本機 WebP,並直接回傳圖片 URL:

import { readFile } from "node:fs/promises";
import { basename } from "node:path";
interface UploadResponse {
code: number;
msg: string;
data?: Array<{ url?: string }>;
}
export async function uploadImage(filePath: string): Promise<string> {
const fileBuffer = await readFile(filePath);
const formData = new FormData();
formData.append(
"files",
new Blob([new Uint8Array(fileBuffer)], { type: "image/webp" }),
basename(filePath),
);
formData.append("path", "blog/covers");
formData.append("keepName", "true");
const apiKey = process.env.ROIM_PICX_API_KEY;
if (!apiKey) {
throw new Error("ROIM_PICX_API_KEY is not configured");
}
const response = await fetch("https://你的圖床網域/rest/upload", {
method: "POST",
headers: {
"X-API-Key": apiKey,
},
body: formData,
});
const result = (await response.json()) as UploadResponse;
if (!response.ok || result.code !== 200) {
throw new Error(`Upload failed: ${response.status} ${result.msg}`);
}
const url = result.data?.[0]?.url;
if (!url) {
throw new Error(`Upload succeeded but URL was missing: ${JSON.stringify(result)}`);
}
return url;
}

執行完測試後,清除目前 shell 的 API Key:

Terminal window
unset ROIM_PICX_API_KEY

API Key 出現 no such table 時怎麼處理?#

若建立或驗證 API Key 時出現:

no such table: api_keys

代表 D1 還沒有套用建立 api_keys 表的後續 migration。請回到前面的 D1 步驟,執行所有尚未套用的 migration:

Terminal window
npx wrangler d1 migrations apply roim-picx \
--remote \
--config wrangler.local.jsonc

這也是不能只執行 0001_init.sql 的另一個實際原因。

本次舊版升級的實測結果#

本次環境原本只有 R2 圖片,D1 是新建立的空資料庫。完成後的結果是:

  • 00010011 共 11 個 migration 全部成功套用。
  • Production 重新部署後,Token 登入恢復正常。
  • 同步前 D1 的 images 是 0 筆。
  • 第一批同步 100 張,第二批同步 81 張。
  • 最終 D1 有 181 筆圖片 metadata,errors 為 0,最後一批 hasMorefalse
  • 181 張圖片檔仍保存在原本的 R2,沒有搬動或複製;D1 只新增新版列表與管理功能需要的資料。

實際升級的關鍵不是「把圖片搬到 D1」,而是保留 R2 裡的圖片檔,再把它們登記進新版 D1。之後新上傳的圖片會由新版同時寫入 R2 檔案與 D1 metadata。

發布前檢查清單#

第一次安裝與舊版升級都可以用這份清單收尾:

  1. KV、R2、D1 的 Variable name 分別是 XKPICXDB
  2. BASE_URL 使用正式 HTTPS 網域。
  3. PICX_AUTH_TOKEN 存在 Cloudflare Secret,沒有寫進 Git。
  4. ALLOW_TOKEN_LOGIN 是字串 true
  5. migration 使用 --remote,且 list 顯示沒有待執行項目。
  6. Binding 或變數修改後,已有新的 Production deployment。
  7. 可以登入、上傳、重新整理列表與開啟管理頁。
  8. 舊版升級已同步到 hasMore: false,並確認 errors: 0
  9. 完成後已執行 unset PICX_TOKEN

常見問題#

Q: roim-picx 的 D1 database 一定要叫 picx-db 嗎?#

A: 不需要。D1 資料庫可以叫 roim-picxpicx-db 或其他名稱;真正固定的是 Pages Functions 的 Variable name 必須是 DB。Wrangler 指令中的資料庫名稱也要改成你實際建立的名稱。

Q: 為什麼不能只執行 0001_init.sql?#

A: 0001_init.sql 只建立初始結構。目前實測版本還有 00020011,包含後續登入方式、儲存類型、系統設定、相簿、NSFW、效能索引與 API Key 等變更。使用 wrangler d1 migrations apply 才會依序套用並記錄所有 migration。

Q: Token login failed 是否代表 PICX_AUTH_TOKEN 填錯?#

A: 不一定。程式在 Token 已匹配、且 ALLOW_TOKEN_LOGIN 開啟後,仍要讀寫 D1;這段發生例外也會顯示 Token login failed。新版剛升級時,應先檢查 D1 是否完成 migration、Production 是否綁定 DB,以及修改 Binding 後是否真的重新部署。

Q: 同步舊圖會把 R2 圖片搬進 D1 嗎?#

A: 不會。R2 繼續保存圖片檔,D1 只新增圖片 key、大小、類型、資料夾、上傳者與建立時間等 metadata。同步完成後,新版才能從 D1 列出與管理原本的 R2 圖片。

Q: roim-picx 可以用 API 上傳圖片並直接取得網址嗎?#

A: 可以。建立 px_ 開頭的 API Key 後,使用 X-API-Key Header 呼叫 POST /rest/upload,並以 multipart/form-datafiles 欄位上傳圖片。成功後可從回應的 data[0].url 取得公開網址,適合接圖片生成、排程產文或 Astro frontmatter 更新流程。

Q: roim-picx 和 CloudFlare-ImgBed 應該選哪一個?#

A: 如果需求集中在自用圖床、R2 圖片、網頁管理、相簿與 API 上傳,roim-picx 的範圍比較接近這篇的使用情境。若還需要一般檔案、WebDAV、多種儲存後端或個人雲端硬碟功能,則可以評估 CloudFlare-ImgBed。真正的差異是需求範圍,不是單看哪個功能數量更多。

參考資料:

roimdev/roim-picx GitHub repository

MarSeventh/CloudFlare-ImgBed GitHub repository

Cloudflare D1 Migrations

Cloudflare Wrangler D1 Commands

Cloudflare Pages Functions Bindings

roim-picx Issue #30:Token 登入失敗

roim-picx 安裝與舊版升級:Cloudflare R2、D1 設定教學
https://laplusda.com/posts/roim-picx-cloudflare-install-upgrade/
作者
Zero
發佈於
2026-07-15
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。