318 lines
8.6 KiB
Bash
Executable File
318 lines
8.6 KiB
Bash
Executable File
#!/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.
|
|
###############################################################################
|
|
|
|
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"
|
|
BACKUP_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
|
|
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
|
|
}
|
|
|
|
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"
|
|
|
|
[[ -f "$SSH_KEY" ]] || fail "clé SSH absente : $SSH_KEY"
|
|
chmod 600 "$SSH_KEY" || fail "impossible de chmod 600 sur la clé privée"
|
|
|
|
[[ -f "${SSH_KEY}.pub" ]] || log "clé publique absente : ${SSH_KEY}.pub"
|
|
[[ ! -f "${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_dir="$(dirname "$SSH_KEY")"
|
|
known_hosts="${ssh_dir}/known_hosts"
|
|
|
|
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}:${BACKUP_REMOTE_SSH_PORT} à known_hosts"
|
|
ssh-keyscan -p "$BACKUP_REMOTE_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_timeout
|
|
ssh_timeout="${SSH_CONNECT_TIMEOUT:-8}"
|
|
|
|
ssh \
|
|
-i "$SSH_KEY" \
|
|
-p "$BACKUP_REMOTE_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}"
|
|
|
|
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"
|
|
|
|
sudo -n -u postgres true >/dev/null 2>&1 || fail "sudo -n -u postgres indisponible après préparation PostgreSQL"
|
|
log "Préparation PostgreSQL validée."
|
|
}
|
|
|
|
[[ -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 |