512 lines
18 KiB
Bash
Executable File
512 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
CONFIG_DIR="${SCRIPT_DIR}/Config"
|
|
GLOBAL_ENV_FILE_DEFAULT="${CONFIG_DIR}/global.env"
|
|
TARGETS_DIR_DEFAULT="${CONFIG_DIR}/Targets"
|
|
|
|
GLOBAL_ENV_FILE="${GLOBAL_ENV_FILE:-$GLOBAL_ENV_FILE_DEFAULT}"
|
|
TARGETS_DIR="${TARGETS_DIR:-$TARGETS_DIR_DEFAULT}"
|
|
|
|
TARGET_NAME="${TARGET_NAME:-}"
|
|
CLI_TARGET=""
|
|
JSON_ONLY="${JSON_ONLY:-no}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--global-env-file)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --global-env-file" >&2; exit 1; }
|
|
GLOBAL_ENV_FILE="$2"
|
|
shift 2
|
|
;;
|
|
--targets-dir)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --targets-dir" >&2; exit 1; }
|
|
TARGETS_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--target)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --target" >&2; exit 1; }
|
|
CLI_TARGET="$2"
|
|
shift 2
|
|
;;
|
|
--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_stdout() {
|
|
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
|
|
}
|
|
|
|
log() {
|
|
print_stdout "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
|
}
|
|
|
|
fail() {
|
|
local msg="$1"
|
|
if [[ "$JSON_ONLY" == "yes" ]]; then
|
|
printf '{"status":%s,"message":%s}\n' \
|
|
"$(json_escape "error")" \
|
|
"$(json_escape "$msg")"
|
|
else
|
|
echo "ERROR: $msg" >&2
|
|
fi
|
|
exit 1
|
|
}
|
|
|
|
success() {
|
|
local msg="$1"
|
|
if [[ "$JSON_ONLY" == "yes" ]]; then
|
|
printf '{"status":%s,"message":%s}\n' \
|
|
"$(json_escape "success")" \
|
|
"$(json_escape "$msg")"
|
|
else
|
|
log "$msg"
|
|
fi
|
|
}
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $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
|
|
}
|
|
|
|
shell_quote() {
|
|
printf "%q" "$1"
|
|
}
|
|
|
|
cleanup() {
|
|
rm -f "${TMP_ENV_FILE:-}"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
copy_file_to_remote_via_ssh() {
|
|
local local_file="$1"
|
|
local remote_final_path="$2"
|
|
local remote_mode="$3"
|
|
local remote_parent
|
|
local remote_tmp
|
|
|
|
[[ -f "$local_file" ]] || fail "fichier source introuvable : $local_file"
|
|
[[ -r "$local_file" ]] || fail "fichier source non lisible : $local_file"
|
|
|
|
remote_parent="$(dirname "$remote_final_path")"
|
|
remote_tmp="/tmp/bootstrap_copy.$$.$RANDOM.tmp"
|
|
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "
|
|
set -euo pipefail
|
|
mkdir -p $(shell_quote "$remote_parent")
|
|
test -d $(shell_quote "$remote_parent")
|
|
test -w $(shell_quote "$remote_parent")
|
|
" >/dev/null 2>&1 || fail "dossier distant absent ou non inscriptible : $remote_parent"
|
|
|
|
cat "$local_file" | ssh "${SSH_OPTS[@]}" "$REMOTE" "
|
|
set -euo pipefail
|
|
cat > $(shell_quote "$remote_tmp")
|
|
" >/dev/null 2>&1 || fail "échec de copie distante via SSH vers ${remote_tmp}"
|
|
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "
|
|
set -euo pipefail
|
|
install -m $(shell_quote "$remote_mode") $(shell_quote "$remote_tmp") $(shell_quote "$remote_final_path")
|
|
rm -f $(shell_quote "$remote_tmp")
|
|
" >/dev/null 2>&1 || fail "échec d'installation distante : $remote_final_path"
|
|
}
|
|
|
|
TARGET_NAME="${CLI_TARGET:-${TARGET_NAME:-}}"
|
|
[[ -n "$TARGET_NAME" ]] || fail "target manquante"
|
|
|
|
TARGET_ENV_SOURCE="${TARGETS_DIR}/${TARGET_NAME}.env"
|
|
|
|
[[ -f "$GLOBAL_ENV_FILE" ]] || fail "fichier global introuvable : $GLOBAL_ENV_FILE"
|
|
[[ -f "$TARGET_ENV_SOURCE" ]] || fail "fichier cible introuvable : $TARGET_ENV_SOURCE"
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$GLOBAL_ENV_FILE"
|
|
# shellcheck disable=SC1090
|
|
source "$TARGET_ENV_SOURCE"
|
|
set +a
|
|
|
|
BOOTSTRAP_HOST="${TARGET_HOST:-}"
|
|
BOOTSTRAP_PORT="${TARGET_PORT:-22}"
|
|
BOOTSTRAP_USER="${TARGET_BOOTSTRAP_USER:-}"
|
|
BOOTSTRAP_SSH_KEY="${TARGET_BOOTSTRAP_SSH_KEY:-}"
|
|
|
|
TARGET_REPO_URL="${TARGET_REPO_URL:-${GLOBAL_REPO_URL:-}}"
|
|
TARGET_REPO_BRANCH="${TARGET_REPO_BRANCH:-${GLOBAL_REPO_BRANCH:-}}"
|
|
TARGET_REPO_DIR="${TARGET_REPO_DIR:-}"
|
|
TARGET_ENV_FILE_PATH="${TARGET_ENV_FILE:-}"
|
|
|
|
TARGET_ENV_NAME_VALUE="${TARGET_ENV_NAME:-}"
|
|
TARGET_PGHOST_VALUE="${TARGET_PGHOST:-${GLOBAL_PGHOST:-}}"
|
|
TARGET_PGPORT_VALUE="${TARGET_PGPORT:-${GLOBAL_PGPORT:-}}"
|
|
TARGET_PGUSER_VALUE="${TARGET_PGUSER:-}"
|
|
TARGET_PGPASSWORD_VALUE="${TARGET_PGPASSWORD:-}"
|
|
TARGET_DBS_VALUE="${TARGET_DBS:-}"
|
|
|
|
TARGET_BACKUP_REMOTE_USER_VALUE="${TARGET_BACKUP_REMOTE_USER:-${GLOBAL_BACKUP_REMOTE_USER:-}}"
|
|
TARGET_BACKUP_REMOTE_HOST_VALUE="${TARGET_BACKUP_REMOTE_HOST:-${GLOBAL_BACKUP_REMOTE_HOST:-}}"
|
|
TARGET_BACKUP_REMOTE_SSH_PORT_VALUE="${TARGET_BACKUP_REMOTE_SSH_PORT:-${GLOBAL_BACKUP_REMOTE_PORT:-22}}"
|
|
GLOBAL_BACKUP_REMOTE_BASE_DIR_VALUE="${GLOBAL_BACKUP_REMOTE_BASE_DIR:-}"
|
|
TARGET_BACKUP_SUBDIR_VALUE="${TARGET_BACKUP_SUBDIR:-}"
|
|
TARGET_BACKUP_LOG_DIR_VALUE="${TARGET_BACKUP_LOG_DIR:-}"
|
|
|
|
TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE="${TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY:-${GLOBAL_BACKUP_SSH_PRIVATE_KEY:-}}"
|
|
TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE="${TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY:-${GLOBAL_BACKUP_SSH_PUBLIC_KEY:-}}"
|
|
TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE="${TARGET_BACKUP_KNOWN_HOSTS_STRICT:-${GLOBAL_BACKUP_KNOWN_HOSTS_STRICT:-yes}}"
|
|
|
|
TARGET_LOCAL_RESTORE_BASE_DIR_VALUE="${TARGET_LOCAL_RESTORE_BASE_DIR:-${TARGET_REPO_DIR}/restore_tmp}"
|
|
TARGET_REMOTE_ROLES_DIR_NAME_VALUE="${TARGET_REMOTE_ROLES_DIR_NAME:-${GLOBAL_REMOTE_ROLES_DIR_NAME:-user}}"
|
|
TARGET_SSH_KEY_VALUE="${TARGET_SSH_KEY:-/home/${BOOTSTRAP_USER}/.ssh/id_ed25519_backup_readonly}"
|
|
TARGET_AUTO_INSTALL_POSTGRES_VALUE="${TARGET_AUTO_INSTALL_POSTGRES:-${GLOBAL_AUTO_INSTALL_POSTGRES:-yes}}"
|
|
TARGET_AUTO_CREATE_PGUSER_VALUE="${TARGET_AUTO_CREATE_PGUSER:-${GLOBAL_AUTO_CREATE_PGUSER:-yes}}"
|
|
TARGET_PGUSER_SUPERUSER_VALUE="${TARGET_PGUSER_SUPERUSER:-${GLOBAL_PGUSER_SUPERUSER:-no}}"
|
|
TARGET_AUTO_CONFIGURE_SUDOERS_VALUE="${TARGET_AUTO_CONFIGURE_SUDOERS:-${GLOBAL_AUTO_CONFIGURE_SUDOERS:-no}}"
|
|
TARGET_EXCLUDED_RESTORE_ROLES_VALUE="${TARGET_EXCLUDED_RESTORE_ROLES:-${GLOBAL_EXCLUDED_RESTORE_ROLES:-postgres}}"
|
|
|
|
TARGET_RUNTIME_USER_VALUE="${TARGET_RUNTIME_USER:-$BOOTSTRAP_USER}"
|
|
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO_VALUE="${TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO:-${GLOBAL_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO:-yes}}"
|
|
|
|
[[ -n "$BOOTSTRAP_HOST" ]] || fail "TARGET_HOST manquante"
|
|
[[ "$BOOTSTRAP_PORT" =~ ^[0-9]+$ ]] || fail "TARGET_PORT invalide"
|
|
[[ -n "$BOOTSTRAP_USER" ]] || fail "TARGET_BOOTSTRAP_USER manquante"
|
|
[[ -n "$BOOTSTRAP_SSH_KEY" ]] || fail "TARGET_BOOTSTRAP_SSH_KEY manquante"
|
|
[[ -f "$BOOTSTRAP_SSH_KEY" ]] || fail "clé bootstrap introuvable : $BOOTSTRAP_SSH_KEY"
|
|
[[ -r "$BOOTSTRAP_SSH_KEY" ]] || fail "clé bootstrap non lisible : $BOOTSTRAP_SSH_KEY"
|
|
|
|
[[ -n "$TARGET_REPO_URL" ]] || fail "GLOBAL_REPO_URL/TARGET_REPO_URL manquant"
|
|
[[ -n "$TARGET_REPO_BRANCH" ]] || fail "GLOBAL_REPO_BRANCH/TARGET_REPO_BRANCH manquant"
|
|
[[ -n "$TARGET_REPO_DIR" ]] || fail "TARGET_REPO_DIR manquante"
|
|
[[ -n "$TARGET_ENV_FILE_PATH" ]] || fail "TARGET_ENV_FILE manquante"
|
|
|
|
[[ -n "$TARGET_ENV_NAME_VALUE" ]] || fail "TARGET_ENV_NAME manquante"
|
|
[[ -n "$TARGET_PGHOST_VALUE" ]] || fail "TARGET_PGHOST/GLOBAL_PGHOST manquant"
|
|
[[ -n "$TARGET_PGPORT_VALUE" ]] || fail "TARGET_PGPORT/GLOBAL_PGPORT manquant"
|
|
[[ -n "$TARGET_PGUSER_VALUE" ]] || fail "TARGET_PGUSER manquante"
|
|
[[ -n "$TARGET_PGPASSWORD_VALUE" ]] || fail "TARGET_PGPASSWORD manquante"
|
|
[[ -n "$TARGET_DBS_VALUE" ]] || fail "TARGET_DBS manquante"
|
|
|
|
[[ -n "$TARGET_BACKUP_REMOTE_USER_VALUE" ]] || fail "GLOBAL_BACKUP_REMOTE_USER/TARGET_BACKUP_REMOTE_USER manquant"
|
|
[[ -n "$TARGET_BACKUP_REMOTE_HOST_VALUE" ]] || fail "GLOBAL_BACKUP_REMOTE_HOST/TARGET_BACKUP_REMOTE_HOST manquant"
|
|
[[ -n "$GLOBAL_BACKUP_REMOTE_BASE_DIR_VALUE" ]] || fail "GLOBAL_BACKUP_REMOTE_BASE_DIR manquant"
|
|
[[ -n "$TARGET_BACKUP_SUBDIR_VALUE" ]] || fail "TARGET_BACKUP_SUBDIR manquante"
|
|
[[ -n "$TARGET_BACKUP_LOG_DIR_VALUE" ]] || fail "TARGET_BACKUP_LOG_DIR manquante"
|
|
TARGET_BACKUP_REMOTE_DIR_VALUE="${GLOBAL_BACKUP_REMOTE_BASE_DIR_VALUE%/}/${TARGET_BACKUP_SUBDIR_VALUE}"
|
|
|
|
[[ -n "$TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE" ]] || fail "GLOBAL_BACKUP_SSH_PRIVATE_KEY/TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY manquant"
|
|
[[ -f "$TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE" ]] || fail "clé privée backup introuvable : $TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE"
|
|
[[ -r "$TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE" ]] || fail "clé privée backup non lisible : $TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE"
|
|
|
|
if [[ -n "$TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE" ]]; then
|
|
[[ -f "$TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE" ]] || fail "clé publique backup introuvable : $TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE"
|
|
[[ -r "$TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE" ]] || fail "clé publique backup non lisible : $TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE"
|
|
fi
|
|
|
|
[[ "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE" =~ ^[0-9]+$ ]] || fail "port backup invalide"
|
|
to_bool_yes_no "$TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE" >/dev/null || fail "TARGET_BACKUP_KNOWN_HOSTS_STRICT invalide"
|
|
to_bool_yes_no "$TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO_VALUE" >/dev/null || fail "TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO invalide"
|
|
|
|
ALLOW_PASSWORDLESS_SUDO="$(to_bool_yes_no "$TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO_VALUE")"
|
|
|
|
require_cmd ssh
|
|
require_cmd python3
|
|
|
|
SSH_OPTS=(
|
|
-i "$BOOTSTRAP_SSH_KEY"
|
|
-p "$BOOTSTRAP_PORT"
|
|
-o IdentitiesOnly=yes
|
|
-o BatchMode=yes
|
|
-o StrictHostKeyChecking=accept-new
|
|
-o ConnectTimeout=8
|
|
)
|
|
|
|
REMOTE="${BOOTSTRAP_USER}@${BOOTSTRAP_HOST}"
|
|
|
|
log "Test de connexion SSH bootstrap vers ${REMOTE}:${BOOTSTRAP_PORT}"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "exit 0" >/dev/null 2>&1 \
|
|
|| fail "connexion SSH bootstrap impossible vers ${REMOTE}"
|
|
|
|
REMOTE_SETUP_CMD="
|
|
set -euo pipefail
|
|
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
run_root() {
|
|
if [ \"\$(id -u)\" -eq 0 ]; then
|
|
\"\$@\"
|
|
return 0
|
|
fi
|
|
|
|
if command -v sudo >/dev/null 2>&1; then
|
|
sudo -n \"\$@\" || {
|
|
echo 'sudo -n indisponible pour le bootstrap' >&2
|
|
exit 1
|
|
}
|
|
return 0
|
|
fi
|
|
|
|
echo 'ni root ni sudo disponible pour le bootstrap' >&2
|
|
exit 1
|
|
}
|
|
|
|
if ! command -v apt-get >/dev/null 2>&1; then
|
|
echo 'apt-get absent sur la cible' >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_root apt-get update
|
|
run_root apt-get install -y bash git python3 sudo curl openssh-client ca-certificates postgresql-client
|
|
|
|
mkdir -p $(shell_quote "$(dirname "$TARGET_REPO_DIR")")
|
|
mkdir -p $(shell_quote "$(dirname "$TARGET_ENV_FILE_PATH")")
|
|
mkdir -p $(shell_quote "$TARGET_BACKUP_LOG_DIR_VALUE")
|
|
mkdir -p $(shell_quote "$TARGET_LOCAL_RESTORE_BASE_DIR_VALUE")
|
|
mkdir -p $(shell_quote "$(dirname "$TARGET_SSH_KEY_VALUE")")
|
|
|
|
chmod 700 $(shell_quote "$(dirname "$TARGET_SSH_KEY_VALUE")") || true
|
|
touch $(shell_quote "$(dirname "$TARGET_SSH_KEY_VALUE")/known_hosts")
|
|
chmod 644 $(shell_quote "$(dirname "$TARGET_SSH_KEY_VALUE")/known_hosts") || true
|
|
"
|
|
|
|
log "Installation du socle minimal sur la cible"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SETUP_CMD" \
|
|
|| fail "échec de préparation système distante"
|
|
|
|
TMP_ENV_FILE="$(mktemp)"
|
|
|
|
cat >"$TMP_ENV_FILE" <<EOF
|
|
ENV_NAME=$(printf '%s\n' "$TARGET_ENV_NAME_VALUE")
|
|
PGHOST=$(printf '%s\n' "$TARGET_PGHOST_VALUE")
|
|
PGPORT=$(printf '%s\n' "$TARGET_PGPORT_VALUE")
|
|
PGUSER=$(printf '%s\n' "$TARGET_PGUSER_VALUE")
|
|
PGPASSWORD=$(printf '%s\n' "$TARGET_PGPASSWORD_VALUE")
|
|
DBS=$(printf '%s\n' "$TARGET_DBS_VALUE")
|
|
|
|
BACKUP_REMOTE_USER=$(printf '%s\n' "$TARGET_BACKUP_REMOTE_USER_VALUE")
|
|
BACKUP_REMOTE_HOST=$(printf '%s\n' "$TARGET_BACKUP_REMOTE_HOST_VALUE")
|
|
BACKUP_REMOTE_DIR=$(printf '%s\n' "$TARGET_BACKUP_REMOTE_DIR_VALUE")
|
|
BACKUP_REMOTE_SSH_PORT=$(printf '%s\n' "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE")
|
|
|
|
BACKUP_LOG_DIR=$(printf '%s\n' "$TARGET_BACKUP_LOG_DIR_VALUE")
|
|
LOCAL_RESTORE_BASE_DIR=$(printf '%s\n' "$TARGET_LOCAL_RESTORE_BASE_DIR_VALUE")
|
|
REMOTE_ROLES_DIR_NAME=$(printf '%s\n' "$TARGET_REMOTE_ROLES_DIR_NAME_VALUE")
|
|
SSH_KEY=$(printf '%s\n' "$TARGET_SSH_KEY_VALUE")
|
|
|
|
AUTO_INSTALL_POSTGRES=$(printf '%s\n' "$TARGET_AUTO_INSTALL_POSTGRES_VALUE")
|
|
AUTO_CREATE_PGUSER=$(printf '%s\n' "$TARGET_AUTO_CREATE_PGUSER_VALUE")
|
|
PGUSER_SUPERUSER=$(printf '%s\n' "$TARGET_PGUSER_SUPERUSER_VALUE")
|
|
AUTO_CONFIGURE_SUDOERS=$(printf '%s\n' "$TARGET_AUTO_CONFIGURE_SUDOERS_VALUE")
|
|
EXCLUDED_RESTORE_ROLES=$(printf '%s\n' "$TARGET_EXCLUDED_RESTORE_ROLES_VALUE")
|
|
EOF
|
|
|
|
log "Copie du .env cible"
|
|
copy_file_to_remote_via_ssh "$TMP_ENV_FILE" "$TARGET_ENV_FILE_PATH" "600"
|
|
|
|
REMOTE_SSH_DIR="$(dirname "$TARGET_SSH_KEY_VALUE")"
|
|
REMOTE_KNOWN_HOSTS="${REMOTE_SSH_DIR}/known_hosts"
|
|
|
|
log "Copie de la clé privée backup sur la cible"
|
|
copy_file_to_remote_via_ssh "$TARGET_BACKUP_SOURCE_SSH_PRIVATE_KEY_VALUE" "$TARGET_SSH_KEY_VALUE" "600"
|
|
|
|
if [[ -n "$TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE" ]]; then
|
|
log "Copie de la clé publique backup sur la cible"
|
|
copy_file_to_remote_via_ssh "$TARGET_BACKUP_SOURCE_SSH_PUBLIC_KEY_VALUE" "${TARGET_SSH_KEY_VALUE}.pub" "644"
|
|
fi
|
|
|
|
REMOTE_SSH_PERMS_CMD="
|
|
set -euo pipefail
|
|
chmod 700 $(shell_quote "$REMOTE_SSH_DIR")
|
|
chmod 600 $(shell_quote "$TARGET_SSH_KEY_VALUE")
|
|
if [[ -f $(shell_quote "${TARGET_SSH_KEY_VALUE}.pub") ]]; then
|
|
chmod 644 $(shell_quote "${TARGET_SSH_KEY_VALUE}.pub")
|
|
fi
|
|
touch $(shell_quote "$REMOTE_KNOWN_HOSTS")
|
|
chmod 644 $(shell_quote "$REMOTE_KNOWN_HOSTS")
|
|
"
|
|
|
|
log "Correction des permissions SSH côté cible"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SSH_PERMS_CMD" \
|
|
|| fail "échec de correction des permissions SSH sur la cible"
|
|
|
|
REMOTE_KNOWN_HOSTS_CMD="
|
|
set -euo pipefail
|
|
|
|
if ! command -v ssh-keyscan >/dev/null 2>&1; then
|
|
echo 'ssh-keyscan absent sur la cible' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! ssh-keygen -F $(shell_quote "$TARGET_BACKUP_REMOTE_HOST_VALUE") -f $(shell_quote "$REMOTE_KNOWN_HOSTS") >/dev/null 2>&1; then
|
|
ssh-keyscan -p $(shell_quote "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE") -H $(shell_quote "$TARGET_BACKUP_REMOTE_HOST_VALUE") >> $(shell_quote "$REMOTE_KNOWN_HOSTS") 2>/dev/null
|
|
fi
|
|
"
|
|
|
|
log "Ajout du serveur de backup dans known_hosts côté cible"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_KNOWN_HOSTS_CMD" \
|
|
|| fail "échec de préparation known_hosts sur la cible"
|
|
|
|
STRICT_OPTION="yes"
|
|
case "${TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE,,}" in
|
|
yes|y|oui|o|true|1) STRICT_OPTION="yes" ;;
|
|
no|n|non|false|0) STRICT_OPTION="no" ;;
|
|
*) fail "TARGET_BACKUP_KNOWN_HOSTS_STRICT invalide" ;;
|
|
esac
|
|
|
|
REMOTE_BACKUP_TEST_CMD="
|
|
set -euo pipefail
|
|
|
|
ssh \
|
|
-i $(shell_quote "$TARGET_SSH_KEY_VALUE") \
|
|
-p $(shell_quote "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE") \
|
|
-o IdentitiesOnly=yes \
|
|
-o BatchMode=yes \
|
|
-o ConnectTimeout=8 \
|
|
-o StrictHostKeyChecking=$(shell_quote "$STRICT_OPTION") \
|
|
$(shell_quote "${TARGET_BACKUP_REMOTE_USER_VALUE}@${TARGET_BACKUP_REMOTE_HOST_VALUE}") \
|
|
test -d $(shell_quote "$TARGET_BACKUP_REMOTE_DIR_VALUE")
|
|
"
|
|
|
|
log "Test de la connexion SSH cible -> backup"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_BACKUP_TEST_CMD" \
|
|
|| fail "la cible ne peut pas accéder au serveur de backup avec la clé fournie"
|
|
|
|
if [[ "$ALLOW_PASSWORDLESS_SUDO" == "yes" ]]; then
|
|
REMOTE_SUDOERS_CMD="
|
|
set -euo pipefail
|
|
|
|
run_root() {
|
|
if [ \"\$(id -u)\" -eq 0 ]; then
|
|
\"\$@\"
|
|
return 0
|
|
fi
|
|
|
|
if command -v sudo >/dev/null 2>&1; then
|
|
sudo -n \"\$@\" || {
|
|
echo 'sudo -n indisponible pour installer sudoers' >&2
|
|
exit 1
|
|
}
|
|
return 0
|
|
fi
|
|
|
|
echo 'ni root ni sudo disponible pour sudoers' >&2
|
|
exit 1
|
|
}
|
|
|
|
if ! command -v visudo >/dev/null 2>&1; then
|
|
run_root apt-get update
|
|
run_root apt-get install -y sudo
|
|
fi
|
|
|
|
TMP_SUDOERS_FILE=\$(mktemp)
|
|
cat >\"\$TMP_SUDOERS_FILE\" <<EOF
|
|
${TARGET_RUNTIME_USER_VALUE} ALL=(root) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /usr/bin/systemctl
|
|
${TARGET_RUNTIME_USER_VALUE} ALL=(postgres) NOPASSWD: /usr/bin/psql
|
|
EOF
|
|
|
|
chmod 440 \"\$TMP_SUDOERS_FILE\"
|
|
|
|
visudo -cf \"\$TMP_SUDOERS_FILE\" >/dev/null 2>&1 || {
|
|
rm -f \"\$TMP_SUDOERS_FILE\"
|
|
echo 'fichier sudoers généré invalide' >&2
|
|
exit 1
|
|
}
|
|
|
|
run_root install -m 440 \"\$TMP_SUDOERS_FILE\" /etc/sudoers.d/rebuild-bdd-${TARGET_RUNTIME_USER_VALUE}
|
|
rm -f \"\$TMP_SUDOERS_FILE\"
|
|
"
|
|
|
|
log "Installation du sudoers non interactif minimal"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SUDOERS_CMD" \
|
|
|| fail "échec d'installation du sudoers non interactif"
|
|
else
|
|
log "Installation du sudoers non interactif désactivée."
|
|
fi
|
|
|
|
REMOTE_REPO_CMD="
|
|
set -euo pipefail
|
|
|
|
if [[ ! -d $(shell_quote "${TARGET_REPO_DIR}/.git") ]]; then
|
|
rm -rf $(shell_quote "$TARGET_REPO_DIR")
|
|
git clone --branch $(shell_quote "$TARGET_REPO_BRANCH") --single-branch $(shell_quote "$TARGET_REPO_URL") $(shell_quote "$TARGET_REPO_DIR")
|
|
else
|
|
git -C $(shell_quote "$TARGET_REPO_DIR") fetch --prune origin
|
|
git -C $(shell_quote "$TARGET_REPO_DIR") checkout -f $(shell_quote "$TARGET_REPO_BRANCH")
|
|
git -C $(shell_quote "$TARGET_REPO_DIR") reset --hard origin/$(shell_quote "$TARGET_REPO_BRANCH")
|
|
fi
|
|
|
|
chmod 700 $(shell_quote "$TARGET_REPO_DIR/run-rebuild-bdd.sh") 2>/dev/null || true
|
|
chmod 700 $(shell_quote "$TARGET_REPO_DIR/rebuild-bdd-core.sh") 2>/dev/null || true
|
|
chmod 700 $(shell_quote "$TARGET_REPO_DIR/Checkup/check-postgresql.sh") 2>/dev/null || true
|
|
chmod 700 $(shell_quote "$TARGET_REPO_DIR/Checkup/check-target-readiness.sh") 2>/dev/null || true
|
|
"
|
|
|
|
log "Clone / mise à jour du dépôt distant"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_REPO_CMD" \
|
|
|| fail "échec de synchronisation du dépôt sur la cible"
|
|
|
|
REMOTE_VALIDATE_SUDO_ROOT_CMD="
|
|
set -euo pipefail
|
|
command -v sudo >/dev/null 2>&1 || {
|
|
echo 'sudo absent sur la cible' >&2
|
|
exit 1
|
|
}
|
|
sudo -n true >/dev/null 2>&1 || {
|
|
echo 'sudo -n indisponible' >&2
|
|
exit 1
|
|
}
|
|
"
|
|
|
|
log "Validation initiale de sudo -n"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_VALIDATE_SUDO_ROOT_CMD" \
|
|
|| fail "sudo -n invalide sur la cible"
|
|
|
|
REMOTE_RUN_CHECK_PG_CMD="
|
|
set -euo pipefail
|
|
|
|
CHECK_SCRIPT=$(shell_quote "${TARGET_REPO_DIR}/Checkup/check-postgresql.sh")
|
|
ENV_FILE=$(shell_quote "$TARGET_ENV_FILE_PATH")
|
|
|
|
[[ -x \"\$CHECK_SCRIPT\" ]] || chmod 700 \"\$CHECK_SCRIPT\"
|
|
|
|
\"\$CHECK_SCRIPT\" --env-file \"\$ENV_FILE\" --non-interactive
|
|
"
|
|
|
|
log "Préparation PostgreSQL via check-postgresql.sh"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_RUN_CHECK_PG_CMD" \
|
|
|| fail "échec de préparation PostgreSQL pendant le bootstrap"
|
|
|
|
REMOTE_VALIDATE_SUDO_POSTGRES_CMD="
|
|
set -euo pipefail
|
|
sudo -n -u postgres true >/dev/null 2>&1 || {
|
|
echo 'sudo -n -u postgres indisponible après préparation PostgreSQL' >&2
|
|
exit 1
|
|
}
|
|
"
|
|
|
|
log "Validation finale de sudo -n -u postgres"
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_VALIDATE_SUDO_POSTGRES_CMD" \
|
|
|| fail "sudo -n -u postgres invalide sur la cible"
|
|
|
|
success "bootstrap initial terminé pour ${TARGET_NAME}" |