部署出錯沒人告訴你。用獨立 credentials、EDITOR 腳本、readback 校驗、env 自動切換四道護欄,讓 Claude 能參與但不失控。
部署和寫程式最大的不同是:一次性、高風險、回滾麻煩。寫錯一行程式,測試跑一次就發現。寫錯一行 credentials,你要到第一次真實支付失敗才發現——這時候使用者已經付了錢、錢沒進你帳戶、日誌裡全是 400。
最近用 Claude 把 how2claude 從本地開發推上正式環境,要配 Stripe live 帳號、x402 mainnet 錢包、Google OAuth、Kamal secrets。Claude 不知道哪些值是測試環境、哪些是真的,不知道 sk_live_ 和 sk_test_ 差一個字母就是天壤之別。護欄得自己搭。
Rails 預設的 config/credentials.yml.enc,解密金鑰 config/master.key。用這個預設你就輸了。
如果你把 sk_live_xxx 塞進這個檔,然後本地跑測試——測試程式就在用正式 Stripe。跑一次就扣一次錢。
換成兩個:
- config/credentials.yml.enc + config/master.key:dev/test 用,塞 sk_test_xxx
- config/credentials/production.yml.enc + config/credentials/production.key:正式用,塞 sk_live_xxx
.gitignore 裡兩個 .key 都忽略,兩個 .enc 都 commit。production.key 單獨放在機器上。
然後 Kamal secrets 要指對檔:
# .kamal/secrets
-RAILS_MASTER_KEY=$(cat config/master.key)
+RAILS_MASTER_KEY=$(cat config/credentials/production.key)
容器裡的 RAILS_MASTER_KEY 指向正式金鑰,解出來就是正式 credentials。Claude 寫這行的時候我特意盯著——預設產生的 Kamal 模板是 config/master.key(dev 的),完全靜默地把 dev Stripe key 部署到正式環境。
bin/rails credentials:edit --environment production 要開一個互動式編輯器。Claude 驅動不了互動式編輯器。你也不想手動複製十幾個密鑰(一個打錯全家福崩)。
用這個 pattern:
EDITOR="ruby script/set_prod_webhook_secret.rb" \
bin/rails credentials:edit --environment production
rm script/set_prod_webhook_secret.rb
script/set_prod_webhook_secret.rb 長這樣:
# ARGV[0] 是解密後的暫存 YAML 檔路徑
file = ARGV[0]
require "yaml"
data = YAML.load_file(file) || {}
data["stripe"] ||= {}
data["stripe"]["webhook_secret"] = "whsec_GHWObNAKFh2HPOlJpbGmlYfIiKz1C8EY"
File.write(file, data.to_yaml)
Rails 把解密後的 YAML 寫到暫存檔,把檔案路徑當參數叫 "EDITOR",退出後重新加密。我們的「EDITOR」實際上是個 Ruby 腳本,精確修改一個鍵,存回。
好處:
- 精確:只動 stripe.webhook_secret 這一個欄位,不動其他。
- 冪等:腳本跑兩次結果一樣。
- 可稽核:腳本本身就是異動的 diff。Claude 寫完腳本我看一眼就知道它要改什麼。
- 刪除即忘:rm 之後磁碟上沒有明文 credential 殘留,shell history 裡也沒有 whsec_... 的複製貼上痕跡。
每個 credential 都寫一個腳本:set_stripe_live_key.rb、set_webhook_secret.rb、set_price_ids.rb、set_wallet_address.rb。寫完立刻 rm。
寫進去之後,不要相信它寫對了。讀回來:
bin/rails runner -e production "
c = Rails.application.credentials
puts 'sk_live set: ' + c.dig(:stripe, :secret_key).to_s.start_with?('sk_live_').to_s
puts 'webhook_secret set: ' + c.dig(:stripe, :webhook_secret).to_s.start_with?('whsec_').to_s
puts 'wallet prefix: ' + c.dig(:x402, :wallet_address).to_s[0..5]
puts 'wallet len: ' + c.dig(:x402, :wallet_address).to_s.length.to_s
"
關鍵是前綴和長度校驗,不是光印出來看。
sk_live_ 開頭。如果讀出來是 sk_test_ 開頭,Claude 把 test key 塞進了 prod 檔——這個 bug 在第一次真實支付前你不會發現。whsec_ 開頭。格式對不對一眼看出來。0x...。長度不對就說明混進了別的字元。前綴校驗加上去,手誤、漏欄位、混環境這類錯誤基本都能擋住。
容易想到「在 deploy 前把 network 改成 mainnet」——這種切換靠人記憶,遲早出事。
x402 initializer 裡直接寫死規則:
# config/initializers/x402.rb
X402.configure do |c|
c.wallet_address = Rails.application.credentials.dig(:x402, :wallet_address)
c.chain = Rails.env.production? ? "base" : "base-sepolia"
end
同一份程式,dev 跑 testnet(sepolia),prod 跑 mainnet(base)。部署時不用改任何東西。Claude 也不會「忘了切換」,因為切換不需要它。
同樣的邏輯用在 basescan_tx_url、Plan 可見性(dev-only plans 正式環境不顯示)、Stripe price ID 選擇……凡是 dev 和 prod 不一樣的設定,都用 Rails.env 翻,不要依賴部署時記得改。
四道護欄搭完,第一次真金白銀的支付仍然必須你自己點一次。
上一篇《讓 Claude 除錯玄學 bug》講過,我在正式環境第一次點 x402 支付,console 直接報 invalid_string at payTo——錢包位址第 43 位是一個從中文輸入法混進來的全形問號。前綴校驗擋不住(0x 開頭沒變),測試抓不到(因為測試不發真實交易),只有真實點一次才暴露。
部署的護欄不是給 Claude 看的,是給你看的——把可驗證的東西(前綴、長度、env 切換)自動化,省下的人工注意力用來盯那些自動化擋不住的真實互動。
讓 Claude 做部署,完整流程:
.key 檔。EDITOR=script 塞進去,立刻 rm。Rails.env 翻轉,不要部署時手動切。部署不是「更危險的寫程式」,部署是「寫錯了沒人馬上告訴你」的寫程式。護欄的作用是把「沒人告訴你」變成「15 秒內就能告訴你」。