From fbc3c51a07ac2e4cab6adccad29b364ff760e9cb Mon Sep 17 00:00:00 2001 From: Matteo Dunoyer Date: Thu, 5 Mar 2026 10:22:01 +0100 Subject: [PATCH] init: first commit --- README.md | 1 + backup_pg.sh | 210 ++++++++++++++++++++++++++++++++++++++++ check_statut_recette.sh | 147 ++++++++++++++++++++++++++++ 3 files changed, 358 insertions(+) create mode 100644 README.md create mode 100755 backup_pg.sh create mode 100755 check_statut_recette.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..59f21e2 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Scripts Serveur MALIO diff --git a/backup_pg.sh b/backup_pg.sh new file mode 100755 index 0000000..132c62d --- /dev/null +++ b/backup_pg.sh @@ -0,0 +1,210 @@ +#!/usr/bin/env bash +set -euo pipefail + +####################################### +# Configuration +####################################### +DBS=("sirh" "inventory" "ferme") + +PGHOST="localhost" +PGPORT="5432" +PGUSER="backup_liot" +PGPASSWORD="backup_liot" + +IA_SSH="malio-b@192.168.0.179" +IA_BASE_DIR="/home/malio-b/backups" + +SSH_KEY="/home/malio/.ssh/id_ed25519_backup" +SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10) + +LOG_DIR="/var/log/pg_backup" +mkdir -p "$LOG_DIR" + +TS="$(date +'%Y-%m-%d_%H-%M-%S')" +BACKUP_DIR_NAME="backup_${TS}" +LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log" + +TMP_DIR="/tmp/pg_dump_${BACKUP_DIR_NAME}" +mkdir -p "$TMP_DIR" + +exec > >(tee -a "$LOG_FILE") 2>&1 +log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; } + +export PGPASSWORD + +####################################### +# Discord (Webhook) +####################################### +DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1478503102888935506/YCtJM09QZiKNMiCe5u7vCQb52VcLjHAd9wwEsKNltlJVcy7sKvoMTOJkvEKOOrk-Wpkh" + +discord_ping() { + local details="${1:-}" + + [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0 + + local color dumps_display users_display + if [[ -n "${DUMPS_OK:-}" && -n "${USERS_OK:-}" ]]; then + color="🟢" + else + color="🔴" + fi + + dumps_display=$([[ -n "${DUMPS_OK:-}" ]] && echo "✅" || echo "❌") + users_display=$([[ -n "${USERS_OK:-}" ]] && echo "✅" || echo "❌") + + local msg="**@here BACKUP BDD RECETTE ${color}**\n" + msg+="Name: ${BACKUP_DIR_NAME}\n" + msg+="Dumps transfer: ${dumps_display}\n" + msg+="Users transfer: ${users_display}\n" + [[ -n "$details" ]] && msg+="Details: $details" + + curl -fsS -H "Content-Type: application/json" \ + -d "{\"content\":\"$msg\"}" \ + "$DISCORD_WEBHOOK_URL" >/dev/null || true +} + +####################################### +# Statuts init +####################################### +DUMPS_OK=true +USERS_OK=true +DUMP_ERRORS="" +USER_ERRORS="" + +####################################### +# Lock (évite 2 backups en même temps) +####################################### +LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d" +if ! mkdir "$LOCK_DIR" 2>/dev/null; then + log "ERROR: Backup déjà en cours (lock: $LOCK_DIR)" + DUMPS_OK= + USERS_OK= + discord_ping "Lock exists: $LOCK_DIR" + exit 1 +fi +trap 'rm -rf "$LOCK_DIR"' EXIT + +####################################### +# Remote dir +####################################### +REMOTE_DIR="${IA_BASE_DIR}/${BACKUP_DIR_NAME}" + +log "Starting backup process" +log "Remote directory: ${REMOTE_DIR}" + +log "Creating remote directory" +if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "mkdir -p '${REMOTE_DIR}'"; then + log "ERROR: Création dossier distant impossible: ${REMOTE_DIR}" + DUMPS_OK= + USERS_OK= + discord_ping "Remote mkdir KO: ${REMOTE_DIR}" + exit 1 +fi + +####################################### +# Export PostgreSQL roles (no passwords) +####################################### +ROLES_FILE="${TMP_DIR}/roles_${TS}.sql" +log "Exporting PostgreSQL roles" + +set +e +psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -Atq <<'SQL' > "$ROLES_FILE" +SELECT + format( + 'DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %L) THEN + CREATE ROLE %I; + END IF; + END $$;', + rolname, rolname + ) +FROM pg_roles +WHERE rolname !~ '^pg_' +ORDER BY rolname; + +SELECT + format( + 'ALTER ROLE %I WITH %s%s%s%s%s%s%s%s;', + rolname, + CASE WHEN rolsuper THEN 'SUPERUSER ' ELSE 'NOSUPERUSER ' END, + CASE WHEN rolinherit THEN 'INHERIT ' ELSE 'NOINHERIT ' END, + CASE WHEN rolcreaterole THEN 'CREATEROLE ' ELSE 'NOCREATEROLE ' END, + CASE WHEN rolcreatedb THEN 'CREATEDB ' ELSE 'NOCREATEDB ' END, + CASE WHEN rolcanlogin THEN 'LOGIN ' ELSE 'NOLOGIN ' END, + CASE WHEN rolreplication THEN 'REPLICATION ' ELSE 'NOREPLICATION ' END, + CASE WHEN rolbypassrls THEN 'BYPASSRLS ' ELSE 'NOBYPASSRLS ' END, + 'CONNECTION LIMIT ' || rolconnlimit + ) +FROM pg_roles +WHERE rolname !~ '^pg_' +ORDER BY rolname; +SQL +RET=$? +if [[ $RET -ne 0 ]]; then + USERS_OK= + USER_ERRORS+="roles_export " + log "ERROR: Users export failed" +else + log "Roles export completed: $ROLES_FILE" +fi +set -e + +log "Sending roles file to IA server" +set +e +scp "${SSH_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${REMOTE_DIR}/" +RET=$? +if [[ $RET -ne 0 ]]; then + USERS_OK= + USER_ERRORS+="roles_scp " + log "ERROR: Users transfer failed (roles file)" +else + log "Roles transfer completed" +fi +set -e + +####################################### +# Dump des bases + transfert (continue même si KO) +####################################### +set +e +for DB in "${DBS[@]}"; do + FILE="${TMP_DIR}/${DB}_${TS}.dump" + + log "Dumping database: $DB" + pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc --no-owner --no-acl -d "$DB" -f "$FILE" + RET=$? + if [[ $RET -ne 0 ]]; then + DUMPS_OK= + DUMP_ERRORS+="${DB} " + log "ERROR: Dump failed for $DB" + continue + fi + log "Dump completed: $FILE" + + log "Sending dump to IA server" + scp "${SSH_OPTS[@]}" "$FILE" "$IA_SSH:${REMOTE_DIR}/" + RET=$? + if [[ $RET -ne 0 ]]; then + DUMPS_OK= + DUMP_ERRORS+="${DB}(scp) " + log "ERROR: Transfer failed for $DB" + continue + fi + log "Transfer completed for $DB" +done +set -e + +####################################### +# Nettoyage +####################################### +log "Cleaning temporary files" +rm -rf "$TMP_DIR" + +####################################### +# Envoi message Discord (final) +####################################### +DETAILS="" +[[ -z "${DUMPS_OK:-}" ]] && DETAILS+="Dumps KO: ${DUMP_ERRORS} " +[[ -z "${USERS_OK:-}" ]] && DETAILS+="Users KO: ${USER_ERRORS} " +discord_ping "$DETAILS" + +log "Backup finished" diff --git a/check_statut_recette.sh b/check_statut_recette.sh new file mode 100755 index 0000000..0d1533b --- /dev/null +++ b/check_statut_recette.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +set -u + +####################################### +# Sites à vérifier +####################################### +SITES=( + "ferme.malio-dev.fr" + "sirh.malio-dev.fr" + "inventory.malio-dev.fr" +) + +SCHEME="http" +CONNECT_TIMEOUT=3 +MAX_TIME=8 + +####################################### +# Logs +####################################### +LOG_DIR="/var/log/app_health" +mkdir -p "$LOG_DIR" +LOG_FILE="${LOG_DIR}/app_health_$(date +'%Y-%m-%d').log" + +####################################### +# Discord +####################################### +DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1478379245842600007/tSxi3G6PbCn89pOdeqK34LR7c-GhXfT-lSCPolwBywJXcpa3ihL8rN4QRwsTjF6SS3w0" + +discord_ping() { + local site="$1" + local status="$2" + local detail="$3" + + [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0 + + local color icon + + if [[ "$status" == "OK" ]]; then + color="🟢" + icon="✅" + else + color="🔴" + icon="❌" + fi + + local msg="**CHECK APP RECETTE $color**\n" + msg+="Application: ${site}\n" + msg+="Status: ${icon}\n" + msg+="Details: ${detail}" + + curl -fsS -H "Content-Type: application/json" \ + -d "{\"content\":\"$msg\"}" \ + "$DISCORD_WEBHOOK_URL" >/dev/null || true +} + +####################################### +# Logging +####################################### +log_line() { + # 2026-03-04 14:12:33 | LEVEL | site | message + printf "%s | %s | %s | %s\n" \ + "$(date +'%Y-%m-%d %H:%M:%S')" "$1" "$2" "$3" | tee -a "$LOG_FILE" + + # Envoi Discord par application + discord_ping "$2" "$1" "$3" +} + +####################################### +# DNS +####################################### +dns_ok() { + getent hosts "$1" >/dev/null 2>&1 +} + +####################################### +# Check application +####################################### +check_site() { + + local host="$1" + local url="${SCHEME}://${host}/" + + if ! dns_ok "$host"; then + log_line "DOWN" "$host" "Résolution impossible (getent hosts)" + return 1 + fi + + local http_code curl_exit stderr + + stderr="$(mktemp)" + + http_code="$( + curl -sS -o /dev/null \ + -w '%{http_code}' \ + --connect-timeout "$CONNECT_TIMEOUT" \ + --max-time "$MAX_TIME" \ + "$url" 2>"$stderr" + )" + + curl_exit=$? + + if [ $curl_exit -ne 0 ]; then + local err + err="$(head -n 1 "$stderr" | tr -d '\r')" + rm -f "$stderr" + + log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}" + return 1 + fi + + rm -f "$stderr" + + if [[ "$http_code" =~ ^[0-9]{3}$ ]]; then + if [ "$http_code" -ge 200 ] && [ "$http_code" -le 399 ]; then + log_line "OK" "$host" "HTTP $http_code" + return 0 + fi + + log_line "DOWN" "$host" "HTTP $http_code (erreur appli)" + return 1 + fi + + log_line "DOWN" "$host" "Code HTTP inattendu: $http_code" + return 1 +} + +####################################### +# Main +####################################### +main() { + + local failures=0 + + for site in "${SITES[@]}"; do + if ! check_site "$site"; then + failures=$((failures + 1)) + fi + done + + if [ "$failures" -gt 0 ]; then + exit 2 + fi + + exit 0 +} + +main "$@"