Merge pull request #340 from outis1one/claude/ionos-script-integration-x32ofw
Claude/ionos script integration x32ofw
This commit is contained in:
+160
-46
@@ -932,8 +932,19 @@ install_backup() {
|
||||
echo ""
|
||||
local _ADD_SFTP_MIRROR=""
|
||||
local _sftp_default_host="${DR_SYNC_HOST:-}"
|
||||
prompt_yn " Add a direct SFTP mirror to another box$( [ -n "$_sftp_default_host" ] && echo " (e.g. $_sftp_default_host, same as the DR-spare above)")? (y/n):" "n" _ADD_SFTP_MIRROR
|
||||
prompt_yn " Add a direct mirror to another box$( [ -n "$_sftp_default_host" ] && echo " (e.g. $_sftp_default_host, same as the DR-spare above)")? (y/n):" "n" _ADD_SFTP_MIRROR
|
||||
if [[ "$_ADD_SFTP_MIRROR" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
echo " 1) SFTP — Kopia's own SFTP backend, syncs straight to a directory"
|
||||
echo " 2) S3 (Garage) — a Garage instance already running on that box"
|
||||
echo " (services/garage.sh) — reuses Kopia's S3 backend, the same one"
|
||||
echo " already proven reliable for the Backblaze B2 mirror above,"
|
||||
echo " instead of Kopia's less-exercised SFTP backend"
|
||||
echo ""
|
||||
local _MIRROR_TYPE_CHOICE=""
|
||||
prompt_text " Mirror type [1]:" "1" _MIRROR_TYPE_CHOICE
|
||||
_MIRROR_TYPE_CHOICE="${_MIRROR_TYPE_CHOICE:-1}"
|
||||
|
||||
local _SFTP_DEST=""
|
||||
prompt_text " SSH destination, user@host (~/.ssh/config aliases work too):" "$_sftp_default_host" _SFTP_DEST
|
||||
if [ -z "$_SFTP_DEST" ]; then
|
||||
@@ -966,58 +977,161 @@ install_backup() {
|
||||
# same Port line correctly.
|
||||
_SFTP_PORT="${_SFTP_PORT:-22}"
|
||||
log_info " Using ${_SFTP_USER}@${_SFTP_HOSTNAME}:${_SFTP_PORT} for this mirror (resolved via ~/.ssh/config)."
|
||||
# Suggest a subdirectory of the DR-spare's own path (if one is
|
||||
# configured) rather than an unrelated default — reusing the
|
||||
# same spare location the operator already picked, but in its
|
||||
# own /kopia-data subdirectory so the actual repository data
|
||||
# (Kopia's own blob-store files) doesn't end up visually mixed
|
||||
# in with the two plain config files the DR-spare sync writes
|
||||
# directly into DR_SYNC_PATH itself.
|
||||
local _SFTP_PATH_DEFAULT="~/backups/kopia-mirror"
|
||||
[ -n "${DR_SYNC_PATH:-}" ] && _SFTP_PATH_DEFAULT="${DR_SYNC_PATH%/}/kopia-data"
|
||||
local _SFTP_PATH="" _MIRROR_NAME=""
|
||||
prompt_text " Remote path for the repo:" "$_SFTP_PATH_DEFAULT" _SFTP_PATH
|
||||
_SFTP_PATH="${_SFTP_PATH:-$_SFTP_PATH_DEFAULT}"
|
||||
prompt_text " Short name for this mirror (letters/numbers/underscores):" "spare" _MIRROR_NAME
|
||||
_MIRROR_NAME="${_MIRROR_NAME:-spare}"
|
||||
|
||||
local _MIRROR_NAME=""
|
||||
prompt_text " Short name for this mirror (letters/numbers/underscores):" "$( [ "$_MIRROR_TYPE_CHOICE" = "2" ] && echo garage || echo spare )" _MIRROR_NAME
|
||||
_MIRROR_NAME="${_MIRROR_NAME:-$( [ "$_MIRROR_TYPE_CHOICE" = "2" ] && echo garage || echo spare )}"
|
||||
_MIRROR_NAME="${_MIRROR_NAME//[^a-zA-Z0-9_]/_}"
|
||||
|
||||
# sync-to sftp doesn't shell out to the system ssh client, so it
|
||||
# needs an explicit key/known_hosts file rather than picking up
|
||||
# whatever plain `ssh` already trusts automatically.
|
||||
_backup_ensure_root_ssh_key "$_SFTP_DEST"
|
||||
local _SFTP_KEYFILE="$_ROOT_SSH_KEYFILE"
|
||||
|
||||
if [ -z "$_SFTP_KEYFILE" ]; then
|
||||
log_warning " No SSH key available for root — can't add this mirror."
|
||||
elif ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$_SFTP_DEST" true 2>/dev/null; then
|
||||
# Both types need at least a working passwordless SSH connection
|
||||
# to this box — SFTP because Kopia authenticates with a keyfile
|
||||
# over that same connection; S3/Garage because that's how this
|
||||
# script reads Garage's own credentials remotely (the actual S3
|
||||
# dry-run below talks straight to Garage's S3 API port, no SSH
|
||||
# involved in that part at all).
|
||||
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$_SFTP_DEST" true 2>/dev/null; then
|
||||
log_warning " Couldn't SSH to $_SFTP_DEST without a password — not adding this"
|
||||
log_warning " mirror until that works: ssh-copy-id $_SFTP_DEST"
|
||||
else
|
||||
log_info "Verifying SFTP mirror (dry-run sync against the 'default' repo)..."
|
||||
local _sftp_err
|
||||
if _sftp_err="$(env KOPIA_PASSWORD="${DEST_PASSWORDS[default]}" "$KOPIA_BIN" \
|
||||
--config-file="${DEST_CONFIGS[default]}" repository sync-to sftp \
|
||||
--host="$_SFTP_HOSTNAME" --port="$_SFTP_PORT" --username="$_SFTP_USER" --path="$_SFTP_PATH" \
|
||||
--keyfile="$_SFTP_KEYFILE" --known-hosts=/root/.ssh/known_hosts \
|
||||
--dry-run 2>&1)"; then
|
||||
EXTRA_MIRROR_TYPE["$_MIRROR_NAME"]="sftp"
|
||||
EXTRA_MIRROR_ARGS["$_MIRROR_NAME"]="--host=$_SFTP_HOSTNAME --port=$_SFTP_PORT --username=$_SFTP_USER --path=$_SFTP_PATH --keyfile=$_SFTP_KEYFILE --known-hosts=/root/.ssh/known_hosts"
|
||||
# Reusing an existing mirror name reconfigures it (the
|
||||
# associative-array assignments above already do that)
|
||||
# without duplicating it in the space-separated name list.
|
||||
if [[ " $EXTRA_MIRROR_NAMES " != *" $_MIRROR_NAME "* ]]; then
|
||||
if [ -z "$EXTRA_MIRROR_NAMES" ]; then
|
||||
EXTRA_MIRROR_NAMES="$_MIRROR_NAME"
|
||||
else
|
||||
EXTRA_MIRROR_NAMES="$EXTRA_MIRROR_NAMES $_MIRROR_NAME"
|
||||
# Loop so "Garage isn't installed yet" can offer a real retry/
|
||||
# fall-back-to-SFTP/skip choice instead of just dropping the
|
||||
# whole mirror — $_SFTP_DEST and $_MIRROR_NAME are already
|
||||
# resolved above, so none of that has to be re-entered no
|
||||
# matter which way this loop exits. Nothing collected earlier
|
||||
# in this function (destinations, passwords, schedule, B2,
|
||||
# DR-spare, ...) is inside this loop at all, so choosing to
|
||||
# skip here never loses any of that either — it's already
|
||||
# sitting in local variables the "Write backup.conf" step
|
||||
# below reads regardless of what happens with this one mirror.
|
||||
while true; do
|
||||
if [ "$_MIRROR_TYPE_CHOICE" = "2" ]; then
|
||||
# ── S3 (Garage) ──────────────────────────────────────
|
||||
# Read the real bucket/key/port straight from the
|
||||
# remote instance's own .env rather than asking the
|
||||
# operator to retype them here — those values are
|
||||
# generated once by services/garage.sh and never
|
||||
# touched again on its own Update runs, so this is
|
||||
# always reading the box's actual current
|
||||
# configuration, not something baked in here.
|
||||
local _garage_env
|
||||
_garage_env="$(ssh -o BatchMode=yes -o ConnectTimeout=5 "$_SFTP_DEST" "cat ~/docker/garage/.env 2>/dev/null")"
|
||||
if [ -z "$_garage_env" ]; then
|
||||
echo ""
|
||||
log_warning " Garage isn't installed on $_SFTP_DEST yet."
|
||||
echo " 1) Install it now, then retry this check"
|
||||
echo " (in another session on $_SFTP_DEST: sudo ./setup.sh garage"
|
||||
echo " — or: sudo bash garage.sh, on a box without this repo)"
|
||||
echo " 2) Use SFTP instead for this mirror (same destination/name)"
|
||||
echo " 3) Skip this mirror — everything else you've entered stays"
|
||||
echo ""
|
||||
local _GARAGE_MISSING_CHOICE=""
|
||||
prompt_text " Choice [3]:" "3" _GARAGE_MISSING_CHOICE
|
||||
case "${_GARAGE_MISSING_CHOICE:-3}" in
|
||||
1)
|
||||
read -r -p " Press Enter once Garage is installed on $_SFTP_DEST to retry (or Ctrl-C to give up): " _
|
||||
continue
|
||||
;;
|
||||
2)
|
||||
_MIRROR_TYPE_CHOICE="1"
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
log_info " Skipping this mirror."
|
||||
break
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
local _g_bucket _g_key_id _g_key_secret _g_port
|
||||
_g_bucket="$(echo "$_garage_env" | sed -nE "s/^GARAGE_BUCKET='?([^']*)'?\$/\1/p")"
|
||||
_g_key_id="$(echo "$_garage_env" | sed -nE "s/^GARAGE_ACCESS_KEY_ID='?([^']*)'?\$/\1/p")"
|
||||
_g_key_secret="$(echo "$_garage_env" | sed -nE "s/^GARAGE_ACCESS_KEY_SECRET='?([^']*)'?\$/\1/p")"
|
||||
_g_port="$(echo "$_garage_env" | sed -nE "s/^GARAGE_S3_API_PORT=([0-9]+)\$/\1/p")"
|
||||
if [ -z "$_g_bucket" ] || [ -z "$_g_key_id" ] || [ -z "$_g_key_secret" ] || [ -z "$_g_port" ]; then
|
||||
log_warning " Garage is installed on $_SFTP_DEST but its .env is missing something"
|
||||
log_warning " expected — check ~/docker/garage/.env there. Not adding this mirror."
|
||||
break
|
||||
fi
|
||||
|
||||
local _g_endpoint="${_SFTP_HOSTNAME}:${_g_port}"
|
||||
log_info "Verifying S3 (Garage) mirror at ${_g_endpoint}, bucket '$_g_bucket' (dry-run)..."
|
||||
local _s3_err
|
||||
if _s3_err="$(env KOPIA_PASSWORD="${DEST_PASSWORDS[default]}" "$KOPIA_BIN" \
|
||||
--config-file="${DEST_CONFIGS[default]}" repository sync-to s3 \
|
||||
--bucket="$_g_bucket" --endpoint="$_g_endpoint" \
|
||||
--access-key="$_g_key_id" --secret-access-key="$_g_key_secret" \
|
||||
--disable-tls --dry-run 2>&1)"; then
|
||||
EXTRA_MIRROR_TYPE["$_MIRROR_NAME"]="s3"
|
||||
EXTRA_MIRROR_ARGS["$_MIRROR_NAME"]="--bucket=$_g_bucket --endpoint=$_g_endpoint --access-key=$_g_key_id --secret-access-key=$_g_key_secret --disable-tls"
|
||||
if [[ " $EXTRA_MIRROR_NAMES " != *" $_MIRROR_NAME "* ]]; then
|
||||
if [ -z "$EXTRA_MIRROR_NAMES" ]; then
|
||||
EXTRA_MIRROR_NAMES="$_MIRROR_NAME"
|
||||
else
|
||||
EXTRA_MIRROR_NAMES="$EXTRA_MIRROR_NAMES $_MIRROR_NAME"
|
||||
fi
|
||||
fi
|
||||
log_success " S3 (Garage) mirror '$_MIRROR_NAME' verified — will run after every backup."
|
||||
else
|
||||
log_warning " S3 (Garage) dry-run failed — not adding this mirror:"
|
||||
log_warning " $_s3_err"
|
||||
fi
|
||||
break
|
||||
else
|
||||
# ── SFTP ─────────────────────────────────────────────
|
||||
# Suggest a subdirectory of the DR-spare's own path (if
|
||||
# one is configured) rather than an unrelated default
|
||||
# — reusing the same spare location the operator
|
||||
# already picked, but in its own /kopia-data
|
||||
# subdirectory so the actual repository data (Kopia's
|
||||
# own blob-store files) doesn't end up visually mixed
|
||||
# in with the two plain config files the DR-spare sync
|
||||
# writes directly into DR_SYNC_PATH itself.
|
||||
local _SFTP_PATH_DEFAULT="~/backups/kopia-mirror"
|
||||
[ -n "${DR_SYNC_PATH:-}" ] && _SFTP_PATH_DEFAULT="${DR_SYNC_PATH%/}/kopia-data"
|
||||
local _SFTP_PATH=""
|
||||
prompt_text " Remote path for the repo:" "$_SFTP_PATH_DEFAULT" _SFTP_PATH
|
||||
_SFTP_PATH="${_SFTP_PATH:-$_SFTP_PATH_DEFAULT}"
|
||||
|
||||
# sync-to sftp doesn't shell out to the system ssh
|
||||
# client, so it needs an explicit key/known_hosts file
|
||||
# rather than picking up whatever plain `ssh` already
|
||||
# trusts automatically.
|
||||
_backup_ensure_root_ssh_key "$_SFTP_DEST"
|
||||
local _SFTP_KEYFILE="$_ROOT_SSH_KEYFILE"
|
||||
|
||||
if [ -z "$_SFTP_KEYFILE" ]; then
|
||||
log_warning " No SSH key available for root — can't add this mirror."
|
||||
break
|
||||
fi
|
||||
|
||||
log_info "Verifying SFTP mirror (dry-run sync against the 'default' repo)..."
|
||||
local _sftp_err
|
||||
if _sftp_err="$(env KOPIA_PASSWORD="${DEST_PASSWORDS[default]}" "$KOPIA_BIN" \
|
||||
--config-file="${DEST_CONFIGS[default]}" repository sync-to sftp \
|
||||
--host="$_SFTP_HOSTNAME" --port="$_SFTP_PORT" --username="$_SFTP_USER" --path="$_SFTP_PATH" \
|
||||
--keyfile="$_SFTP_KEYFILE" --known-hosts=/root/.ssh/known_hosts \
|
||||
--dry-run 2>&1)"; then
|
||||
EXTRA_MIRROR_TYPE["$_MIRROR_NAME"]="sftp"
|
||||
EXTRA_MIRROR_ARGS["$_MIRROR_NAME"]="--host=$_SFTP_HOSTNAME --port=$_SFTP_PORT --username=$_SFTP_USER --path=$_SFTP_PATH --keyfile=$_SFTP_KEYFILE --known-hosts=/root/.ssh/known_hosts"
|
||||
# Reusing an existing mirror name reconfigures it
|
||||
# (the associative-array assignments above already
|
||||
# do that) without duplicating it in the
|
||||
# space-separated name list.
|
||||
if [[ " $EXTRA_MIRROR_NAMES " != *" $_MIRROR_NAME "* ]]; then
|
||||
if [ -z "$EXTRA_MIRROR_NAMES" ]; then
|
||||
EXTRA_MIRROR_NAMES="$_MIRROR_NAME"
|
||||
else
|
||||
EXTRA_MIRROR_NAMES="$EXTRA_MIRROR_NAMES $_MIRROR_NAME"
|
||||
fi
|
||||
fi
|
||||
log_success " SFTP mirror '$_MIRROR_NAME' verified — will run after every backup."
|
||||
else
|
||||
log_warning " SFTP dry-run failed — not adding this mirror:"
|
||||
log_warning " $_sftp_err"
|
||||
fi
|
||||
break
|
||||
fi
|
||||
log_success " SFTP mirror '$_MIRROR_NAME' verified — will run after every backup."
|
||||
else
|
||||
log_warning " SFTP dry-run failed — not adding this mirror:"
|
||||
log_warning " $_sftp_err"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
+23
-6
@@ -174,11 +174,16 @@ install_garage() {
|
||||
find_free_port RPC_PORT "$RPC_PORT"
|
||||
find_free_port ADMIN_PORT "$ADMIN_PORT"
|
||||
|
||||
# Suggested defaults are generated fresh at runtime, not fixed strings
|
||||
# baked into this script — same reasoning as not hardcoding what a
|
||||
# remote reader (services/backup.sh) should expect the name to be:
|
||||
# this is the operator's name to pick, not this repo's.
|
||||
local BUCKET_NAME="" KEY_NAME=""
|
||||
prompt_text " Bucket name:" "kopia-backup" BUCKET_NAME
|
||||
BUCKET_NAME="${BUCKET_NAME:-kopia-backup}"
|
||||
prompt_text " Access key name:" "kopia" KEY_NAME
|
||||
KEY_NAME="${KEY_NAME:-kopia}"
|
||||
local _default_bucket="kopia-$(date +%s)" _default_key="key-$(date +%s)"
|
||||
prompt_text " Bucket name:" "$_default_bucket" BUCKET_NAME
|
||||
BUCKET_NAME="${BUCKET_NAME:-$_default_bucket}"
|
||||
prompt_text " Access key name:" "$_default_key" KEY_NAME
|
||||
KEY_NAME="${KEY_NAME:-$_default_key}"
|
||||
|
||||
mkdir -p "$DIR"/{data,meta}
|
||||
ensure_docker_dir_ownership "$DIR"
|
||||
@@ -232,6 +237,11 @@ COMPOSE
|
||||
|
||||
cat > .env << ENV
|
||||
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
|
||||
|
||||
# Read directly (over SSH) by another box's services/backup.sh when adding
|
||||
# this instance as a Kopia sync-to s3 mirror target — keep this key name
|
||||
# stable, other scripts depend on it.
|
||||
GARAGE_S3_API_PORT=${S3_API_PORT}
|
||||
ENV
|
||||
chmod 600 .env
|
||||
|
||||
@@ -277,8 +287,15 @@ ENV
|
||||
local _key_out
|
||||
_key_out="$(docker exec garage /garage key create "$KEY_NAME" 2>&1)"
|
||||
local ACCESS_KEY_ID ACCESS_KEY_SECRET
|
||||
ACCESS_KEY_ID="$(echo "$_key_out" | awk -F': ' '/^Key ID:/{print $2}')"
|
||||
ACCESS_KEY_SECRET="$(echo "$_key_out" | awk -F': ' '/^Secret key:/{print $2}')"
|
||||
# Garage's real CLI output pads labels with extra spaces for column
|
||||
# alignment (e.g. "Key ID: GKxxxx", not just "Key ID: GKxxxx")
|
||||
# — a fixed ": " separator leaves that padding stuck to the value.
|
||||
# ':[[:space:]]+' as a regex field separator consumes ALL of it,
|
||||
# however many spaces there actually are. Confirmed live: the fixed
|
||||
# single-space version left leading spaces baked into .env, which
|
||||
# would have broken S3 auth (access keys have to match exactly).
|
||||
ACCESS_KEY_ID="$(echo "$_key_out" | awk -F':[[:space:]]+' '/^Key ID:/{print $2}')"
|
||||
ACCESS_KEY_SECRET="$(echo "$_key_out" | awk -F':[[:space:]]+' '/^Secret key:/{print $2}')"
|
||||
|
||||
if [ -z "$ACCESS_KEY_ID" ] || [ -z "$ACCESS_KEY_SECRET" ]; then
|
||||
log_error "Couldn't parse the access key from 'garage key create' output:"
|
||||
|
||||
Reference in New Issue
Block a user