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 ""
|
echo ""
|
||||||
local _ADD_SFTP_MIRROR=""
|
local _ADD_SFTP_MIRROR=""
|
||||||
local _sftp_default_host="${DR_SYNC_HOST:-}"
|
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
|
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=""
|
local _SFTP_DEST=""
|
||||||
prompt_text " SSH destination, user@host (~/.ssh/config aliases work too):" "$_sftp_default_host" _SFTP_DEST
|
prompt_text " SSH destination, user@host (~/.ssh/config aliases work too):" "$_sftp_default_host" _SFTP_DEST
|
||||||
if [ -z "$_SFTP_DEST" ]; then
|
if [ -z "$_SFTP_DEST" ]; then
|
||||||
@@ -966,58 +977,161 @@ install_backup() {
|
|||||||
# same Port line correctly.
|
# same Port line correctly.
|
||||||
_SFTP_PORT="${_SFTP_PORT:-22}"
|
_SFTP_PORT="${_SFTP_PORT:-22}"
|
||||||
log_info " Using ${_SFTP_USER}@${_SFTP_HOSTNAME}:${_SFTP_PORT} for this mirror (resolved via ~/.ssh/config)."
|
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
|
local _MIRROR_NAME=""
|
||||||
# same spare location the operator already picked, but in its
|
prompt_text " Short name for this mirror (letters/numbers/underscores):" "$( [ "$_MIRROR_TYPE_CHOICE" = "2" ] && echo garage || echo spare )" _MIRROR_NAME
|
||||||
# own /kopia-data subdirectory so the actual repository data
|
_MIRROR_NAME="${_MIRROR_NAME:-$( [ "$_MIRROR_TYPE_CHOICE" = "2" ] && echo garage || echo spare )}"
|
||||||
# (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}"
|
|
||||||
_MIRROR_NAME="${_MIRROR_NAME//[^a-zA-Z0-9_]/_}"
|
_MIRROR_NAME="${_MIRROR_NAME//[^a-zA-Z0-9_]/_}"
|
||||||
|
|
||||||
# sync-to sftp doesn't shell out to the system ssh client, so it
|
# Both types need at least a working passwordless SSH connection
|
||||||
# needs an explicit key/known_hosts file rather than picking up
|
# to this box — SFTP because Kopia authenticates with a keyfile
|
||||||
# whatever plain `ssh` already trusts automatically.
|
# over that same connection; S3/Garage because that's how this
|
||||||
_backup_ensure_root_ssh_key "$_SFTP_DEST"
|
# script reads Garage's own credentials remotely (the actual S3
|
||||||
local _SFTP_KEYFILE="$_ROOT_SSH_KEYFILE"
|
# dry-run below talks straight to Garage's S3 API port, no SSH
|
||||||
|
# involved in that part at all).
|
||||||
if [ -z "$_SFTP_KEYFILE" ]; then
|
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$_SFTP_DEST" true 2>/dev/null; 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
|
|
||||||
log_warning " Couldn't SSH to $_SFTP_DEST without a password — not adding this"
|
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"
|
log_warning " mirror until that works: ssh-copy-id $_SFTP_DEST"
|
||||||
else
|
else
|
||||||
log_info "Verifying SFTP mirror (dry-run sync against the 'default' repo)..."
|
# Loop so "Garage isn't installed yet" can offer a real retry/
|
||||||
local _sftp_err
|
# fall-back-to-SFTP/skip choice instead of just dropping the
|
||||||
if _sftp_err="$(env KOPIA_PASSWORD="${DEST_PASSWORDS[default]}" "$KOPIA_BIN" \
|
# whole mirror — $_SFTP_DEST and $_MIRROR_NAME are already
|
||||||
--config-file="${DEST_CONFIGS[default]}" repository sync-to sftp \
|
# resolved above, so none of that has to be re-entered no
|
||||||
--host="$_SFTP_HOSTNAME" --port="$_SFTP_PORT" --username="$_SFTP_USER" --path="$_SFTP_PATH" \
|
# matter which way this loop exits. Nothing collected earlier
|
||||||
--keyfile="$_SFTP_KEYFILE" --known-hosts=/root/.ssh/known_hosts \
|
# in this function (destinations, passwords, schedule, B2,
|
||||||
--dry-run 2>&1)"; then
|
# DR-spare, ...) is inside this loop at all, so choosing to
|
||||||
EXTRA_MIRROR_TYPE["$_MIRROR_NAME"]="sftp"
|
# skip here never loses any of that either — it's already
|
||||||
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"
|
# sitting in local variables the "Write backup.conf" step
|
||||||
# Reusing an existing mirror name reconfigures it (the
|
# below reads regardless of what happens with this one mirror.
|
||||||
# associative-array assignments above already do that)
|
while true; do
|
||||||
# without duplicating it in the space-separated name list.
|
if [ "$_MIRROR_TYPE_CHOICE" = "2" ]; then
|
||||||
if [[ " $EXTRA_MIRROR_NAMES " != *" $_MIRROR_NAME "* ]]; then
|
# ── S3 (Garage) ──────────────────────────────────────
|
||||||
if [ -z "$EXTRA_MIRROR_NAMES" ]; then
|
# Read the real bucket/key/port straight from the
|
||||||
EXTRA_MIRROR_NAMES="$_MIRROR_NAME"
|
# remote instance's own .env rather than asking the
|
||||||
else
|
# operator to retype them here — those values are
|
||||||
EXTRA_MIRROR_NAMES="$EXTRA_MIRROR_NAMES $_MIRROR_NAME"
|
# 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
|
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
|
fi
|
||||||
log_success " SFTP mirror '$_MIRROR_NAME' verified — will run after every backup."
|
done
|
||||||
else
|
|
||||||
log_warning " SFTP dry-run failed — not adding this mirror:"
|
|
||||||
log_warning " $_sftp_err"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
+23
-6
@@ -174,11 +174,16 @@ install_garage() {
|
|||||||
find_free_port RPC_PORT "$RPC_PORT"
|
find_free_port RPC_PORT "$RPC_PORT"
|
||||||
find_free_port ADMIN_PORT "$ADMIN_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=""
|
local BUCKET_NAME="" KEY_NAME=""
|
||||||
prompt_text " Bucket name:" "kopia-backup" BUCKET_NAME
|
local _default_bucket="kopia-$(date +%s)" _default_key="key-$(date +%s)"
|
||||||
BUCKET_NAME="${BUCKET_NAME:-kopia-backup}"
|
prompt_text " Bucket name:" "$_default_bucket" BUCKET_NAME
|
||||||
prompt_text " Access key name:" "kopia" KEY_NAME
|
BUCKET_NAME="${BUCKET_NAME:-$_default_bucket}"
|
||||||
KEY_NAME="${KEY_NAME:-kopia}"
|
prompt_text " Access key name:" "$_default_key" KEY_NAME
|
||||||
|
KEY_NAME="${KEY_NAME:-$_default_key}"
|
||||||
|
|
||||||
mkdir -p "$DIR"/{data,meta}
|
mkdir -p "$DIR"/{data,meta}
|
||||||
ensure_docker_dir_ownership "$DIR"
|
ensure_docker_dir_ownership "$DIR"
|
||||||
@@ -232,6 +237,11 @@ COMPOSE
|
|||||||
|
|
||||||
cat > .env << ENV
|
cat > .env << ENV
|
||||||
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
|
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
|
ENV
|
||||||
chmod 600 .env
|
chmod 600 .env
|
||||||
|
|
||||||
@@ -277,8 +287,15 @@ ENV
|
|||||||
local _key_out
|
local _key_out
|
||||||
_key_out="$(docker exec garage /garage key create "$KEY_NAME" 2>&1)"
|
_key_out="$(docker exec garage /garage key create "$KEY_NAME" 2>&1)"
|
||||||
local ACCESS_KEY_ID ACCESS_KEY_SECRET
|
local ACCESS_KEY_ID ACCESS_KEY_SECRET
|
||||||
ACCESS_KEY_ID="$(echo "$_key_out" | awk -F': ' '/^Key ID:/{print $2}')"
|
# Garage's real CLI output pads labels with extra spaces for column
|
||||||
ACCESS_KEY_SECRET="$(echo "$_key_out" | awk -F': ' '/^Secret key:/{print $2}')"
|
# 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
|
if [ -z "$ACCESS_KEY_ID" ] || [ -z "$ACCESS_KEY_SECRET" ]; then
|
||||||
log_error "Couldn't parse the access key from 'garage key create' output:"
|
log_error "Couldn't parse the access key from 'garage key create' output:"
|
||||||
|
|||||||
Reference in New Issue
Block a user