Compare commits

5 Commits

21 changed files with 512 additions and 190 deletions

View File

@@ -101,6 +101,38 @@ REMOTE_DIR=/home/backup/backups/vaultwarden
# 5. Chargement des variables dans le script # 5. Chargement des variables dans le script
<details>
<summary style="list-style: none; cursor: pointer;">
<strong>EggMaster</strong>
</summary>
<details>
<summary style="list-style: none; cursor: pointer;">Question 2</summary>
Quel format minimal faut-il donner a `printf` pour afficher une chaine brute ?
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Indice commande 2</summary>
```text
'%s'
```
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Fragment 2</summary>
```text
xlIHBldGl0IHN0YWdpYWlyZSBtYXR0ZW8gZHVu
```
</details>
</details>
Le script charge directement le fichier `.env` avec `source` et exporte automatiquement les variables pendant le chargement. Le script charge directement le fichier `.env` avec `source` et exporte automatiquement les variables pendant le chargement.
Mécanisme utilisé : Mécanisme utilisé :
@@ -291,7 +323,7 @@ crontab -e
Ajouter : Ajouter :
```bash ```bash
0 19 * * * /home/<USER>/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh >> /var/log/vaultwarden_backup.log 2>&1 0 19 * * * /home/<USER>/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh 2>&1
``` ```
Signification : Signification :
@@ -353,4 +385,3 @@ Le script automatise :
Ce système permet dobtenir **une sauvegarde fiable, centralisée et surveillée de Vaultwarden**. Ce système permet dobtenir **une sauvegarde fiable, centralisée et surveillée de Vaultwarden**.
```

View File

@@ -41,6 +41,10 @@ set +a
: "${REMOTE_USER:?Variable REMOTE_USER manquante dans .env}" : "${REMOTE_USER:?Variable REMOTE_USER manquante dans .env}"
: "${REMOTE_HOST:?Variable REMOTE_HOST manquante dans .env}" : "${REMOTE_HOST:?Variable REMOTE_HOST manquante dans .env}"
: "${REMOTE_DIR:?Variable REMOTE_DIR 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}" : "${SSH_KEY:?Variable SSH_KEY manquante dans .env}"
: "${BACKUP_REMOTE_SSH_PORT:=22}" : "${BACKUP_REMOTE_SSH_PORT:=22}"
: "${SSH_CONNECT_TIMEOUT:=10}" : "${SSH_CONNECT_TIMEOUT:=10}"
@@ -109,11 +113,14 @@ mkdir -p "$LOCAL_BACKUP"
####################################### #######################################
# Notification Discord # Notification Discord
####################################### #######################################
discord_ping() { send_discord() {
local success="$1" local success="$1"
local details="${2:-}" local details="${2:-}"
local payload=""
[[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0 [[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0
require_cmd jq || return 0
require_cmd curl || return 0
local icon status_line local icon status_line
if [[ "$success" == "true" ]]; then if [[ "$success" == "true" ]]; then
@@ -132,7 +139,6 @@ discord_ping() {
msg+="Data transfer: ${status_line}\n" msg+="Data transfer: ${status_line}\n"
[[ -n "$details" ]] && msg+="Détails: ${details}" [[ -n "$details" ]] && msg+="Détails: ${details}"
local payload
payload="$(jq -n --arg content "$msg" '{content: $content}')" payload="$(jq -n --arg content "$msg" '{content: $content}')"
curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true
} }
@@ -143,7 +149,7 @@ discord_ping() {
fail() { fail() {
local detail="$1" local detail="$1"
log "ERROR: $detail" log "ERROR: $detail"
discord_ping "false" "$detail" send_discord "false" "$detail"
exit 1 exit 1
} }
@@ -151,6 +157,21 @@ require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1" 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 # Vérifications préalables
####################################### #######################################
@@ -209,5 +230,5 @@ rm -f "$LOCAL_BACKUP_FILE" || fail "Impossible de supprimer le backup local $LOC
# Fin # Fin
####################################### #######################################
log "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR" 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" echo "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"

View File

@@ -5,6 +5,20 @@ et applique le versionnement semantique.
## [Unreleased] ## [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 ## [1.0.0] - 2026-03-18
### Added ### Added

View File

@@ -11,6 +11,37 @@ Le script :
3. compare le taux doccupation au seuil configuré 3. compare le taux doccupation au seuil configuré
4. envoie une alerte Discord si le seuil est dépassé 4. envoie une alerte Discord si le seuil est dépassé
<details>
<summary style="list-style: none; cursor: pointer;">
<strong>EggMaster</strong>
</summary>
<details>
<summary style="list-style: none; cursor: pointer;">Question 3</summary>
Quel operateur shell permet d'envoyer la sortie d'une commande vers la suivante ?
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Indice commande 3</summary>
```text
|
```
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Fragment 3</summary>
```text
b3llciB2b2ljaSB1biBsaWVuIG1hZ2lxdWUgZW
```
</details>
</details>
## Pré-requis ## Pré-requis
Installation recommandée sur Ubuntu Server : Installation recommandée sur Ubuntu Server :

View File

@@ -52,6 +52,20 @@ if [[ -n "${DISCORD_WEBHOOK_URL:-}" ]]; then
require_cmd curl require_cmd curl
fi 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 # 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)" 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 send_discord "$msgLimit"
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
fi fi

View File

@@ -120,12 +120,20 @@ Un modèle est fourni :
global.env.exemple global.env.exemple
``` ```
Ce fichier concerne la configuration legacy de `RecetteScripts`.
Utilisation : Utilisation :
```bash ```bash
cp global.env.exemple global.env cp global.env.exemple global.env
``` ```
Pour la configuration de `RebuildBdd`, voir la documentation dédiée :
```bash
RebuildBdd/README.md
```
## Configuration locale ## Configuration locale
* Chaque module peut contenir son propre `.env` * 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** * `RecetteScripts` = **legacy en cours de migration**
* Objectif : convergence vers une **chaîne unique, robuste et automatisable (web/API)** * Objectif : convergence vers une **chaîne unique, robuste et automatisable (web/API)**
---
<details>
<summary style="list-style: none; cursor: pointer;">
<strong>EggMaster</strong>
</summary>
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`.
<details>
<summary style="list-style: none; cursor: pointer;">Question 1</summary>
Quelle commande shell permet d'afficher exactement une chaine, sans interpretation particuliere, avant de la transmettre a une autre commande ?
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Indice commande 1</summary>
```text
printf
```
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Fragment 1</summary>
```text
YmllbiB2dSB0dSBtJ2FzIHRyb3V2ZXIgbW9pIG
```
</details>
</details>

View File

@@ -41,19 +41,19 @@ fail() {
exit 1 exit 1
} }
require_cmd() { has_cmd() {
command -v "$1" >/dev/null 2>&1 command -v "$1" >/dev/null 2>&1
} }
postgres_server_ready() { postgres_server_ready() {
require_cmd postgres || return 1 has_cmd postgres || return 1
require_cmd pg_ctlcluster || return 1 has_cmd pg_ctlcluster || return 1
require_cmd pg_lsclusters || return 1 has_cmd pg_lsclusters || return 1
return 0 return 0
} }
ensure_postgres_cluster() { 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 return 0
fi 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)" version="$(find /etc/postgresql -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort -V | tail -n 1)"
fi 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)" version="$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1)"
fi fi
@@ -82,15 +82,15 @@ collect_postgres_diagnostics() {
if "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager >/dev/null 2>&1; then if "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager >/dev/null 2>&1; then
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: OK; " 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' ' ' ); " diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: $( "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager 2>/dev/null | tail -n 5 | tr '\n' ' ' ); "
fi 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' ' '); " diagnostics+="pg_lsclusters: $(pg_lsclusters --no-header 2>/dev/null | tr '\n' ' '); "
fi 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' ' ' ); " diagnostics+="journalctl: $( "$SUDO_BIN" journalctl -u "$POSTGRES_SERVICE_NAME" -n 10 --no-pager 2>/dev/null | tr '\n' ' ' ); "
fi fi
@@ -102,11 +102,11 @@ start_postgres_service() {
return 0 return 0
fi 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 return 0
fi fi
if require_cmd pg_lsclusters && require_cmd pg_ctlcluster; then if has_cmd pg_lsclusters && has_cmd pg_ctlcluster; then
local version cluster local version cluster
while read -r version cluster _; do while read -r version cluster _; do
[[ -n "$version" && -n "$cluster" ]] || continue [[ -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_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}"
read -r -a POSTGRES_PACKAGES <<< "$POSTGRES_PACKAGE_LIST"
[[ "${#POSTGRES_PACKAGES[@]}" -gt 0 ]] || fail "POSTGRES_PACKAGE_LIST vide"
export PGPASSWORD export PGPASSWORD
if ! require_cmd "$SUDO_BIN"; then if ! has_cmd "$SUDO_BIN"; then
fail "sudo absent sur la cible" fail "sudo absent sur la cible"
fi fi
@@ -154,12 +157,12 @@ fi
POSTGRES_INSTALLED="no" 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" [[ "${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" 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" POSTGRES_INSTALLED="yes"
log "Installation PostgreSQL terminée." log "Installation PostgreSQL terminée."
else else
@@ -178,15 +181,17 @@ else
fi fi
log "Vérification de la disponibilité de PostgreSQL..." log "Vérification de la disponibilité de PostgreSQL..."
PG_READY=false
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" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
PG_READY=true
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 [[ "$PG_READY" != true ]]; then
fail "PostgreSQL ne répond pas correctement" fail "PostgreSQL ne répond pas correctement"
fi fi

View File

@@ -30,6 +30,7 @@ while [[ $# -gt 0 ]]; do
shift 2 shift 2
;; ;;
--non-interactive) --non-interactive)
# Flag accepté pour compatibilité avec les autres scripts du workflow.
NON_INTERACTIVE="yes" NON_INTERACTIVE="yes"
shift shift
;; ;;
@@ -92,6 +93,7 @@ to_bool_yes_no() {
v="${v,,}" v="${v,,}"
case "$v" in case "$v" in
yes|y|oui|o|true|1) echo "yes" ;; 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" ;; no|n|non|false|0|"") echo "no" ;;
*) return 1 ;; *) return 1 ;;
esac esac

View File

@@ -8,18 +8,18 @@ RESTORE_ROLES=yes
# Dépôt scripts # Dépôt scripts
GLOBAL_REPO_URL=git@gitea.example.tld:team/RebuildBdd.git GLOBAL_REPO_URL=git@gitea.example.tld:team/RebuildBdd.git
GLOBAL_REPO_BRANCH=main GLOBAL_REPO_BRANCH=main
# Backup central # Backup central
GLOBAL_BACKUP_REMOTE_USER=backup GLOBAL_BACKUP_REMOTE_USER=backup
GLOBAL_BACKUP_REMOTE_HOST=192.168.1.60 GLOBAL_BACKUP_REMOTE_HOST=<BACKUP_HOST>
GLOBAL_BACKUP_REMOTE_PORT=22 GLOBAL_BACKUP_REMOTE_PORT=22
GLOBAL_BACKUP_REMOTE_BASE_DIR=/home/backup/backups GLOBAL_BACKUP_REMOTE_BASE_DIR=/home/backup/backups
# Clé SSH de lecture backup copiée sur les cibles # 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_PRIVATE_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_backup_readonly
GLOBAL_BACKUP_SSH_PUBLIC_KEY=/home/matteo/.ssh/id_ed25519_backup_readonly.pub GLOBAL_BACKUP_SSH_PUBLIC_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_backup_readonly.pub
GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes
# Defaults PostgreSQL # Defaults PostgreSQL
GLOBAL_PGHOST=127.0.0.1 GLOBAL_PGHOST=127.0.0.1
@@ -35,4 +35,4 @@ GLOBAL_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
GLOBAL_AUTO_INSTALL_POSTGRES=yes GLOBAL_AUTO_INSTALL_POSTGRES=yes
GLOBAL_AUTO_CREATE_PGUSER=yes GLOBAL_AUTO_CREATE_PGUSER=yes
GLOBAL_PGUSER_SUPERUSER=no GLOBAL_PGUSER_SUPERUSER=no
GLOBAL_AUTO_CONFIGURE_SUDOERS=no GLOBAL_AUTO_CONFIGURE_SUDOERS=no

View File

@@ -3,25 +3,25 @@
############################################################################### ###############################################################################
# SSH bootstrap cible # SSH bootstrap cible
TARGET_HOST=192.168.1.60 TARGET_HOST=<TARGET_HOST>
TARGET_PORT=22 TARGET_PORT=22
TARGET_BOOTSTRAP_USER=backup_liot TARGET_BOOTSTRAP_USER=<BOOTSTRAP_USER>
TARGET_BOOTSTRAP_SSH_KEY=/home/matteo/.ssh/id_ed25519_target_prod TARGET_BOOTSTRAP_SSH_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_target_prod
TARGET_RUNTIME_USER=backup_liot TARGET_RUNTIME_USER=<RUNTIME_USER>
# Bootstrap # Bootstrap
TARGET_ENABLE_BOOTSTRAP=yes TARGET_ENABLE_BOOTSTRAP=yes
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
# Repo local cible # Repo local cible
TARGET_REPO_DIR=/home/backup_liot/RebuildBdd TARGET_REPO_DIR=/home/<RUNTIME_USER>/RebuildBdd
TARGET_ENV_FILE=/home/backup_liot/RebuildBdd/.env TARGET_ENV_FILE=/home/<RUNTIME_USER>/RebuildBdd/.env
# PostgreSQL cible # PostgreSQL cible
TARGET_ENV_NAME=PROD TARGET_ENV_NAME=PROD
TARGET_PGHOST=127.0.0.1 TARGET_PGHOST=127.0.0.1
TARGET_PGPORT=5432 TARGET_PGPORT=5432
TARGET_PGUSER=backup_liot TARGET_PGUSER=<PGUSER>
TARGET_PGPASSWORD=change_me_pg_password TARGET_PGPASSWORD=change_me_pg_password
TARGET_DBS="sirh inventory ferme" TARGET_DBS="sirh inventory ferme"
@@ -29,9 +29,9 @@ TARGET_DBS="sirh inventory ferme"
TARGET_BACKUP_SUBDIR=bdd-prod TARGET_BACKUP_SUBDIR=bdd-prod
# Logs / tmp / ssh cible # Logs / tmp / ssh cible
TARGET_BACKUP_LOG_DIR=/home/backup_liot/logs/rebuild_bdd TARGET_BACKUP_LOG_DIR=/home/<RUNTIME_USER>/logs/rebuild_bdd
TARGET_LOCAL_RESTORE_BASE_DIR=/home/backup_liot/RebuildBdd/restore_tmp TARGET_LOCAL_RESTORE_BASE_DIR=/home/<RUNTIME_USER>/RebuildBdd/restore_tmp
TARGET_SSH_KEY=/home/backup_liot/.ssh/id_ed25519_backup_readonly TARGET_SSH_KEY=/home/<RUNTIME_USER>/.ssh/id_ed25519_backup_readonly
# Options cible # Options cible
TARGET_REMOTE_ROLES_DIR_NAME=user TARGET_REMOTE_ROLES_DIR_NAME=user

View File

@@ -3,25 +3,25 @@
############################################################################### ###############################################################################
# SSH bootstrap cible # SSH bootstrap cible
TARGET_HOST=192.168.1.50 TARGET_HOST=<TARGET_HOST>
TARGET_PORT=22 TARGET_PORT=22
TARGET_BOOTSTRAP_USER=backup_liot TARGET_BOOTSTRAP_USER=<BOOTSTRAP_USER>
TARGET_BOOTSTRAP_SSH_KEY=/home/matteo/.ssh/id_ed25519_target_test TARGET_BOOTSTRAP_SSH_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_target_test
TARGET_RUNTIME_USER=backup_liot TARGET_RUNTIME_USER=<RUNTIME_USER>
# Bootstrap # Bootstrap
TARGET_ENABLE_BOOTSTRAP=yes TARGET_ENABLE_BOOTSTRAP=yes
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
# Repo local cible # Repo local cible
TARGET_REPO_DIR=/home/backup_liot/RebuildBdd TARGET_REPO_DIR=/home/<RUNTIME_USER>/RebuildBdd
TARGET_ENV_FILE=/home/backup_liot/RebuildBdd/.env TARGET_ENV_FILE=/home/<RUNTIME_USER>/RebuildBdd/.env
# PostgreSQL cible # PostgreSQL cible
TARGET_ENV_NAME=RECETTE TARGET_ENV_NAME=RECETTE
TARGET_PGHOST=127.0.0.1 TARGET_PGHOST=127.0.0.1
TARGET_PGPORT=5432 TARGET_PGPORT=5432
TARGET_PGUSER=backup_liot TARGET_PGUSER=<PGUSER>
TARGET_PGPASSWORD=change_me_pg_password TARGET_PGPASSWORD=change_me_pg_password
TARGET_DBS="sirh inventory ferme" TARGET_DBS="sirh inventory ferme"
@@ -29,9 +29,9 @@ TARGET_DBS="sirh inventory ferme"
TARGET_BACKUP_SUBDIR=bdd-recette TARGET_BACKUP_SUBDIR=bdd-recette
# Logs / tmp / ssh cible # Logs / tmp / ssh cible
TARGET_BACKUP_LOG_DIR=/home/backup_liot/logs/rebuild_bdd TARGET_BACKUP_LOG_DIR=/home/<RUNTIME_USER>/logs/rebuild_bdd
TARGET_LOCAL_RESTORE_BASE_DIR=/home/backup_liot/RebuildBdd/restore_tmp TARGET_LOCAL_RESTORE_BASE_DIR=/home/<RUNTIME_USER>/RebuildBdd/restore_tmp
TARGET_SSH_KEY=/home/backup_liot/.ssh/id_ed25519_backup_readonly TARGET_SSH_KEY=/home/<RUNTIME_USER>/.ssh/id_ed25519_backup_readonly
# Options cible # Options cible
TARGET_REMOTE_ROLES_DIR_NAME=user TARGET_REMOTE_ROLES_DIR_NAME=user

View File

@@ -36,6 +36,38 @@ En pratique :
--- ---
<details>
<summary style="list-style: none; cursor: pointer;">
<strong>EggMaster</strong>
</summary>
<details>
<summary style="list-style: none; cursor: pointer;">Question 4</summary>
Quel utilitaire standard permet de decoder la chaine reconstituee ?
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Indice commande 4</summary>
```text
base64
```
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Fragment 4</summary>
```text
4gcmVjb21wZW5zZSBodHRwczovL3d3dy55b3V0
```
</details>
</details>
## Architecture ## Architecture
### Configuration ### Configuration
@@ -110,14 +142,14 @@ Usage :
```bash ```bash
./create-target-config.sh \ ./create-target-config.sh \
--target test \ --target test \
--host 192.168.1.50 \ --host <TARGET_HOST> \
--port 22 \ --port 22 \
--bootstrap-user backup_liot \ --bootstrap-user <BOOTSTRAP_USER> \
--bootstrap-key /home/user/.ssh/id_ed25519_target_test \ --bootstrap-key /home/user/.ssh/id_ed25519_target_test \
--runtime-user backup_liot \ --runtime-user <RUNTIME_USER> \
--repo-dir /home/backup_liot/RebuildBdd \ --repo-dir /home/<RUNTIME_USER>/RebuildBdd \
--env-name RECETTE \ --env-name RECETTE \
--pguser backup_liot \ --pguser <PGUSER> \
--pgpassword secret \ --pgpassword secret \
--dbs "sirh inventory ferme" \ --dbs "sirh inventory ferme" \
--backup-subdir bdd-recette --backup-subdir bdd-recette
@@ -443,7 +475,7 @@ Exemple :
"environment": "RECETTE", "environment": "RECETTE",
"database": "sirh", "database": "sirh",
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump", "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/<RUNTIME_USER>/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
} }
``` ```
@@ -459,7 +491,7 @@ Exemple :
"environment": "RECETTE", "environment": "RECETTE",
"database": "sirh", "database": "sirh",
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump", "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/<RUNTIME_USER>/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
} }
``` ```
@@ -500,7 +532,7 @@ TARGET_BACKUP_LOG_DIR
Exemple : Exemple :
```bash ```bash
/home/backup_liot/logs/rebuild_bdd/ /home/<RUNTIME_USER>/logs/rebuild_bdd/
``` ```
Le chemin du log est renvoyé dans le JSON final. Le chemin du log est renvoyé dans le JSON final.
@@ -537,14 +569,14 @@ Avant mise en production, tester au minimum :
```bash ```bash
./create-target-config.sh \ ./create-target-config.sh \
--target test \ --target test \
--host 192.168.1.50 \ --host <TARGET_HOST> \
--port 22 \ --port 22 \
--bootstrap-user backup_liot \ --bootstrap-user <BOOTSTRAP_USER> \
--bootstrap-key /home/matteo/.ssh/id_ed25519_target_test \ --bootstrap-key /home/<LOCAL_USER>/.ssh/id_ed25519_target_test \
--runtime-user backup_liot \ --runtime-user <RUNTIME_USER> \
--repo-dir /home/backup_liot/RebuildBdd \ --repo-dir /home/<RUNTIME_USER>/RebuildBdd \
--env-name RECETTE \ --env-name RECETTE \
--pguser backup_liot \ --pguser <PGUSER> \
--pgpassword secret \ --pgpassword secret \
--dbs "sirh inventory ferme" \ --dbs "sirh inventory ferme" \
--backup-subdir bdd-recette --backup-subdir bdd-recette
@@ -578,5 +610,3 @@ Le projet permet désormais une utilisation :
* intégrée au web ; * intégrée au web ;
avec préparation des cibles, exécution non interactive et retour JSON. avec préparation des cibles, exécution non interactive et retour JSON.
```

View File

@@ -94,6 +94,7 @@ to_bool_yes_no() {
v="${v,,}" v="${v,,}"
case "$v" in case "$v" in
yes|y|oui|o|true|1) echo "yes" ;; 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" ;; no|n|non|false|0|"") echo "no" ;;
*) return 1 ;; *) return 1 ;;
esac esac
@@ -385,6 +386,13 @@ log "Correction des permissions SSH côté cible"
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SSH_PERMS_CMD" \ ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SSH_PERMS_CMD" \
|| fail "échec de correction des permissions SSH sur la cible" || 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=" REMOTE_KNOWN_HOSTS_CMD="
set -euo pipefail 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" \ ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_KNOWN_HOSTS_CMD" \
|| fail "échec de préparation known_hosts sur la cible" || 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=" REMOTE_BACKUP_TEST_CMD="
set -euo pipefail set -euo pipefail

View File

@@ -79,6 +79,7 @@ to_bool_yes_no() {
v="${v,,}" v="${v,,}"
case "$v" in case "$v" in
yes|y|oui|o|true|1) echo "yes" ;; 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" ;; no|n|non|false|0|"") echo "no" ;;
*) return 1 ;; *) return 1 ;;
esac esac

View File

@@ -97,6 +97,10 @@ fail() {
} }
require_cmd() { require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
}
has_cmd() {
command -v "$1" >/dev/null 2>&1 command -v "$1" >/dev/null 2>&1
} }
@@ -121,6 +125,7 @@ to_bool_yes_no() {
v="${v,,}" v="${v,,}"
case "$v" in case "$v" in
yes|y|oui|o|true|1) echo "yes" ;; 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" ;; no|n|non|false|0|"") echo "no" ;;
*) return 1 ;; *) return 1 ;;
esac esac
@@ -136,6 +141,13 @@ sql_escape_literal() {
printf "%s" "$s" 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() { build_excluded_roles_regex() {
local roles_string="${1:-}" local roles_string="${1:-}"
local role local role
@@ -167,6 +179,22 @@ build_excluded_roles_regex() {
printf '%s' "$joined" 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() { cleanup() {
rm -f \ rm -f \
"${LOCAL_DB_DUMP_FILE:-}" \ "${LOCAL_DB_DUMP_FILE:-}" \
@@ -251,7 +279,7 @@ else
fi fi
for cmd in ssh scp psql pg_restore createdb dropdb python3 grep sed find basename curl; do 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 done
CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh" CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh"
@@ -263,6 +291,7 @@ fi
[[ -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"
[[ ! -L "$SSH_KEY" ]] || fail "clé SSH source backup ne doit pas être un lien symbolique : $SSH_KEY"
export PGPASSWORD export PGPASSWORD
@@ -318,7 +347,7 @@ for candidate in "${DBS_ARRAY[@]}"; do
done done
[[ -n "$DB" ]] || fail "base refusée : non présente dans DBS" [[ -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 "Environnement : $ENV_NAME"
log "Base cible : $DB" log "Base cible : $DB"
@@ -478,27 +507,13 @@ pg_restore \
"$LOCAL_DB_DUMP_FILE" \ "$LOCAL_DB_DUMP_FILE" \
>>"$LOG_FILE" 2>&1 || fail "échec restauration base ${DB}" >>"$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} SUCCESS_MESSAGE="✅ REBUILD BDD ${ENV_NAME}
Base restaurée : ${DB} Base restaurée : ${DB}
Hôte PostgreSQL : ${PGHOST}:${PGPORT} Hôte PostgreSQL : ${PGHOST}:${PGPORT}
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP") Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
Log : ${LOG_FILE}" Log : ${LOG_FILE}"
send_discord_message "$SUCCESS_MESSAGE" send_discord "$SUCCESS_MESSAGE"
log "Restauration terminée avec succès pour ${DB}" log "Restauration terminée avec succès pour ${DB}"
print_json_and_exit "success" "restauration terminée avec succès" 0 print_json_and_exit "success" "restauration terminée avec succès" 0

View File

@@ -88,6 +88,7 @@ to_bool_yes_no() {
v="${v,,}" v="${v,,}"
case "$v" in case "$v" in
yes|y|oui|o|true|1) echo "yes" ;; 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" ;; no|n|non|false|0|"") echo "no" ;;
*) return 1 ;; *) return 1 ;;
esac esac
@@ -123,6 +124,10 @@ REQUESTED_DB="${CLI_DB:-${REQUESTED_DB:-}}"
ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}" ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}"
RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}" RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}"
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-$(date '+%Y%m%d%H%M%S')_$RANDOM}}" 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" 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" RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide"

View File

@@ -289,12 +289,40 @@ CHECK APP RECETTE 🟢
``` ```
--- ---
<details>
<summary style="list-style: none; cursor: pointer;">
<strong>EggMaster</strong>
</summary>
<details>
<summary style="list-style: none; cursor: pointer;">Question 5</summary>
Quelle option demande explicitement un decodage plutot qu'un encodage ?
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Indice commande 5</summary>
```text
-d
```
</details>
<details>
<summary style="list-style: none; cursor: pointer;">Fragment 5</summary>
```text
dWJlLmNvbS93YXRjaD92PWRRdzR3OVdnWGNR
```
</details>
</details>
# 7. Script : rebuild-bdd-recette.sh # 7. Script : rebuild-bdd-recette.sh
Script :
## Objectif ## Objectif
Restaurer une base PostgreSQL à partir dun dump distant. Restaurer une base PostgreSQL à partir dun dump distant.

View File

@@ -18,7 +18,8 @@ umask 077
# 6. exporte les rôles PostgreSQL ; # 6. exporte les rôles PostgreSQL ;
# 7. dump chaque base au format personnalisé PostgreSQL ; # 7. dump chaque base au format personnalisé PostgreSQL ;
# 8. transfère chaque fichier vers le serveur distant ; # 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 : # 10. envoie un bilan sur Discord :
# - 1 message global si tout est OK ; # - 1 message global si tout est OK ;
# - en cas derreur partielle : # - en cas derreur partielle :
@@ -50,6 +51,10 @@ set +a
####################################### #######################################
: "${ENV_NAME:?Variable ENV_NAME manquante}" : "${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}" : "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}" : "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}" : "${PGUSER:?Variable PGUSER manquante}"
@@ -58,6 +63,10 @@ set +a
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}" : "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}" : "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR 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_KEY:?Variable SSH_KEY manquante}"
: "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}" : "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}"
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}" : "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
@@ -87,7 +96,7 @@ for DB in "${DBS_ARRAY[@]}"; do
done done
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}" 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_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
BACKUP_KNOWN_HOSTS_STRICT="${BACKUP_KNOWN_HOSTS_STRICT:-yes}" BACKUP_KNOWN_HOSTS_STRICT="${BACKUP_KNOWN_HOSTS_STRICT:-yes}"
BACKUP_KNOWN_HOSTS_FILE="${BACKUP_KNOWN_HOSTS_FILE:-${HOME}/.ssh/known_hosts}" 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}" BACKUP_DIR_NAME="backup_${TS}"
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log" LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
exec > >(tee -a "$LOG_FILE") 2>&1
TMP_DIR="$(mktemp -d /tmp/pg_dump_XXXXXX)" || { TMP_DIR="$(mktemp -d /tmp/pg_dump_XXXXXX)" || {
echo "ERROR: impossible de créer le dossier temporaire" >&2 echo "ERROR: impossible de créer le dossier temporaire" >&2
exit 1 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() { require_cmd() {
command -v "$1" >/dev/null 2>&1 command -v "$1" >/dev/null 2>&1 || fail "commande manquante : $1"
} }
safe_remove_dir() { safe_remove_dir() {
@@ -183,10 +197,7 @@ export PGPASSWORD
####################################### #######################################
for cmd in ssh scp curl jq pg_dump pg_dumpall mktemp; do for cmd in ssh scp curl jq pg_dump pg_dumpall mktemp; do
require_cmd "$cmd" || { require_cmd "$cmd"
echo "ERROR: commande manquante : $cmd" >&2
exit 1
}
done done
[[ -f "$SSH_KEY" ]] || { [[ -f "$SSH_KEY" ]] || {
@@ -213,14 +224,14 @@ chmod 600 "$SSH_KEY" || true
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}" DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
DISCORD_PING="${DISCORD_PING:-@here}" DISCORD_PING="${DISCORD_PING:-@here}"
discord_send() { send_discord() {
local msg="$1" local msg="$1"
local payload
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0 [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
local payload
payload="$(jq -n --arg content "$msg" '{content: $content}')" || { payload="$(jq -n --arg content "$msg" '{content: $content}')" || {
log "ERROR: impossible de construire le payload JSON Discord" log "ERROR: impossible de construire le payload JSON Discord"
return 1 return 0
} }
curl -fsS \ curl -fsS \
@@ -242,7 +253,7 @@ Dumps transfer: ✅
Users transfer: ✅ Users transfer: ✅
EOF EOF
)" )"
discord_send "$msg" send_discord "$msg"
} }
####################################### #######################################
@@ -256,7 +267,7 @@ discord_msg_users_ok_simple() {
Users backup validé Users backup validé
EOF EOF
)" )"
discord_send "$msg" send_discord "$msg"
} }
discord_msg_users_error() { discord_msg_users_error() {
@@ -288,7 +299,7 @@ EOF
)" )"
fi fi
discord_send "$msg" send_discord "$msg"
} }
####################################### #######################################
@@ -303,7 +314,7 @@ discord_msg_db_ok_simple() {
Backup validé : ${db} Backup validé : ${db}
EOF EOF
)" )"
discord_send "$msg" send_discord "$msg"
} }
discord_msg_db_error() { discord_msg_db_error() {
@@ -338,7 +349,7 @@ EOF
)" )"
fi 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_DIR="/tmp/pg_multi_dump_stream.lock.d"
LOCK_PID_FILE="${LOCK_DIR}/pid"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then if ! mkdir "$LOCK_DIR" 2>/dev/null; then
log "ERROR: Backup déjà en cours" stale_lock="no"
discord_msg_users_error "" "" "Lock already exists" existing_pid=""
exit 1
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 fi
cleanup() { cleanup() {
@@ -397,18 +433,18 @@ fi
ROLES_FILE="${TMP_DIR}/user_${TS}.sql" ROLES_FILE="${TMP_DIR}/user_${TS}.sql"
set +e
log "Export des rôles PostgreSQL" log "Export des rôles PostgreSQL"
pg_dumpall \ if pg_dumpall \
-h "$PGHOST" \ -h "$PGHOST" \
-p "$PGPORT" \ -p "$PGPORT" \
-U "$PGUSER" \ -U "$PGUSER" \
--globals-only \ --globals-only \
> "$ROLES_FILE" > "$ROLES_FILE"; then
RET=0
RET=$? else
RET=$?
fi
if [[ $RET -ne 0 ]]; then if [[ $RET -ne 0 ]]; then
USERS_OK= USERS_OK=
@@ -419,8 +455,11 @@ else
fi fi
if [[ -n "${USERS_EXPORT_OK:-}" ]]; then if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/" if scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/"; then
RET=$? RET=0
else
RET=$?
fi
if [[ $RET -ne 0 ]]; then if [[ $RET -ne 0 ]]; then
USERS_OK= USERS_OK=
@@ -435,14 +474,10 @@ if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
fi fi
fi fi
set -e
####################################### #######################################
# Dump des bases # Dump des bases
####################################### #######################################
set +e
for DB in "${DBS_ARRAY[@]}"; do for DB in "${DBS_ARRAY[@]}"; do
FILE="${TMP_DIR}/${DB}_${TS}.dump" FILE="${TMP_DIR}/${DB}_${TS}.dump"
@@ -452,8 +487,11 @@ for DB in "${DBS_ARRAY[@]}"; do
log "Dump $DB" log "Dump $DB"
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE" if pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"; then
RET=$? RET=0
else
RET=$?
fi
if [[ $RET -ne 0 ]]; then if [[ $RET -ne 0 ]]; then
DUMPS_OK= DUMPS_OK=
@@ -463,8 +501,11 @@ for DB in "${DBS_ARRAY[@]}"; do
continue continue
fi fi
scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/" if scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/"; then
RET=$? RET=0
else
RET=$?
fi
if [[ $RET -ne 0 ]]; then if [[ $RET -ne 0 ]]; then
DUMPS_OK= DUMPS_OK=
@@ -473,18 +514,17 @@ for DB in "${DBS_ARRAY[@]}"; do
fi fi
done done
set -e
####################################### #######################################
# Rotation distante # Rotation distante
####################################### #######################################
log "Starting remote rotation: delete backups older than ${RETENTION_DAYS} days" log "Starting remote rotation: delete backups older than ${RETENTION_DAYS} days"
set +e if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"; then
RET=0
ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete" else
RET=$? RET=$?
fi
if [[ $RET -ne 0 ]]; then if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for users" log "ERROR: remote rotation failed for users"
@@ -493,8 +533,11 @@ else
fi fi
for DB in "${DBS_ARRAY[@]}"; do 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" if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"; then
RET=$? RET=0
else
RET=$?
fi
if [[ $RET -ne 0 ]]; then if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for ${DB}" log "ERROR: remote rotation failed for ${DB}"
@@ -503,8 +546,6 @@ for DB in "${DBS_ARRAY[@]}"; do
fi fi
done done
set -e
log "Remote rotation finished" log "Remote rotation finished"
####################################### #######################################

View File

@@ -44,13 +44,23 @@ set +a
: "${CHECK_MAX_TIME:?Variable CHECK_MAX_TIME manquante}" : "${CHECK_MAX_TIME:?Variable CHECK_MAX_TIME manquante}"
: "${APP_URLS:?Variable APP_URLS 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 # Sites à vérifier
####################################### #######################################
read -r -a SITES <<< "$APP_URLS" read -r -a SITES <<< "$APP_URLS"
SCHEME="http" SCHEME="${APP_SCHEME:-http}"
CONNECT_TIMEOUT="${CHECK_CONNECT_TIMEOUT}" CONNECT_TIMEOUT="${CHECK_CONNECT_TIMEOUT}"
MAX_TIME="${CHECK_MAX_TIME}" MAX_TIME="${CHECK_MAX_TIME}"
@@ -75,6 +85,16 @@ DISCORD_PING="${DISCORD_PING:-@here}"
SUMMARY_LINES=() SUMMARY_LINES=()
FAILURES=0 FAILURES=0
TMPFILES=()
cleanup() {
local tmpfile
for tmpfile in "${TMPFILES[@]}"; do
[[ -n "$tmpfile" ]] || continue
rm -f -- "$tmpfile"
done
}
trap cleanup EXIT
####################################### #######################################
# Logging # Logging
@@ -115,8 +135,21 @@ add_summary_line() {
####################################### #######################################
# Envoi du message Discord récapitulatif # 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 [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
should_send_discord || return 0
local header_icon ping_prefix="" local header_icon ping_prefix=""
if [[ "$FAILURES" -eq 0 ]]; then if [[ "$FAILURES" -eq 0 ]]; then
@@ -134,7 +167,7 @@ send_discord_summary() {
done done
local payload 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" \ curl -fsS -H "Content-Type: application/json" \
-d "$payload" \ -d "$payload" \
@@ -158,6 +191,7 @@ check_site() {
local http_code curl_exit err local http_code curl_exit err
local stderr local stderr
stderr="$(mktemp)" stderr="$(mktemp)"
TMPFILES+=("$stderr")
http_code="$( http_code="$(
curl -sS -o /dev/null \ curl -sS -o /dev/null \
@@ -170,15 +204,12 @@ check_site() {
if [[ "$curl_exit" -ne 0 ]]; then if [[ "$curl_exit" -ne 0 ]]; then
err="$(head -n 1 "$stderr" | tr -d '\r')" err="$(head -n 1 "$stderr" | tr -d '\r')"
rm -f "$stderr"
log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}" log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}"
add_summary_line "$host" "DOWN" "DOWN - curl" add_summary_line "$host" "DOWN" "DOWN - curl"
return 1 return 1
fi fi
rm -f "$stderr"
if [[ "$http_code" =~ ^[0-9]{3}$ ]]; then if [[ "$http_code" =~ ^[0-9]{3}$ ]]; then
if [[ "$http_code" -ge 200 && "$http_code" -le 399 ]]; then if [[ "$http_code" -ge 200 && "$http_code" -le 399 ]]; then
log_line "OK" "$host" "HTTP $http_code" log_line "OK" "$host" "HTTP $http_code"
@@ -210,7 +241,7 @@ main() {
done done
FAILURES="$failures" FAILURES="$failures"
send_discord_summary send_discord
if [[ "$failures" -gt 0 ]]; then if [[ "$failures" -gt 0 ]]; then
exit 2 exit 2

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
umask 077
############################################################################### ###############################################################################
# rebuild-bdd-recette.sh # rebuild-bdd-recette.sh
@@ -49,6 +50,10 @@ set +a
# Variables obligatoires # Variables obligatoires
############################################################################### ###############################################################################
: "${ENV_NAME:?Variable ENV_NAME manquante}" : "${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}" : "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}" : "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}" : "${PGUSER:?Variable PGUSER manquante}"
@@ -110,10 +115,15 @@ cleanup() {
"${FILTERED_ROLES_FILE:-}" \ "${FILTERED_ROLES_FILE:-}" \
"${ROLES_CREATE_LIST:-}" \ "${ROLES_CREATE_LIST:-}" \
"${ROLES_APPLY_FILE:-}" "${ROLES_APPLY_FILE:-}"
rm -rf "${LOCAL_RESTORE_DIR:-}" 2>/dev/null || true
} }
trap cleanup EXIT trap cleanup EXIT
require_cmd() { require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
}
has_cmd() {
command -v "$1" >/dev/null 2>&1 command -v "$1" >/dev/null 2>&1
} }
@@ -124,11 +134,10 @@ sql_escape_literal() {
} }
validate_db_name() { validate_db_name() {
local db_name="$1" local db_name="${1:-}"
[[ -n "$db_name" ]] || fail "nom de base vide" [[ -n "$db_name" ]] || return 1
[[ "$db_name" =~ ^[A-Za-z0-9_]+$ ]] || \ [[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1
fail "nom de base invalide : seuls les lettres, chiffres et underscores sont autorisés"
} }
build_excluded_roles_regex() { build_excluded_roles_regex() {
@@ -150,31 +159,22 @@ build_excluded_roles_regex() {
# Envoi Discord # Envoi Discord
# #
# Envoi simple d'un message texte via webhook 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 message="$1"
local payload="" local payload=""
[[ -n "$DISCORD_WEBHOOK_URL" ]] || { [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
log "WEBHOOK_URL non défini : notification Discord ignorée." has_cmd jq || return 0
return 0 has_cmd curl || return 0
}
if ! require_cmd curl; then payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
log "curl absent : notification Discord ignorée."
return 0
fi
payload="$(jq -n --arg content "$message" '{content: $content}')" || { curl -fsS "$DISCORD_WEBHOOK_URL" \
log "Impossible de construire le payload JSON Discord."
return 0
}
curl -sS -X POST "$DISCORD_WEBHOOK_URL" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$payload" \ -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" [[ -f "$SSH_KEY" ]] || fail "clé SSH introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "clé SSH non lisible : $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" [[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT 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" [[ "$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 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..." log "PostgreSQL absent : installation en cours..."
sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update" sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update"
@@ -242,15 +243,17 @@ fi
# Attente disponibilité PostgreSQL # Attente disponibilité PostgreSQL
############################################################################### ###############################################################################
log "Vérification de la disponibilité de PostgreSQL..." log "Vérification de la disponibilité de PostgreSQL..."
PG_READY=false
for _ in {1..20}; do for _ in {1..20}; do
if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
PG_READY=true
log "PostgreSQL répond correctement." log "PostgreSQL répond correctement."
break break
fi fi
sleep 1 sleep 1
done 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" fail "PostgreSQL ne répond pas correctement"
fi fi
@@ -297,7 +300,7 @@ else
read -r -p "Nom exact de la base à restaurer : " DB read -r -p "Nom exact de la base à restaurer : " DB
fi fi
validate_db_name "$DB" validate_db_name "$DB" || fail "nom de base invalide"
log "Environnement : $ENV_NAME" log "Environnement : $ENV_NAME"
log "Base cible sélectionnée : $DB" log "Base cible sélectionnée : $DB"
@@ -418,6 +421,8 @@ if [[ -n "$LOCAL_ROLES_FILE" ]]; then
cp "$LOCAL_ROLES_FILE" "$FILTERED_ROLES_FILE" cp "$LOCAL_ROLES_FILE" "$FILTERED_ROLES_FILE"
fi 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}" log "Fichier des rôles filtré généré : ${FILTERED_ROLES_FILE}"
sed -nE 's/^CREATE ROLE "?([^" ;]+)"?;$/\1/p' "$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") Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
Log : ${LOG_FILE}" Log : ${LOG_FILE}"
send_discord_message "$SUCCESS_MESSAGE" send_discord "$SUCCESS_MESSAGE"

View File

@@ -89,6 +89,7 @@ SSH_TIMEOUT=10
############################################# #############################################
# Nombre de jours de conservation des sauvegardes # Nombre de jours de conservation des sauvegardes
# Utilisé par backup-bdd-recette.sh et backup-vaultwarden.sh
BACKUP_RETENTION_DAYS=10 BACKUP_RETENTION_DAYS=10
@@ -96,12 +97,11 @@ BACKUP_RETENTION_DAYS=10
# APPLICATIONS À SURVEILLER # APPLICATIONS À SURVEILLER
############################################# #############################################
# Liste des applications à vérifier # Liste des applications à vérifier (séparées par espace)
APPS=" APP_URLS="ferme.malio-dev.fr inventory.malio-dev.fr sirh.malio-dev.fr"
ferme.malio-dev.fr
inventory.malio-dev.fr # Schéma utilisé pour les applications surveillées
sirh.malio-dev.fr APP_SCHEME="http"
"
############################################# #############################################