feat : update web available rebuild bdd (WIP)

This commit is contained in:
AkiNoKure
2026-03-17 10:11:00 +01:00
parent 2971ef0ff9
commit 94537de551
5 changed files with 694 additions and 21 deletions

View File

@@ -59,6 +59,7 @@ set +a
AUTO_INSTALL_POSTGRES="${AUTO_INSTALL_POSTGRES:-yes}"
AUTO_CREATE_PGUSER="${AUTO_CREATE_PGUSER:-yes}"
PGUSER_SUPERUSER="${PGUSER_SUPERUSER:-no}"
POSTGRES_PACKAGE_LIST="${POSTGRES_PACKAGE_LIST:-postgresql postgresql-client postgresql-contrib}"
POSTGRES_SERVICE_NAME="${POSTGRES_SERVICE_NAME:-postgresql}"
SUDO_BIN="${SUDO_BIN:-sudo}"
@@ -69,51 +70,70 @@ if ! require_cmd "$SUDO_BIN"; then
fail "sudo absent sur la cible"
fi
if ! "$SUDO_BIN" -n true >/dev/null 2>&1; then
fail "sudo non interactif indisponible pour l'utilisateur courant"
fi
if ! "$SUDO_BIN" -n -u postgres true >/dev/null 2>&1; then
fail "sudo -n -u postgres indisponible"
fi
if [[ ! "$PGPORT" =~ ^[0-9]+$ ]]; then
fail "PGPORT invalide : $PGPORT"
fi
POSTGRES_INSTALLED="no"
if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb; then
[[ "${AUTO_INSTALL_POSTGRES,,}" == "yes" ]] || fail "PostgreSQL absent et AUTO_INSTALL_POSTGRES=no"
log "PostgreSQL absent : installation en cours..."
"$SUDO_BIN" apt update >/dev/null 2>&1 || fail "échec de apt update"
"$SUDO_BIN" apt install -y $POSTGRES_PACKAGE_LIST >/dev/null 2>&1 || fail "échec de l'installation PostgreSQL"
"$SUDO_BIN" -n apt update >/dev/null 2>&1 || fail "échec de apt update"
"$SUDO_BIN" -n apt install -y $POSTGRES_PACKAGE_LIST >/dev/null 2>&1 || fail "échec de l'installation PostgreSQL"
POSTGRES_INSTALLED="yes"
log "Installation PostgreSQL terminée."
else
log "PostgreSQL déjà installé."
fi
if ! "$SUDO_BIN" systemctl is-active --quiet "$POSTGRES_SERVICE_NAME"; then
if ! "$SUDO_BIN" -n systemctl is-active --quiet "$POSTGRES_SERVICE_NAME"; then
log "Démarrage du service PostgreSQL..."
"$SUDO_BIN" systemctl start "$POSTGRES_SERVICE_NAME" >/dev/null 2>&1 || fail "impossible de démarrer PostgreSQL"
"$SUDO_BIN" -n systemctl start "$POSTGRES_SERVICE_NAME" >/dev/null 2>&1 || fail "impossible de démarrer PostgreSQL"
else
log "Service PostgreSQL déjà actif."
fi
log "Vérification de la disponibilité de PostgreSQL..."
for _ in {1..20}; do
if "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
if "$SUDO_BIN" -n -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
log "PostgreSQL répond correctement."
break
fi
sleep 1
done
if ! "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
if ! "$SUDO_BIN" -n -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
fail "PostgreSQL ne répond pas correctement"
fi
if [[ "${AUTO_CREATE_PGUSER,,}" == "yes" ]]; then
ROLE_EXISTS="$(
"$SUDO_BIN" -u postgres psql -d postgres -tAc \
"$SUDO_BIN" -n -u postgres psql -d postgres -tAc \
"SELECT 1 FROM pg_roles WHERE rolname='${PGUSER//\'/\'\'}'" 2>/dev/null || true
)"
if [[ "$ROLE_EXISTS" != "1" ]]; then
log "Création du rôle PostgreSQL ${PGUSER}..."
"$SUDO_BIN" -u postgres psql -d postgres -c \
"CREATE ROLE \"${PGUSER}\" WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${PGPASSWORD//\'/\'\'}';" \
ROLE_ATTRIBUTES="LOGIN CREATEDB CREATEROLE"
if [[ "${PGUSER_SUPERUSER,,}" == "yes" ]]; then
ROLE_ATTRIBUTES="LOGIN SUPERUSER CREATEDB CREATEROLE"
fi
"$SUDO_BIN" -n -u postgres psql -d postgres -c \
"CREATE ROLE \"${PGUSER}\" WITH ${ROLE_ATTRIBUTES} PASSWORD '${PGPASSWORD//\'/\'\'}';" \
>/dev/null 2>&1 || fail "échec de création du rôle ${PGUSER}"
log "Rôle PostgreSQL ${PGUSER} créé."
else
log "Rôle PostgreSQL ${PGUSER} déjà présent."

View File

@@ -0,0 +1,338 @@
#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# check-target-readiness.sh
#
# Prépare la machine cible pour permettre l'exécution non interactive du
# script de rebuild depuis une interface web.
#
# Ce script :
# - charge et valide le .env ;
# - crée/corrige les dossiers et permissions locales ;
# - génère la clé SSH si absente ;
# - alimente known_hosts pour le serveur de backup ;
# - teste la connexion SSH non interactive vers le serveur de backup ;
# - vérifie/installe la config sudoers si autorisée ;
# - lance ensuite check-postgresql.sh.
###############################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
DEFAULT_ENV_FILE="${REPO_DIR}/.env"
ENV_FILE="${ENV_FILE:-$DEFAULT_ENV_FILE}"
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
;;
--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 '"request_id":%s,' "$(json_escape "${REQUEST_ID:-}")"
printf '"environment":%s,' "$(json_escape "${ENV_NAME:-}")"
printf '"log_file":%s' "$(json_escape "${LOG_FILE:-}")"
printf '}\n'
exit "$exit_code"
}
print_stdout() {
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
}
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" >>"$LOG_FILE"
print_stdout "$msg"
}
fail() {
log "ERROR: $*"
print_json_and_exit "error" "$*" 1
}
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
}
require_env_vars() {
local missing=()
local var
for var in \
ENV_NAME PGHOST PGPORT PGUSER PGPASSWORD DBS \
BACKUP_REMOTE_USER BACKUP_REMOTE_HOST BACKUP_REMOTE_DIR \
SSH_KEY BACKUP_LOG_DIR
do
[[ -n "${!var:-}" ]] || missing+=("$var")
done
if (( ${#missing[@]} > 0 )); then
fail "variables .env manquantes : ${missing[*]}"
fi
}
validate_env_values() {
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
[[ -n "$DBS" ]] || fail "DBS vide"
[[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || fail "PGUSER invalide"
[[ -n "$BACKUP_REMOTE_HOST" ]] || fail "BACKUP_REMOTE_HOST vide"
[[ -n "$BACKUP_REMOTE_USER" ]] || fail "BACKUP_REMOTE_USER vide"
[[ -n "$BACKUP_REMOTE_DIR" ]] || fail "BACKUP_REMOTE_DIR vide"
}
prepare_log_file() {
mkdir -p "$BACKUP_LOG_DIR" || {
echo '{"status":"error","message":"impossible de créer le dossier de logs"}'
exit 1
}
local ts safe_request_id
ts="$(date '+%Y-%m-%d_%H-%M-%S')"
safe_request_id="${REQUEST_ID:-manual}"
safe_request_id="${safe_request_id//[^a-zA-Z0-9_.-]/_}"
LOG_FILE="${BACKUP_LOG_DIR}/check_target_${ENV_NAME,,}_${safe_request_id}_${ts}.log"
touch "$LOG_FILE" || {
echo '{"status":"error","message":"impossible de créer le fichier de log"}'
exit 1
}
}
prepare_local_paths() {
local restore_base
restore_base="${LOCAL_RESTORE_BASE_DIR:-${REPO_DIR}/restore_tmp}"
mkdir -p "$BACKUP_LOG_DIR" || fail "création BACKUP_LOG_DIR impossible"
[[ -w "$BACKUP_LOG_DIR" ]] || fail "BACKUP_LOG_DIR non inscriptible"
mkdir -p "$restore_base" || fail "création LOCAL_RESTORE_BASE_DIR impossible"
[[ -w "$restore_base" ]] || fail "LOCAL_RESTORE_BASE_DIR non inscriptible"
log "Dossiers locaux prêts."
}
prepare_scripts_permissions() {
local core_script check_pg_script
core_script="${REPO_DIR}/rebuild-bdd-core.sh"
check_pg_script="${REPO_DIR}/Checkup/check-postgresql.sh"
[[ -f "$core_script" ]] || fail "script core introuvable : $core_script"
[[ -f "$check_pg_script" ]] || fail "script PostgreSQL introuvable : $check_pg_script"
chmod 700 "$core_script" || fail "chmod impossible sur $core_script"
chmod 700 "$check_pg_script" || fail "chmod impossible sur $check_pg_script"
log "Permissions scripts corrigées."
}
prepare_ssh_key() {
local key_dir
key_dir="$(dirname "$SSH_KEY")"
mkdir -p "$key_dir" || fail "impossible de créer le dossier SSH : $key_dir"
chmod 700 "$key_dir" || fail "impossible de chmod 700 sur $key_dir"
if [[ ! -f "$SSH_KEY" ]]; then
log "Clé SSH absente : génération de ${SSH_KEY}"
ssh-keygen -t ed25519 -N "" -f "$SSH_KEY" >/dev/null 2>&1 || \
fail "impossible de générer la clé SSH"
fi
chmod 600 "$SSH_KEY" || fail "impossible de chmod 600 sur la clé privée"
[[ -f "${SSH_KEY}.pub" ]] || fail "clé publique absente : ${SSH_KEY}.pub"
chmod 644 "${SSH_KEY}.pub" || fail "impossible de chmod 644 sur la clé publique"
log "Clé SSH prête."
}
prepare_known_hosts() {
local ssh_dir known_hosts ssh_port
ssh_dir="$(dirname "$SSH_KEY")"
known_hosts="${ssh_dir}/known_hosts"
ssh_port="${BACKUP_REMOTE_SSH_PORT:-22}"
touch "$known_hosts" || fail "impossible de créer known_hosts"
chmod 644 "$known_hosts" || fail "impossible de chmod 644 sur known_hosts"
if ! ssh-keygen -F "$BACKUP_REMOTE_HOST" -f "$known_hosts" >/dev/null 2>&1; then
log "Ajout de ${BACKUP_REMOTE_HOST}:${ssh_port} à known_hosts"
ssh-keyscan -p "$ssh_port" -H "$BACKUP_REMOTE_HOST" >>"$known_hosts" 2>/dev/null || \
fail "échec de récupération de la clé hôte pour ${BACKUP_REMOTE_HOST}"
else
log "Host déjà présent dans known_hosts."
fi
}
test_backup_ssh() {
local ssh_port ssh_timeout
ssh_port="${BACKUP_REMOTE_SSH_PORT:-22}"
ssh_timeout="${SSH_CONNECT_TIMEOUT:-8}"
ssh \
-i "$SSH_KEY" \
-p "$ssh_port" \
-o IdentitiesOnly=yes \
-o BatchMode=yes \
-o ConnectTimeout="$ssh_timeout" \
-o StrictHostKeyChecking=yes \
"${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}" \
"test -d '$BACKUP_REMOTE_DIR'" \
>>"$LOG_FILE" 2>&1 || \
fail "connexion SSH backup impossible, clé non autorisée, ou dossier distant absent"
log "Connexion SSH backup validée."
}
install_sudoers_if_allowed() {
local auto_configure sudoers_file tmp_file
auto_configure="$(to_bool_yes_no "${AUTO_CONFIGURE_SUDOERS:-no}")" || fail "AUTO_CONFIGURE_SUDOERS invalide"
if [[ "$auto_configure" != "yes" ]]; then
log "Installation sudoers automatique désactivée."
return 0
fi
if ! sudo -n true >/dev/null 2>&1; then
fail "AUTO_CONFIGURE_SUDOERS=yes mais sudo -n n'est pas disponible ; configuration initiale manuelle requise"
fi
require_cmd visudo
sudoers_file="/etc/sudoers.d/rebuild-bdd-${USER}"
tmp_file="$(mktemp)"
cat >"$tmp_file" <<EOF
${USER} ALL=(root) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /usr/bin/systemctl
${USER} ALL=(postgres) NOPASSWD: /usr/bin/psql
EOF
chmod 440 "$tmp_file"
visudo -cf "$tmp_file" >/dev/null 2>&1 || {
rm -f "$tmp_file"
fail "fichier sudoers généré invalide"
}
sudo install -m 440 "$tmp_file" "$sudoers_file" || {
rm -f "$tmp_file"
fail "impossible d'installer $sudoers_file"
}
rm -f "$tmp_file"
log "Fichier sudoers installé : $sudoers_file"
}
check_sudo_non_interactive() {
sudo -n true >/dev/null 2>&1 || \
fail "sudo non interactif indisponible pour ${USER}"
sudo -n -u postgres true >/dev/null 2>&1 || \
fail "sudo -n -u postgres indisponible pour ${USER}"
log "sudo non interactif validé."
}
run_postgresql_check() {
local check_script
check_script="${REPO_DIR}/Checkup/check-postgresql.sh"
[[ -x "$check_script" ]] || fail "script introuvable ou non exécutable : $check_script"
"$check_script" \
--env-file "$ENV_FILE" \
--request-id "$REQUEST_ID" \
--non-interactive \
>>"$LOG_FILE" 2>&1 || fail "échec de préparation PostgreSQL"
log "Préparation PostgreSQL validée."
}
###############################################################################
# Main
###############################################################################
[[ -f "$ENV_FILE" ]] || {
echo '{"status":"error","message":"fichier .env introuvable"}'
exit 1
}
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-}}"
require_env_vars
validate_env_values
prepare_log_file
require_cmd bash
require_cmd python3
require_cmd ssh
require_cmd ssh-keygen
require_cmd ssh-keyscan
require_cmd sudo
prepare_local_paths
prepare_scripts_permissions
prepare_ssh_key
prepare_known_hosts
test_backup_ssh
install_sudoers_if_allowed
check_sudo_non_interactive
run_postgresql_check
log "Machine cible prête pour le rebuild."
print_json_and_exit "success" "machine cible prête" 0

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# bootstrap-target-host.sh
#
# Bootstrap initial d'une machine cible neuve ou quasi neuve.
#
# Ce script est lancé depuis la machine de pilotage et :
# 1. charge le .env local ;
# 2. récupère la configuration bootstrap de la cible ;
# 3. teste la connexion SSH de bootstrap ;
# 4. installe le socle minimal sur la cible ;
# 5. crée les dossiers de travail ;
# 6. génère le .env cible ;
# 7. clone ou met à jour le dépôt distant.
#
# À ce stade, il ne gère pas encore :
# - la clé SSH backup ;
# - known_hosts backup ;
# - sudoers PostgreSQL ;
# - le lancement du rebuild.
###############################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_ENV_FILE="${SCRIPT_DIR}/.env"
ENV_FILE="${ENV_FILE:-$DEFAULT_ENV_FILE}"
TARGET_NAME="${TARGET_NAME:-}"
CLI_TARGET=""
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
;;
--json-only)
JSON_ONLY="yes"
shift
;;
*)
echo "Argument inconnu : $1" >&2
exit 1
;;
esac
done
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":"error","message":"%s"}\n' "$(printf '%s' "$msg" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' | sed 's/^"//;s/"$//')"
else
echo "ERROR: $msg" >&2
fi
exit 1
}
success() {
local msg="$1"
if [[ "$JSON_ONLY" == "yes" ]]; then
printf '{"status":"success","message":"%s"}\n' "$(printf '%s' "$msg" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' | sed 's/^"//;s/"$//')"
else
log "$msg"
fi
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
}
sanitize_key() {
local value="${1:-}"
value="${value//[^a-zA-Z0-9_]/_}"
printf '%s' "$value"
}
get_env_var() {
local var_name="$1"
printf '%s' "${!var_name:-}"
}
shell_quote() {
printf "%q" "$1"
}
[[ -f "$ENV_FILE" ]] || fail "fichier .env introuvable : $ENV_FILE"
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
TARGET_NAME="${CLI_TARGET:-${TARGET_NAME:-}}"
[[ -n "$TARGET_NAME" ]] || fail "target manquante"
SAFE_TARGET="$(sanitize_key "$TARGET_NAME")"
BOOTSTRAP_HOST="$(get_env_var "TARGET_HOST_${SAFE_TARGET}")"
BOOTSTRAP_PORT="$(get_env_var "TARGET_PORT_${SAFE_TARGET}")"
BOOTSTRAP_USER="$(get_env_var "TARGET_BOOTSTRAP_USER_${SAFE_TARGET}")"
BOOTSTRAP_SSH_KEY="$(get_env_var "TARGET_BOOTSTRAP_SSH_KEY_${SAFE_TARGET}")"
TARGET_REPO_URL="$(get_env_var "TARGET_REPO_URL_${SAFE_TARGET}")"
TARGET_REPO_BRANCH="$(get_env_var "TARGET_REPO_BRANCH_${SAFE_TARGET}")"
TARGET_REPO_DIR="$(get_env_var "TARGET_REPO_DIR_${SAFE_TARGET}")"
TARGET_ENV_FILE_PATH="$(get_env_var "TARGET_ENV_FILE_${SAFE_TARGET}")"
TARGET_ENV_NAME_VALUE="$(get_env_var "TARGET_ENV_NAME_${SAFE_TARGET}")"
TARGET_PGHOST_VALUE="$(get_env_var "TARGET_PGHOST_${SAFE_TARGET}")"
TARGET_PGPORT_VALUE="$(get_env_var "TARGET_PGPORT_${SAFE_TARGET}")"
TARGET_PGUSER_VALUE="$(get_env_var "TARGET_PGUSER_${SAFE_TARGET}")"
TARGET_PGPASSWORD_VALUE="$(get_env_var "TARGET_PGPASSWORD_${SAFE_TARGET}")"
TARGET_DBS_VALUE="$(get_env_var "TARGET_DBS_${SAFE_TARGET}")"
TARGET_BACKUP_REMOTE_USER_VALUE="$(get_env_var "TARGET_BACKUP_REMOTE_USER_${SAFE_TARGET}")"
TARGET_BACKUP_REMOTE_HOST_VALUE="$(get_env_var "TARGET_BACKUP_REMOTE_HOST_${SAFE_TARGET}")"
TARGET_BACKUP_REMOTE_DIR_VALUE="$(get_env_var "TARGET_BACKUP_REMOTE_DIR_${SAFE_TARGET}")"
TARGET_BACKUP_REMOTE_SSH_PORT_VALUE="$(get_env_var "TARGET_BACKUP_REMOTE_SSH_PORT_${SAFE_TARGET}")"
TARGET_BACKUP_LOG_DIR_VALUE="$(get_env_var "TARGET_BACKUP_LOG_DIR_${SAFE_TARGET}")"
TARGET_LOCAL_RESTORE_BASE_DIR_VALUE="$(get_env_var "TARGET_LOCAL_RESTORE_BASE_DIR_${SAFE_TARGET}")"
TARGET_REMOTE_ROLES_DIR_NAME_VALUE="$(get_env_var "TARGET_REMOTE_ROLES_DIR_NAME_${SAFE_TARGET}")"
TARGET_SSH_KEY_VALUE="$(get_env_var "TARGET_SSH_KEY_${SAFE_TARGET}")"
TARGET_AUTO_INSTALL_POSTGRES_VALUE="$(get_env_var "TARGET_AUTO_INSTALL_POSTGRES_${SAFE_TARGET}")"
TARGET_AUTO_CREATE_PGUSER_VALUE="$(get_env_var "TARGET_AUTO_CREATE_PGUSER_${SAFE_TARGET}")"
TARGET_PGUSER_SUPERUSER_VALUE="$(get_env_var "TARGET_PGUSER_SUPERUSER_${SAFE_TARGET}")"
TARGET_AUTO_CONFIGURE_SUDOERS_VALUE="$(get_env_var "TARGET_AUTO_CONFIGURE_SUDOERS_${SAFE_TARGET}")"
TARGET_EXCLUDED_RESTORE_ROLES_VALUE="$(get_env_var "TARGET_EXCLUDED_RESTORE_ROLES_${SAFE_TARGET}")"
[[ -n "$BOOTSTRAP_HOST" ]] || fail "TARGET_HOST_${SAFE_TARGET} manquante"
[[ -n "$BOOTSTRAP_PORT" ]] || BOOTSTRAP_PORT="22"
[[ -n "$BOOTSTRAP_USER" ]] || fail "TARGET_BOOTSTRAP_USER_${SAFE_TARGET} manquante"
[[ -n "$BOOTSTRAP_SSH_KEY" ]] || fail "TARGET_BOOTSTRAP_SSH_KEY_${SAFE_TARGET} 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 "TARGET_REPO_URL_${SAFE_TARGET} manquante"
[[ -n "$TARGET_REPO_BRANCH" ]] || fail "TARGET_REPO_BRANCH_${SAFE_TARGET} manquante"
[[ -n "$TARGET_REPO_DIR" ]] || fail "TARGET_REPO_DIR_${SAFE_TARGET} manquante"
[[ -n "$TARGET_ENV_FILE_PATH" ]] || fail "TARGET_ENV_FILE_${SAFE_TARGET} manquante"
[[ -n "$TARGET_ENV_NAME_VALUE" ]] || fail "TARGET_ENV_NAME_${SAFE_TARGET} manquante"
[[ -n "$TARGET_PGHOST_VALUE" ]] || fail "TARGET_PGHOST_${SAFE_TARGET} manquante"
[[ -n "$TARGET_PGPORT_VALUE" ]] || fail "TARGET_PGPORT_${SAFE_TARGET} manquante"
[[ -n "$TARGET_PGUSER_VALUE" ]] || fail "TARGET_PGUSER_${SAFE_TARGET} manquante"
[[ -n "$TARGET_PGPASSWORD_VALUE" ]] || fail "TARGET_PGPASSWORD_${SAFE_TARGET} manquante"
[[ -n "$TARGET_DBS_VALUE" ]] || fail "TARGET_DBS_${SAFE_TARGET} manquante"
[[ -n "$TARGET_BACKUP_REMOTE_USER_VALUE" ]] || fail "TARGET_BACKUP_REMOTE_USER_${SAFE_TARGET} manquante"
[[ -n "$TARGET_BACKUP_REMOTE_HOST_VALUE" ]] || fail "TARGET_BACKUP_REMOTE_HOST_${SAFE_TARGET} manquante"
[[ -n "$TARGET_BACKUP_REMOTE_DIR_VALUE" ]] || fail "TARGET_BACKUP_REMOTE_DIR_${SAFE_TARGET} manquante"
[[ -n "$TARGET_BACKUP_LOG_DIR_VALUE" ]] || fail "TARGET_BACKUP_LOG_DIR_${SAFE_TARGET} manquante"
[[ -n "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE" ]] || TARGET_BACKUP_REMOTE_SSH_PORT_VALUE="22"
[[ -n "$TARGET_LOCAL_RESTORE_BASE_DIR_VALUE" ]] || TARGET_LOCAL_RESTORE_BASE_DIR_VALUE="${TARGET_REPO_DIR}/restore_tmp"
[[ -n "$TARGET_REMOTE_ROLES_DIR_NAME_VALUE" ]] || TARGET_REMOTE_ROLES_DIR_NAME_VALUE="user"
[[ -n "$TARGET_SSH_KEY_VALUE" ]] || TARGET_SSH_KEY_VALUE="/home/${BOOTSTRAP_USER}/.ssh/id_ed25519_backup_readonly"
[[ -n "$TARGET_AUTO_INSTALL_POSTGRES_VALUE" ]] || TARGET_AUTO_INSTALL_POSTGRES_VALUE="yes"
[[ -n "$TARGET_AUTO_CREATE_PGUSER_VALUE" ]] || TARGET_AUTO_CREATE_PGUSER_VALUE="yes"
[[ -n "$TARGET_PGUSER_SUPERUSER_VALUE" ]] || TARGET_PGUSER_SUPERUSER_VALUE="no"
[[ -n "$TARGET_AUTO_CONFIGURE_SUDOERS_VALUE" ]] || TARGET_AUTO_CONFIGURE_SUDOERS_VALUE="no"
[[ -n "$TARGET_EXCLUDED_RESTORE_ROLES_VALUE" ]] || TARGET_EXCLUDED_RESTORE_ROLES_VALUE="postgres"
require_cmd ssh
require_cmd scp
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
if command -v apt-get >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y bash git python3 sudo curl openssh-client ca-certificates
else
apt-get update
apt-get install -y bash git python3 sudo curl openssh-client ca-certificates
fi
else
echo 'apt-get absent sur la cible' >&2
exit 1
fi
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")")
"
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)"
cleanup() {
rm -f "$TMP_ENV_FILE"
}
trap cleanup EXIT
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"
scp "${SSH_OPTS[@]}" "$TMP_ENV_FILE" "${REMOTE}:$(printf '%q' "$TARGET_ENV_FILE_PATH")" >/dev/null 2>&1 \
|| fail "échec de copie du .env cible"
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"
success "bootstrap initial terminé pour ${TARGET_NAME}"

View File

@@ -232,13 +232,11 @@ for cmd in ssh scp psql pg_restore createdb dropdb python3 grep sed find basenam
done
CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh"
[[ -x "$CHECK_SCRIPT" ]] || fail "script introuvable ou non exécutable : $CHECK_SCRIPT"
"$CHECK_SCRIPT" \
--env-file "$ENV_FILE" \
--request-id "$SAFE_REQUEST_ID" \
--non-interactive \
>>"$LOG_FILE" 2>&1 || fail "échec de préparation PostgreSQL locale"
if [[ -x "$CHECK_SCRIPT" ]]; then
log "Précheck PostgreSQL déjà effectué par check-target-readiness.sh"
else
fail "script introuvable ou non exécutable : $CHECK_SCRIPT"
fi
[[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "clé SSH source backup non lisible : $SSH_KEY"

View File

@@ -245,9 +245,16 @@ 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")
PRECHECK_SCRIPT=$(shell_quote "${TARGET_REPO_DIR}/Checkup/check-target-readiness.sh")
TARGET_ENV_FILE=$(shell_quote "$TARGET_ENV_FILE")
REQUESTED_DB=$(shell_quote "$REQUESTED_DB")
ALLOW_OVERWRITE=$(shell_quote "$ALLOW_OVERWRITE")
RESTORE_ROLES=$(shell_quote "$RESTORE_ROLES")
REQUEST_ID=$(shell_quote "$REQUEST_ID")
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; }
command -v python3 >/dev/null 2>&1 || { echo '{\"status\":\"error\",\"message\":\"python3 absent sur la cible\"}'; exit 1; }
mkdir -p \"\$(dirname \"\$REPO_DIR\")\"
@@ -261,14 +268,38 @@ else
fi
[[ -f \"\$CORE_SCRIPT\" ]] || { echo '{\"status\":\"error\",\"message\":\"script core introuvable sur la cible\"}'; exit 1; }
[[ -f \"\$PRECHECK_SCRIPT\" ]] || { echo '{\"status\":\"error\",\"message\":\"script précheck introuvable sur la cible\"}'; exit 1; }
chmod 700 \"\$CORE_SCRIPT\"
chmod 700 \"\$PRECHECK_SCRIPT\"
PRECHECK_JSON=\"/tmp/check_target_\${REQUEST_ID}.json\"
\"\$PRECHECK_SCRIPT\" \
--env-file \"\$TARGET_ENV_FILE\" \
--request-id \"\$REQUEST_ID\" \
--non-interactive \
--json-only >\"\$PRECHECK_JSON\"
PRECHECK_STATUS=\"\$(python3 - <<'PY' \"\$PRECHECK_JSON\"
import json, sys
with open(sys.argv[1], 'r', encoding='utf-8') as f:
data = json.load(f)
print(data.get('status', 'error'))
PY
)\"
if [[ \"\$PRECHECK_STATUS\" != \"success\" ]]; then
cat \"\$PRECHECK_JSON\"
exit 1
fi
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") \
--env-file \"\$TARGET_ENV_FILE\" \
--db \"\$REQUESTED_DB\" \
--overwrite \"\$ALLOW_OVERWRITE\" \
--restore-roles \"\$RESTORE_ROLES\" \
--request-id \"\$REQUEST_ID\" \
--non-interactive \
--json-only
"