276 lines
7.6 KiB
Bash
Executable File
276 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DEFAULT_ENV_FILE="${SCRIPT_DIR}/.env"
|
|
|
|
ENV_FILE="${ENV_FILE:-$DEFAULT_ENV_FILE}"
|
|
|
|
CLI_TARGET=""
|
|
CLI_DB=""
|
|
CLI_OVERWRITE=""
|
|
CLI_RESTORE_ROLES=""
|
|
CLI_REQUEST_ID=""
|
|
NON_INTERACTIVE="${NON_INTERACTIVE:-no}"
|
|
JSON_ONLY="${JSON_ONLY:-no}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--env-file)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --env-file" >&2; exit 1; }
|
|
ENV_FILE="$2"
|
|
shift 2
|
|
;;
|
|
--target)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --target" >&2; exit 1; }
|
|
CLI_TARGET="$2"
|
|
shift 2
|
|
;;
|
|
--db)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --db" >&2; exit 1; }
|
|
CLI_DB="$2"
|
|
shift 2
|
|
;;
|
|
--overwrite)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --overwrite" >&2; exit 1; }
|
|
CLI_OVERWRITE="$2"
|
|
shift 2
|
|
;;
|
|
--restore-roles)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --restore-roles" >&2; exit 1; }
|
|
CLI_RESTORE_ROLES="$2"
|
|
shift 2
|
|
;;
|
|
--request-id)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --request-id" >&2; exit 1; }
|
|
CLI_REQUEST_ID="$2"
|
|
shift 2
|
|
;;
|
|
--non-interactive)
|
|
NON_INTERACTIVE="yes"
|
|
shift
|
|
;;
|
|
--json-only)
|
|
JSON_ONLY="yes"
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Argument inconnu : $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
json_escape() {
|
|
python3 - <<'PY' "$1"
|
|
import json, sys
|
|
print(json.dumps(sys.argv[1]))
|
|
PY
|
|
}
|
|
|
|
print_json_and_exit() {
|
|
local status="$1"
|
|
local message="$2"
|
|
local exit_code="$3"
|
|
|
|
printf '{'
|
|
printf '"status":%s,' "$(json_escape "$status")"
|
|
printf '"message":%s,' "$(json_escape "$message")"
|
|
printf '"target":%s,' "$(json_escape "${TARGET:-}")"
|
|
printf '"request_id":%s' "$(json_escape "${REQUEST_ID:-}")"
|
|
printf '}\n'
|
|
exit "$exit_code"
|
|
}
|
|
|
|
fail() {
|
|
print_json_and_exit "error" "$*" 1
|
|
}
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
to_bool_yes_no() {
|
|
local v="${1:-}"
|
|
v="${v,,}"
|
|
case "$v" in
|
|
yes|y|oui|o|true|1) echo "yes" ;;
|
|
no|n|non|false|0|"") echo "no" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
is_tty() {
|
|
[[ -t 0 && -t 1 ]]
|
|
}
|
|
|
|
print_stdout() {
|
|
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
|
|
}
|
|
|
|
sanitize_key() {
|
|
local s="${1:-}"
|
|
s="${s//[^a-zA-Z0-9_]/_}"
|
|
printf "%s" "$s"
|
|
}
|
|
|
|
get_target_var() {
|
|
local target="$1"
|
|
local key="$2"
|
|
local safe_target
|
|
safe_target="$(sanitize_key "$target")"
|
|
local var_name="TARGET_${key}_${safe_target}"
|
|
printf "%s" "${!var_name:-}"
|
|
}
|
|
|
|
shell_quote() {
|
|
printf "%q" "$1"
|
|
}
|
|
|
|
[[ -f "$ENV_FILE" ]] || {
|
|
echo '{"status":"error","message":"fichier .env IA introuvable"}'
|
|
exit 1
|
|
}
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
set +a
|
|
|
|
for cmd in bash ssh python3; do
|
|
require_cmd "$cmd" || fail "commande requise absente sur IA : $cmd"
|
|
done
|
|
|
|
TARGET="${CLI_TARGET:-${TARGET:-}}"
|
|
REQUESTED_DB="${CLI_DB:-${REQUESTED_DB:-}}"
|
|
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-}}"
|
|
ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}"
|
|
RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}"
|
|
|
|
ALLOW_OVERWRITE="$(to_bool_yes_no "$ALLOW_OVERWRITE_RAW")" || fail "ALLOW_OVERWRITE invalide"
|
|
RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide"
|
|
|
|
: "${TARGETS:?Variable TARGETS manquante dans le .env IA}"
|
|
|
|
read -r -a TARGETS_ARRAY <<< "$TARGETS"
|
|
[[ "${#TARGETS_ARRAY[@]}" -gt 0 ]] || fail "aucune cible définie dans TARGETS"
|
|
|
|
if [[ -z "$TARGET" ]]; then
|
|
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
|
|
fail "TARGET manquante en mode non interactif"
|
|
fi
|
|
|
|
if is_tty; then
|
|
print_stdout "Cibles disponibles :"
|
|
for i in "${!TARGETS_ARRAY[@]}"; do
|
|
print_stdout " $((i + 1))) ${TARGETS_ARRAY[$i]}"
|
|
done
|
|
echo
|
|
read -r -p "Sélectionnez le numéro de la cible : " TARGET_INDEX
|
|
[[ "$TARGET_INDEX" =~ ^[0-9]+$ ]] || fail "numéro de cible invalide"
|
|
(( TARGET_INDEX >= 1 && TARGET_INDEX <= ${#TARGETS_ARRAY[@]} )) || fail "numéro hors plage"
|
|
TARGET="${TARGETS_ARRAY[$((TARGET_INDEX - 1))]}"
|
|
else
|
|
fail "TARGET manquante et aucune interaction terminal disponible"
|
|
fi
|
|
fi
|
|
|
|
TARGET_ALLOWED="no"
|
|
for candidate in "${TARGETS_ARRAY[@]}"; do
|
|
if [[ "$candidate" == "$TARGET" ]]; then
|
|
TARGET_ALLOWED="yes"
|
|
break
|
|
fi
|
|
done
|
|
[[ "$TARGET_ALLOWED" == "yes" ]] || fail "cible refusée : non présente dans TARGETS"
|
|
|
|
TARGET_HOST="$(get_target_var "$TARGET" "HOST")"
|
|
TARGET_USER="$(get_target_var "$TARGET" "USER")"
|
|
TARGET_SSH_KEY="$(get_target_var "$TARGET" "SSH_KEY")"
|
|
TARGET_SSH_PORT="$(get_target_var "$TARGET" "SSH_PORT")"
|
|
TARGET_SSH_CONNECT_TIMEOUT="$(get_target_var "$TARGET" "SSH_CONNECT_TIMEOUT")"
|
|
|
|
TARGET_REPO_URL="$(get_target_var "$TARGET" "REPO_URL")"
|
|
TARGET_REPO_BRANCH="$(get_target_var "$TARGET" "REPO_BRANCH")"
|
|
TARGET_REPO_DIR="$(get_target_var "$TARGET" "REPO_DIR")"
|
|
TARGET_CORE_SCRIPT="$(get_target_var "$TARGET" "CORE_SCRIPT")"
|
|
TARGET_ENV_FILE="$(get_target_var "$TARGET" "ENV_FILE")"
|
|
|
|
TARGET_SSH_PORT="${TARGET_SSH_PORT:-22}"
|
|
TARGET_SSH_CONNECT_TIMEOUT="${TARGET_SSH_CONNECT_TIMEOUT:-8}"
|
|
TARGET_REPO_BRANCH="${TARGET_REPO_BRANCH:-main}"
|
|
|
|
[[ -n "$TARGET_HOST" ]] || fail "TARGET_HOST_${TARGET} manquante"
|
|
[[ -n "$TARGET_USER" ]] || fail "TARGET_USER_${TARGET} manquante"
|
|
[[ -n "$TARGET_SSH_KEY" ]] || fail "TARGET_SSH_KEY_${TARGET} manquante"
|
|
[[ -n "$TARGET_REPO_URL" ]] || fail "TARGET_REPO_URL_${TARGET} manquante"
|
|
[[ -n "$TARGET_REPO_DIR" ]] || fail "TARGET_REPO_DIR_${TARGET} manquante"
|
|
[[ -n "$TARGET_CORE_SCRIPT" ]] || fail "TARGET_CORE_SCRIPT_${TARGET} manquante"
|
|
[[ -n "$TARGET_ENV_FILE" ]] || fail "TARGET_ENV_FILE_${TARGET} manquante"
|
|
|
|
[[ -f "$TARGET_SSH_KEY" ]] || fail "clé SSH cible introuvable : $TARGET_SSH_KEY"
|
|
|
|
if [[ -z "$REQUEST_ID" ]]; then
|
|
REQUEST_ID="$(date '+%Y%m%d%H%M%S')_$$"
|
|
fi
|
|
|
|
if [[ -z "$REQUESTED_DB" ]]; then
|
|
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
|
|
fail "REQUESTED_DB manquante en mode non interactif"
|
|
fi
|
|
|
|
if is_tty; then
|
|
read -r -p "Nom exact de la base à restaurer : " REQUESTED_DB
|
|
[[ -n "$REQUESTED_DB" ]] || fail "nom de base vide"
|
|
else
|
|
fail "REQUESTED_DB manquante et aucune interaction terminal disponible"
|
|
fi
|
|
fi
|
|
|
|
[[ "$REQUESTED_DB" =~ ^[a-zA-Z0-9_]+$ ]] || fail "nom de base invalide"
|
|
|
|
SSH_OPTS=(
|
|
-i "$TARGET_SSH_KEY"
|
|
-p "$TARGET_SSH_PORT"
|
|
-o IdentitiesOnly=yes
|
|
-o BatchMode=yes
|
|
-o ConnectTimeout="$TARGET_SSH_CONNECT_TIMEOUT"
|
|
-o StrictHostKeyChecking=yes
|
|
)
|
|
|
|
REMOTE_BOOTSTRAP_CMD="
|
|
set -euo pipefail
|
|
|
|
REPO_DIR=$(shell_quote "$TARGET_REPO_DIR")
|
|
REPO_URL=$(shell_quote "$TARGET_REPO_URL")
|
|
REPO_BRANCH=$(shell_quote "$TARGET_REPO_BRANCH")
|
|
CORE_SCRIPT=$(shell_quote "$TARGET_CORE_SCRIPT")
|
|
|
|
command -v git >/dev/null 2>&1 || { echo '{\"status\":\"error\",\"message\":\"git absent sur la cible\"}'; exit 1; }
|
|
command -v bash >/dev/null 2>&1 || { echo '{\"status\":\"error\",\"message\":\"bash absent sur la cible\"}'; exit 1; }
|
|
|
|
mkdir -p \"\$(dirname \"\$REPO_DIR\")\"
|
|
|
|
if [[ ! -d \"\$REPO_DIR/.git\" ]]; then
|
|
rm -rf \"\$REPO_DIR\"
|
|
git clone --branch \"\$REPO_BRANCH\" --single-branch \"\$REPO_URL\" \"\$REPO_DIR\"
|
|
else
|
|
git -C \"\$REPO_DIR\" fetch --prune origin
|
|
git -C \"\$REPO_DIR\" checkout -f \"\$REPO_BRANCH\"
|
|
git -C \"\$REPO_DIR\" reset --hard \"origin/\$REPO_BRANCH\"
|
|
fi
|
|
|
|
[[ -f \"\$CORE_SCRIPT\" ]] || { echo '{\"status\":\"error\",\"message\":\"script core introuvable sur la cible\"}'; exit 1; }
|
|
chmod 700 \"\$CORE_SCRIPT\"
|
|
|
|
exec \"\$CORE_SCRIPT\" \
|
|
--env-file $(shell_quote "$TARGET_ENV_FILE") \
|
|
--db $(shell_quote "$REQUESTED_DB") \
|
|
--overwrite $(shell_quote "$ALLOW_OVERWRITE") \
|
|
--restore-roles $(shell_quote "$RESTORE_ROLES") \
|
|
--request-id $(shell_quote "$REQUEST_ID") \
|
|
--non-interactive \
|
|
--json-only
|
|
"
|
|
|
|
exec ssh "${SSH_OPTS[@]}" "${TARGET_USER}@${TARGET_HOST}" "$REMOTE_BOOTSTRAP_CMD" |