diff --git a/BackupVaultWarden/README.md b/BackupVaultWarden/README.md
index 80a01ae..4f9f98e 100644
--- a/BackupVaultWarden/README.md
+++ b/BackupVaultWarden/README.md
@@ -101,6 +101,38 @@ REMOTE_DIR=/home/backup/backups/vaultwarden
# 5. Chargement des variables dans le script
+
+
+ EggMaster
+
+
+
+ Question 2
+
+Quel format minimal faut-il donner a `printf` pour afficher une chaine brute ?
+
+
+
+
+ Indice commande 2
+
+```text
+'%s'
+```
+
+
+
+
+ Fragment 2
+
+```text
+xlIHBldGl0IHN0YWdpYWlyZSBtYXR0ZW8gZHVu
+```
+
+
+
+
+
Le script charge directement le fichier `.env` avec `source` et exporte automatiquement les variables pendant le chargement.
Mécanisme utilisé :
@@ -291,7 +323,7 @@ crontab -e
Ajouter :
```bash
-0 19 * * * /home//Malio-ops/BackupVaultWarden/backup-vaultwarden.sh >> /var/log/vaultwarden_backup.log 2>&1
+0 19 * * * /home//Malio-ops/BackupVaultWarden/backup-vaultwarden.sh 2>&1
```
Signification :
@@ -353,4 +385,3 @@ Le script automatise :
Ce système permet d’obtenir **une sauvegarde fiable, centralisée et surveillée de Vaultwarden**.
-```
diff --git a/BackupVaultWarden/backup-vaultwarden.sh b/BackupVaultWarden/backup-vaultwarden.sh
index 9a362bb..fe31f5e 100755
--- a/BackupVaultWarden/backup-vaultwarden.sh
+++ b/BackupVaultWarden/backup-vaultwarden.sh
@@ -41,6 +41,10 @@ set +a
: "${REMOTE_USER:?Variable REMOTE_USER manquante dans .env}"
: "${REMOTE_HOST:?Variable REMOTE_HOST manquante dans .env}"
: "${REMOTE_DIR:?Variable REMOTE_DIR manquante dans .env}"
+[[ "$REMOTE_DIR" =~ ^[a-zA-Z0-9/_.-]+$ ]] || {
+ echo "ERROR: Variable REMOTE_DIR invalide dans .env" >&2
+ exit 1
+}
: "${SSH_KEY:?Variable SSH_KEY manquante dans .env}"
: "${BACKUP_REMOTE_SSH_PORT:=22}"
: "${SSH_CONNECT_TIMEOUT:=10}"
@@ -109,11 +113,14 @@ mkdir -p "$LOCAL_BACKUP"
#######################################
# Notification Discord
#######################################
-discord_ping() {
+send_discord() {
local success="$1"
local details="${2:-}"
+ local payload=""
[[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0
+ require_cmd jq || return 0
+ require_cmd curl || return 0
local icon status_line
if [[ "$success" == "true" ]]; then
@@ -132,7 +139,6 @@ discord_ping() {
msg+="Data transfer: ${status_line}\n"
[[ -n "$details" ]] && msg+="Détails: ${details}"
- local payload
payload="$(jq -n --arg content "$msg" '{content: $content}')"
curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true
}
@@ -143,7 +149,7 @@ discord_ping() {
fail() {
local detail="$1"
log "ERROR: $detail"
- discord_ping "false" "$detail"
+ send_discord "false" "$detail"
exit 1
}
@@ -151,6 +157,21 @@ require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
}
+#######################################
+# Verrou d'execution
+#######################################
+LOCK_DIR="/tmp/vaultwarden_backup.lock.d"
+
+if ! mkdir "$LOCK_DIR" 2>/dev/null; then
+ fail "Backup deja en cours"
+fi
+
+cleanup() {
+ rm -f "${LOCAL_BACKUP_FILE:-}"
+ rm -rf -- "$LOCK_DIR"
+}
+trap cleanup EXIT
+
#######################################
# Vérifications préalables
#######################################
@@ -209,5 +230,5 @@ rm -f "$LOCAL_BACKUP_FILE" || fail "Impossible de supprimer le backup local $LOC
# Fin
#######################################
log "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
-discord_ping "true" "Backup envoyé avec succès vers $REMOTE_HOST"
+send_discord "true" "Backup envoyé avec succès vers $REMOTE_HOST"
echo "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca87d16..f0ee94e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,20 @@ et applique le versionnement semantique.
## [Unreleased]
+### Changed
+- Harmonisation des fonctions d'envoi Discord dans les scripts de sauvegarde, de supervision et de reconstruction.
+- Ajout d'un fichier de log dedie a l'orchestrateur `RebuildBdd/run-rebuild-bdd.sh`.
+- Renforcement de la journalisation et de la gestion des erreurs dans `RecetteScripts/backup-bdd-recette.sh`.
+
+### Fixed
+- Nettoyage du backup local temporaire dans `BackupVaultWarden/backup-vaultwarden.sh` en sortie de script.
+- Gestion plus robuste des dependances shell et des verifications de disponibilite PostgreSQL dans les scripts `RebuildBdd`.
+- Acceptation explicite du flag `--non-interactive` dans `RebuildBdd/Checkup/check-target-readiness.sh` pour compatibilite de workflow.
+- Validation des noms de base et refus des cles SSH symboliques dans les scripts de reconstruction.
+- Suppression des notifications Discord fragiles quand `jq` ou `curl` sont absents, avec envoi silencieux en secours.
+- Detection et nettoyage des verrous perimes dans `RecetteScripts/backup-bdd-recette.sh`.
+- Filtrage des privileges `SUPERUSER` lors de la restauration des roles dans `RecetteScripts/rebuild-bdd-recette.sh`.
+
## [1.0.0] - 2026-03-18
### Added
diff --git a/CheckStorage/README.md b/CheckStorage/README.md
index 6f8f1a6..5f892cb 100644
--- a/CheckStorage/README.md
+++ b/CheckStorage/README.md
@@ -11,6 +11,37 @@ Le script :
3. compare le taux d’occupation au seuil configuré
4. envoie une alerte Discord si le seuil est dépassé
+
+
+ EggMaster
+
+
+
+ Question 3
+
+Quel operateur shell permet d'envoyer la sortie d'une commande vers la suivante ?
+
+
+
+
+ Indice commande 3
+
+```text
+|
+```
+
+
+
+
+ Fragment 3
+
+```text
+b3llciB2b2ljaSB1biBsaWVuIG1hZ2lxdWUgZW
+```
+
+
+
+
## Pré-requis
Installation recommandée sur Ubuntu Server :
diff --git a/CheckStorage/check-storage.sh b/CheckStorage/check-storage.sh
index 3084667..2e974bd 100755
--- a/CheckStorage/check-storage.sh
+++ b/CheckStorage/check-storage.sh
@@ -52,6 +52,20 @@ if [[ -n "${DISCORD_WEBHOOK_URL:-}" ]]; then
require_cmd curl
fi
+send_discord() {
+ local message="$1"
+ local payload=""
+
+ [[ -n "${DISCORD_WEBHOOK_URL:-}" ]] || return 0
+
+ payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
+
+ curl -fsS \
+ -H "Content-Type: application/json" \
+ -d "$payload" \
+ "$DISCORD_WEBHOOK_URL" >/dev/null || true
+}
+
###############################################################################
# RÉCUPÉRATION DES INFORMATIONS DISQUE
###############################################################################
@@ -77,15 +91,7 @@ if [ "$usage" -ge "$limit" ]; then
msgLimit="${DISCORD_PING}\n**CHECK STOCKAGE :red_circle:**\nLimite autorisée : ${limit}%\nUtilisation actuelle : ${usage}%\nEspace restant : ${free}%\nUtilisé / total : ${used_gb} GB / ${total_gb} GB\nDisponible : ${avail_gb} GB\nHeure : $(date)"
- if [[ -n "${DISCORD_WEBHOOK_URL:-}" ]]; then
- payload="$(jq -n --arg content "$msgLimit" '{content: $content}')"
-
- curl -fsS \
- -H "Accept: application/json" \
- -H "Content-Type: application/json; charset=utf-8" \
- -d "$payload" \
- "$DISCORD_WEBHOOK_URL" >/dev/null || true
- fi
+ send_discord "$msgLimit"
fi
diff --git a/README.md b/README.md
index 977fb67..555bd28 100644
--- a/README.md
+++ b/README.md
@@ -120,12 +120,20 @@ Un modèle est fourni :
global.env.exemple
```
+Ce fichier concerne la configuration legacy de `RecetteScripts`.
+
Utilisation :
```bash
cp global.env.exemple global.env
```
+Pour la configuration de `RebuildBdd`, voir la documentation dédiée :
+
+```bash
+RebuildBdd/README.md
+```
+
## Configuration locale
* Chaque module peut contenir son propre `.env`
@@ -185,3 +193,50 @@ Le dépôt est maintenant pensé prioritairement pour des cibles **Ubuntu Server
* `RecetteScripts` = **legacy en cours de migration**
* Objectif : convergence vers une **chaîne unique, robuste et automatisable (web/API)**
+---
+
+
+
+ EggMaster
+
+
+Un message est disperse dans les `README` du depot.
+
+Ordre de reconstruction :
+
+1. `README.md`
+2. `BackupVaultWarden/README.md`
+3. `CheckStorage/README.md`
+4. `RebuildBdd/README.md`
+5. `RecetteScripts/README.md`
+
+La commande de dechiffrement n'est pas donnee directement.
+Elle se reconstruit aussi via des questions cachees dans les `README`.
+
+
+ Question 1
+
+Quelle commande shell permet d'afficher exactement une chaine, sans interpretation particuliere, avant de la transmettre a une autre commande ?
+
+
+
+
+ Indice commande 1
+
+```text
+printf
+```
+
+
+
+
+ Fragment 1
+
+```text
+YmllbiB2dSB0dSBtJ2FzIHRyb3V2ZXIgbW9pIG
+```
+
+
+
+
+
diff --git a/RebuildBdd/Checkup/check-postgresql.sh b/RebuildBdd/Checkup/check-postgresql.sh
index e28de4b..61c27af 100755
--- a/RebuildBdd/Checkup/check-postgresql.sh
+++ b/RebuildBdd/Checkup/check-postgresql.sh
@@ -41,19 +41,19 @@ fail() {
exit 1
}
-require_cmd() {
+has_cmd() {
command -v "$1" >/dev/null 2>&1
}
postgres_server_ready() {
- require_cmd postgres || return 1
- require_cmd pg_ctlcluster || return 1
- require_cmd pg_lsclusters || return 1
+ has_cmd postgres || return 1
+ has_cmd pg_ctlcluster || return 1
+ has_cmd pg_lsclusters || return 1
return 0
}
ensure_postgres_cluster() {
- if ! require_cmd pg_lsclusters || ! require_cmd pg_createcluster; then
+ if ! has_cmd pg_lsclusters || ! has_cmd pg_createcluster; then
return 0
fi
@@ -66,7 +66,7 @@ ensure_postgres_cluster() {
version="$(find /etc/postgresql -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort -V | tail -n 1)"
fi
- if [[ -z "$version" ]] && require_cmd psql; then
+ if [[ -z "$version" ]] && has_cmd psql; then
version="$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1)"
fi
@@ -82,15 +82,15 @@ collect_postgres_diagnostics() {
if "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager >/dev/null 2>&1; then
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: OK; "
- elif require_cmd systemctl; then
+ elif has_cmd systemctl; then
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: $( "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager 2>/dev/null | tail -n 5 | tr '\n' ' ' ); "
fi
- if require_cmd pg_lsclusters; then
+ if has_cmd pg_lsclusters; then
diagnostics+="pg_lsclusters: $(pg_lsclusters --no-header 2>/dev/null | tr '\n' ' '); "
fi
- if require_cmd journalctl; then
+ if has_cmd journalctl; then
diagnostics+="journalctl: $( "$SUDO_BIN" journalctl -u "$POSTGRES_SERVICE_NAME" -n 10 --no-pager 2>/dev/null | tr '\n' ' ' ); "
fi
@@ -102,11 +102,11 @@ start_postgres_service() {
return 0
fi
- if require_cmd service && "$SUDO_BIN" service "$POSTGRES_SERVICE_NAME" start >/dev/null 2>&1; then
+ if has_cmd service && "$SUDO_BIN" service "$POSTGRES_SERVICE_NAME" start >/dev/null 2>&1; then
return 0
fi
- if require_cmd pg_lsclusters && require_cmd pg_ctlcluster; then
+ if has_cmd pg_lsclusters && has_cmd pg_ctlcluster; then
local version cluster
while read -r version cluster _; do
[[ -n "$version" && -n "$cluster" ]] || continue
@@ -137,10 +137,13 @@ 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}"
+read -r -a POSTGRES_PACKAGES <<< "$POSTGRES_PACKAGE_LIST"
+
+[[ "${#POSTGRES_PACKAGES[@]}" -gt 0 ]] || fail "POSTGRES_PACKAGE_LIST vide"
export PGPASSWORD
-if ! require_cmd "$SUDO_BIN"; then
+if ! has_cmd "$SUDO_BIN"; then
fail "sudo absent sur la cible"
fi
@@ -154,12 +157,12 @@ fi
POSTGRES_INSTALLED="no"
-if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb || ! postgres_server_ready; then
+if ! has_cmd psql || ! has_cmd pg_restore || ! has_cmd createdb || ! has_cmd dropdb || ! postgres_server_ready; 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" apt install -y "${POSTGRES_PACKAGES[@]}" >/dev/null 2>&1 || fail "échec de l'installation PostgreSQL"
POSTGRES_INSTALLED="yes"
log "Installation PostgreSQL terminée."
else
@@ -178,15 +181,17 @@ else
fi
log "Vérification de la disponibilité de PostgreSQL..."
+PG_READY=false
for _ in {1..20}; do
if "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
+ PG_READY=true
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 [[ "$PG_READY" != true ]]; then
fail "PostgreSQL ne répond pas correctement"
fi
diff --git a/RebuildBdd/Checkup/check-target-readiness.sh b/RebuildBdd/Checkup/check-target-readiness.sh
index 109fdb2..8dbd361 100755
--- a/RebuildBdd/Checkup/check-target-readiness.sh
+++ b/RebuildBdd/Checkup/check-target-readiness.sh
@@ -30,6 +30,7 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
--non-interactive)
+ # Flag accepté pour compatibilité avec les autres scripts du workflow.
NON_INTERACTIVE="yes"
shift
;;
@@ -92,6 +93,7 @@ to_bool_yes_no() {
v="${v,,}"
case "$v" in
yes|y|oui|o|true|1) echo "yes" ;;
+ # Valeur vide traitée comme "no" pour conserver le comportement historique.
no|n|non|false|0|"") echo "no" ;;
*) return 1 ;;
esac
diff --git a/RebuildBdd/Config/.env.exemple b/RebuildBdd/Config/.env.exemple
index ff66962..344a48b 100644
--- a/RebuildBdd/Config/.env.exemple
+++ b/RebuildBdd/Config/.env.exemple
@@ -8,18 +8,18 @@ RESTORE_ROLES=yes
# Dépôt scripts
GLOBAL_REPO_URL=git@gitea.example.tld:team/RebuildBdd.git
-GLOBAL_REPO_BRANCH=main
-
-# Backup central
-GLOBAL_BACKUP_REMOTE_USER=backup
-GLOBAL_BACKUP_REMOTE_HOST=192.168.1.60
-GLOBAL_BACKUP_REMOTE_PORT=22
-GLOBAL_BACKUP_REMOTE_BASE_DIR=/home/backup/backups
-
-# Clé SSH de lecture backup copiée sur les cibles
-GLOBAL_BACKUP_SSH_PRIVATE_KEY=/home/matteo/.ssh/id_ed25519_backup_readonly
-GLOBAL_BACKUP_SSH_PUBLIC_KEY=/home/matteo/.ssh/id_ed25519_backup_readonly.pub
-GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes
+GLOBAL_REPO_BRANCH=main
+
+# Backup central
+GLOBAL_BACKUP_REMOTE_USER=backup
+GLOBAL_BACKUP_REMOTE_HOST=
+GLOBAL_BACKUP_REMOTE_PORT=22
+GLOBAL_BACKUP_REMOTE_BASE_DIR=/home/backup/backups
+
+# Clé SSH de lecture backup copiée sur les cibles
+GLOBAL_BACKUP_SSH_PRIVATE_KEY=/home//.ssh/id_ed25519_backup_readonly
+GLOBAL_BACKUP_SSH_PUBLIC_KEY=/home//.ssh/id_ed25519_backup_readonly.pub
+GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes
# Defaults PostgreSQL
GLOBAL_PGHOST=127.0.0.1
@@ -35,4 +35,4 @@ GLOBAL_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
GLOBAL_AUTO_INSTALL_POSTGRES=yes
GLOBAL_AUTO_CREATE_PGUSER=yes
GLOBAL_PGUSER_SUPERUSER=no
-GLOBAL_AUTO_CONFIGURE_SUDOERS=no
\ No newline at end of file
+GLOBAL_AUTO_CONFIGURE_SUDOERS=no
diff --git a/RebuildBdd/Config/Targets/prod.env.exemple b/RebuildBdd/Config/Targets/prod.env.exemple
index a7d6263..cf29bb5 100644
--- a/RebuildBdd/Config/Targets/prod.env.exemple
+++ b/RebuildBdd/Config/Targets/prod.env.exemple
@@ -3,25 +3,25 @@
###############################################################################
# SSH bootstrap cible
-TARGET_HOST=192.168.1.60
+TARGET_HOST=
TARGET_PORT=22
-TARGET_BOOTSTRAP_USER=backup_liot
-TARGET_BOOTSTRAP_SSH_KEY=/home/matteo/.ssh/id_ed25519_target_prod
-TARGET_RUNTIME_USER=backup_liot
+TARGET_BOOTSTRAP_USER=
+TARGET_BOOTSTRAP_SSH_KEY=/home//.ssh/id_ed25519_target_prod
+TARGET_RUNTIME_USER=
# Bootstrap
TARGET_ENABLE_BOOTSTRAP=yes
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
# Repo local cible
-TARGET_REPO_DIR=/home/backup_liot/RebuildBdd
-TARGET_ENV_FILE=/home/backup_liot/RebuildBdd/.env
+TARGET_REPO_DIR=/home//RebuildBdd
+TARGET_ENV_FILE=/home//RebuildBdd/.env
# PostgreSQL cible
TARGET_ENV_NAME=PROD
TARGET_PGHOST=127.0.0.1
TARGET_PGPORT=5432
-TARGET_PGUSER=backup_liot
+TARGET_PGUSER=
TARGET_PGPASSWORD=change_me_pg_password
TARGET_DBS="sirh inventory ferme"
@@ -29,9 +29,9 @@ TARGET_DBS="sirh inventory ferme"
TARGET_BACKUP_SUBDIR=bdd-prod
# Logs / tmp / ssh cible
-TARGET_BACKUP_LOG_DIR=/home/backup_liot/logs/rebuild_bdd
-TARGET_LOCAL_RESTORE_BASE_DIR=/home/backup_liot/RebuildBdd/restore_tmp
-TARGET_SSH_KEY=/home/backup_liot/.ssh/id_ed25519_backup_readonly
+TARGET_BACKUP_LOG_DIR=/home//logs/rebuild_bdd
+TARGET_LOCAL_RESTORE_BASE_DIR=/home//RebuildBdd/restore_tmp
+TARGET_SSH_KEY=/home//.ssh/id_ed25519_backup_readonly
# Options cible
TARGET_REMOTE_ROLES_DIR_NAME=user
diff --git a/RebuildBdd/Config/Targets/test.env.exemple b/RebuildBdd/Config/Targets/test.env.exemple
index 3ca9e61..cb0be0e 100644
--- a/RebuildBdd/Config/Targets/test.env.exemple
+++ b/RebuildBdd/Config/Targets/test.env.exemple
@@ -3,25 +3,25 @@
###############################################################################
# SSH bootstrap cible
-TARGET_HOST=192.168.1.50
+TARGET_HOST=
TARGET_PORT=22
-TARGET_BOOTSTRAP_USER=backup_liot
-TARGET_BOOTSTRAP_SSH_KEY=/home/matteo/.ssh/id_ed25519_target_test
-TARGET_RUNTIME_USER=backup_liot
+TARGET_BOOTSTRAP_USER=
+TARGET_BOOTSTRAP_SSH_KEY=/home//.ssh/id_ed25519_target_test
+TARGET_RUNTIME_USER=
# Bootstrap
TARGET_ENABLE_BOOTSTRAP=yes
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
# Repo local cible
-TARGET_REPO_DIR=/home/backup_liot/RebuildBdd
-TARGET_ENV_FILE=/home/backup_liot/RebuildBdd/.env
+TARGET_REPO_DIR=/home//RebuildBdd
+TARGET_ENV_FILE=/home//RebuildBdd/.env
# PostgreSQL cible
TARGET_ENV_NAME=RECETTE
TARGET_PGHOST=127.0.0.1
TARGET_PGPORT=5432
-TARGET_PGUSER=backup_liot
+TARGET_PGUSER=
TARGET_PGPASSWORD=change_me_pg_password
TARGET_DBS="sirh inventory ferme"
@@ -29,9 +29,9 @@ TARGET_DBS="sirh inventory ferme"
TARGET_BACKUP_SUBDIR=bdd-recette
# Logs / tmp / ssh cible
-TARGET_BACKUP_LOG_DIR=/home/backup_liot/logs/rebuild_bdd
-TARGET_LOCAL_RESTORE_BASE_DIR=/home/backup_liot/RebuildBdd/restore_tmp
-TARGET_SSH_KEY=/home/backup_liot/.ssh/id_ed25519_backup_readonly
+TARGET_BACKUP_LOG_DIR=/home//logs/rebuild_bdd
+TARGET_LOCAL_RESTORE_BASE_DIR=/home//RebuildBdd/restore_tmp
+TARGET_SSH_KEY=/home//.ssh/id_ed25519_backup_readonly
# Options cible
TARGET_REMOTE_ROLES_DIR_NAME=user
diff --git a/RebuildBdd/README.md b/RebuildBdd/README.md
index 4013efb..48521f7 100644
--- a/RebuildBdd/README.md
+++ b/RebuildBdd/README.md
@@ -36,6 +36,38 @@ En pratique :
---
+
+
+ EggMaster
+
+
+
+ Question 4
+
+Quel utilitaire standard permet de decoder la chaine reconstituee ?
+
+
+
+
+ Indice commande 4
+
+```text
+base64
+```
+
+
+
+
+ Fragment 4
+
+```text
+4gcmVjb21wZW5zZSBodHRwczovL3d3dy55b3V0
+```
+
+
+
+
+
## Architecture
### Configuration
@@ -110,14 +142,14 @@ Usage :
```bash
./create-target-config.sh \
--target test \
- --host 192.168.1.50 \
+ --host \
--port 22 \
- --bootstrap-user backup_liot \
+ --bootstrap-user \
--bootstrap-key /home/user/.ssh/id_ed25519_target_test \
- --runtime-user backup_liot \
- --repo-dir /home/backup_liot/RebuildBdd \
+ --runtime-user \
+ --repo-dir /home//RebuildBdd \
--env-name RECETTE \
- --pguser backup_liot \
+ --pguser \
--pgpassword secret \
--dbs "sirh inventory ferme" \
--backup-subdir bdd-recette
@@ -443,7 +475,7 @@ Exemple :
"environment": "RECETTE",
"database": "sirh",
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump",
- "log_file": "/home/backup_liot/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
+ "log_file": "/home//logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
}
```
@@ -459,7 +491,7 @@ Exemple :
"environment": "RECETTE",
"database": "sirh",
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump",
- "log_file": "/home/backup_liot/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
+ "log_file": "/home//logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
}
```
@@ -500,7 +532,7 @@ TARGET_BACKUP_LOG_DIR
Exemple :
```bash
-/home/backup_liot/logs/rebuild_bdd/
+/home//logs/rebuild_bdd/
```
Le chemin du log est renvoyé dans le JSON final.
@@ -537,14 +569,14 @@ Avant mise en production, tester au minimum :
```bash
./create-target-config.sh \
--target test \
- --host 192.168.1.50 \
+ --host \
--port 22 \
- --bootstrap-user backup_liot \
- --bootstrap-key /home/matteo/.ssh/id_ed25519_target_test \
- --runtime-user backup_liot \
- --repo-dir /home/backup_liot/RebuildBdd \
+ --bootstrap-user \
+ --bootstrap-key /home//.ssh/id_ed25519_target_test \
+ --runtime-user \
+ --repo-dir /home//RebuildBdd \
--env-name RECETTE \
- --pguser backup_liot \
+ --pguser \
--pgpassword secret \
--dbs "sirh inventory ferme" \
--backup-subdir bdd-recette
@@ -578,5 +610,3 @@ Le projet permet désormais une utilisation :
* intégrée au web ;
avec préparation des cibles, exécution non interactive et retour JSON.
-
-```
diff --git a/RebuildBdd/bootstrap-target-host.sh b/RebuildBdd/bootstrap-target-host.sh
index e367c43..2ff9e9c 100755
--- a/RebuildBdd/bootstrap-target-host.sh
+++ b/RebuildBdd/bootstrap-target-host.sh
@@ -94,6 +94,7 @@ to_bool_yes_no() {
v="${v,,}"
case "$v" in
yes|y|oui|o|true|1) echo "yes" ;;
+ # Valeur vide traitée comme "no" pour conserver le comportement historique.
no|n|non|false|0|"") echo "no" ;;
*) return 1 ;;
esac
@@ -385,6 +386,13 @@ log "Correction des permissions SSH côté cible"
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SSH_PERMS_CMD" \
|| fail "échec de correction des permissions SSH sur la cible"
+STRICT_OPTION="yes"
+case "${TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE,,}" in
+ yes|y|oui|o|true|1) STRICT_OPTION="yes" ;;
+ no|n|non|false|0) STRICT_OPTION="no" ;;
+ *) fail "TARGET_BACKUP_KNOWN_HOSTS_STRICT invalide" ;;
+esac
+
REMOTE_KNOWN_HOSTS_CMD="
set -euo pipefail
@@ -406,13 +414,6 @@ log "Ajout du serveur de backup dans known_hosts côté cible"
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_KNOWN_HOSTS_CMD" \
|| fail "échec de préparation known_hosts sur la cible"
-STRICT_OPTION="yes"
-case "${TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE,,}" in
- yes|y|oui|o|true|1) STRICT_OPTION="yes" ;;
- no|n|non|false|0) STRICT_OPTION="no" ;;
- *) fail "TARGET_BACKUP_KNOWN_HOSTS_STRICT invalide" ;;
-esac
-
REMOTE_BACKUP_TEST_CMD="
set -euo pipefail
diff --git a/RebuildBdd/create-target-config.sh b/RebuildBdd/create-target-config.sh
index fe60867..63ad499 100644
--- a/RebuildBdd/create-target-config.sh
+++ b/RebuildBdd/create-target-config.sh
@@ -79,6 +79,7 @@ to_bool_yes_no() {
v="${v,,}"
case "$v" in
yes|y|oui|o|true|1) echo "yes" ;;
+ # Valeur vide traitée comme "no" pour conserver le comportement historique.
no|n|non|false|0|"") echo "no" ;;
*) return 1 ;;
esac
diff --git a/RebuildBdd/rebuild-bdd-core.sh b/RebuildBdd/rebuild-bdd-core.sh
index 0293402..25ed421 100755
--- a/RebuildBdd/rebuild-bdd-core.sh
+++ b/RebuildBdd/rebuild-bdd-core.sh
@@ -97,6 +97,10 @@ fail() {
}
require_cmd() {
+ command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
+}
+
+has_cmd() {
command -v "$1" >/dev/null 2>&1
}
@@ -121,6 +125,7 @@ to_bool_yes_no() {
v="${v,,}"
case "$v" in
yes|y|oui|o|true|1) echo "yes" ;;
+ # Valeur vide traitée comme "no" pour conserver le comportement historique.
no|n|non|false|0|"") echo "no" ;;
*) return 1 ;;
esac
@@ -136,6 +141,13 @@ sql_escape_literal() {
printf "%s" "$s"
}
+validate_db_name() {
+ local db_name="${1:-}"
+
+ [[ -n "$db_name" ]] || return 1
+ [[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1
+}
+
build_excluded_roles_regex() {
local roles_string="${1:-}"
local role
@@ -167,6 +179,22 @@ build_excluded_roles_regex() {
printf '%s' "$joined"
}
+send_discord() {
+ local message="$1"
+ local payload=""
+
+ [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
+ has_cmd jq || return 0
+ has_cmd curl || return 0
+
+ payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
+
+ curl -fsS "$DISCORD_WEBHOOK_URL" \
+ -H "Content-Type: application/json" \
+ -d "$payload" \
+ >/dev/null || true
+}
+
cleanup() {
rm -f \
"${LOCAL_DB_DUMP_FILE:-}" \
@@ -251,7 +279,7 @@ else
fi
for cmd in ssh scp psql pg_restore createdb dropdb python3 grep sed find basename curl; do
- require_cmd "$cmd" || fail "commande requise absente : $cmd"
+ require_cmd "$cmd"
done
CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh"
@@ -263,6 +291,7 @@ fi
[[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "clé SSH source backup non lisible : $SSH_KEY"
+[[ ! -L "$SSH_KEY" ]] || fail "clé SSH source backup ne doit pas être un lien symbolique : $SSH_KEY"
export PGPASSWORD
@@ -318,7 +347,7 @@ for candidate in "${DBS_ARRAY[@]}"; do
done
[[ -n "$DB" ]] || fail "base refusée : non présente dans DBS"
-[[ "$DB" =~ ^[a-zA-Z0-9_]+$ ]] || fail "nom de base invalide"
+validate_db_name "$DB" || fail "nom de base invalide"
log "Environnement : $ENV_NAME"
log "Base cible : $DB"
@@ -478,27 +507,13 @@ pg_restore \
"$LOCAL_DB_DUMP_FILE" \
>>"$LOG_FILE" 2>&1 || fail "échec restauration base ${DB}"
-send_discord_message() {
- local message="$1"
- local payload=""
-
- [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
-
- payload="$(python3 -c 'import json,sys; print(json.dumps({"content": sys.argv[1]}))' "$message")" || return 0
-
- curl -sS -X POST "$DISCORD_WEBHOOK_URL" \
- -H "Content-Type: application/json" \
- -d "$payload" \
- >/dev/null || true
-}
-
SUCCESS_MESSAGE="✅ REBUILD BDD ${ENV_NAME}
Base restaurée : ${DB}
Hôte PostgreSQL : ${PGHOST}:${PGPORT}
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
Log : ${LOG_FILE}"
-send_discord_message "$SUCCESS_MESSAGE"
+send_discord "$SUCCESS_MESSAGE"
log "Restauration terminée avec succès pour ${DB}"
print_json_and_exit "success" "restauration terminée avec succès" 0
diff --git a/RebuildBdd/run-rebuild-bdd.sh b/RebuildBdd/run-rebuild-bdd.sh
index cb8b91f..8b462ee 100755
--- a/RebuildBdd/run-rebuild-bdd.sh
+++ b/RebuildBdd/run-rebuild-bdd.sh
@@ -88,6 +88,7 @@ to_bool_yes_no() {
v="${v,,}"
case "$v" in
yes|y|oui|o|true|1) echo "yes" ;;
+ # Valeur vide traitée comme "no" pour conserver le comportement historique.
no|n|non|false|0|"") echo "no" ;;
*) return 1 ;;
esac
@@ -123,6 +124,10 @@ REQUESTED_DB="${CLI_DB:-${REQUESTED_DB:-}}"
ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}"
RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}"
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-$(date '+%Y%m%d%H%M%S')_$RANDOM}}"
+LOG_DIR="${RUN_REBUILD_BDD_LOG_DIR:-${SCRIPT_DIR}/logs}"
+mkdir -p "$LOG_DIR"
+LOG_FILE="${LOG_DIR}/run_rebuild_bdd_${REQUEST_ID}.log"
+exec > >(tee -a "$LOG_FILE") 2>&1
ALLOW_OVERWRITE="$(to_bool_yes_no "$ALLOW_OVERWRITE_RAW")" || fail "ALLOW_OVERWRITE invalide"
RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide"
diff --git a/RecetteScripts/README.md b/RecetteScripts/README.md
index ca78ea9..87e6972 100644
--- a/RecetteScripts/README.md
+++ b/RecetteScripts/README.md
@@ -289,12 +289,40 @@ CHECK APP RECETTE 🟢
```
---
+
+
+ EggMaster
+
+
+
+ Question 5
+
+Quelle option demande explicitement un decodage plutot qu'un encodage ?
+
+
+
+
+ Indice commande 5
+
+```text
+-d
+```
+
+
+
+
+ Fragment 5
+
+```text
+dWJlLmNvbS93YXRjaD92PWRRdzR3OVdnWGNR
+```
+
+
+
+
# 7. Script : rebuild-bdd-recette.sh
-Script :
-
-
## Objectif
Restaurer une base PostgreSQL à partir d’un dump distant.
diff --git a/RecetteScripts/backup-bdd-recette.sh b/RecetteScripts/backup-bdd-recette.sh
index 94cfc98..2743098 100755
--- a/RecetteScripts/backup-bdd-recette.sh
+++ b/RecetteScripts/backup-bdd-recette.sh
@@ -18,7 +18,8 @@ umask 077
# 6. exporte les rôles PostgreSQL ;
# 7. dump chaque base au format personnalisé PostgreSQL ;
# 8. transfère chaque fichier vers le serveur distant ;
-# 9. applique une rotation distante sur 10 jours ;
+# 9. applique une rotation distante selon BACKUP_RETENTION_DAYS
+# (10 jours par défaut) ;
# 10. envoie un bilan sur Discord :
# - 1 message global si tout est OK ;
# - en cas d’erreur partielle :
@@ -50,6 +51,10 @@ set +a
#######################################
: "${ENV_NAME:?Variable ENV_NAME manquante}"
+[[ "$ENV_NAME" =~ ^[a-zA-Z0-9_-]+$ ]] || {
+ echo "Variable ENV_NAME invalide : $ENV_NAME" >&2
+ exit 1
+}
: "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}"
@@ -58,6 +63,10 @@ set +a
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR manquante}"
+[[ "$BACKUP_REMOTE_DIR" =~ ^[a-zA-Z0-9/_.-]+$ ]] || {
+ echo "Variable BACKUP_REMOTE_DIR invalide : $BACKUP_REMOTE_DIR" >&2
+ exit 1
+}
: "${SSH_KEY:?Variable SSH_KEY manquante}"
: "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}"
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
@@ -87,7 +96,7 @@ for DB in "${DBS_ARRAY[@]}"; do
done
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
-RETENTION_DAYS=10
+RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-10}"
BACKUP_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
BACKUP_KNOWN_HOSTS_STRICT="${BACKUP_KNOWN_HOSTS_STRICT:-yes}"
BACKUP_KNOWN_HOSTS_FILE="${BACKUP_KNOWN_HOSTS_FILE:-${HOME}/.ssh/known_hosts}"
@@ -153,17 +162,22 @@ TS="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_DIR_NAME="backup_${TS}"
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
+exec > >(tee -a "$LOG_FILE") 2>&1
+
TMP_DIR="$(mktemp -d /tmp/pg_dump_XXXXXX)" || {
echo "ERROR: impossible de créer le dossier temporaire" >&2
exit 1
}
-exec > >(tee -a "$LOG_FILE") 2>&1
+log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
-log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; }
+fail() {
+ log "ERROR: $*"
+ exit 1
+}
require_cmd() {
- command -v "$1" >/dev/null 2>&1
+ command -v "$1" >/dev/null 2>&1 || fail "commande manquante : $1"
}
safe_remove_dir() {
@@ -183,10 +197,7 @@ export PGPASSWORD
#######################################
for cmd in ssh scp curl jq pg_dump pg_dumpall mktemp; do
- require_cmd "$cmd" || {
- echo "ERROR: commande manquante : $cmd" >&2
- exit 1
- }
+ require_cmd "$cmd"
done
[[ -f "$SSH_KEY" ]] || {
@@ -213,14 +224,14 @@ chmod 600 "$SSH_KEY" || true
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
DISCORD_PING="${DISCORD_PING:-@here}"
-discord_send() {
+send_discord() {
local msg="$1"
+ local payload
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
- local payload
payload="$(jq -n --arg content "$msg" '{content: $content}')" || {
log "ERROR: impossible de construire le payload JSON Discord"
- return 1
+ return 0
}
curl -fsS \
@@ -242,7 +253,7 @@ Dumps transfer: ✅
Users transfer: ✅
EOF
)"
- discord_send "$msg"
+ send_discord "$msg"
}
#######################################
@@ -256,7 +267,7 @@ discord_msg_users_ok_simple() {
Users backup validé
EOF
)"
- discord_send "$msg"
+ send_discord "$msg"
}
discord_msg_users_error() {
@@ -288,7 +299,7 @@ EOF
)"
fi
- discord_send "$msg"
+ send_discord "$msg"
}
#######################################
@@ -303,7 +314,7 @@ discord_msg_db_ok_simple() {
Backup validé : ${db}
EOF
)"
- discord_send "$msg"
+ send_discord "$msg"
}
discord_msg_db_error() {
@@ -338,7 +349,7 @@ EOF
)"
fi
- discord_send "$msg"
+ send_discord "$msg"
}
#######################################
@@ -361,11 +372,36 @@ declare -A DB_DETAILS
#######################################
LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d"
+LOCK_PID_FILE="${LOCK_DIR}/pid"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
- log "ERROR: Backup déjà en cours"
- discord_msg_users_error "" "" "Lock already exists"
- exit 1
+ stale_lock="no"
+ existing_pid=""
+
+ if [[ -f "$LOCK_PID_FILE" ]]; then
+ existing_pid="$(<"$LOCK_PID_FILE")"
+ fi
+
+ if [[ "$existing_pid" =~ ^[0-9]+$ ]] && kill -0 "$existing_pid" 2>/dev/null; then
+ log "ERROR: Backup déjà en cours (PID ${existing_pid})"
+ discord_msg_users_error "" "" "Lock already exists (PID ${existing_pid})"
+ exit 1
+ fi
+
+ stale_lock="yes"
+ log "WARNING: lock périmé détecté, nettoyage en cours"
+ rm -rf -- "$LOCK_DIR"
+
+ mkdir "$LOCK_DIR" 2>/dev/null || fail "impossible de recréer le lock après nettoyage"
+fi
+
+echo $$ > "$LOCK_PID_FILE" || {
+ rm -rf -- "$LOCK_DIR"
+ fail "impossible d'écrire le PID du lock"
+}
+
+if [[ "${stale_lock:-no}" == "yes" ]]; then
+ log "Lock périmé nettoyé."
fi
cleanup() {
@@ -397,18 +433,18 @@ fi
ROLES_FILE="${TMP_DIR}/user_${TS}.sql"
-set +e
-
log "Export des rôles PostgreSQL"
-pg_dumpall \
+if pg_dumpall \
-h "$PGHOST" \
-p "$PGPORT" \
-U "$PGUSER" \
--globals-only \
- > "$ROLES_FILE"
-
-RET=$?
+ > "$ROLES_FILE"; then
+ RET=0
+else
+ RET=$?
+fi
if [[ $RET -ne 0 ]]; then
USERS_OK=
@@ -419,8 +455,11 @@ else
fi
if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
- scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/"
- RET=$?
+ if scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/"; then
+ RET=0
+ else
+ RET=$?
+ fi
if [[ $RET -ne 0 ]]; then
USERS_OK=
@@ -435,14 +474,10 @@ if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
fi
fi
-set -e
-
#######################################
# Dump des bases
#######################################
-set +e
-
for DB in "${DBS_ARRAY[@]}"; do
FILE="${TMP_DIR}/${DB}_${TS}.dump"
@@ -452,8 +487,11 @@ for DB in "${DBS_ARRAY[@]}"; do
log "Dump $DB"
- pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"
- RET=$?
+ if pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"; then
+ RET=0
+ else
+ RET=$?
+ fi
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
@@ -463,8 +501,11 @@ for DB in "${DBS_ARRAY[@]}"; do
continue
fi
- scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/"
- RET=$?
+ if scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/"; then
+ RET=0
+ else
+ RET=$?
+ fi
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
@@ -473,18 +514,17 @@ for DB in "${DBS_ARRAY[@]}"; do
fi
done
-set -e
-
#######################################
# Rotation distante
#######################################
log "Starting remote rotation: delete backups older than ${RETENTION_DAYS} days"
-set +e
-
-ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"
-RET=$?
+if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"; then
+ RET=0
+else
+ RET=$?
+fi
if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for users"
@@ -493,8 +533,11 @@ else
fi
for DB in "${DBS_ARRAY[@]}"; do
- ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"
- RET=$?
+ if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"; then
+ RET=0
+ else
+ RET=$?
+ fi
if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for ${DB}"
@@ -503,8 +546,6 @@ for DB in "${DBS_ARRAY[@]}"; do
fi
done
-set -e
-
log "Remote rotation finished"
#######################################
diff --git a/RecetteScripts/check-statut-recette.sh b/RecetteScripts/check-statut-recette.sh
index 6791028..a86d2ba 100755
--- a/RecetteScripts/check-statut-recette.sh
+++ b/RecetteScripts/check-statut-recette.sh
@@ -44,13 +44,23 @@ set +a
: "${CHECK_MAX_TIME:?Variable CHECK_MAX_TIME manquante}"
: "${APP_URLS:?Variable APP_URLS manquante}"
+[[ "$CHECK_CONNECT_TIMEOUT" =~ ^[0-9]+$ ]] || {
+ echo "ERROR: Variable CHECK_CONNECT_TIMEOUT invalide" >&2
+ exit 1
+}
+
+[[ "$CHECK_MAX_TIME" =~ ^[0-9]+$ ]] || {
+ echo "ERROR: Variable CHECK_MAX_TIME invalide" >&2
+ exit 1
+}
+
#######################################
# Sites à vérifier
#######################################
read -r -a SITES <<< "$APP_URLS"
-SCHEME="http"
+SCHEME="${APP_SCHEME:-http}"
CONNECT_TIMEOUT="${CHECK_CONNECT_TIMEOUT}"
MAX_TIME="${CHECK_MAX_TIME}"
@@ -75,6 +85,16 @@ DISCORD_PING="${DISCORD_PING:-@here}"
SUMMARY_LINES=()
FAILURES=0
+TMPFILES=()
+
+cleanup() {
+ local tmpfile
+ for tmpfile in "${TMPFILES[@]}"; do
+ [[ -n "$tmpfile" ]] || continue
+ rm -f -- "$tmpfile"
+ done
+}
+trap cleanup EXIT
#######################################
# Logging
@@ -115,8 +135,21 @@ add_summary_line() {
#######################################
# Envoi du message Discord récapitulatif
#######################################
-send_discord_summary() {
+should_send_discord() {
+ if [[ "$FAILURES" -gt 0 ]]; then
+ return 0
+ fi
+
+ local current_hour current_minute
+ current_hour="$(date +'%H')"
+ current_minute="$(date +'%M')"
+
+ [[ "$current_hour" == "19" && "$current_minute" -ge 0 && "$current_minute" -le 4 ]]
+}
+
+send_discord() {
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
+ should_send_discord || return 0
local header_icon ping_prefix=""
if [[ "$FAILURES" -eq 0 ]]; then
@@ -134,7 +167,7 @@ send_discord_summary() {
done
local payload
- payload="$(jq -n --arg content "$msg" '{content: $content}')"
+ payload="$(jq -n --arg content "$msg" '{content: $content}')" || return 0
curl -fsS -H "Content-Type: application/json" \
-d "$payload" \
@@ -158,6 +191,7 @@ check_site() {
local http_code curl_exit err
local stderr
stderr="$(mktemp)"
+ TMPFILES+=("$stderr")
http_code="$(
curl -sS -o /dev/null \
@@ -170,15 +204,12 @@ check_site() {
if [[ "$curl_exit" -ne 0 ]]; then
err="$(head -n 1 "$stderr" | tr -d '\r')"
- rm -f "$stderr"
log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}"
add_summary_line "$host" "DOWN" "DOWN - curl"
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"
@@ -210,7 +241,7 @@ main() {
done
FAILURES="$failures"
- send_discord_summary
+ send_discord
if [[ "$failures" -gt 0 ]]; then
exit 2
diff --git a/RecetteScripts/rebuild-bdd-recette.sh b/RecetteScripts/rebuild-bdd-recette.sh
index 03af19c..ac7b107 100644
--- a/RecetteScripts/rebuild-bdd-recette.sh
+++ b/RecetteScripts/rebuild-bdd-recette.sh
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
+umask 077
###############################################################################
# rebuild-bdd-recette.sh
@@ -49,6 +50,10 @@ set +a
# Variables obligatoires
###############################################################################
: "${ENV_NAME:?Variable ENV_NAME manquante}"
+[[ "$ENV_NAME" =~ ^[a-zA-Z0-9_-]+$ ]] || {
+ echo "Variable ENV_NAME invalide : $ENV_NAME" >&2
+ exit 1
+}
: "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}"
@@ -110,10 +115,15 @@ cleanup() {
"${FILTERED_ROLES_FILE:-}" \
"${ROLES_CREATE_LIST:-}" \
"${ROLES_APPLY_FILE:-}"
+ rm -rf "${LOCAL_RESTORE_DIR:-}" 2>/dev/null || true
}
trap cleanup EXIT
require_cmd() {
+ command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
+}
+
+has_cmd() {
command -v "$1" >/dev/null 2>&1
}
@@ -124,11 +134,10 @@ sql_escape_literal() {
}
validate_db_name() {
- local db_name="$1"
+ local db_name="${1:-}"
- [[ -n "$db_name" ]] || fail "nom de base vide"
- [[ "$db_name" =~ ^[A-Za-z0-9_]+$ ]] || \
- fail "nom de base invalide : seuls les lettres, chiffres et underscores sont autorisés"
+ [[ -n "$db_name" ]] || return 1
+ [[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1
}
build_excluded_roles_regex() {
@@ -150,31 +159,22 @@ build_excluded_roles_regex() {
# Envoi Discord
#
# Envoi simple d'un message texte via webhook Discord.
-# Si WEBHOOK_URL n'est pas défini, on ignore silencieusement l'envoi.
+# Si DISCORD_WEBHOOK_URL n'est pas défini, on ignore silencieusement l'envoi.
###############################################################################
-send_discord_message() {
+send_discord() {
local message="$1"
local payload=""
- [[ -n "$DISCORD_WEBHOOK_URL" ]] || {
- log "WEBHOOK_URL non défini : notification Discord ignorée."
- return 0
- }
+ [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
+ has_cmd jq || return 0
+ has_cmd curl || return 0
- if ! require_cmd curl; then
- log "curl absent : notification Discord ignorée."
- return 0
- fi
+ payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
- payload="$(jq -n --arg content "$message" '{content: $content}')" || {
- log "Impossible de construire le payload JSON Discord."
- return 0
- }
-
- curl -sS -X POST "$DISCORD_WEBHOOK_URL" \
+ curl -fsS "$DISCORD_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$payload" \
- >/dev/null || log "Échec d'envoi de la notification Discord."
+ >/dev/null || true
}
###############################################################################
@@ -182,6 +182,7 @@ send_discord_message() {
###############################################################################
[[ -f "$SSH_KEY" ]] || fail "clé SSH introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "clé SSH non lisible : $SSH_KEY"
+[[ ! -L "$SSH_KEY" ]] || fail "clé SSH ne doit pas être un lien symbolique : $SSH_KEY"
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
[[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || fail "PGUSER invalide"
@@ -215,7 +216,7 @@ REMOTE_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
###############################################################################
POSTGRES_INSTALLED=false
-if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb; then
+if ! has_cmd psql || ! has_cmd pg_restore || ! has_cmd createdb || ! has_cmd dropdb; then
log "PostgreSQL absent : installation en cours..."
sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update"
@@ -242,15 +243,17 @@ fi
# Attente disponibilité PostgreSQL
###############################################################################
log "Vérification de la disponibilité de PostgreSQL..."
+PG_READY=false
for _ in {1..20}; do
if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
+ PG_READY=true
log "PostgreSQL répond correctement."
break
fi
sleep 1
done
-if ! sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
+if [[ "$PG_READY" != true ]]; then
fail "PostgreSQL ne répond pas correctement"
fi
@@ -297,7 +300,7 @@ else
read -r -p "Nom exact de la base à restaurer : " DB
fi
-validate_db_name "$DB"
+validate_db_name "$DB" || fail "nom de base invalide"
log "Environnement : $ENV_NAME"
log "Base cible sélectionnée : $DB"
@@ -418,6 +421,8 @@ if [[ -n "$LOCAL_ROLES_FILE" ]]; then
cp "$LOCAL_ROLES_FILE" "$FILTERED_ROLES_FILE"
fi
+ sed -i -E '/^ALTER ROLE .* (NO)?SUPERUSER\b/d' "$FILTERED_ROLES_FILE"
+
log "Fichier des rôles filtré généré : ${FILTERED_ROLES_FILE}"
sed -nE 's/^CREATE ROLE "?([^" ;]+)"?;$/\1/p' "$FILTERED_ROLES_FILE" \
@@ -498,4 +503,4 @@ Hôte PostgreSQL : ${PGHOST}:${PGPORT}
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
Log : ${LOG_FILE}"
-send_discord_message "$SUCCESS_MESSAGE"
+send_discord "$SUCCESS_MESSAGE"
diff --git a/global.env.exemple b/global.env.exemple
index 1088e64..f531fe6 100644
--- a/global.env.exemple
+++ b/global.env.exemple
@@ -89,6 +89,7 @@ SSH_TIMEOUT=10
#############################################
# Nombre de jours de conservation des sauvegardes
+# Utilisé par backup-bdd-recette.sh et backup-vaultwarden.sh
BACKUP_RETENTION_DAYS=10
@@ -96,12 +97,11 @@ BACKUP_RETENTION_DAYS=10
# APPLICATIONS À SURVEILLER
#############################################
-# Liste des applications à vérifier
-APPS="
-ferme.malio-dev.fr
-inventory.malio-dev.fr
-sirh.malio-dev.fr
-"
+# Liste des applications à vérifier (séparées par espace)
+APP_URLS="ferme.malio-dev.fr inventory.malio-dev.fr sirh.malio-dev.fr"
+
+# Schéma utilisé pour les applications surveillées
+APP_SCHEME="http"
#############################################