Skip to content

feat(backups): Protect my data setup flow - #30

Merged
im-tyler merged 11 commits into
mainfrom
simple-setup
Nov 7, 2026
Merged

im-tyler merged 11 commits into
mainfrom
simple-setup

Conversation

@im-tyler

@im-tyler im-tyler commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

A simple setup flow for backups, with every manual control kept under Advanced.

  • Preflight: checks each server before asking for any key. Tools and binary upgrades need consent.
  • Backblaze setup: a temporary key creates the bucket, lifecycle rules and one limited key per server, and is then forgotten.
  • Recovery key: generated in the browser, so Dash only ever gets the public key. Recovery is proved in the browser against a real backup.
  • Turn on: found databases are included by default, and you can back up now or wait for the schedule.
  • Protected: shown only after a verified backup and a passing restore test per covered database. It expires when a test is over 21 days old.
  • Also: new, rebuilt and retired servers, Dash's own volumes, docs/recovery.md, CLI v0.1.55 and contracts rev 21.

Plan and design review were done first, then a code review (2 blockers, 3 high, all fixed with regression tests).

Only tested against a fake B2 and a stubbed CLI. Backblaze behaviour the docs leave open needs a real-account test, and the key instructions are marked as a draft until then. docs/recovery.md hasn't been rehearsed and says so.

🤖 Generated with Claude Code

im-tyler and others added 11 commits November 6, 2026 17:43
Adds the backup-preflight, backup-head and backup-detach envelopes to the
validator's expectation table. MANIFEST rev 21: "Dash simple backup setup".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One IIFE exposing generate, derivePublic, decryptStart and parseIdentityFile,
with forceWebCryptoOff(true) because Dash runs on plain HTTP where crypto.subtle
is undefined. Tested with subtle deleted against files from the native age CLI.
scripts/vendor-age/build.sh reproduces the bundle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Authorize, buckets, keys and file versions through net/http with an injectable
base URL. Replies are validated, API hosts are limited to backblazeb2.com, and
errors carry a plain message from the status and code, never the store's message.
Rules merge by prefix, keep rules Dash does not own byte for byte, and refuse
upload-to-hide over the base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preflight with consented tool and binary changes, Backblaze bucket and rules, one
limited key per server, public recovery key, coverage, turn on through the policy
with allow_empty for empty servers, detached runs, proof of recovery through a
ranged read, rebuilt and retired servers. Gated on backup-setup-v1, every route
under administer.credentials and audited. Records hold key ids, never a secret.

Fixes the probe, which read a refused delete on B2 as the recommended setup and
would have hidden a retention that could not work: it now checks the object is
really hidden, and stops otherwise. Live guidance moves from 30 to 14 days.

Protected now has a meaning: a verified backup on each covered target and a passing
restore test for each covered database. Dash's own volumes are excluded when there
is no recovery key, because they hold its SSH key, and the page reports the real
dash/volumes entry. The hand-written-jobs list shows only when such jobs are found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/recovery.md covers everything lost, A's data onto B, Dash itself and hidden
copies, with how to make the read-only key the flow never shows, and says plainly
that it has not been rehearsed. docs/rebuild-dash.md now points to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six steps in plain wording: servers, storage, recovery key, alerts, turn on, prove.
The recovery key is made and checked in the browser; only its public half is sent.
Keys live in memory and go when the flow closes. New, rebuilt and retired servers
have short actions. The manual forms sit unchanged under a collapsed Advanced, open
on a CLI too old for the flow. Holds at 375px with no horizontal scroll.

Also fixes two existing errors on the Backups page, where x-text read fields a row
may not have, and the subtitle that joined two counts into "10 covered".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wrong key and a key that is not allowed now read differently. Only a delete whose
message says the key does not exist counts as gone: any other bad_request leaves the
key alive. Revisions read as a number or a string, snapshot buckets are listed and
named, and a window rule at the bucket root is per server, never a whole-bucket rule.
EffectiveWindow reports what is really in force.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keys (B1, M4, M9): a retry after a failed rotation deleted the key the server still
used, because reachability cannot say which key a server holds. Older keys now go only
after a push whose own call succeeded and then verified, in that operation. Anything
else is pushed again, a failed push is never reported as pushed, retire honors every
older key, and a crash keeps them on record. Servers with nothing to lose are pushed
first, an auth failure there stops before configured servers, and replacing a key Dash
did not make needs an explicit yes.

Protected (B2, H2, M1, M5, M7): a database in no backup means not protected, and says
which. Unmanaged databases are adopted unless unticked, and a deliberate skip reads
'Protected, except X (you chose to skip)'. A server holding databases is never empty, so
it never gets allow_empty. A restore test older than 21 days, or repeated guard skips,
reads overdue. Lifecycle follows the live read and is a note for buckets this flow set
up, not an alert, until the spike confirms native rules show through S3. Turned on needs
one server to succeed.

Storage (H1, M2, M3, M8, M11, M12): the bucket root uses per-server rules and needs an
empty bucket. Retire never sends rules to an S3-managed bucket, and a partial removal
reports and can finish. A refused hide says what happened and names both capabilities;
manual setups accepted before still connect, with a warning. The suggested bucket gets
its warnings at check time. Turn-on keeps existing caps and settings.

Dash's own volumes (M6): excluded only on the server proved to host this Dash, by
TEPLOY_APP and the dev.teploy.product=dash label through server siblings, which the
images now carry. If that cannot be proved, a warning, never an exclude by name.

Also: re-auth on turn-on and forced marker clears, with a path for single sign-on; key
ids for administer.credentials only; dismissible leftovers; old run errors clear after a
success; the real window is recorded; switching buckets keeps other servers' prefixes.
The fake Backblaze now refuses requests its docs would refuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plain steps to get a key that work in the Backblaze console and on a phone, and when a
key lacks permissions, the two ways forward: the Master Application Key, explained, or
by hand under Advanced. This wording is a draft until the real-account spike. Unmanaged
databases are ticked by default. A key that opened a backup but could not be recorded
is no longer blamed on the key, and offers a retry. Replacing a key needs a yes, and
the suggested bucket shows its warnings. Removes Copy and the password manager advice,
since checking and proving only take a file, warns on closing with an unsaved key, and
defers revoking the download. The bucket-lock tick is renamed for what it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turn-on writes no policy or cron line there and starts no run, and leaves an existing
policy alone and says so. The server reads 'Skipped: you chose not to back up this
server', stays out of the failure and alert paths, and is named in the headline:
'Protected, except <server> (you chose to skip)'. A database that appears later still
gets named and alerted, because nobody decided about it. A server with a database that
was not deliberately unticked still gets no allow_empty. Re-opening setup can tick it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@im-tyler
im-tyler merged commit 2a44be2 into main Nov 7, 2026
3 checks passed
@im-tyler
im-tyler deleted the simple-setup branch November 7, 2026 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant