Skip to content

feat(storage-boxes): 新增 hetzner_get_storage_box_stats 與 hetzner_assert_storage_box_space 工具 - #54

Merged
terry90918 merged 3 commits into
mainfrom
feat/storage-box-stats-and-assert
Jul 8, 2026
Merged

feat(storage-boxes): 新增 hetzner_get_storage_box_stats 與 hetzner_assert_storage_box_space 工具#54
terry90918 merged 3 commits into
mainfrom
feat/storage-box-stats-and-assert

Conversation

@terry90918

@terry90918 terry90918 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

為什麼現在才進 main

這個功能原本是 develop 上的 4c23c35(2026-06-25),從未合併

developmain 已雙向分歧:develop 有這個功能,但缺少 main 後來加的 security hardening(isSafePathSegment path-traversal 防護、escapeHtml XSS 轉義、SSH host-key pinning)。若直接把 develop 重設成 main,這兩個工具就會消失。

本 PR 把該 commit cherry-pick 到 main 基底(零衝突),先把功能救回來,之後 develop 才能安全地重設對齊 main

內容

兩個工具,129 行實作 + 159 行測試:

  • hetzner_get_storage_box_stats — 回傳用量統計(used / total / available / usage_percent)
  • hetzner_assert_storage_box_space — 斷言剩餘空間足夠,供備份前檢查

Cherry-pick 到新基底後的安全複查

這個 commit 寫在 hardening 之前,乾淨 cherry-pick 正是新功能可能悄悄繞過新防護的時機,因此逐項複查:

防護 是否適用 結論
isSafePathSegment() 不適用 兩個工具的參數是 id: z.number().int().positive(),非字串,/storage_boxes/${id} 無 path-traversal 面。同檔案其他 handler 只對 username / snapshot_id 等字串參數呼叫它
escapeHtml() 不適用 輸出只有計算出的數值(used_gibusage_percent 等),不渲染 box name / description / labels,無未轉義的使用者可控字串
SSH host-key pinning 不相關 走 HTTP Storage Box API,不碰 src/tools/server-ssh.ts

另檢查:computeStorageBoxStatstotal_bytes === 0 有除零防護;size 欄位在 HetznerStorageBoxSchema 為必要欄位,缺漏時由 zod 擋下並走 handleApiError,不會產生 NaN;兩個 handler 都 try/catch 並回 isError: true,與同檔案其他 handler 一致。

驗證

  • 365 tests / typecheck / lint 全綠
  • main 的 security 修正確認未被覆蓋:src/utils.tsisSafePathSegment 仍在、storage-boxes.ts 有 4 處呼叫它、reference.tsescapeHtml 仍在、server-ssh.ts 維持 421 行
  • runtime 實打 Hetzner APItools/list 回傳 42 個工具(含這兩個新的),hetzner_get_storage_box_stats(id=561406) 回傳 Used: 477.7 GiB / Total: 1024.0 GiB,與 jurislm-backup-fsn1 的實際用量相符
  • 新測試涵蓋 zero-usage、69% used、除零防護、四捨五入、>= 邊界、API 失敗

Test plan

  • bun run test(365 passed)
  • bun run typecheck
  • bun run lint
  • 兩個新工具實打真實 Hetzner Storage Box API
  • 確認 cherry-pick 未回退 main 的 security hardening
  • merge 後 develop 即可安全重設對齊 main

Summary by CodeRabbit

  • 新功能
    • 新增存储盒容量统计能力:展示已用、总量、可用空间与使用率(支持字节与 GiB)。
    • 新增两个工具:获取存储盒统计信息,以及校验指定容量需求是否满足。
  • 改进
    • 工具支持 JSON 与 Markdown 输出格式,便于机器读取与可读展示。
    • 覆盖零容量、超配导致可用空间为负、使用率四舍五入、以及失败时错误标记等边界场景。
  • 测试
    • 扩展存储盒工具注册与行为单测,覆盖关键字段与各类成功/失败分支。

…t_storage_box_space 工具

新增兩個 Storage Box 空間管理工具(issue #115):

- hetzner_get_storage_box_stats:回傳 used_bytes/used_gib、total_bytes/total_gib、
  available_gib、usage_percent(2 位小數),stats.size(data+snapshots 合計)為已用空間
- hetzner_assert_storage_box_space:接受 required_gib 參數,空間不足時回傳 isError:true,
  供備份 pipeline 在執行前做 pre-flight check
- 抽出 computeStorageBoxStats() 共用輔助函式(exported for testing)

344/344 tests pass;lint 0 error;tsc clean
Copilot AI review requested due to automatic review settings July 8, 2026 10:24
@terry90918 terry90918 added the enhancement New feature or request label Jul 8, 2026
@terry90918 terry90918 self-assigned this Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa4329f2-f3aa-4d1c-85db-4d81634f263e

📥 Commits

Reviewing files that changed from the base of the PR and between d163bae and 5df4614.

📒 Files selected for processing (1)
  • src/tools/storage-boxes.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/tools/storage-boxes.ts

📝 Walkthrough

Walkthrough

本次變更在 storage-boxes 工具模組中新增容量統計計算與兩個 MCP 工具,並補充對應測試與註冊斷言。

Changes

Storage Box 統計與容量校驗

Layer / File(s) Summary
統計計算與格式化
src/tools/storage-boxes.ts
新增 StorageBoxStatscomputeStorageBoxStats 與容量換算常量,並更新 formatBytes 共用換算邏輯。
統計與校驗工具
src/tools/storage-boxes.ts
新增 hetzner_get_storage_box_statshetzner_assert_storage_box_space,支援 JSON/Markdown 輸出與空間不足時的 isError
測試更新
tests/tools/storage-boxes.test.ts
更新匯入與註冊斷言,並新增統計函式、兩個新工具的行為、邊界與 schema 驗證測試。

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related issues

Possibly related PRs

  • jurislm/hetzner-mcp#23:同樣涉及 src/tools/storage-boxes.ts 的使用率計算與 Markdown 顯示更新,與本次容量統計功能有直接關聯。
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 標題清楚指出新增的兩個 Storage Box 工具,且與本次變更內容一致。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/storage-box-stats-and-assert

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

此 PR 將先前僅存在於 develop 的 Storage Box 容量統計與空間斷言功能,cherry-pick 回 main 基底,以補回可用性並維持既有安全強化不被回退。

Changes:

  • 新增 hetzner_get_storage_box_stats 工具:回傳 Storage Box 用量統計(used/total/available/usage_percent)。
  • 新增 hetzner_assert_storage_box_space 工具:備份/排程前置檢查可用空間是否達到需求,空間不足時回傳 isError: true
  • 補上 computeStorageBoxStats 的單元測試與兩個新工具的 handler 整合測試。

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/tools/storage-boxes.ts 新增用量統計計算函式與兩個新工具註冊/handler 實作
tests/tools/storage-boxes.test.ts 更新工具註冊數量斷言,新增 compute/tool handler 測試覆蓋

Comment thread src/tools/storage-boxes.ts
Comment thread src/tools/storage-boxes.ts
Comment thread src/tools/storage-boxes.ts
Comment thread src/tools/storage-boxes.ts
…iption

1. hetzner_assert_storage_box_space 補上 response_format(意見 3、4)
   本檔 read-only 工具皆提供 markdown/json 切換,唯獨這個沒有。
   補上後 handler 實際尊重該參數:JSON 模式回傳 { ok, required_gib, ...stats },
   且 ok=false 時仍維持 isError: true,讓自動化流程可任選其一分支。

2. description 明確標示 used_* 含 snapshots(意見 2)
   原文寫 "current data usage",但 computeStorageBoxStats 用的是 stats.size
   (= size_data + size_snapshots)。已改為明確寫出,並補上 available_* 在
   超額時為負值的說明。

3. available 改由 bytes 整數相減再換算(意見 1,部分採納)
   新增 available_bytes 欄位,與既有的 used_bytes / total_bytes 對稱。

   why 部分採納:意見中「浮點誤差」的理由不成立。GiB = 2^30,整數除以 2 的冪
   只改指數不動尾數,是精確運算。實測 20 萬次隨機組合(含非 GiB 整數倍的 total),
   (total/GiB - used/GiB) 與 (total-used)/GiB 誤差恆為 0。改寫的價值在於把
   single source of truth 留在 bytes、並補上缺漏的 available_bytes,不是修 bug。

   why 不 clamp 到 >= 0:Hetzner 把 snapshots 計入 stats.size,超額的 box
   回報負可用空間是正確資訊。clamp 成 0 只會把「你超額 5 GiB」藏成「剩 0 GiB」,
   而 assert 的結果完全不變(任何 required_gib > 0 都會失敗)。

驗證:
- 369 tests / typecheck / lint 全綠(新增 4 個測試:available_bytes 與
  available_gib 在非 GiB 整數倍下完全一致、超額時回報負值、JSON 模式 ok=true
  無 isError、JSON 模式 ok=false 仍有 isError)
- runtime 實打 Hetzner Storage Box 561406:
    required_gib=100 → ok=true  isError=false available_gib=546.27
    required_gib=900 → ok=false isError=true  available_gib=546.27
@terry90918

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/tools/storage-boxes.ts (1)

57-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

邏輯正確,available_bytes 不做非負鉗制的設計說明清楚。

除零保護、GiB 換算與超額配額(負值)情境都處理得宜,且註解已充分說明設計動機,測試也涵蓋了這些邊界情況。

小提醒:1024 ** 3 這個魔術數字在本函式(第 71 行)與下方 formatBytes(第 92 行)各自出現一次,可考慮抽成模組層共用常數(例如 const BYTES_PER_GIB = 1024 ** 3)以避免未來兩處不同步。純屬錦上添花,不影響本次功能正確性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/storage-boxes.ts` around lines 57 - 88, The logic in
computeStorageBoxStats is fine, but the GiB conversion literal is duplicated and
should be centralized. Extract the repeated 1024 ** 3 value into a shared
module-level constant (for example, a BYTES_PER_GIB symbol) and use it both in
computeStorageBoxStats and formatBytes so the storage-box sizing logic stays
consistent and easier to maintain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/tools/storage-boxes.ts`:
- Around line 57-88: The logic in computeStorageBoxStats is fine, but the GiB
conversion literal is duplicated and should be centralized. Extract the repeated
1024 ** 3 value into a shared module-level constant (for example, a
BYTES_PER_GIB symbol) and use it both in computeStorageBoxStats and formatBytes
so the storage-box sizing logic stays consistent and easier to maintain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7eef7fa3-01d2-4ae3-b090-2fe01aa631ed

📥 Commits

Reviewing files that changed from the base of the PR and between 3f22d39 and d163bae.

📒 Files selected for processing (2)
  • src/tools/storage-boxes.ts
  • tests/tools/storage-boxes.test.ts

CodeRabbit review 指出 `1024 ** 3` 在 computeStorageBoxStats 與 formatBytes
各出現一次,未來可能不同步。

實際掃描發現是三處,不是兩處——CodeRabbit 漏了 formatBytes 裡的 `1024 ** 2`
(MiB 換算)。兩個單位都抽成模組層常數。

也符合 ~/.claude/rules/common/coding-style.md 的「Magic Numbers — Use named
constants for meaningful thresholds, delays, and limits」。

驗證:
- 369 tests / typecheck / lint 全綠
- grep 確認檔內已無裸露的 `1024 ** N`(僅剩常數定義處)
- runtime 實打 Storage Box 561406,數值與重構前一致:
    used_gib=477.73 total_gib=1024.00 available_gib=546.27 usage=46.65%
    available_bytes=586554540032(= total_bytes - used_bytes,整數精確)
@terry90918

Copy link
Copy Markdown
Collaborator Author

已採納,修正見 5df4614

抽出模組層常數 BYTES_PER_GIBBYTES_PER_MIB

實際掃描發現是三處而非兩處 —— formatBytes 裡還有一個 1024 ** 2(MiB 換算)被漏掉了,一併抽出。grep -n "1024 \*\* [0-9]" 確認檔內已無裸露的魔術數字(僅剩常數定義處)。

驗證:369 tests / typecheck / lint 全綠;runtime 實打 Storage Box 561406,數值與重構前完全一致(used_gib=477.73 total_gib=1024.00 available_gib=546.27 usage=46.65%available_bytes=586554540032total_bytes - used_bytes 相符)。

🤖 Addressed by Claude Code

@terry90918
terry90918 merged commit 3973582 into main Jul 8, 2026
4 checks passed
@terry90918
terry90918 deleted the feat/storage-box-stats-and-assert branch July 8, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants