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_INSTALL_POSTGRES="${AUTO_INSTALL_POSTGRES:-yes}"
AUTO_CREATE_PGUSER="${AUTO_CREATE_PGUSER:-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_PACKAGE_LIST="${POSTGRES_PACKAGE_LIST:-postgresql postgresql-client postgresql-contrib}"
POSTGRES_SERVICE_NAME="${POSTGRES_SERVICE_NAME:-postgresql}" POSTGRES_SERVICE_NAME="${POSTGRES_SERVICE_NAME:-postgresql}"
SUDO_BIN="${SUDO_BIN:-sudo}" SUDO_BIN="${SUDO_BIN:-sudo}"
@@ -69,51 +70,70 @@ if ! require_cmd "$SUDO_BIN"; then
fail "sudo absent sur la cible" fail "sudo absent sur la cible"
fi 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" POSTGRES_INSTALLED="no"
if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb; then 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" [[ "${AUTO_INSTALL_POSTGRES,,}" == "yes" ]] || fail "PostgreSQL absent et AUTO_INSTALL_POSTGRES=no"
log "PostgreSQL absent : installation en cours..." log "PostgreSQL absent : installation en cours..."
"$SUDO_BIN" apt update >/dev/null 2>&1 || fail "échec de apt update" "$SUDO_BIN" -n 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 install -y $POSTGRES_PACKAGE_LIST >/dev/null 2>&1 || fail "échec de l'installation PostgreSQL"
POSTGRES_INSTALLED="yes" POSTGRES_INSTALLED="yes"
log "Installation PostgreSQL terminée." log "Installation PostgreSQL terminée."
else else
log "PostgreSQL déjà installé." log "PostgreSQL déjà installé."
fi 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..." 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 else
log "Service PostgreSQL déjà actif." log "Service PostgreSQL déjà actif."
fi fi
log "Vérification de la disponibilité de PostgreSQL..." log "Vérification de la disponibilité de PostgreSQL..."
for _ in {1..20}; do 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." log "PostgreSQL répond correctement."
break break
fi fi
sleep 1 sleep 1
done 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" fail "PostgreSQL ne répond pas correctement"
fi fi
if [[ "${AUTO_CREATE_PGUSER,,}" == "yes" ]]; then if [[ "${AUTO_CREATE_PGUSER,,}" == "yes" ]]; then
ROLE_EXISTS="$( 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 "SELECT 1 FROM pg_roles WHERE rolname='${PGUSER//\'/\'\'}'" 2>/dev/null || true
)" )"
if [[ "$ROLE_EXISTS" != "1" ]]; then if [[ "$ROLE_EXISTS" != "1" ]]; then
log "Création du rôle PostgreSQL ${PGUSER}..." 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}" >/dev/null 2>&1 || fail "échec de création du rôle ${PGUSER}"
log "Rôle PostgreSQL ${PGUSER} créé." log "Rôle PostgreSQL ${PGUSER} créé."
else else
log "Rôle PostgreSQL ${PGUSER} déjà présent." 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 done
CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh" CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh"
[[ -x "$CHECK_SCRIPT" ]] || fail "script introuvable ou non exécutable : $CHECK_SCRIPT" if [[ -x "$CHECK_SCRIPT" ]]; then
log "Précheck PostgreSQL déjà effectué par check-target-readiness.sh"
"$CHECK_SCRIPT" \ else
--env-file "$ENV_FILE" \ fail "script introuvable ou non exécutable : $CHECK_SCRIPT"
--request-id "$SAFE_REQUEST_ID" \ fi
--non-interactive \
>>"$LOG_FILE" 2>&1 || fail "échec de préparation PostgreSQL locale"
[[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY" [[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "clé SSH source backup non lisible : $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_URL=$(shell_quote "$TARGET_REPO_URL")
REPO_BRANCH=$(shell_quote "$TARGET_REPO_BRANCH") REPO_BRANCH=$(shell_quote "$TARGET_REPO_BRANCH")
CORE_SCRIPT=$(shell_quote "$TARGET_CORE_SCRIPT") 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 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 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\")\" mkdir -p \"\$(dirname \"\$REPO_DIR\")\"
@@ -261,14 +268,38 @@ else
fi fi
[[ -f \"\$CORE_SCRIPT\" ]] || { echo '{\"status\":\"error\",\"message\":\"script core introuvable sur la cible\"}'; exit 1; } [[ -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 \"\$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\" \ exec \"\$CORE_SCRIPT\" \
--env-file $(shell_quote "$TARGET_ENV_FILE") \ --env-file \"\$TARGET_ENV_FILE\" \
--db $(shell_quote "$REQUESTED_DB") \ --db \"\$REQUESTED_DB\" \
--overwrite $(shell_quote "$ALLOW_OVERWRITE") \ --overwrite \"\$ALLOW_OVERWRITE\" \
--restore-roles $(shell_quote "$RESTORE_ROLES") \ --restore-roles \"\$RESTORE_ROLES\" \
--request-id $(shell_quote "$REQUEST_ID") \ --request-id \"\$REQUEST_ID\" \
--non-interactive \ --non-interactive \
--json-only --json-only
" "