#!/bin/bash # ============================================================================= # WOOO AIOps - Configure host-local Backblaze B2 credentials for offsite backup # 2026-05-06 ogt + Codex: 提供不進 repo 的 offsite.env 設定 helper。 # # Secrets policy: # - Writes only to /backup/scripts/offsite.env by default. # - File mode is 0600. # - Never prints credential values. # - Prefer interactive prompt on 110; --write-from-env is for controlled ops. # ============================================================================= set -euo pipefail BACKUP_BASE="${BACKUP_BASE:-/backup}" OFFSITE_ENV_FILE="${BACKUP_OFFSITE_ENV_FILE:-${BACKUP_BASE}/scripts/offsite.env}" MODE="status" usage() { cat <<'USAGE' Usage: configure-offsite-b2.sh --status configure-offsite-b2.sh --interactive B2_ACCOUNT_ID=... B2_APPLICATION_KEY=... B2_BUCKET=... configure-offsite-b2.sh --write-from-env This writes /backup/scripts/offsite.env with mode 0600. Do not paste secrets into chat, repo files, LOGBOOK, Telegram, or Prometheus labels. USAGE } while [ "$#" -gt 0 ]; do case "$1" in --status) MODE="status" shift ;; --interactive) MODE="interactive" shift ;; --write-from-env) MODE="write-from-env" shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done configured() { local value="$1" [ -n "${value}" ] && [ "${value}" != "CHANGE_ME" ] && [ "${value}" != "REDACTED" ] } quote_shell() { printf "%q" "$1" } load_existing() { if [ -f "${OFFSITE_ENV_FILE}" ]; then # shellcheck disable=SC1090 source "${OFFSITE_ENV_FILE}" fi } show_status() { load_existing echo "OFFSITE_ENV_FILE=${OFFSITE_ENV_FILE}" if [ -f "${OFFSITE_ENV_FILE}" ]; then mode="$(stat -c '%a' "${OFFSITE_ENV_FILE}" 2>/dev/null || stat -f '%Lp' "${OFFSITE_ENV_FILE}" 2>/dev/null || echo unknown)" echo "OFFSITE_ENV_PRESENT=1" echo "OFFSITE_ENV_MODE=${mode}" else echo "OFFSITE_ENV_PRESENT=0" fi configured "${B2_ACCOUNT_ID:-}" && echo "B2_ACCOUNT_ID_CONFIGURED=1" || echo "B2_ACCOUNT_ID_CONFIGURED=0" configured "${B2_APPLICATION_KEY:-}" && echo "B2_APPLICATION_KEY_CONFIGURED=1" || echo "B2_APPLICATION_KEY_CONFIGURED=0" configured "${B2_BUCKET:-}" && echo "B2_BUCKET_CONFIGURED=1" || echo "B2_BUCKET_CONFIGURED=0" command -v rclone >/dev/null 2>&1 && echo "RCLONE_PRESENT=1" || echo "RCLONE_PRESENT=0" } validate_inputs() { if ! configured "${B2_ACCOUNT_ID:-}"; then echo "B2_ACCOUNT_ID is required" >&2 return 1 fi if ! configured "${B2_APPLICATION_KEY:-}"; then echo "B2_APPLICATION_KEY is required" >&2 return 1 fi if ! configured "${B2_BUCKET:-}"; then echo "B2_BUCKET is required" >&2 return 1 fi } write_env() { validate_inputs parent_dir="$(dirname "${OFFSITE_ENV_FILE}")" if [ ! -d "${parent_dir}" ]; then install -d -m 0750 "${parent_dir}" fi tmp="$(mktemp "${OFFSITE_ENV_FILE}.tmp.XXXXXX")" chmod 0600 "${tmp}" cat > "${tmp}" <&2 exit 2 fi load_existing read -r -p "B2_ACCOUNT_ID: " B2_ACCOUNT_ID read -r -s -p "B2_APPLICATION_KEY: " B2_APPLICATION_KEY printf '\n' read -r -p "B2_BUCKET [${B2_BUCKET:-wooo-aiops-backup}]: " bucket_input B2_BUCKET="${bucket_input:-${B2_BUCKET:-wooo-aiops-backup}}" read -r -p "RCLONE_BWLIMIT [${RCLONE_BWLIMIT:-8M}]: " bwlimit_input RCLONE_BWLIMIT="${bwlimit_input:-${RCLONE_BWLIMIT:-8M}}" write_env } case "${MODE}" in status) show_status ;; interactive) interactive_write ;; write-from-env) write_env ;; esac