Read back existing destinations, ntfy, schedule, and B2 fields on rerun

Requested after a rerun silently reset DR_SYNC_PATH (fixed separately) —
auditing the rest of install_backup() turned up the same class of bug in
several other places, one of them worse than the one that prompted this:

- Default destination repo path defaulted to $ACTUAL_HOME/backups/... even
  when the real configured repo was somewhere else entirely (this user's
  actual path is /root/backups/kopia-backup) — accepting the shown default
  on a rerun would have pointed the installer at the wrong location.
- Extra (non-"default") destinations weren't preserved AT ALL on a rerun —
  skipping "Add more destinations?" silently dropped every extra
  destination, and anything mapped to it, from the rewritten backup.conf.
- The per-service destination-assignment prompt always showed "[default]"
  regardless of the service's actual existing mapping.
- ntfy URL/token always started blank, silently disabling notifications on
  any rerun where they weren't retyped.
- The schedule prompt always defaulted to option 1 (daily 02:00) instead of
  reading back whatever OnCalendar was actually already running.
- B2's four sub-fields (bucket/endpoint/key ID/secret) always started
  blank even when reconfiguring an already-working REMOTE_TYPE=s3 setup —
  a mispaste on any one of the four meant retyping all four blind, since
  there was nothing to fall back to per-field (the existing REMOTE_ARGS was
  already preserved as a whole on a blank/failed attempt, just not offered
  back as individual editable defaults).

All six read the same way: pull the existing value from backup.conf (or,
for the schedule, from the live systemd timer unit — schedule isn't stored
in backup.conf) and use it as the prompt default, so accepting the default
keeps what's already there instead of silently reverting it. Verified all
six against a mock backup.conf + timer fixture with pre-existing values for
every field this touches.

Known remaining gap: KEEP_LATEST (retention count) still isn't read back —
doing so correctly needs the repo already connected, which happens later
in this same function's flow. Flagging rather than rushing a reorder here.
This commit is contained in:
Claude
2026-08-14 22:51:37 +00:00
parent b9152369ef
commit d2568ecb09
+85 -15
View File
@@ -421,6 +421,11 @@ install_backup() {
echo ""
local DEFAULT_DEST="$ACTUAL_HOME/backups/kopia-backup"
if [ -f "$CONF_FILE" ]; then
local _existing_default_repo
_existing_default_repo="$(grep '^DEST_default_REPO=' "$CONF_FILE" 2>/dev/null | sed -E 's/^DEST_default_REPO="(.*)"$/\1/')"
[ -n "$_existing_default_repo" ] && DEFAULT_DEST="$_existing_default_repo"
fi
local _repo=""
prompt_text " Default repository path [${DEFAULT_DEST}]:" "$DEFAULT_DEST" _repo
_repo="${_repo/#\~/$ACTUAL_HOME}"; _repo="${_repo%/}"
@@ -430,6 +435,28 @@ install_backup() {
DEST_REPOS["default"]="$_repo"
DEST_CONFIGS["default"]="/etc/kopia-backup/default.config"
# Preserve any extra (non-"default") destinations already configured.
# This script has no update/fresh distinction, so without this,
# skipping the "Add more destinations?" prompt below on a rerun would
# silently drop every extra destination — and anything mapped to it —
# from the rewritten backup.conf, rather than just leaving it as-is.
if [ -f "$CONF_FILE" ]; then
local _existing_dest_names _en _existing_repo _existing_cfg
_existing_dest_names="$(grep '^DEST_NAMES=' "$CONF_FILE" 2>/dev/null | sed -E 's/^DEST_NAMES="(.*)"$/\1/')"
for _en in $_existing_dest_names; do
[ "$_en" = "default" ] && continue
_existing_repo="$(grep "^DEST_${_en}_REPO=" "$CONF_FILE" 2>/dev/null | sed -E "s/^DEST_${_en}_REPO=\"(.*)\"\$/\1/")"
[ -z "$_existing_repo" ] && continue
_existing_cfg="$(grep "^DEST_${_en}_CONFIG=" "$CONF_FILE" 2>/dev/null | sed -E "s/^DEST_${_en}_CONFIG=\"(.*)\"\$/\1/")"
DEST_REPOS["$_en"]="$_existing_repo"
DEST_CONFIGS["$_en"]="${_existing_cfg:-/etc/kopia-backup/${_en}.config}"
DEST_NAMES_ARR+=("$_en")
done
if [ "${#DEST_NAMES_ARR[@]}" -gt 1 ]; then
log_info " Keeping already-configured destination(s): ${DEST_NAMES_ARR[*]:1}"
fi
fi
local _extra=""
prompt_yn " Add more destinations (for services on different drives)? (y/N):" "n" _extra
if [[ "$_extra" =~ ^[Yy]$ ]]; then
@@ -440,12 +467,16 @@ install_backup() {
[ -z "$_dn" ] && break
_dn="${_dn//[^a-zA-Z0-9_]/_}"
[ "$_dn" = "default" ] && { log_warning " 'default' is reserved — use another name."; continue; }
prompt_text " Path for '$_dn' repository:" "" _dr
# Typing an already-known name reconfigures its path rather than
# duplicating it in DEST_NAMES_ARR.
prompt_text " Path for '$_dn' repository:" "${DEST_REPOS[$_dn]:-}" _dr
[ -z "$_dr" ] && continue
_dr="${_dr/#\~/$ACTUAL_HOME}"; _dr="${_dr%/}"
DEST_REPOS["$_dn"]="$_dr"
DEST_CONFIGS["$_dn"]="/etc/kopia-backup/${_dn}.config"
DEST_NAMES_ARR+=("$_dn")
if [[ " ${DEST_NAMES_ARR[*]} " != *" $_dn "* ]]; then
DEST_NAMES_ARR+=("$_dn")
fi
log_success " Destination '$_dn' → $_dr"
done
fi
@@ -464,11 +495,17 @@ install_backup() {
printf " %-16s %s\n" "$dn" "${DEST_REPOS[$dn]}"
done
echo ""
echo " Press Enter to accept the default for each service."
echo " Press Enter to accept the shown default for each service."
echo ""
local _d
local _d _svc_var _existing_svc_dest
for svc in "${ALL_SVCS[@]}"; do
prompt_text " $svc [default]:" "default" _d
_svc_var="${svc//-/_}"
_existing_svc_dest="default"
if [ -f "$CONF_FILE" ]; then
_existing_svc_dest="$(grep -E "^#?SVC_${_svc_var}=" "$CONF_FILE" 2>/dev/null | tail -1 | sed -E 's/^#?SVC_[A-Za-z0-9_]+="(.*)"$/\1/')"
[ -z "$_existing_svc_dest" ] && _existing_svc_dest="default"
fi
prompt_text " $svc [$_existing_svc_dest]:" "$_existing_svc_dest" _d
if [ -n "$_d" ] && [ "$_d" != "default" ] && [ -n "${DEST_REPOS[$_d]:-}" ]; then
SVC_DEST_MAP["$svc"]="$_d"
fi
@@ -518,13 +555,27 @@ install_backup() {
echo " 3) Weekly (Sunday 02:00)"
echo " 4) Custom (systemd OnCalendar)"
echo ""
local _sch=""
prompt_text " How often? [1]:" "1" _sch
# Preselect whatever's already scheduled, read back from the live timer
# unit rather than backup.conf (the schedule isn't stored there — it's
# baked directly into the .timer file). Without this, re-running the
# installer and just hitting Enter through this prompt would silently
# revert a customized schedule back to "1) Daily at 02:00" every time.
local _sch="1" _existing_oncal=""
if [ -f "/etc/systemd/system/${SVC_NAME}.timer" ]; then
_existing_oncal="$(grep '^OnCalendar=' "/etc/systemd/system/${SVC_NAME}.timer" 2>/dev/null | cut -d= -f2-)"
case "$_existing_oncal" in
"*-*-* 02,14:00:00") _sch="2" ;;
"Sun *-*-* 02:00:00") _sch="3" ;;
"*-*-* 02:00:00"|"") _sch="1" ;;
*) _sch="4" ;;
esac
fi
prompt_text " How often? [$_sch]:" "$_sch" _sch
local ONCALENDAR SCHED_LABEL
case "${_sch:-1}" in
2) ONCALENDAR="*-*-* 02,14:00:00"; SCHED_LABEL="every 12 hours" ;;
3) ONCALENDAR="Sun *-*-* 02:00:00"; SCHED_LABEL="weekly Sunday 02:00" ;;
4) prompt_text " OnCalendar expression:" "*-*-* 02:00:00" ONCALENDAR; SCHED_LABEL="$ONCALENDAR" ;;
4) prompt_text " OnCalendar expression:" "${_existing_oncal:-*-*-* 02:00:00}" ONCALENDAR; SCHED_LABEL="$ONCALENDAR" ;;
*) ONCALENDAR="*-*-* 02:00:00"; SCHED_LABEL="daily at 02:00" ;;
esac
local KEEP_LATEST=""
@@ -542,9 +593,13 @@ install_backup() {
echo " Example URL: https://ntfy.sh/my-backup-alerts"
echo ""
local NTFY_URL="" NTFY_TOKEN=""
prompt_text " ntfy topic URL (blank to skip):" "" NTFY_URL
if [ -f "$CONF_FILE" ]; then
NTFY_URL="$(grep "^NTFY_URL=" "$CONF_FILE" 2>/dev/null | sed -E "s/^NTFY_URL='(.*)'\$/\1/")"
NTFY_TOKEN="$(grep "^NTFY_TOKEN=" "$CONF_FILE" 2>/dev/null | sed -E "s/^NTFY_TOKEN='(.*)'\$/\1/")"
fi
prompt_text " ntfy topic URL (blank to skip)${NTFY_URL:+ — already set to $NTFY_URL}:" "$NTFY_URL" NTFY_URL
if [ -n "$NTFY_URL" ]; then
prompt_text " ntfy access token (blank if public/no auth):" "" NTFY_TOKEN
prompt_text " ntfy access token (blank if public/no auth${NTFY_TOKEN:+ — one is already set, Enter keeps it}):" "$NTFY_TOKEN" NTFY_TOKEN
fi
# ── Disaster-recovery spare box (optional) ────────────────────────────────
@@ -774,14 +829,29 @@ install_backup() {
# live: a paste into the hidden Application Key field can silently
# capture nothing depending on the terminal/SSH client, with no
# other symptom until this point.
local B2_BUCKET="" B2_ENDPOINT="" B2_KEY_ID="" B2_APP_KEY=""
prompt_text " Bucket name:" "" B2_BUCKET
# Pre-fill from whatever's already configured (only meaningful if the
# existing REMOTE_TYPE really is s3/B2 — a REMOTE_ARGS left over from
# a different provider, e.g. sftp, wouldn't parse into anything
# useful here and is harmlessly skipped). Otherwise reconfiguring
# just to rotate one field means blindly retyping all four, and a
# mispaste on any one of them loses the other three that were
# already typed correctly this run.
local _existing_b2_bucket="" _existing_b2_endpoint="" _existing_b2_keyid="" _existing_b2_appkey=""
if [ "$REMOTE_TYPE" = "s3" ]; then
_existing_b2_bucket="$(echo "$REMOTE_ARGS" | grep -oE -- '--bucket=[^ ]*' | cut -d= -f2-)"
_existing_b2_endpoint="$(echo "$REMOTE_ARGS" | grep -oE -- '--endpoint=[^ ]*' | cut -d= -f2-)"
_existing_b2_keyid="$(echo "$REMOTE_ARGS" | grep -oE -- '--access-key=[^ ]*' | cut -d= -f2-)"
_existing_b2_appkey="$(echo "$REMOTE_ARGS" | grep -oE -- '--secret-access-key=[^ ]*' | cut -d= -f2-)"
fi
local B2_BUCKET="$_existing_b2_bucket" B2_ENDPOINT="$_existing_b2_endpoint" B2_KEY_ID="$_existing_b2_keyid" B2_APP_KEY=""
prompt_text " Bucket name:" "$B2_BUCKET" B2_BUCKET
echo " (${#B2_BUCKET} characters entered)"
prompt_text " Endpoint (e.g. s3.us-west-004.backblazeb2.com):" "" B2_ENDPOINT
prompt_text " Endpoint (e.g. s3.us-west-004.backblazeb2.com):" "$B2_ENDPOINT" B2_ENDPOINT
echo " (${#B2_ENDPOINT} characters entered)"
prompt_text " Application Key ID:" "" B2_KEY_ID
prompt_text " Application Key ID:" "$B2_KEY_ID" B2_KEY_ID
echo " (${#B2_KEY_ID} characters entered)"
read -rsp " Application Key (input hidden): " B2_APP_KEY; echo
read -rsp " Application Key (input hidden${_existing_b2_appkey:+ — leave blank to keep the existing one}): " B2_APP_KEY; echo
[ -z "$B2_APP_KEY" ] && B2_APP_KEY="$_existing_b2_appkey"
echo " (${#B2_APP_KEY} characters entered)"
local _B2_MISSING=""