Guide + automate Backblaze B2 offsite mirror setup in backup.sh

Answers a direct ask: offsite mirroring existed only as a REMOTE_TYPE/
REMOTE_ARGS placeholder in backup.conf with a comment pointing at
`kopia repository sync-to --help` — no interactive setup at all, B2 or
otherwise.

Checked before building anything: Kopia's dedicated `sync-to b2`
provider is marked [DEPRECATED] on kopia.io's own command reference.
B2 also offers an S3-compatible endpoint (s3.<region>.backblazeb2.com,
same application key works as the access/secret key pair), and Kopia's
`sync-to s3` provider isn't deprecated — so this targets that path
instead of building on a command on its way out.

What's now automated vs. guided, deliberately split:
- Bucket creation and the application key are walked through as console
  steps, not automated. Object Lock specifically is a one-time,
  bucket-creation-only decision with a real tradeoff (undeletable-by-
  design vs. genuinely can't delete early) that shouldn't be silently
  flipped either way by a script on someone's behalf.
- Once the operator has a bucket + endpoint + scoped application key
  (B2 requires a key scoped to one bucket, not the account master key —
  noted in the walkthrough), this becomes mechanical: run a
  `sync-to s3 --dry-run` against the just-created 'default' repo to
  verify the credentials actually work, and only then write
  REMOTE_TYPE=s3 / REMOTE_ARGS into backup.conf. A bad bucket name or
  key leaves REMOTE_TYPE at "none" with a clear error instead of saving
  a broken config that fails silently at 2am.
- Encryption isn't a separate step — Kopia already encrypts client-side
  with the repository password set earlier in this same flow; called
  that out explicitly since it was asked about as if it needed its own
  setup step.

Also fixed a regression the new prompt would otherwise have caused:
backup.sh has no update/fresh distinction and re-asks everything on
every run, so an already-configured offsite mirror is now read back
from the existing backup.conf and preserved by default — answering "no"
on a re-run no longer silently resets REMOTE_TYPE to "none".

Verified the control flow (not just bash -n) against a mock kopia
binary and stubbed prompts: good credentials wire up REMOTE_TYPE/
REMOTE_ARGS correctly, a rejected credential leaves REMOTE_TYPE at
"none" rather than saving something broken, an existing configured
value survives a "no" answer on re-run, and blank fields skip cleanly
without attempting a dry-run at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
This commit is contained in:
Claude
2026-08-13 19:28:00 +00:00
parent 93d5459a67
commit c48ed039e0
+105 -2
View File
@@ -520,6 +520,106 @@ install_backup() {
done
unset -f kp_d
# ── 7b. Offsite mirror (Backblaze B2) ────────────────────────────────────
# Kopia's dedicated "b2" sync-to provider is marked [DEPRECATED] in
# Kopia's own docs (kopia.io/docs/reference/command-line/common/
# repository-sync-to-b2/) — confirmed before writing this rather than
# building on a command that's on its way out. B2's S3-compatible
# endpoint plus the actively-maintained `sync-to s3` provider is the
# supported path instead: same B2 application key, just pointed at
# B2's own s3.<region>.backblazeb2.com endpoint instead of AWS.
#
# Bucket creation and the application key can't be automated here on
# purpose — Object Lock in particular is a deliberate, one-time choice
# B2 only lets you make at bucket creation, not something safe for a
# script to flip on (or skip) silently on someone's behalf. This walks
# through both console steps, then handles the mechanical part: taking
# the resulting bucket/endpoint/key and writing a verified
# REMOTE_TYPE/REMOTE_ARGS into backup.conf.
#
# Encryption is NOT a separate step here — Kopia already encrypts
# everything client-side (AES-256-GCM) using the repository password
# set above, before any of it leaves this box. B2's own optional
# Server-Side Encryption toggle is redundant on top of that; harmless
# to also enable for defense-in-depth, but nothing here depends on it.
local REMOTE_TYPE="none" REMOTE_ARGS=""
if [ -f "$CONF_FILE" ]; then
# Preserve whatever's already configured if this is a re-run and
# the operator doesn't re-answer the prompt below — re-running this
# installer has no update/fresh distinction, so without this an
# already-working offsite mirror would silently reset to "none".
REMOTE_TYPE="$(grep '^REMOTE_TYPE=' "$CONF_FILE" 2>/dev/null | cut -d= -f2- | tr -d '"')"
REMOTE_ARGS="$(grep '^REMOTE_ARGS=' "$CONF_FILE" 2>/dev/null | cut -d= -f2- | tr -d '"')"
[ -z "$REMOTE_TYPE" ] && REMOTE_TYPE="none"
fi
echo ""
echo "═══════════════════════════════════════════════════════"
echo " OFFSITE MIRROR (optional)"
echo "═══════════════════════════════════════════════════════"
echo ""
echo " Mirrors every local repo above to Backblaze B2 after each backup run —"
echo " the actual '1 copy offsite' piece of a real 3-2-1 backup. Skip this if"
echo " you don't have a B2 account yet, or would rather set REMOTE_TYPE/"
echo " REMOTE_ARGS in backup.conf by hand later."
if [ "$REMOTE_TYPE" != "none" ]; then
echo ""
echo " Offsite mirroring is already configured (REMOTE_TYPE=$REMOTE_TYPE)."
echo " Answering yes below replaces it; answering no leaves it as-is."
fi
echo ""
local _setup_b2=""
prompt_yn " Set up Backblaze B2 offsite mirroring now? (y/N):" "n" _setup_b2
if [[ "$_setup_b2" =~ ^[Yy]$ ]]; then
echo ""
echo " Two one-time steps in the B2 web console first — this script can't do"
echo " these for you:"
echo ""
echo " 1) Buckets → Create a Bucket"
echo " - Files in Bucket: Private"
echo " - Object Lock: your call. ON means backups in this bucket can't be"
echo " deleted or overwritten for a retention period you choose, even by"
echo " someone holding valid credentials for it — protects the offsite"
echo " copy if this box is ever compromised, at the cost of genuinely not"
echo " being able to delete early yourself either. Can only be set at"
echo " bucket creation, not turned on later."
echo " - Note the endpoint shown on the bucket's details page afterward,"
echo " e.g. s3.us-west-004.backblazeb2.com — you'll need it below."
echo ""
echo " 2) Account → App Keys → Add a New Application Key"
echo " - Allow access to: the bucket you just created (not 'All')"
echo " - Type: Read and Write"
echo " - B2 shows the application key ONLY once — copy both values now,"
echo " you can't retrieve the key itself again afterward."
echo ""
local B2_BUCKET="" B2_ENDPOINT="" B2_KEY_ID="" B2_APP_KEY=""
prompt_text " Bucket name:" "" B2_BUCKET
prompt_text " Endpoint (e.g. s3.us-west-004.backblazeb2.com):" "" B2_ENDPOINT
prompt_text " Application Key ID:" "" B2_KEY_ID
read -rsp " Application Key (input hidden): " B2_APP_KEY; echo
if [ -z "$B2_BUCKET" ] || [ -z "$B2_ENDPOINT" ] || [ -z "$B2_KEY_ID" ] || [ -z "$B2_APP_KEY" ]; then
log_warning "One or more fields left blank — skipping B2 setup this run."
else
log_info "Verifying B2 credentials (dry-run sync against the 'default' repo)..."
local _b2_err
if _b2_err="$(env KOPIA_PASSWORD="${DEST_PASSWORDS[default]}" "$KOPIA_BIN" \
--config-file="${DEST_CONFIGS[default]}" repository sync-to s3 \
--bucket="$B2_BUCKET" --access-key="$B2_KEY_ID" \
--secret-access-key="$B2_APP_KEY" --endpoint="$B2_ENDPOINT" \
--dry-run 2>&1)"; then
REMOTE_TYPE="s3"
REMOTE_ARGS="--bucket=$B2_BUCKET --access-key=$B2_KEY_ID --secret-access-key=$B2_APP_KEY --endpoint=$B2_ENDPOINT"
log_success "B2 credentials verified — offsite mirroring will run after each backup."
else
log_warning "B2 dry-run failed — check bucket name, endpoint, and key permissions:"
log_warning "$_b2_err"
log_warning "Not enabling offsite mirroring this run. Re-run this installer once"
log_warning "fixed, or hand-edit REMOTE_TYPE/REMOTE_ARGS in backup.conf directly."
fi
fi
fi
# ── 8. Write backup.conf ─────────────────────────────────────────────────
log_info "Writing $CONF_FILE ..."
{
@@ -556,9 +656,12 @@ install_backup() {
echo ""
echo "# ── Optional offsite mirror ─────────────────────────────────────────────────"
echo "# Mirror ALL repos offsite after each run (see kopia repository sync-to --help)."
echo "# B2: use the s3 provider against B2's S3-compatible endpoint, not the b2"
echo "# provider — kopia.io marks repository-sync-to-b2 as deprecated. Example:"
echo "# REMOTE_TYPE=s3 REMOTE_ARGS=\"--bucket=NAME --access-key=KEYID --secret-access-key=KEY --endpoint=s3.us-west-004.backblazeb2.com\""
echo "# Example SFTP: REMOTE_TYPE=sftp REMOTE_ARGS=\"--host H --username U --path /srv/...\""
echo "REMOTE_TYPE=\"none\""
echo "REMOTE_ARGS=\"\""
echo "REMOTE_TYPE=\"$REMOTE_TYPE\""
echo "REMOTE_ARGS=\"$REMOTE_ARGS\""
echo ""
echo "# ── Notifications (ntfy) ─────────────────────────────────────────────────────"
echo "# Set NTFY_URL to receive backup success/failure alerts."