From 94537de551ab0440560178ec4f796936f67df278 Mon Sep 17 00:00:00 2001 From: AkiNoKure Date: Tue, 17 Mar 2026 10:11:00 +0100 Subject: [PATCH] feat : update web available rebuild bdd (WIP) --- RebuildBdd/Checkup/check-postgresql.sh | 38 ++- RebuildBdd/Checkup/check-target-readiness.sh | 338 +++++++++++++++++++ RebuildBdd/bootstrap-target-host.sh | 286 ++++++++++++++++ RebuildBdd/rebuild-bdd-core.sh | 12 +- RebuildBdd/run-rebuild-bdd.sh | 41 ++- 5 files changed, 694 insertions(+), 21 deletions(-) create mode 100644 RebuildBdd/Checkup/check-target-readiness.sh create mode 100644 RebuildBdd/bootstrap-target-host.sh diff --git a/RebuildBdd/Checkup/check-postgresql.sh b/RebuildBdd/Checkup/check-postgresql.sh index 849a6bf..13ffc2b 100755 --- a/RebuildBdd/Checkup/check-postgresql.sh +++ b/RebuildBdd/Checkup/check-postgresql.sh @@ -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." diff --git a/RebuildBdd/Checkup/check-target-readiness.sh b/RebuildBdd/Checkup/check-target-readiness.sh new file mode 100644 index 0000000..60d9f9a --- /dev/null +++ b/RebuildBdd/Checkup/check-target-readiness.sh @@ -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" </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 \ No newline at end of file diff --git a/RebuildBdd/bootstrap-target-host.sh b/RebuildBdd/bootstrap-target-host.sh new file mode 100644 index 0000000..c23ad21 --- /dev/null +++ b/RebuildBdd/bootstrap-target-host.sh @@ -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" </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}" \ No newline at end of file diff --git a/RebuildBdd/rebuild-bdd-core.sh b/RebuildBdd/rebuild-bdd-core.sh index 20374b6..4de89e8 100755 --- a/RebuildBdd/rebuild-bdd-core.sh +++ b/RebuildBdd/rebuild-bdd-core.sh @@ -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" diff --git a/RebuildBdd/run-rebuild-bdd.sh b/RebuildBdd/run-rebuild-bdd.sh index e4f1b59..03fcc6e 100755 --- a/RebuildBdd/run-rebuild-bdd.sh +++ b/RebuildBdd/run-rebuild-bdd.sh @@ -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 "