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-picx | CloudFlare-ImgBed |
|---|---|---|
| 主要定位 | 圖床、圖片管理與相簿 | 圖床、檔案託管與個人雲端空間 |
| 日常操作 | 上傳、搜尋、目錄、相簿、圖片分享 | 檔案生命週期、目錄、權限、API、WebDAV |
| 儲存後端 | R2、Hugging Face | R2、S3、WebDAV、Telegram、Discord 等 |
| 部署範圍 | Pages、Functions、R2、KV、D1 | Serverless 或 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、環境變數或不相容調整。
比較保守的做法是:
- Fork 到自己的 GitHub 帳號。
- 保留目前已驗證可運作的 commit。
- 不要自動把上游
main直接推進正式環境。 - 更新前先檢查
migrations/、.env.example、README 與程式差異。 - 重要更新前備份 D1,並確認 R2 圖片有可恢復來源。
架構已經不只是簡單圖床
新版需要 R2、D1、KV、Pages Functions 與登入設定。D1 平常不需要反覆建立,但每次更新只要出現新的 migration,就要先判斷執行順序與正式資料庫狀態,再重新部署驗證。
文件可能落後目前程式碼
這次實際版本已有 0001 到 0011,但專案內仍可看到只執行 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 namespace | XK | imgs |
| R2 bucket | PICX | media |
| D1 database | DB | roim-picx |
XK、PICX、DB 是程式使用的 Binding 名稱,不能自行改成資源名稱。右側的 imgs、media、roim-picx 才是你在 Cloudflare 建立的實際資源,名稱可以不同。
第一次安裝 roim-picx
第一次安裝沒有舊 R2 資料,重點是先建立 Pages 專案與三種儲存資源,再把 D1 migration 完整套用到正式資料庫。
Step 1:Fork 專案並建立 Cloudflare Pages
先 Fork roimdev/roim-picx,再把自己的 Fork clone 到電腦:
git clone https://github.com/<你的 GitHub 帳號>/roim-picx.gitcd roim-picxnpm install到 Cloudflare Dashboard 建立 Pages 專案,連接剛才的 GitHub repository。建置設定可使用:
Build command: npm run buildBuild output directory: dist先讓 Pages 完成第一次建置。資源 Binding 與環境變數可以在專案建立後補上。
Step 2:建立 KV、R2 與 D1
在 Cloudflare Dashboard 建立:
- 一個 KV namespace,保存工作階段等鍵值資料。
- 一個 R2 bucket,保存實際圖片檔案。
- 一個 D1 database,保存圖片 metadata 與其他結構化資料。
名稱可以自行決定。本文後續以這組名稱示範:
KV namespace: imgsR2 bucket: mediaD1 database: roim-picxStep 3:設定 Production Bindings
回到 Pages 專案,進入 Settings → Bindings。有些 Dashboard 版本會把入口放在 Settings → Functions。
新增三個 Production Binding:
KV namespace bindingVariable name: XKKV namespace: imgs
R2 bucket bindingVariable name: PICXR2 bucket: media
D1 database bindingVariable name: DBD1 database: roim-picx如果 D1 資料庫就叫 roim-picx,Variable name 仍然要填 DB,不能也填成 roim-picx。Cloudflare 的 Binding 是 Pages Functions 存取資源時使用的程式介面。
Step 4:加入最小環境變數
只使用 Token 登入與 R2 儲存時,先設定以下 Production 變數:
| 變數 | 值 | 用途 |
|---|---|---|
BASE_URL | https://你的圖床網域 | 產生完整網址 |
PICX_AUTH_TOKEN | 自行產生的長隨機字串 | 管理員登入 Token |
ALLOW_TOKEN_LOGIN | true | 開放 Token 登入 |
STORAGE_TYPE | R2 | 使用 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:
npx wrangler loginnpx wrangler whoaminpx 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:
echo 'wrangler.local.jsonc' >> .git/info/excludeStep 6:一次套用所有 D1 migrations
目前專案有 0001 到 0011 共 11 個 migration。不要只執行 0001_init.sql,否則相簿、系統設定、NSFW、API Key 等後續資料結構不會建立。
請在專案根目錄執行:
npx wrangler d1 migrations apply roim-picx \ --remote \ --config wrangler.local.jsonc--remote 不能省略。Cloudflare 現行 Wrangler 的 D1 指令預設使用本機資料庫,沒有加上 --remote 不會初始化 Pages 正式環境使用的 D1。
完成後檢查是否還有未套用的 migration:
npx wrangler d1 migrations list roim-picx \ --remote \ --config wrangler.local.jsonc正常應顯示沒有 migration 等待執行。也可以查詢遠端資料表:
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 後,再測試:
- 使用
PICX_AUTH_TOKEN登入。 - 上傳一張測試圖片。
- 重新整理圖片列表。
- 確認管理頁能讀取統計資料。
第一次安裝到這裡就完成,不需要執行歷史圖片同步。
從沒有 D1 的舊版升級
舊版升級不能把部署成功當成結束。圖片檔雖然還在 R2,但新版圖片列表會查 D1 的 images 表;D1 沒有 metadata 時,畫面看起來就像圖片全部消失。
Step 1:保留舊 R2 與 KV
不要刪除或重建原本的 R2 bucket。舊圖片檔仍然保存在裡面,升級只是在既有架構旁新增 D1。
先把 Fork 或本機部署分支更新到新版,再確認 Cloudflare 仍綁定原有資源:
KV:XK → 原本的 KV namespaceR2:PICX → 原本的 R2 bucket接著建立新的 D1 database,並補上:
D1:DB → roim-picxStep 2:初始化 D1 並重新部署
照前一節建立 wrangler.local.jsonc,再對遠端 D1 套用全部 migration:
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 disabled | ALLOW_TOKEN_LOGIN 是否為字串 true |
Token login failed | D1 migration、Production 的 DB Binding、是否已重新部署 |
本次真正的原因是 Production 仍在執行兩個月前的舊 deployment,新增的 D1 Binding 還沒進入正式環境。重新部署後即可登入,不需要重設 Token。這個現象也與專案的 Issue #30 相符。
Step 4:確認新版 D1 還沒有舊圖資料
登入成功但舊圖片沒出現時,先查 images 筆數:
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。先暫存在目前終端機工作階段:
read -s "PICX_TOKEN?請輸入登入 Token:"echo先執行 dry run,不寫入 D1:
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 後,再正式同步第一批:
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}只要 hasMore 是 true,就把實際的 nextCursor 放進下一次請求:
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:
unset PICX_TOKENunset 只會清除目前終端機的環境變數,不會刪除 Cloudflare Secret、D1、R2 或剛完成的同步資料。
安裝完成後:用 API 上傳並取得 URL
完成第一次安裝或舊版升級後,可以建立 roim-picx API Key,讓腳本直接呼叫:
POST https://你的圖床網域/rest/uploadAPI Key 可放在其中一種 Header:
X-API-Key: px_xxxxxxxx.xxxxxxxxxxxxx或:
Authorization: Bearer px_xxxxxxxx.xxxxxxxxxxxxxAPI 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:
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 暫存在目前終端機:
read -s "ROIM_PICX_API_KEY?請輸入 roim-picx API Key:"echo接著上傳圖片:
curl -sS -X POST \ "https://你的圖床網域/rest/upload" \ -H "X-API-Key: $ROIM_PICX_API_KEY" \ -F "files=@./image.webp"上傳 API 使用 multipart/form-data,檔案欄位名稱一定要是:
files不是常見的 file 或 image。批次上傳時,重複提供多個 files:
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 |
path | R2 儲存目錄 | blog/2026/ |
keepName | 保留原始檔名 | true |
expireAt | 過期時間,Unix 毫秒 | 1780000000000 |
tags | 標籤,以逗號分隔 | blog,cover |
albumId | 加入指定相簿 | 12 |
storageType | 儲存後端 | R2 或 HF |
nsfw | NSFW 標記 | true |
nsfwScore | NSFW 分數 | 0.85 |
例如上傳到文章封面目錄、保留原始檔名並加入標籤:
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:
unset ROIM_PICX_API_KEYAPI Key 出現 no such table 時怎麼處理?
若建立或驗證 API Key 時出現:
no such table: api_keys代表 D1 還沒有套用建立 api_keys 表的後續 migration。請回到前面的 D1 步驟,執行所有尚未套用的 migration:
npx wrangler d1 migrations apply roim-picx \ --remote \ --config wrangler.local.jsonc這也是不能只執行 0001_init.sql 的另一個實際原因。
本次舊版升級的實測結果
本次環境原本只有 R2 圖片,D1 是新建立的空資料庫。完成後的結果是:
0001~0011共 11 個 migration 全部成功套用。- Production 重新部署後,Token 登入恢復正常。
- 同步前 D1 的
images是 0 筆。 - 第一批同步 100 張,第二批同步 81 張。
- 最終 D1 有 181 筆圖片 metadata,
errors為 0,最後一批hasMore為false。 - 181 張圖片檔仍保存在原本的 R2,沒有搬動或複製;D1 只新增新版列表與管理功能需要的資料。
實際升級的關鍵不是「把圖片搬到 D1」,而是保留 R2 裡的圖片檔,再把它們登記進新版 D1。之後新上傳的圖片會由新版同時寫入 R2 檔案與 D1 metadata。
發布前檢查清單
第一次安裝與舊版升級都可以用這份清單收尾:
- KV、R2、D1 的 Variable name 分別是
XK、PICX、DB。 BASE_URL使用正式 HTTPS 網域。PICX_AUTH_TOKEN存在 Cloudflare Secret,沒有寫進 Git。ALLOW_TOKEN_LOGIN是字串true。- migration 使用
--remote,且 list 顯示沒有待執行項目。 - Binding 或變數修改後,已有新的 Production deployment。
- 可以登入、上傳、重新整理列表與開啟管理頁。
- 舊版升級已同步到
hasMore: false,並確認errors: 0。 - 完成後已執行
unset PICX_TOKEN。
常見問題
Q: roim-picx 的 D1 database 一定要叫 picx-db 嗎?
A: 不需要。D1 資料庫可以叫 roim-picx、picx-db 或其他名稱;真正固定的是 Pages Functions 的 Variable name 必須是 DB。Wrangler 指令中的資料庫名稱也要改成你實際建立的名稱。
Q: 為什麼不能只執行 0001_init.sql?
A: 0001_init.sql 只建立初始結構。目前實測版本還有 0002 到 0011,包含後續登入方式、儲存類型、系統設定、相簿、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-data 的 files 欄位上傳圖片。成功後可從回應的 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 Wrangler D1 Commands
回報錯字、失效連結,或告訴我你想看的延伸主題。