B2 Native API v3 client for the ACG B2 account: status, buckets, keys, files, bucket-size, usage/cost ($0.00695/GB), gated create/delete bucket+key, and gated lifecycle-based delete-prefix/lifecycle-remove for prefix purges. Read-only by default; destructive ops require --confirm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
name, description
| name | description |
|---|---|
| b2 | Manage Arizona Computer Guru's (ACG) Backblaze B2 storage account via the B2 Native API v3. Talks to the LIVE production B2 account (accountId 46f69bc61163, region us-west-001) that holds the per-client MSP360/CloudBerry backup destinations. List buckets and application keys, list files / file versions, compute per-bucket stored size, and produce the headline storage-cost report (the mspbackups storage-cost calc). Provision buckets and scoped backup keys and delete buckets/keys (all destructive ops are gated behind --confirm). Read-only by default. Invoke for: "backblaze", "b2", "b2 storage", "bucket", "storage cost", "backup storage", "mspbackups storage", "list buckets b2". |
Backblaze B2 Skill
Standalone CLI client for the Backblaze B2 Native API v3. Talks to the live ACG
B2 account. Read-only by default; destructive operations are gated behind
--confirm.
Running the CLI
This machine's Python launcher is py (per identity.json). The scripts also
work with python/python3.
# from the scripts dir, or pass full paths
py "$CLAUDETOOLS_ROOT/.claude/skills/b2/scripts/b2.py" status
py "$CLAUDETOOLS_ROOT/.claude/skills/b2/scripts/b2.py" buckets
py "$CLAUDETOOLS_ROOT/.claude/skills/b2/scripts/b2.py" usage --json
Transport auto-selects: uses httpx if installed, otherwise stdlib urllib
(no third-party dependency required).
Credentials
Credentials are NEVER hardcoded. At runtime the client loads them from the SOPS vault:
bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" \
get-field projects/claudetools/backblaze-b2.sops.yaml key_id
bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" \
get-field projects/claudetools/backblaze-b2.sops.yaml credentials.application_key
CLAUDETOOLS_ROOT resolves from the env var, else claudetools_root in
.claude/identity.json, else the repo root derived from the script's own
location (no hardcoded drive letters). For testing you can override with the
B2_KEY_ID and B2_APPLICATION_KEY env vars (both must be set to use the
override).
Authorization is HTTP Basic against b2_authorize_account (username = key id,
password = application key). The skill uses the "ClaudeTools" application key
(00146f69bc611630000000009).
Cache model (important — the token is a SECRET)
After a successful authorize, the CLI caches the auth result at
.claude/skills/b2/.cache/auth.json:
- Cached:
authorizationToken(the bearer token, valid ~24h), the per-accountapiUrl/s3ApiUrl/downloadUrl,accountId, key capabilities, and key scope (bucketId/namePrefix). - TTL: treated as valid for ~23h. The client re-authorizes when the cache is
stale, or automatically on a
401withexpired_auth_token/bad_auth_token(exactly one re-authorize + retry per call). - This cache holds a live secret. It is gitignored twice (root
.gitignoreplus a local.gitignorein the skill dir) and must never be committed.
The per-account apiUrl always comes from the live authorize response — there
is no config file for endpoints (project rule).
Safety gating
Destructive subcommands refuse to run without --confirm; without it they print
what they would do and exit non-zero (3):
create-bucket <name> [--type allPrivate|allPublic] --confirmcreate-key --name <n> --capabilities <c1,c2,...> [--bucket <name>] [--prefix <p>] [--duration-seconds N] --confirmdelete-bucket <name> --confirm(B2 refuses to delete a non-empty bucket — the error is surfaced verbatim)delete-key <applicationKeyId> --confirmdelete-prefix <name> <prefix> [<prefix> ...] --confirm(schedules an IRREVERSIBLE lifecycle purge — see "Prefix purge / lifecycle" below)lifecycle-remove <name> <prefix> [<prefix> ...] --confirm(removes lifecycle rules)
create-key prints the returned applicationKey exactly ONCE with a warning —
B2 never shows it again, so store it in the vault immediately.
raw refuses any method name containing create / delete / update / hide
/ cancel unless --confirm is passed. raw prints the upstream response
verbatim, which may carry sensitive data (keys, tokens) — review before pasting
into tickets/logs. Destructive calls are NEVER retried automatically.
Common commands
B2="py $CLAUDETOOLS_ROOT/.claude/skills/b2/scripts/b2.py"
# Status / inventory
$B2 status
$B2 buckets
$B2 keys
# Files
$B2 files ACG-Internal # latest names
$B2 files ACG-Internal --prefix MBS- --limit 50
$B2 files ACG-Internal --versions # all versions
# Size + cost
$B2 bucket-size ACG-Internal
$B2 usage # headline cost report, all buckets
$B2 usage --bucket ACG-Dataforth # one bucket
$B2 cost --json # alias for usage
# Provisioning (gated)
$B2 create-bucket ACG-NewClient --confirm
$B2 create-key --name acg-newclient-backup \
--capabilities listBuckets,listFiles,readFiles,writeFiles,deleteFiles \
--bucket ACG-NewClient --confirm
$B2 delete-bucket ACG-OldClient --confirm
$B2 delete-key 00146f69bc611630000000abc --confirm
# Lifecycle / prefix purge (see next section)
$B2 lifecycle ACG-Internal # read-only: list current rules
$B2 delete-prefix ACG-Internal "MBS-<guid>/CBB_<machine>/" --confirm
$B2 lifecycle-remove ACG-Internal "MBS-<guid>/CBB_<machine>/" --confirm
# Power use — any v3 method directly
$B2 raw --method b2_list_buckets --body '{"accountId":"46f69bc61163"}'
Prefix purge / lifecycle
Some backup destinations hold 1.2M+ file versions under a single machine
prefix. Per-file deletion (b2_delete_file_version per version) is impractical
at that scale, so this skill purges a prefix via a B2 bucket lifecycle rule
instead. B2's own daily lifecycle pass does the deletion server-side.
How the purge mechanism works
A purge rule is:
{ "fileNamePrefix": "MBS-<guid>/CBB_<machine>/",
"daysFromUploadingToHiding": 1,
"daysFromHidingToDeleting": 1 }
With both day-counts at 1, B2's daily lifecycle pass hides any file more
than 1 day old, then deletes hidden files more than 1 day old. Every version
under the prefix (all are years old) becomes eligible immediately, so the prefix
is fully purged within ~24-48h (two daily passes). This is irreversible:
there is no recycle bin and no undo once the pass deletes a version.
fileNamePrefix: "" means the WHOLE BUCKET — the skill refuses that and any
other too-broad prefix (see safety below).
Lifecycle rules REPLACE, they don't append
b2_update_bucket's lifecycleRules replaces the entire array — it is not
additive. The skill therefore always reads the current rules (b2_list_buckets
scoped to the bucketId), merges the change, and writes the full set back. This
read-merge-write of the complete array is what preserves pre-existing rules.
This account's b2_update_bucket does NOT support ifRevisionMatch (it
returns HTTP 400 bad_request: unknown field ... ifRevisionMatch), so the skill
sends no optimistic-lock token and writes are last-write-wins. There is no
409-conflict retry (no revision guard to violate); the skill makes at most one
defensive retry on a transient 5xx, then fails — it never loops. The bucket
revision is still read and displayed for reference, but is never sent back.
B2 allows up to 100 lifecycle rules per bucket; the skill refuses an update
that would exceed that.
Commands
B2="py $CLAUDETOOLS_ROOT/.claude/skills/b2/scripts/b2.py"
# 1. READ-ONLY: see the bucket's current lifecycle rules + revision
$B2 lifecycle ACG-Internal
$B2 lifecycle ACG-Internal --json
# 2. DRY-RUN: what WOULD be scheduled (no --confirm -> exits 3, writes nothing)
$B2 delete-prefix ACG-Internal "MBS-<guid>/CBB_<machine>/"
# 3. COMMIT the purge (gated). Idempotent: an identical rule already present
# is skipped. You can pass several prefixes at once.
$B2 delete-prefix ACG-Internal \
"MBS-<guid>/CBB_OLDPC1/" \
"MBS-<guid>/CBB_OLDPC2/" --confirm
# 4. After ~24-48h, verify the data is gone (size should have dropped)
$B2 bucket-size ACG-Internal
# 5. Clean up the now-spent purge rule(s) so they don't sit on the bucket
$B2 lifecycle-remove ACG-Internal \
"MBS-<guid>/CBB_OLDPC1/" \
"MBS-<guid>/CBB_OLDPC2/" --confirm
Safety validations (hard-fail even with --confirm)
delete-prefix refuses (exit 2) any prefix that is:
- empty,
"/","*", or contains no/— too broad (would risk a whole-bucket or whole-account-tree purge); - exactly an account root
MBS-<guid>/(one path segment + trailing slash) — this would purge ALL machines under that account. Override only with the extra--allow-account-rootflag (NOT used by default — purge machine-levelCBB_prefixes instead).
A valid machine target looks like MBS-<guid>/CBB_<machine>/. The command also
warns (does not fail) when a prefix lacks a trailing /, since a slash-less
prefix can match a sibling whose name merely starts with the same characters.
Without --confirm, delete-prefix prints the exact rule(s) it WOULD add and a
prominent [WARNING] about irreversible deletion, then exits non-zero (3) —
matching the other gated commands. lifecycle-remove is similarly gated
(removing a rule is far less dangerous than adding one, but gated for
consistency); without --confirm it lists which existing rules it would remove
and exits 3.
Cleanup workflow
delete-prefix -> wait ~24-48h for B2's lifecycle pass -> bucket-size to
verify the stored size dropped -> lifecycle-remove to strip the spent purge
rule. Leaving the rule in place is harmless (everything under the prefix is
already gone) but tidy buckets are easier to reason about.
Storage cost
ACG's Backblaze B2 cost basis is $0.00695 per GB stored, defined as
RATE_PER_GB_USD in scripts/b2_client.py (recorded in
.claude/memory/reference_backblaze_storage_rate.md) and used by the GuruRMM
mspbackups storage-cost calc. Override at the CLI with --rate.
GB is decimal (bytes / 1e9), matching how storage providers bill — NOT
2^30. Cost = GB * rate.
Size MUST be summed over all file versions (b2_list_file_versions,
action == "upload"), not just the latest names, because B2 bills every stored
version. bucket-size and usage do this automatically. For very large buckets
this issues many list transactions — usage prints a [WARNING] to that effect.
Account structure (for context)
- accountId
46f69bc61163, regionus-west-001. - 12 buckets (all
allPrivate), mostly per-client MSP360/CloudBerry backup destinations with keys likeMBS-<guid>/CBB_<CLIENT>/...: ACG-BST, ACG-Brett, ACG-Dataforth, ACG-GLAZTECH, ACG-IX, ACG-Internal, ACG-Lens, ACG-PST, ACG-REDNOUR, Horseshoe, MSPBackups20200311, VWP-Backup.usagederives a client label by stripping theACG-prefix. - 2 application keys:
cloudberrykey(the MSP360/CloudBerry key) andClaudeTools(the key this skill uses).
Reference
Full verified v3 auth flow, every method used with request/response shape, the
file-version billing note, pagination, the error shape, and the cost formula:
references/api-reference.md.