28 Commits

Author SHA1 Message Date
AkiNoKure
37fe2f5239 feat : script deploiement de script 2026-03-13 10:22:29 +01:00
210594b008 Merge pull request 'feat/392-script-reconstruction-bdd' (#11) from feat/392-script-reconstruction-bdd into develop
Reviewed-on: #11
2026-03-12 08:53:22 +00:00
AkiNoKure
e221e82108 feat : script de reconstruction de bdd 2026-03-12 09:49:35 +01:00
AkiNoKure
fabc9be4d4 fix : pb chemin env 2026-03-11 17:11:30 +01:00
AkiNoKure
9d4a5050e9 feat : rebuild-bdd-recette 2026-03-11 11:14:44 +01:00
5729d0d484 Merge pull request 'fix/code-review' (#10) from fix/code-review into develop
Reviewed-on: #10
2026-03-10 15:17:56 +00:00
AkiNoKure
89b1229efb fix : code review 2026-03-10 16:17:11 +01:00
AkiNoKure
f9b1d1da24 Merge remote-tracking branch 'origin/fix/code-review' into fix/code-review
# Conflicts:
#	CODE_REVIEW.md
2026-03-10 16:16:51 +01:00
AkiNoKure
049574ffeb fix : code review 2026-03-10 16:15:21 +01:00
c257270982 Actualiser CODE_REVIEW.md 2026-03-10 15:14:25 +00:00
AkiNoKure
f72328e0ce fix : code review 2026-03-10 15:54:22 +01:00
AkiNoKure
29eff11b23 Merge remote-tracking branch 'refs/remotes/origin/fix/code-review' into fix/code-review 2026-03-10 09:09:18 +01:00
Matthieu
99072361c5 docs: ajout de la revue de code complète du dépôt
Revue couvrant les 4 scripts (backup-vaultwarden, check-storage,
backup-bdd-recette, check-statut-recette) avec identification des
problèmes de sécurité, qualité et suggestions d'amélioration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:20:13 +01:00
623424343e Merge pull request 'feat : ajout de la rotation pour les scripts de backup' (#9) from feat/384-correctif into develop
Reviewed-on: #9
2026-03-09 15:51:09 +00:00
AkiNoKure
066ede6000 feat : ajout de la rotation pour les scripts de backup 2026-03-09 16:46:50 +01:00
30df5ca8d6 Merge pull request 'feat/384-correctif' (#8) from feat/384-correctif into develop
Reviewed-on: MALIO-DEV/Scripts-Serveur#8
2026-03-09 12:23:15 +00:00
AkiNoKure
4b76e88853 Merge branch 'develop' into feat/384-correctif 2026-03-09 13:21:32 +01:00
AkiNoKure
99f8694250 fix : corrections orthographique 2026-03-09 13:19:32 +01:00
7f18e2f2e9 Merge pull request 'fix : review' (#7) from feat/384-correctif into develop
Reviewed-on: MALIO-DEV/Scripts-Serveur#7
2026-03-09 10:50:01 +00:00
AkiNoKure
d0ceea8bad fix : review 2026-03-09 11:48:21 +01:00
dd226592db Merge pull request 'feat/384-correctif' (#6) from feat/384-correctif into develop
Reviewed-on: MALIO-DEV/Scripts-Serveur#6
2026-03-09 10:35:47 +00:00
AkiNoKure
e81b953ac2 fix : discord message 2026-03-09 11:31:37 +01:00
AkiNoKure
c80a74adc5 fix : versionning 2026-03-09 11:03:28 +01:00
AkiNoKure
97eeffd9ea fix : correctifs multiple 2026-03-09 10:49:29 +01:00
AkiNoKure
14359b111f Merge branch 'refs/heads/develop' into feat/384-correctif 2026-03-09 10:48:21 +01:00
e860fd0f16 Merge pull request 'fix : correctif du script' (#5) from fix/372-script-storage into develop
Reviewed-on: MALIO-DEV/Scripts-Serveur#5
2026-03-09 09:32:44 +00:00
Lethary
fbbb68af88 fix : correctif du script 2026-03-09 10:30:06 +01:00
AkiNoKure
c2d1b716e0 fix : correctifs multiples 2026-03-09 09:08:44 +01:00
22 changed files with 2886 additions and 521 deletions

View File

@@ -1 +0,0 @@
WEBHOOK_URL=

45
.gitignore vendored
View File

@@ -1,9 +1,40 @@
# Secrets / environment ########################################
.env # Environment / secrets
.env.* ########################################
!.env.example
!.env.exemple .env
!.env.exemple
!.env.example
########################################
# Logs
########################################
*.log
logs/
var/log/
backup.log
########################################
# Temporary / system files
########################################
*.tmp
*.swp
*.swo
*~
########################################
# IDE / Editors
########################################
# IDE / editor
.idea/ .idea/
.vscode/ .vscode/
*.iml
########################################
# OS files
########################################
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,33 @@
#############################################
# VAULTWARDEN BACKUP CONFIGURATION
#############################################
# Répertoire contenant les données Vaultwarden
DATA_DIR=
#############################################
# BACKUP LOCAL
#############################################
# Dossier local où seront stockées les archives
LOCAL_BACKUP=
#############################################
# SERVEUR DE BACKUP DISTANT
#############################################
# Utilisateur SSH du serveur distant
REMOTE_USER=
# Host ou IP du serveur distant
REMOTE_HOST=
# Répertoire distant de stockage des backups
REMOTE_DIR=
#############################################
# AUTHENTIFICATION SSH
#############################################
# Chemin vers la clé privée SSH utilisée pour la connexion
SSH_KEY=

356
BackupVaultWarden/README.md Normal file
View File

@@ -0,0 +1,356 @@
markdown
# README — Mise en place du script de sauvegarde Vaultwarden
Ce script permet dautomatiser la sauvegarde de Vaultwarden afin de conserver une copie du dossier `data`, de la transférer vers un serveur distant et denvoyer une notification Discord en cas de succès ou déchec.
---
# 1. Objectif du script
Le script de sauvegarde Vaultwarden permet de :
- sauvegarder les données de Vaultwarden ;
- compresser larchive avec un nom daté ;
- transférer la sauvegarde vers un serveur distant ;
- envoyer une notification Discord ;
- automatiser lexécution via `cron`.
Ce mécanisme permet de sécuriser les mots de passe, les utilisateurs et la configuration stockés dans le dossier `data` de Vaultwarden.
---
# 2. Pré-requis
Avant de mettre en place le script, vérifier que les éléments suivants sont disponibles sur la machine Vaultwarden :
- `bash`
- `tar`
- `scp`
- `ssh`
- `curl`
- `cron`
Installation sur Debian / Ubuntu :
```bash
sudo apt update
sudo apt install -y tar openssh-client curl cron
````
---
# 3. Emplacement du script
Le script est situé dans :
```bash
/home/matt/vaultwarden/Malio-ops/BackupVaultWarden/
```
Structure recommandée :
```bash
/home/matt/vaultwarden/Malio-ops/BackupVaultWarden/
├── backup-vaultwarden.sh
├── .env
└── README.md
```
---
# 4. Configuration sécurisée avec le fichier .env
Les informations sensibles ne doivent pas être stockées directement dans le script.
Elles doivent être placées dans un fichier `.env`.
## Exemple de fichier `.env`
```bash
WEBHOOK_URL=https://discord.com/api/webhooks/...
REMOTE_USER=backup
REMOTE_HOST=192.168.1.50
SSH_KEY=/home/matt/.ssh/id_ed25519_vaultwarden_backup
DATA_DIR=/opt/vaultwarden/data
REMOTE_DIR=/home/backup/backups/vaultwarden
```
## Description des variables
| Variable | Description |
| ----------- | ------------------------------------------------------ |
| WEBHOOK_URL | Webhook Discord pour les notifications |
| REMOTE_USER | Utilisateur du serveur distant |
| REMOTE_HOST | Adresse IP ou DNS du serveur de sauvegarde |
| SSH_KEY | Chemin vers la clé SSH utilisée pour le transfert |
| DATA_DIR | Dossier `data` de Vaultwarden |
| REMOTE_DIR | Dossier de stockage des backups sur le serveur distant |
---
# 5. Chargement des variables dans le script
Le script récupère les variables du fichier `.env`.
Exemple :
```bash
REMOTE_USER=$(grep -E '^REMOTE_USER=' .env | cut -d '=' -f2-)
```
Explication :
* `grep` recherche la variable dans `.env`
* `cut` récupère uniquement la valeur après `=`
* la variable shell reçoit la valeur correspondante
Cela permet :
* d'améliorer la sécurité
* de modifier la configuration sans toucher au script
---
# 6. Connexion au serveur de sauvegarde (Machine IA)
Le transfert des sauvegardes vers la machine IA repose sur une **authentification par clé SSH**.
Cette méthode permet au script de se connecter automatiquement au serveur distant sans mot de passe.
La clé utilisée pour ce script est :
```
~/.ssh/id_ed25519_bitwarden
````
---
## 6.1 Vérifier la présence de la clé SSH
Sur la machine exécutant le script, vérifier que la clé existe :
```bash
ls ~/.ssh/id_ed25519_bitwarden*
````
Les fichiers attendus sont :
```
~/.ssh/id_ed25519_bitwarden
~/.ssh/id_ed25519_bitwarden.pub
```
* `id_ed25519_bitwarden` → clé privée utilisée par le script
* `id_ed25519_bitwarden.pub` → clé publique autorisée sur la machine IA
---
## 6.2 Copier la clé publique sur la machine IA
Envoyer la clé publique vers la machine IA :
```bash
ssh-copy-id -i ~/.ssh/id_ed25519_bitwarden.pub backup@192.168.0.179
```
Cette commande ajoute automatiquement la clé dans :
```
~/.ssh/authorized_keys
```
sur la machine IA.
---
## 6.3 Ajout manuel de la clé (si ssh-copy-id n'est pas disponible)
Afficher la clé publique :
```bash
cat ~/.ssh/id_ed25519_bitwarden.pub
```
Copier son contenu puis lajouter sur la machine IA dans :
```
~/.ssh/authorized_keys
```
---
## 6.4 Vérifier les permissions SSH
Sur la machine locale :
```bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519_bitwarden
chmod 644 ~/.ssh/id_ed25519_bitwarden.pub
```
Sur la machine IA :
```bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
```
---
## 6.5 Tester la connexion
Tester la connexion SSH avec la clé :
```bash
ssh -i ~/.ssh/id_ed25519_bitwarden backup@192.168.0.179
```
Si la configuration est correcte :
* la connexion se fait **sans mot de passe**
* la machine IA accepte la clé SSH
* le script pourra envoyer les sauvegardes automatiquement
---
## 6.6 Déclaration dans le fichier `.env`
La clé utilisée par le script doit être déclarée dans `.env` :
```bash
SSH_KEY=/home/matt/.ssh/id_ed25519_bitwarden
```
Cette clé sera utilisée automatiquement par `scp` lors du transfert des sauvegardes.
# 7. Sauvegarde des données Vaultwarden
Le script crée une archive compressée du dossier `data` :
```bash
tar -czf "$LOCAL_BACKUP" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")"
```
Cela permet dobtenir une sauvegarde portable et compressée.
---
# 8. Transfert vers le serveur distant
Une fois larchive créée :
```bash
scp "${SSH_OPTS[@]}" "$LOCAL_BACKUP" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
```
Le fichier est envoyé vers le serveur de sauvegarde via SCP.
---
# 9. Notification Discord
Le script envoie une notification Discord pour informer de létat de la sauvegarde.
Construction du message :
```bash
local msg="**@here Backup Vaultwarden $color**\n"
msg+="Backup: ${BACKUP_NAME}\n"
msg+="Data transfer: $dumps_display\n"
[[ -n "$details" ]] && msg+="Details: $details"
```
Envoi du message :
```bash
curl -fsS -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" \
"$WEBHOOK_URL"
```
Le message indique :
* si la sauvegarde a réussi
* si elle a échoué
* le nom du backup
* les détails de lerreur
---
# 10. Planification avec cron
Le script est exécuté automatiquement tous les jours à 19h.
Ouvrir le crontab :
```bash
crontab -e
```
Ajouter :
```bash
0 19 * * * /home/matt/vaultwarden/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh >> /var/log/vaultwarden_backup.log 2>&1
```
Signification :
| Champ | Valeur |
| ------------ | ------ |
| minute | 0 |
| heure | 19 |
| jour du mois | * |
| mois | * |
| jour semaine | * |
Le script sexécute donc **tous les jours à 19h00**.
---
# 11. Nettoyage
Une fois la sauvegarde transférée :
```bash
rm -f "$LOCAL_BACKUP"
```
Cela évite de remplir le disque de la machine Vaultwarden.
---
# 12. Test manuel
Avant de mettre le script en cron, tester :
```bash
bash /home/matt/vaultwarden/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh
```
---
# 13. Vérification des logs
Logs :
```bash
cat /var/log/vaultwarden_backup.log
```
---
# 14. Résumé
Le script automatise :
* la sauvegarde du dossier `data`
* la compression et la datation du backup
* le transfert sécurisé via SSH
* la notification Discord
* lexécution automatique via cron
* la configuration sécurisée via `.env`
Ce système permet dobtenir **une sauvegarde fiable, centralisée et surveillée de Vaultwarden**.
```

View File

@@ -5,7 +5,7 @@ set -euo pipefail
# Chemins fixes du script # Chemins fixes du script
####################################### #######################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="/home/matt/vaultwarden/scripts/Scripts-Serveur/backup_vaultwarden/.env" ENV_FILE="${SCRIPT_DIR}/.env"
LOG_FILE="/var/log/vaultwarden_backup.log" LOG_FILE="/var/log/vaultwarden_backup.log"
mkdir -p "$(dirname "$LOG_FILE")" mkdir -p "$(dirname "$LOG_FILE")"
@@ -45,9 +45,11 @@ set +a
# Variables backup # Variables backup
####################################### #######################################
DATE="$(date +'%Y-%m-%d_%H-%M-%S')" DATE="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_NAME="vaultwarden-backup-${DATE}.tar.gz" BACKUP_PREFIX="vaultwarden-backup"
BACKUP_NAME="${BACKUP_PREFIX}-${DATE}.tar.gz"
LOCAL_BACKUP_DIR="$LOCAL_BACKUP" LOCAL_BACKUP_DIR="$LOCAL_BACKUP"
LOCAL_BACKUP_FILE="${LOCAL_BACKUP_DIR}/${BACKUP_NAME}" LOCAL_BACKUP_FILE="${LOCAL_BACKUP_DIR}/${BACKUP_NAME}"
RETENTION_DAYS=10
SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10) SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10)
@@ -66,13 +68,15 @@ discord_ping() {
if [[ "$success" == "true" ]]; then if [[ "$success" == "true" ]]; then
icon="🟢" icon="🟢"
status_line="✅" status_line="✅"
ping=""
else else
icon="🔴" icon="🔴"
status_line="❌" status_line="❌"
ping="@here "
fi fi
local msg local msg
msg="**@here ${icon} Backup Vaultwarden**\n" msg="**${ping}Backup Vaultwarden ${icon}**\n"
msg+="Backup: ${BACKUP_NAME}\n" msg+="Backup: ${BACKUP_NAME}\n"
msg+="Data transfer: ${status_line}\n" msg+="Data transfer: ${status_line}\n"
[[ -n "$details" ]] && msg+="Détails: ${details}" [[ -n "$details" ]] && msg+="Détails: ${details}"
@@ -110,6 +114,8 @@ log "Destination distante : ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}"
tar -czf "$LOCAL_BACKUP_FILE" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")" \ tar -czf "$LOCAL_BACKUP_FILE" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")" \
|| fail "Erreur lors de la compression du dossier $DATA_DIR" || fail "Erreur lors de la compression du dossier $DATA_DIR"
log "Backup local créé : $LOCAL_BACKUP_FILE"
####################################### #######################################
# Création dossier distant # Création dossier distant
####################################### #######################################
@@ -122,6 +128,17 @@ ssh "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "mkdir -p '$REMOTE_DIR'" \
scp "${SSH_OPTS[@]}" "$LOCAL_BACKUP_FILE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" \ scp "${SSH_OPTS[@]}" "$LOCAL_BACKUP_FILE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" \
|| fail "Erreur lors de l'envoi du backup vers $REMOTE_HOST" || fail "Erreur lors de l'envoi du backup vers $REMOTE_HOST"
log "Backup envoyé sur $REMOTE_HOST:$REMOTE_DIR"
#######################################
# Rotation distante - suppression > 10 jours
#######################################
ssh "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" \
"find '$REMOTE_DIR' -type f -name '${BACKUP_PREFIX}-*.tar.gz' -mtime +$RETENTION_DAYS -delete" \
|| fail "Erreur lors de la rotation distante des sauvegardes"
log "Rotation distante OK"
####################################### #######################################
# Nettoyage local # Nettoyage local
####################################### #######################################
@@ -132,4 +149,4 @@ rm -f "$LOCAL_BACKUP_FILE" || fail "Impossible de supprimer le backup local $LOC
####################################### #######################################
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" discord_ping "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"

41
CHANGELOG.md Normal file
View File

@@ -0,0 +1,41 @@
# Changelog
Liste des évolutions du projet Scripts Serveur
## [0.0.1]
### Parameters
Ajouter dans le fichier /RecetteScripts/.env
SSH_TIMEOUT
BACKUP_LOG_DIR
APP_LOG_DIR
DISCORD_WEBHOOK_URL
DISCORD_PING
CHECK_CONNECT_TIMEOUT
CHECK_MAX_TIME
APP_URLS
Ajouter dans le fichier /CheckStorage/.env
WEBHOOK_URL
Ajouter dans le fichier /BackupVaultWarden/.env
DATA_DIR
LOCAL_BACKUP
REMOTE_USER
REMOTE_HOST
REMOTE_DIR
SSH_KEY
### Added
* [#361] Script dump BDD
* [#367] Avoir une notification discord quand les backup sont faites
* [#368] Script pour check que les applis ne sont pas hors-ligne
* first push
* Reorganisation des fichiers et dossiers
* [#372] Script de check si la machine a le stockage plein
* [#378] Script Backup BDD Vaultwarden
* [#381] Variabiliser tous les scripts
* [#384] Fix Correctif
* [#391] Script Déploiement de Scripts
### Changed
### Fixed

View File

@@ -0,0 +1,6 @@
#############################################
# DISCORD
#############################################
# Webhook Discord pour notifications
WEBHOOK_URL=

View File

@@ -20,13 +20,19 @@ La limite d'alerte est fixée à 70% d'utilisation, mais vous pouvez ajuster cet
``` ```
## Utilisation du script ## Utilisation du script
0. Copiez le fichier d'environnement exemple et modifiez les variables selon votre configuration :
```bash
cp .env.example .env
nano .env
```
1. Donnez les permissions d'exécution au script : 1. Donnez les permissions d'exécution au script :
```bash ```bash
chmod +x check_storage.sh chmod +x check-storage.sh
``` ```
2. Exécutez le script pour vérifier l'espace de stockage : 2. Exécutez le script pour vérifier l'espace de stockage :
```bash ```bash
./check_storage.sh ./check-storage.sh
``` ```
## Initialisé un cron pour exécuter le script régulièrement ## Initialisé un cron pour exécuter le script régulièrement
@@ -36,8 +42,8 @@ La limite d'alerte est fixée à 70% d'utilisation, mais vous pouvez ajuster cet
``` ```
2. Ajoutez la ligne suivante pour exécuter le script tous les jours à 7h50 du matin : 2. Ajoutez la ligne suivante pour exécuter le script tous les jours à 7h50 du matin :
```bash ```bash
50 7 * * * /chemin/vers/le/script/check_storage.sh 50 7 * * * /chemin/vers/le/script/check-storage.sh
``` ```
## Avertissement ## Avertissement
Assurez-vous de remplacer `/chemin/vers/le/script/check_storage.sh` par le chemin réel où se trouve le script sur votre système. Assurez-vous de remplacer `/chemin/vers/le/script/check-storage.sh` par le chemin réel où se trouve le script sur votre système.

View File

@@ -0,0 +1,69 @@
#!/bin/bash
set -euo pipefail
###############################################################################
# CHARGEMENT DU .env
###############################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
if [[ ! -f "$ENV_FILE" ]]; then
echo "ERROR: fichier .env introuvable : $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
###############################################################################
# CONFIGURATION
###############################################################################
# Limite maximale d'utilisation du disque en pourcentage
limit=70
###############################################################################
# RÉCUPÉRATION DES INFORMATIONS DISQUE
###############################################################################
read -r total_bytes used_bytes avail_bytes usage <<<"$(df -B1 / | awk 'NR==2 {gsub(/%/,"",$5); print $2, $3, $4, $5}')"
# Calcul du pourcentage d'espace libre
free=$((100 - usage))
###############################################################################
# CONVERSION EN GIGAOCTETS
###############################################################################
used_gb=$(awk -v b="$used_bytes" 'BEGIN {printf "%.2f", b/1024/1024/1024}')
total_gb=$(awk -v b="$total_bytes" 'BEGIN {printf "%.2f", b/1024/1024/1024}')
avail_gb=$(awk -v b="$avail_bytes" 'BEGIN {printf "%.2f", b/1024/1024/1024}')
###############################################################################
# VÉRIFICATION DU SEUIL D'UTILISATION
###############################################################################
if [ "$usage" -ge "$limit" ]; then
msgLimit="@here\n**CHECK STOCKAGE :red_circle:**\nLimite autorisé : ${limit}%\nUtilisation actuelle: ${usage}%\nEspace restant: ${free}%\nUtilise / total: ${used_gb} GB / ${total_gb} GB\nDisponible: ${avail_gb} GB\nHeure: $(date)"
payload="$(jq -n --arg content "$msgLimit" '{content: $content}')"
curl -X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json; charset=utf-8" \
-d "$payload" \
"$WEBHOOK_URL"
fi
###############################################################################
# AFFICHAGE DES INFORMATIONS STOCKAGE
###############################################################################
echo "Espace disponible : ${avail_gb} GB"
echo "Espace utilise / espace total : ${used_gb} GB / ${total_gb} GB"
echo "Name: ${HOSTNAME}"

View File

@@ -1,22 +0,0 @@
#!/bin/bash
limit=70
# Mettre le lien de votre webhook Discord dans un .env
WEBHOOK_URL=$(grep -E '^WEBHOOK_URL=' .env | cut -d '=' -f2-)
# Récupérer l'utilisation du disque en pourcentage
usage=$(df -h / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
# Calculer l'espace libre en pourcentage
free=$((100 - usage))
# Si l'utilisation dépasse la limite, envoyer une alerte sur Discord
if [ "$usage" -ge "$limit" ]; then
msgLimit="@here\n**CHECK STOCKAGE :red_circle:**\nLimite autorisé : ${limit}% \nUtilisation actuelle: ${usage}%\nEspace restant: ${free}%\nHeure: $(date)"
curl -X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json; charset=utf-8" \
-d "{\"content\":\"$msgLimit\"}" \
"$WEBHOOK_URL"
# Log de l'alerte
echo "ALERTE >> ${usage}% d'utilisation, check fait le $(date)"
echo "------------------------------------------------------------"
fi

964
Deployment/Deployment.sh Normal file
View File

@@ -0,0 +1,964 @@
#!/usr/bin/env bash
set -Eeuo pipefail
###############################################################################
# bootstrap-backup-env.sh
#
# Prépare un environnement de déploiement pour :
# - backup-vaultwarden.sh
# - check-storage.sh
# - check-statut-recette.sh
# - backup-bdd-recette.sh
# - rebuild-bdd-recette.sh
#
# Fonctionnalités :
# - idempotent : relançable sans erreur ;
# - installation / mise à jour des dépendances ;
# - création des dossiers ;
# - permissions ;
# - génération des clés SSH si absentes ;
# - récupération depuis un dépôt Git privé volumineux via sparse-checkout ;
# - mise à jour du .env ;
# - injection des valeurs sensibles dans le .env si fournies ;
# - ajout automatique de la clé publique backup sur le serveur distant
# si un accès SSH bootstrap est disponible ;
# - questions interactives si lancé en local et que des variables obligatoires
# sont absentes ;
# - génération d'un fichier scripts.json pour un futur affichage web ;
# - exécutable en local ou envoyé via SSH sur un serveur distant.
###############################################################################
#######################################
# Valeurs par défaut
#######################################
REPO_URL=""
REPO_BRANCH="main"
REPO_SUBDIR=""
INSTALL_DIR="/opt/malio-backup"
DEPLOY_USER="${SUDO_USER:-${USER}}"
DEPLOY_GROUP=""
ENV_FILE_NAME=".env"
GIT_DIR_NAME="repo"
SCRIPTS_DIR_NAME="scripts"
CONFIG_DIR_NAME="config"
LOG_DIR_NAME="logs"
DATA_DIR_NAME="data"
TMP_DIR_NAME="tmp"
SSH_DIR_NAME="ssh"
BACKUP_SSH_KEY_NAME="id_ed25519_backup"
REPO_SSH_KEY_NAME="id_ed25519_repo"
FORCE_CHOWN="false"
NON_INTERACTIVE="false"
# Paramètres fonctionnels
ENV_NAME="RECETTE"
PGHOST="localhost"
PGPORT="5432"
PGUSER_VALUE=""
PGPASSWORD_VALUE=""
DBS_VALUE="sirh inventory ferme"
BACKUP_REMOTE_USER="backup"
BACKUP_REMOTE_HOST=""
BACKUP_REMOTE_DIR="/home/backup/backups/bdd-recette"
SSH_CONNECT_TIMEOUT="10"
RETENTION_DAYS="10"
DISCORD_WEBHOOK_URL_VALUE=""
DISCORD_PING_VALUE=""
WEBHOOK_URL_VALUE=""
VAULTWARDEN_DATA_DIR_VALUE="/var/lib/vaultwarden"
CHECK_STORAGE_PATHS_VALUE="/ /var /home"
APP_1_NAME_VALUE="ferme"
APP_1_URL_VALUE="https://ferme.malio-dev.fr"
APP_2_NAME_VALUE="sirh"
APP_2_URL_VALUE="https://sirh.malio-dev.fr"
APP_3_NAME_VALUE="inventory"
APP_3_URL_VALUE="https://inventory.malio-dev.fr"
# Bootstrap SSH vers le serveur de destination
BOOTSTRAP_SSH_USER=""
BOOTSTRAP_SSH_PORT="22"
BOOTSTRAP_SSH_KEY=""
BOOTSTRAP_SSH_STRICT="accept-new"
INSTALL_BACKUP_KEY_ON_REMOTE="true"
#######################################
# Scripts attendus
#######################################
EXPECTED_SCRIPTS=(
"backup-vaultwarden.sh"
"check-storage.sh"
"check-statut-recette.sh"
"backup-bdd-recette.sh"
"rebuild-bdd-recette.sh"
)
#######################################
# Journalisation
#######################################
timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
log() {
echo "[$(timestamp)] [INFO] $*"
}
warn() {
echo "[$(timestamp)] [WARN] $*" >&2
}
err() {
echo "[$(timestamp)] [ERROR] $*" >&2
}
die() {
err "$*"
exit 1
}
#######################################
# Gestion erreurs
#######################################
on_error() {
local exit_code=$?
err "Échec ligne ${BASH_LINENO[0]} : ${BASH_COMMAND}"
exit "$exit_code"
}
trap on_error ERR
#######################################
# Aide
#######################################
usage() {
cat <<'EOF'
Usage:
bootstrap-backup-env.sh [options]
Options dépôt :
--repo-url URL
--repo-branch BRANCH
--repo-subdir PATH
Options installation :
--install-dir PATH
--deploy-user USER
--deploy-group GROUP
--env-file-name NAME
--force-chown true|false
--non-interactive true|false
Options configuration applicative :
--env-name NAME
--pghost HOST
--pgport PORT
--pguser USER
--pgpassword PASSWORD
--dbs "sirh inventory ferme"
--backup-remote-user USER
--backup-remote-host HOST
--backup-remote-dir PATH
--ssh-connect-timeout SECONDS
--retention-days DAYS
--discord-webhook-url URL
--discord-ping VALUE
--webhook-url URL
--vaultwarden-data-dir PATH
--check-storage-paths "/ /var /home"
--app-1-name NAME
--app-1-url URL
--app-2-name NAME
--app-2-url URL
--app-3-name NAME
--app-3-url URL
Options bootstrap SSH distant :
--bootstrap-ssh-user USER
--bootstrap-ssh-port PORT
--bootstrap-ssh-key PATH
--bootstrap-ssh-strict accept-new|yes|no
--install-backup-key-on-remote true|false
Divers :
--help
Notes :
- si le script est lancé localement en mode interactif, il posera les
questions nécessaires pour compléter les champs obligatoires ;
- la clé publique backup peut être installée automatiquement sur le serveur
distant uniquement si un accès SSH bootstrap existe déjà ;
- le .env peut être rempli automatiquement si les valeurs sont passées en
arguments ou via variables d'environnement avant exécution.
EOF
}
#######################################
# Parsing arguments
#######################################
while [[ $# -gt 0 ]]; do
case "$1" in
--repo-url) REPO_URL="${2:-}"; shift 2 ;;
--repo-branch) REPO_BRANCH="${2:-}"; shift 2 ;;
--repo-subdir) REPO_SUBDIR="${2:-}"; shift 2 ;;
--install-dir) INSTALL_DIR="${2:-}"; shift 2 ;;
--deploy-user) DEPLOY_USER="${2:-}"; shift 2 ;;
--deploy-group) DEPLOY_GROUP="${2:-}"; shift 2 ;;
--env-file-name) ENV_FILE_NAME="${2:-}"; shift 2 ;;
--force-chown) FORCE_CHOWN="${2:-}"; shift 2 ;;
--non-interactive) NON_INTERACTIVE="${2:-}"; shift 2 ;;
--env-name) ENV_NAME="${2:-}"; shift 2 ;;
--pghost) PGHOST="${2:-}"; shift 2 ;;
--pgport) PGPORT="${2:-}"; shift 2 ;;
--pguser) PGUSER_VALUE="${2:-}"; shift 2 ;;
--pgpassword) PGPASSWORD_VALUE="${2:-}"; shift 2 ;;
--dbs) DBS_VALUE="${2:-}"; shift 2 ;;
--backup-remote-user) BACKUP_REMOTE_USER="${2:-}"; shift 2 ;;
--backup-remote-host) BACKUP_REMOTE_HOST="${2:-}"; shift 2 ;;
--backup-remote-dir) BACKUP_REMOTE_DIR="${2:-}"; shift 2 ;;
--ssh-connect-timeout) SSH_CONNECT_TIMEOUT="${2:-}"; shift 2 ;;
--retention-days) RETENTION_DAYS="${2:-}"; shift 2 ;;
--discord-webhook-url) DISCORD_WEBHOOK_URL_VALUE="${2:-}"; shift 2 ;;
--discord-ping) DISCORD_PING_VALUE="${2:-}"; shift 2 ;;
--webhook-url) WEBHOOK_URL_VALUE="${2:-}"; shift 2 ;;
--vaultwarden-data-dir) VAULTWARDEN_DATA_DIR_VALUE="${2:-}"; shift 2 ;;
--check-storage-paths) CHECK_STORAGE_PATHS_VALUE="${2:-}"; shift 2 ;;
--app-1-name) APP_1_NAME_VALUE="${2:-}"; shift 2 ;;
--app-1-url) APP_1_URL_VALUE="${2:-}"; shift 2 ;;
--app-2-name) APP_2_NAME_VALUE="${2:-}"; shift 2 ;;
--app-2-url) APP_2_URL_VALUE="${2:-}"; shift 2 ;;
--app-3-name) APP_3_NAME_VALUE="${2:-}"; shift 2 ;;
--app-3-url) APP_3_URL_VALUE="${2:-}"; shift 2 ;;
--bootstrap-ssh-user) BOOTSTRAP_SSH_USER="${2:-}"; shift 2 ;;
--bootstrap-ssh-port) BOOTSTRAP_SSH_PORT="${2:-}"; shift 2 ;;
--bootstrap-ssh-key) BOOTSTRAP_SSH_KEY="${2:-}"; shift 2 ;;
--bootstrap-ssh-strict) BOOTSTRAP_SSH_STRICT="${2:-}"; shift 2 ;;
--install-backup-key-on-remote) INSTALL_BACKUP_KEY_ON_REMOTE="${2:-}"; shift 2 ;;
--help|-h) usage; exit 0 ;;
*) die "Option inconnue : $1" ;;
esac
done
#######################################
# Surcharge par variables d'environnement
#######################################
PGPASSWORD_VALUE="${PGPASSWORD_VALUE:-${PGPASSWORD:-}}"
DISCORD_WEBHOOK_URL_VALUE="${DISCORD_WEBHOOK_URL_VALUE:-${DISCORD_WEBHOOK_URL:-}}"
DISCORD_PING_VALUE="${DISCORD_PING_VALUE:-${DISCORD_PING:-}}"
WEBHOOK_URL_VALUE="${WEBHOOK_URL_VALUE:-${WEBHOOK_URL:-}}"
#######################################
# Détection mode interactif local
#######################################
is_interactive() {
[[ -t 0 && -t 1 && "${NON_INTERACTIVE}" != "true" ]]
}
#######################################
# Questions interactives
#######################################
prompt_value() {
local var_name="$1"
local prompt_label="$2"
local default_value="${3:-}"
local secret="${4:-false}"
local required="${5:-false}"
local current_value
current_value="${!var_name:-}"
if [[ -n "$current_value" ]]; then
return 0
fi
if ! is_interactive; then
if [[ "$required" == "true" ]]; then
die "Valeur obligatoire manquante : ${var_name}. Fournissez-la en argument ou variable d'environnement."
fi
return 0
fi
local input=""
while true; do
if [[ "$secret" == "true" ]]; then
if [[ -n "$default_value" ]]; then
read -r -s -p "${prompt_label} [valeur masquée, Entrée pour conserver la valeur par défaut] : " input
else
read -r -s -p "${prompt_label} : " input
fi
echo
else
if [[ -n "$default_value" ]]; then
read -r -p "${prompt_label} [${default_value}] : " input
else
read -r -p "${prompt_label} : " input
fi
fi
if [[ -z "$input" && -n "$default_value" ]]; then
input="$default_value"
fi
if [[ "$required" == "true" && -z "$input" ]]; then
warn "Cette valeur est obligatoire."
continue
fi
printf -v "$var_name" '%s' "$input"
break
done
}
ask_required_local_configuration() {
if ! is_interactive; then
return 0
fi
log "Mode interactif local détecté : collecte des données obligatoires."
prompt_value REPO_URL "URL SSH du dépôt Git privé" "$REPO_URL" false true
prompt_value REPO_BRANCH "Branche Git" "$REPO_BRANCH" false true
prompt_value REPO_SUBDIR "Sous-dossier du dépôt contenant les scripts" "$REPO_SUBDIR" false true
prompt_value INSTALL_DIR "Répertoire d'installation" "$INSTALL_DIR" false true
prompt_value DEPLOY_USER "Utilisateur propriétaire du déploiement" "$DEPLOY_USER" false true
if [[ -z "$DEPLOY_GROUP" ]]; then
local deploy_group_default=""
if id "$DEPLOY_USER" >/dev/null 2>&1; then
deploy_group_default="$(id -gn "$DEPLOY_USER")"
fi
prompt_value DEPLOY_GROUP "Groupe propriétaire" "$deploy_group_default" false true
fi
prompt_value ENV_NAME "Nom de l'environnement" "$ENV_NAME" false true
prompt_value PGHOST "Host PostgreSQL" "$PGHOST" false true
prompt_value PGPORT "Port PostgreSQL" "$PGPORT" false true
if [[ -z "$PGUSER_VALUE" ]]; then
prompt_value PGUSER_VALUE "Utilisateur PostgreSQL" "$DEPLOY_USER" false true
fi
prompt_value PGPASSWORD_VALUE "Mot de passe PostgreSQL (PGPASSWORD)" "" true true
prompt_value DBS_VALUE "Bases PostgreSQL à gérer (séparées par des espaces)" "$DBS_VALUE" false true
prompt_value BACKUP_REMOTE_USER "Utilisateur du serveur de sauvegarde" "$BACKUP_REMOTE_USER" false true
prompt_value BACKUP_REMOTE_HOST "Host/IP du serveur de sauvegarde" "$BACKUP_REMOTE_HOST" false true
prompt_value BACKUP_REMOTE_DIR "Répertoire distant de sauvegarde" "$BACKUP_REMOTE_DIR" false true
prompt_value SSH_CONNECT_TIMEOUT "Timeout SSH en secondes" "$SSH_CONNECT_TIMEOUT" false true
prompt_value RETENTION_DAYS "Rétention en jours" "$RETENTION_DAYS" false true
prompt_value VAULTWARDEN_DATA_DIR_VALUE "Répertoire des données Vaultwarden" "$VAULTWARDEN_DATA_DIR_VALUE" false true
prompt_value CHECK_STORAGE_PATHS_VALUE "Chemins à surveiller pour le stockage" "$CHECK_STORAGE_PATHS_VALUE" false true
prompt_value APP_1_NAME_VALUE "Nom application 1" "$APP_1_NAME_VALUE" false true
prompt_value APP_1_URL_VALUE "URL application 1" "$APP_1_URL_VALUE" false true
prompt_value APP_2_NAME_VALUE "Nom application 2" "$APP_2_NAME_VALUE" false true
prompt_value APP_2_URL_VALUE "URL application 2" "$APP_2_URL_VALUE" false true
prompt_value APP_3_NAME_VALUE "Nom application 3" "$APP_3_NAME_VALUE" false true
prompt_value APP_3_URL_VALUE "URL application 3" "$APP_3_URL_VALUE" false true
prompt_value DISCORD_WEBHOOK_URL_VALUE "Discord webhook URL (optionnel)" "$DISCORD_WEBHOOK_URL_VALUE" true false
prompt_value DISCORD_PING_VALUE "Discord ping (optionnel)" "$DISCORD_PING_VALUE" false false
prompt_value WEBHOOK_URL_VALUE "Webhook URL générique (optionnel)" "$WEBHOOK_URL_VALUE" true false
if [[ "$INSTALL_BACKUP_KEY_ON_REMOTE" == "true" ]]; then
prompt_value BOOTSTRAP_SSH_USER "Utilisateur SSH bootstrap pour installer la clé distante" "${BOOTSTRAP_SSH_USER:-$BACKUP_REMOTE_USER}" false true
prompt_value BOOTSTRAP_SSH_PORT "Port SSH bootstrap" "$BOOTSTRAP_SSH_PORT" false true
prompt_value BOOTSTRAP_SSH_KEY "Chemin de la clé SSH bootstrap (optionnel si agent SSH ou accès existant)" "$BOOTSTRAP_SSH_KEY" false false
fi
}
#######################################
# Vérifications initiales
#######################################
validate_required_values() {
[[ -n "$REPO_URL" ]] || die "L'option --repo-url est obligatoire."
[[ -n "$REPO_SUBDIR" ]] || die "L'option --repo-subdir est obligatoire."
[[ -n "$BACKUP_REMOTE_HOST" ]] || die "L'option --backup-remote-host est obligatoire."
[[ -n "$DEPLOY_USER" ]] || die "L'utilisateur de déploiement est obligatoire."
if ! id "$DEPLOY_USER" >/dev/null 2>&1; then
die "Utilisateur inexistant : $DEPLOY_USER"
fi
if [[ -z "$DEPLOY_GROUP" ]]; then
DEPLOY_GROUP="$(id -gn "$DEPLOY_USER")"
fi
if [[ -z "$PGUSER_VALUE" ]]; then
PGUSER_VALUE="$DEPLOY_USER"
fi
}
#######################################
# Chemins calculés
#######################################
compute_paths() {
BASE_DIR="$INSTALL_DIR"
REPO_DIR="$BASE_DIR/$GIT_DIR_NAME"
APP_SCRIPTS_DIR="$BASE_DIR/$SCRIPTS_DIR_NAME"
CONFIG_DIR="$BASE_DIR/$CONFIG_DIR_NAME"
LOG_DIR="$BASE_DIR/$LOG_DIR_NAME"
DATA_DIR="$BASE_DIR/$DATA_DIR_NAME"
TMP_DIR="$BASE_DIR/$TMP_DIR_NAME"
APP_SSH_DIR="$BASE_DIR/$SSH_DIR_NAME"
ENV_FILE="$BASE_DIR/$ENV_FILE_NAME"
SCRIPTS_JSON="$CONFIG_DIR/scripts.json"
BACKUP_SSH_KEY="$APP_SSH_DIR/$BACKUP_SSH_KEY_NAME"
REPO_SSH_KEY="$APP_SSH_DIR/$REPO_SSH_KEY_NAME"
}
#######################################
# Wrapper sudo
#######################################
run_root() {
if [[ "$(id -u)" -eq 0 ]]; then
"$@"
else
sudo "$@"
fi
}
run_as_deploy_user() {
if [[ "$(id -un)" == "$DEPLOY_USER" ]]; then
"$@"
else
run_root sudo -u "$DEPLOY_USER" -H "$@"
fi
}
#######################################
# Détection package manager
#######################################
detect_pkg_manager() {
if command -v apt-get >/dev/null 2>&1; then
echo "apt"
elif command -v dnf >/dev/null 2>&1; then
echo "dnf"
elif command -v yum >/dev/null 2>&1; then
echo "yum"
elif command -v apk >/dev/null 2>&1; then
echo "apk"
elif command -v pacman >/dev/null 2>&1; then
echo "pacman"
else
echo ""
fi
}
install_packages() {
local packages=("$@")
[[ ${#packages[@]} -gt 0 ]] || return 0
case "$PKG_MANAGER" in
apt)
run_root apt-get update -y
run_root apt-get install -y "${packages[@]}"
;;
dnf)
run_root dnf install -y "${packages[@]}"
;;
yum)
run_root yum install -y "${packages[@]}"
;;
apk)
run_root apk add --no-cache "${packages[@]}"
;;
pacman)
run_root pacman -Sy --noconfirm "${packages[@]}"
;;
*)
die "Package manager non géré : $PKG_MANAGER"
;;
esac
}
ensure_cmd() {
local cmd="$1"
shift
local packages=("$@")
if command -v "$cmd" >/dev/null 2>&1; then
log "Dépendance OK : $cmd"
else
warn "Dépendance absente : $cmd"
install_packages "${packages[@]}"
fi
}
install_dependencies() {
case "$PKG_MANAGER" in
apt)
ensure_cmd git git
ensure_cmd ssh openssh-client
ensure_cmd ssh-keygen openssh-client
ensure_cmd rsync rsync
ensure_cmd curl curl
ensure_cmd jq jq
ensure_cmd psql postgresql-client
install_packages ca-certificates
;;
dnf|yum)
ensure_cmd git git
ensure_cmd ssh openssh-clients
ensure_cmd ssh-keygen openssh-clients
ensure_cmd rsync rsync
ensure_cmd curl curl
ensure_cmd jq jq
ensure_cmd psql postgresql
install_packages ca-certificates
;;
apk)
ensure_cmd git git
ensure_cmd ssh openssh-client
ensure_cmd ssh-keygen openssh-keygen
ensure_cmd rsync rsync
ensure_cmd curl curl
ensure_cmd jq jq
ensure_cmd psql postgresql-client
install_packages ca-certificates
;;
pacman)
ensure_cmd git git
ensure_cmd ssh openssh
ensure_cmd ssh-keygen openssh
ensure_cmd rsync rsync
ensure_cmd curl curl
ensure_cmd jq jq
ensure_cmd psql postgresql-libs
install_packages ca-certificates
;;
esac
}
#######################################
# Création dossiers
#######################################
ensure_dir() {
local dir="$1"
local mode="$2"
run_root mkdir -p "$dir"
run_root chmod "$mode" "$dir"
}
prepare_directories() {
ensure_dir "$BASE_DIR" 0755
ensure_dir "$REPO_DIR" 0755
ensure_dir "$APP_SCRIPTS_DIR" 0750
ensure_dir "$CONFIG_DIR" 0750
ensure_dir "$LOG_DIR" 0750
ensure_dir "$DATA_DIR" 0750
ensure_dir "$TMP_DIR" 0750
ensure_dir "$APP_SSH_DIR" 0700
}
#######################################
# Permissions
#######################################
apply_ownership() {
if [[ "$FORCE_CHOWN" == "true" ]]; then
run_root chown -R "${DEPLOY_USER}:${DEPLOY_GROUP}" "$BASE_DIR"
else
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" \
"$BASE_DIR" "$REPO_DIR" "$APP_SCRIPTS_DIR" "$CONFIG_DIR" \
"$LOG_DIR" "$DATA_DIR" "$TMP_DIR" "$APP_SSH_DIR"
fi
}
#######################################
# SSH
#######################################
ensure_ssh_keypair() {
local private_key="$1"
local comment="$2"
if [[ -f "$private_key" && -f "${private_key}.pub" ]]; then
log "Clé SSH déjà présente : $private_key"
run_root chmod 600 "$private_key"
run_root chmod 644 "${private_key}.pub"
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "$private_key" "${private_key}.pub"
return 0
fi
log "Génération de la clé SSH : $private_key"
run_root ssh-keygen -t ed25519 -N "" -C "$comment" -f "$private_key" >/dev/null
run_root chmod 600 "$private_key"
run_root chmod 644 "${private_key}.pub"
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "$private_key" "${private_key}.pub"
}
build_bootstrap_ssh_cmd() {
local target="$1"
local -a cmd=(ssh -p "$BOOTSTRAP_SSH_PORT" -o "StrictHostKeyChecking=$BOOTSTRAP_SSH_STRICT")
if [[ -n "$BOOTSTRAP_SSH_KEY" ]]; then
cmd+=(-i "$BOOTSTRAP_SSH_KEY" -o IdentitiesOnly=yes)
fi
cmd+=("$target")
printf '%q ' "${cmd[@]}"
}
install_backup_key_on_remote() {
[[ "$INSTALL_BACKUP_KEY_ON_REMOTE" == "true" ]] || {
log "Installation de la clé backup sur le serveur distant désactivée."
return 0
}
[[ -n "$BACKUP_REMOTE_HOST" ]] || {
warn "BACKUP_REMOTE_HOST vide, installation de la clé distante ignorée."
return 0
}
local bootstrap_user="${BOOTSTRAP_SSH_USER:-$BACKUP_REMOTE_USER}"
local remote_target="${bootstrap_user}@${BACKUP_REMOTE_HOST}"
local remote_auth_user="$BACKUP_REMOTE_USER"
local pubkey
pubkey="$(run_root cat "${BACKUP_SSH_KEY}.pub")"
log "Tentative d'installation de la clé publique backup sur ${remote_auth_user}@${BACKUP_REMOTE_HOST}"
local ssh_cmd
ssh_cmd="$(build_bootstrap_ssh_cmd "$remote_target")"
if ! eval "$ssh_cmd" "true" >/dev/null 2>&1; then
warn "Accès SSH bootstrap indisponible vers ${remote_target}. Clé backup non installée automatiquement."
return 0
fi
local escaped_pubkey
escaped_pubkey="$(printf '%q' "$pubkey")"
eval "$ssh_cmd" "sudo -u '$remote_auth_user' sh -c '
set -eu
umask 077
mkdir -p ~/.ssh
touch ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
grep -qxF $escaped_pubkey ~/.ssh/authorized_keys || printf \"%s\n\" $escaped_pubkey >> ~/.ssh/authorized_keys
'" >/dev/null
log "Clé publique backup installée / vérifiée sur ${remote_auth_user}@${BACKUP_REMOTE_HOST}"
}
#######################################
# .env
#######################################
ensure_env_file() {
if [[ ! -f "$ENV_FILE" ]]; then
log "Création du fichier : $ENV_FILE"
run_root touch "$ENV_FILE"
fi
run_root chmod 0640 "$ENV_FILE"
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "$ENV_FILE"
}
set_env_value() {
local key="$1"
local value="$2"
local escaped
escaped="$(printf '%s' "$value" | sed 's/[\/&]/\\&/g')"
if run_root grep -qE "^${key}=" "$ENV_FILE"; then
run_root sed -i "s/^${key}=.*/${key}=${escaped}/" "$ENV_FILE"
else
printf '%s=%s\n' "$key" "$value" | run_root tee -a "$ENV_FILE" >/dev/null
fi
}
update_env_defaults() {
set_env_value "ENV_NAME" "$ENV_NAME"
set_env_value "BASE_DIR" "$BASE_DIR"
set_env_value "SCRIPTS_DIR" "$APP_SCRIPTS_DIR"
set_env_value "CONFIG_DIR" "$CONFIG_DIR"
set_env_value "LOG_DIR" "$LOG_DIR"
set_env_value "DATA_DIR" "$DATA_DIR"
set_env_value "TMP_DIR" "$TMP_DIR"
set_env_value "SSH_DIR" "$APP_SSH_DIR"
set_env_value "SSH_KEY" "$BACKUP_SSH_KEY"
set_env_value "REPO_SSH_KEY" "$REPO_SSH_KEY"
set_env_value "PGHOST" "$PGHOST"
set_env_value "PGPORT" "$PGPORT"
set_env_value "PGUSER" "$PGUSER_VALUE"
set_env_value "PGPASSWORD" "${PGPASSWORD_VALUE:-change_me}"
set_env_value "DBS" "\"$DBS_VALUE\""
set_env_value "BACKUP_REMOTE_USER" "$BACKUP_REMOTE_USER"
set_env_value "BACKUP_REMOTE_HOST" "$BACKUP_REMOTE_HOST"
set_env_value "BACKUP_REMOTE_DIR" "$BACKUP_REMOTE_DIR"
set_env_value "SSH_CONNECT_TIMEOUT" "$SSH_CONNECT_TIMEOUT"
set_env_value "RETENTION_DAYS" "$RETENTION_DAYS"
set_env_value "DISCORD_WEBHOOK_URL" "$DISCORD_WEBHOOK_URL_VALUE"
set_env_value "DISCORD_PING" "$DISCORD_PING_VALUE"
set_env_value "WEBHOOK_URL" "$WEBHOOK_URL_VALUE"
set_env_value "VAULTWARDEN_DATA_DIR" "$VAULTWARDEN_DATA_DIR_VALUE"
set_env_value "CHECK_STORAGE_PATHS" "\"$CHECK_STORAGE_PATHS_VALUE\""
set_env_value "APP_1_NAME" "$APP_1_NAME_VALUE"
set_env_value "APP_1_URL" "$APP_1_URL_VALUE"
set_env_value "APP_2_NAME" "$APP_2_NAME_VALUE"
set_env_value "APP_2_URL" "$APP_2_URL_VALUE"
set_env_value "APP_3_NAME" "$APP_3_NAME_VALUE"
set_env_value "APP_3_URL" "$APP_3_URL_VALUE"
}
#######################################
# Git privé + sparse checkout
#######################################
write_git_ssh_wrapper() {
local wrapper="$TMP_DIR/git_ssh_wrapper.sh"
cat > /tmp/.git_ssh_wrapper.$$ <<EOF
#!/usr/bin/env bash
exec ssh -i "$REPO_SSH_KEY" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "\$@"
EOF
run_root mv /tmp/.git_ssh_wrapper.$$ "$wrapper"
run_root chmod 0700 "$wrapper"
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "$wrapper"
echo "$wrapper"
}
sync_repo() {
local wrapper
wrapper="$(write_git_ssh_wrapper)"
if [[ ! -d "$REPO_DIR/.git" ]]; then
log "Clone initial du dépôt"
run_root rm -rf "$REPO_DIR"
run_root mkdir -p "$REPO_DIR"
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "$REPO_DIR"
run_as_deploy_user env GIT_SSH_COMMAND="$wrapper" \
git clone \
--filter=blob:none \
--no-checkout \
--branch "$REPO_BRANCH" \
"$REPO_URL" \
"$REPO_DIR"
run_as_deploy_user git -C "$REPO_DIR" sparse-checkout init --cone
run_as_deploy_user git -C "$REPO_DIR" sparse-checkout set "$REPO_SUBDIR"
run_as_deploy_user git -C "$REPO_DIR" checkout "$REPO_BRANCH"
else
log "Mise à jour du dépôt existant"
run_as_deploy_user git -C "$REPO_DIR" remote set-url origin "$REPO_URL"
run_as_deploy_user git -C "$REPO_DIR" sparse-checkout init --cone || true
run_as_deploy_user git -C "$REPO_DIR" sparse-checkout set "$REPO_SUBDIR"
run_as_deploy_user env GIT_SSH_COMMAND="$wrapper" \
git -C "$REPO_DIR" fetch origin "$REPO_BRANCH" --depth=1
run_as_deploy_user git -C "$REPO_DIR" checkout "$REPO_BRANCH"
run_as_deploy_user git -C "$REPO_DIR" reset --hard "origin/$REPO_BRANCH"
run_as_deploy_user git -C "$REPO_DIR" clean -fd
fi
}
#######################################
# Déploiement scripts
#######################################
deploy_scripts() {
local source_dir="$REPO_DIR/$REPO_SUBDIR"
[[ -d "$source_dir" ]] || die "Sous-dossier introuvable : $source_dir"
local script
for script in "${EXPECTED_SCRIPTS[@]}"; do
[[ -f "$source_dir/$script" ]] || die "Script manquant dans le dépôt : $source_dir/$script"
done
for script in "${EXPECTED_SCRIPTS[@]}"; do
log "Déploiement : $script"
run_root install -m 0750 -o "$DEPLOY_USER" -g "$DEPLOY_GROUP" \
"$source_dir/$script" "$APP_SCRIPTS_DIR/$script"
done
}
verify_scripts() {
local script
for script in "${EXPECTED_SCRIPTS[@]}"; do
[[ -f "$APP_SCRIPTS_DIR/$script" ]] || die "Script absent après déploiement : $APP_SCRIPTS_DIR/$script"
[[ -x "$APP_SCRIPTS_DIR/$script" ]] || die "Script non exécutable : $APP_SCRIPTS_DIR/$script"
done
}
#######################################
# Configuration web
#######################################
generate_scripts_json() {
local tmp_json
tmp_json="$(mktemp)"
cat > "$tmp_json" <<EOF
{
"generated_at": "$(date -Iseconds)",
"base_dir": "$BASE_DIR",
"env_file": "$ENV_FILE",
"scripts": [
{
"id": "backup-vaultwarden",
"label": "Backup Vaultwarden",
"path": "$APP_SCRIPTS_DIR/backup-vaultwarden.sh",
"web_enabled": true,
"params": [
{ "name": "env_file", "type": "string", "required": false, "default": "$ENV_FILE" },
{ "name": "dry_run", "type": "boolean", "required": false, "default": false },
{ "name": "json", "type": "boolean", "required": false, "default": true }
]
},
{
"id": "check-storage",
"label": "Vérification stockage",
"path": "$APP_SCRIPTS_DIR/check-storage.sh",
"web_enabled": true,
"params": [
{ "name": "env_file", "type": "string", "required": false, "default": "$ENV_FILE" },
{ "name": "verbose", "type": "boolean", "required": false, "default": false },
{ "name": "json", "type": "boolean", "required": false, "default": true }
]
},
{
"id": "check-statut-recette",
"label": "Vérification statut recette",
"path": "$APP_SCRIPTS_DIR/check-statut-recette.sh",
"web_enabled": true,
"params": [
{ "name": "env_file", "type": "string", "required": false, "default": "$ENV_FILE" },
{ "name": "timeout", "type": "integer", "required": false, "default": 10 },
{ "name": "json", "type": "boolean", "required": false, "default": true }
]
},
{
"id": "backup-bdd-recette",
"label": "Backup BDD recette",
"path": "$APP_SCRIPTS_DIR/backup-bdd-recette.sh",
"web_enabled": true,
"params": [
{ "name": "env_file", "type": "string", "required": false, "default": "$ENV_FILE" },
{ "name": "db", "type": "select", "required": false, "values": ["sirh", "inventory", "ferme"] },
{ "name": "dry_run", "type": "boolean", "required": false, "default": false },
{ "name": "json", "type": "boolean", "required": false, "default": true }
]
},
{
"id": "rebuild-bdd-recette",
"label": "Rebuild BDD recette",
"path": "$APP_SCRIPTS_DIR/rebuild-bdd-recette.sh",
"web_enabled": true,
"params": [
{ "name": "env_file", "type": "string", "required": false, "default": "$ENV_FILE" },
{ "name": "db", "type": "select", "required": true, "values": ["sirh", "inventory", "ferme"] },
{ "name": "source", "type": "string", "required": false, "default": "latest" },
{ "name": "force", "type": "boolean", "required": false, "default": false },
{ "name": "json", "type": "boolean", "required": false, "default": true }
]
}
]
}
EOF
run_root mv "$tmp_json" "$SCRIPTS_JSON"
run_root chmod 0640 "$SCRIPTS_JSON"
run_root chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "$SCRIPTS_JSON"
}
#######################################
# Résumé
#######################################
print_summary() {
cat <<EOF
========================================================================
Bootstrap terminé
========================================================================
Utilisateur : $DEPLOY_USER:$DEPLOY_GROUP
Base : $BASE_DIR
Scripts : $APP_SCRIPTS_DIR
Config : $CONFIG_DIR
.env : $ENV_FILE
scripts.json : $SCRIPTS_JSON
Repo : $REPO_DIR
Clé SSH backup : $BACKUP_SSH_KEY
Clé SSH repo : $REPO_SSH_KEY
Remote backup : ${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}:${BACKUP_REMOTE_DIR}
Clé publique repo :
$(run_root cat "${REPO_SSH_KEY}.pub")
Clé publique backup :
$(run_root cat "${BACKUP_SSH_KEY}.pub")
Vérifiez :
- la clé repo doit être ajoutée comme deploy key sur le dépôt Git privé ;
- la valeur PGPASSWORD dans $ENV_FILE ;
- les webhooks renseignés dans $ENV_FILE si nécessaires.
========================================================================
EOF
}
#######################################
# Main
#######################################
main() {
ask_required_local_configuration
validate_required_values
compute_paths
PKG_MANAGER="$(detect_pkg_manager)"
[[ -n "$PKG_MANAGER" ]] || die "Gestionnaire de paquets non supporté."
log "Vérification / installation des dépendances"
install_dependencies
log "Création des répertoires"
prepare_directories
log "Préparation du .env"
ensure_env_file
update_env_defaults
log "Préparation des clés SSH"
ensure_ssh_keypair "$BACKUP_SSH_KEY" "${DEPLOY_USER}@backup-runtime"
ensure_ssh_keypair "$REPO_SSH_KEY" "${DEPLOY_USER}@repo-deploy"
log "Installation de la clé publique backup sur le serveur distant"
install_backup_key_on_remote
log "Synchronisation du dépôt Git privé"
sync_repo
log "Déploiement des scripts"
deploy_scripts
verify_scripts
log "Génération de la configuration web scripts.json"
generate_scripts_json
log "Application des permissions"
apply_ownership
print_summary
}
main "$@"

View File

@@ -1,4 +1,40 @@
# Scripts Serveur MALIO # Malio-Ops MALIO
Ce dépôt sert au **versionnement des scripts utilisés dans linfrastructure du projet Ferme**.
Lobjectif est de conserver un historique clair des scripts, suivre les évolutions et permettre leur amélioration progressive.
## Contenu du dépôt
Le dépôt peut contenir différents types de scripts utilisés pour lexploitation technique :
* scripts de **backup PostgreSQL**
* scripts de **monitoring des applications**
* scripts **dautomatisation système**
* scripts **dadministration ou de maintenance**
## Objectif
Ce dépôt permet de :
* suivre les **modifications des scripts dans le temps**
* garder une **version stable et reproductible**
* faciliter la **maintenance et les corrections**
* centraliser les scripts utilisés sur les serveurs
## Organisation
Les évolutions importantes sont documentées dans le fichier :
```
CHANGELOG.md
```
Ce fichier décrit les nouvelles fonctionnalités, corrections et modifications apportées aux scripts.
## Remarque
Ce dépôt est dédié au **versionnement des scripts uniquement**.
Les configurations sensibles (mots de passe, clés, variables denvironnement) ne doivent pas être stockées directement dans le dépôt et doivent être placées dans des fichiers `.env` locaux.
Ce projet contient des scripts pour la gestion et la maintenance des serveurs de MALIO. Ce projet contient des scripts pour la gestion et la maintenance des serveurs de MALIO.

View File

@@ -0,0 +1,81 @@
#############################################
# ENVIRONNEMENT
#############################################
# Nom de l'environnement (ex: DEV / RECETTE / PROD)
ENV_NAME=RECETTE
#############################################
# POSTGRESQL
#############################################
# Host PostgreSQL
PGHOST=localhost
# Port PostgreSQL
PGPORT=5432
# Utilisateur utilisé pour les dumps
PGUSER=nom_de_user
# Mot de passe PostgreSQL
PGPASSWORD=change_me_secure_password
# Bases à sauvegarder (séparées par espace)
DBS="sirh inventory ferme"
#############################################
# SERVEUR DE STOCKAGE DES BACKUPS
#############################################
# Utilisateur du serveur distant
BACKUP_REMOTE_USER=nom_de_user
# Host ou IP du serveur distant
BACKUP_REMOTE_HOST=192.168.1.50
# Dossier distant pour stocker les backups
BACKUP_REMOTE_DIR=/home/nom_de_user/backups/bdd-recette
#############################################
# SSH
#############################################
# Clé SSH utilisée pour envoyer les dumps
SSH_KEY=/home/nom_de_user/.ssh/id_ed25519_backup
# Timeout SSH (secondes)
SSH_TIMEOUT=10
#############################################
# LOGS
#############################################
# Dossier des logs backup
BACKUP_LOG_DIR=/var/log/pg_backup
# Dossier logs monitoring apps
APP_LOG_DIR=/var/log/app_health
#############################################
# DISCORD
#############################################
# Webhook Discord pour notifications
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Ping en cas d'erreur
DISCORD_PING=@here
#############################################
# HEALTH CHECK APPS
#############################################
# Timeout connexion HTTP
CHECK_CONNECT_TIMEOUT=3
# Timeout total curl
CHECK_MAX_TIME=8
# Applications à vérifier
APP_URLS="ferme.example.local sirh.example.local inventory.example.local"

121
RecetteScripts/README.md Normal file
View File

@@ -0,0 +1,121 @@
# Scripts Recette
Ce dossier contient les scripts utilisés pour lenvironnement **RECETTE** du projet **Ferme**.
Les scripts permettent principalement :
* la **sauvegarde automatique des bases de données PostgreSQL**
* la **vérification du statut des applications web**
---
# Scripts disponibles
## backup-bdd-recette.sh
Script permettant de réaliser une **sauvegarde des bases de données PostgreSQL**.
Fonctionnement :
* export des bases PostgreSQL définies dans la configuration
* export des utilisateurs PostgreSQL
* création de dumps au format PostgreSQL (`pg_dump -Fc`)
* transfert des sauvegardes vers un serveur distant
* génération de logs locaux
* envoi de notifications Discord en cas de succès ou derreur
---
## check-statut-recette.sh
Script permettant de **vérifier la disponibilité des applications web**.
Le script effectue les vérifications suivantes :
* résolution DNS du site
* connexion HTTP au service
* vérification du code HTTP retourné
Une application est considérée :
* **OK** si le code HTTP est entre **200 et 399**
* **DOWN** si la résolution DNS échoue ou si une erreur HTTP est détectée
Chaque vérification est enregistrée dans un **fichier de log** et une notification peut être envoyée sur **Discord**.
---
# Installation
1. Clonez le dépôt Git :
```bash
git clone https://gitea.malio.fr/MALIO-DEV/Scripts-Serveur.git
```
2. Accédez au dossier des scripts :
```bash
cd Scripts-Serveur/RecetteScripts
```
3. Copiez le fichier denvironnement :
```bash
cp .env.example .env
```
4. Modifiez les variables du fichier `.env` selon votre configuration.
---
# Utilisation
Donnez les permissions dexécution aux scripts :
```bash
chmod +x backup-bdd-recette.sh
chmod +x check-statut-recette.sh
```
Exécution manuelle :
```bash
./backup-bdd-recette.sh
./check-statut-recette.sh
```
---
# Exécution automatique avec Cron
Ouvrez le crontab :
```bash
crontab -e
```
Exemple de planification :
Backup des bases tous les jours à 19h :
```bash
0 19 * * * /chemin/vers/le/script/backup-bdd-recette.sh
```
Vérification des applications tous les jours à 19h :
```bash
0 19 * * * /chemin/vers/le/script/check-statut-recette.sh
```
---
# Avertissement
Assurez-vous que :
* PostgreSQL est accessible depuis la machine exécutant le script
* la clé SSH pour le transfert des sauvegardes est configurée
* les variables du fichier `.env` sont correctement renseignées
* les commandes `curl`, `psql` et `pg_dump` sont installées sur le système

View File

@@ -0,0 +1,440 @@
#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# backup-bdd-recette.sh
#
# Ce script réalise une sauvegarde logique de plusieurs bases PostgreSQL
# définies dans le fichier .env, exporte également la liste des rôles/users,
# puis transfère lensemble vers une machine distante de stockage.
#
# Fonctionnement global :
# 1. charge la configuration depuis le fichier .env ;
# 2. vérifie les dépendances nécessaires ;
# 3. prépare les chemins, logs et variables de connexion ;
# 4. empêche lexécution simultanée grâce à un verrou ;
# 5. crée les dossiers de destination sur la machine distante ;
# 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 ;
# 10. envoie un bilan sur Discord :
# - 1 message global si tout est OK ;
# - en cas derreur partielle :
# * USERS OK -> message simple ;
# * USERS KO -> message détaillé ;
# * DB OK -> message simple ;
# * DB KO -> message détaillé.
###############################################################################
#######################################
# Chargement du .env
#######################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
if [[ ! -f "$ENV_FILE" ]]; then
echo "ERROR: fichier .env introuvable : $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
#######################################
# Vérification des variables requises
#######################################
: "${ENV_NAME:?Variable ENV_NAME manquante}"
: "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}"
: "${PGPASSWORD:?Variable PGPASSWORD manquante}"
: "${DBS:?Variable DBS manquante}"
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR manquante}"
: "${SSH_KEY:?Variable SSH_KEY manquante}"
: "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}"
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
#######################################
# Configuration principale
#######################################
read -r -a DBS_ARRAY <<< "$DBS"
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
IA_BASE_DIR="${BACKUP_REMOTE_DIR}"
RETENTION_DAYS=10
SSH_OPTS=(
-i "$SSH_KEY"
-o IdentitiesOnly=yes
-o BatchMode=yes
-o ConnectTimeout="${SSH_TIMEOUT}"
)
LOG_DIR="${BACKUP_LOG_DIR}"
mkdir -p "$LOG_DIR"
TS="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_DIR_NAME="backup_${TS}"
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
TMP_DIR="/tmp/pg_dump_${BACKUP_DIR_NAME}"
mkdir -p "$TMP_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; }
require_cmd() {
command -v "$1" >/dev/null 2>&1
}
export PGPASSWORD
#######################################
# Vérification dépendances minimales
#######################################
for cmd in ssh scp curl jq pg_dump pg_dumpall; do
require_cmd "$cmd" || {
echo "ERROR: commande manquante : $cmd" >&2
exit 1
}
done
#######################################
# Configuration Discord
#######################################
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
DISCORD_PING="${DISCORD_PING:-@here}"
discord_send() {
local msg="$1"
[[ -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
}
curl -fsS \
-H "Content-Type: application/json" \
-d "$payload" \
"$DISCORD_WEBHOOK_URL" >/dev/null || true
}
#######################################
# Message global OK
#######################################
discord_msg_global_ok() {
local msg
msg="$(cat <<EOF
**BACKUP BDD ${ENV_NAME} 🟢**
Name: ${BACKUP_DIR_NAME}
Dumps transfer: ✅
Users transfer: ✅
EOF
)"
discord_send "$msg"
}
#######################################
# Messages USERS
#######################################
discord_msg_users_ok_simple() {
local msg
msg="$(cat <<EOF
**BACKUP BDD ${ENV_NAME} 🟢**
Users backup validé
EOF
)"
discord_send "$msg"
}
discord_msg_users_error() {
local export_ok="$1"
local transfer_ok="$2"
local details="$3"
local export_disp transfer_disp
export_disp=$([[ -n "$export_ok" ]] && echo "✅" || echo "❌")
transfer_disp=$([[ -n "$transfer_ok" ]] && echo "✅" || echo "❌")
local msg
if [[ -n "$details" ]]; then
msg="$(cat <<EOF
**${DISCORD_PING} BACKUP BDD ${ENV_NAME} 🔴**
Name: ${BACKUP_DIR_NAME}
Users export: ${export_disp}
Users transfer: ${transfer_disp}
Details: ${details}
EOF
)"
else
msg="$(cat <<EOF
**${DISCORD_PING} BACKUP BDD ${ENV_NAME} 🔴**
Name: ${BACKUP_DIR_NAME}
Users export: ${export_disp}
Users transfer: ${transfer_disp}
EOF
)"
fi
discord_send "$msg"
}
#######################################
# Messages DB
#######################################
discord_msg_db_ok_simple() {
local db="$1"
local msg
msg="$(cat <<EOF
**BACKUP BDD ${ENV_NAME} 🟢**
Backup validé : ${db}
EOF
)"
discord_send "$msg"
}
discord_msg_db_error() {
local db="$1"
local dump_ok="$2"
local transfer_ok="$3"
local details="$4"
local dump_disp transfer_disp
dump_disp=$([[ -n "$dump_ok" ]] && echo "✅" || echo "❌")
transfer_disp=$([[ -n "$transfer_ok" ]] && echo "✅" || echo "❌")
local msg
if [[ -n "$details" ]]; then
msg="$(cat <<EOF
**${DISCORD_PING} BACKUP BDD ${ENV_NAME} 🔴**
Name: ${BACKUP_DIR_NAME}
Database: ${db}
Dump: ${dump_disp}
Transfer: ${transfer_disp}
Details: ${details}
EOF
)"
else
msg="$(cat <<EOF
**${DISCORD_PING} BACKUP BDD ${ENV_NAME} 🔴**
Name: ${BACKUP_DIR_NAME}
Database: ${db}
Dump: ${dump_disp}
Transfer: ${transfer_disp}
EOF
)"
fi
discord_send "$msg"
}
#######################################
# Variables de statut globales
#######################################
DUMPS_OK=true
USERS_OK=true
USERS_EXPORT_OK=true
USERS_TRANSFER_OK=true
USERS_DETAILS=""
declare -A DB_DUMP_OK
declare -A DB_TRANSFER_OK
declare -A DB_DETAILS
#######################################
# Verrou dexécution
#######################################
LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
log "ERROR: Backup déjà en cours"
discord_msg_users_error "" "" "Lock already exists"
exit 1
fi
trap 'rm -rf "$LOCK_DIR" "$TMP_DIR"' EXIT
#######################################
# Préparation du dossier distant
#######################################
REMOTE_DIR="${IA_BASE_DIR}"
log "Creating remote directories"
if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "mkdir -p '${REMOTE_DIR}/ferme' '${REMOTE_DIR}/sirh' '${REMOTE_DIR}/inventory' '${REMOTE_DIR}/user'"; then
log "ERROR: remote mkdir failed"
discord_msg_users_error "" "" "Remote mkdir failed"
exit 1
fi
#######################################
# Export des rôles PostgreSQL
#######################################
ROLES_FILE="${TMP_DIR}/user_${TS}.sql"
set +e
log "Export des rôles PostgreSQL"
pg_dumpall \
-h "$PGHOST" \
-p "$PGPORT" \
-U "$PGUSER" \
--globals-only \
> "$ROLES_FILE"
RET=$?
if [[ $RET -ne 0 ]]; then
USERS_OK=
USERS_EXPORT_OK=
USERS_DETAILS="roles export failed"
else
log "Export des rôles OK : $ROLES_FILE"
fi
if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
scp "${SSH_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${REMOTE_DIR}/user/"
RET=$?
if [[ $RET -ne 0 ]]; then
USERS_OK=
USERS_TRANSFER_OK=
if [[ -n "$USERS_DETAILS" ]]; then
USERS_DETAILS+=" | roles transfer failed"
else
USERS_DETAILS="roles transfer failed"
fi
else
log "Transfert des rôles OK"
fi
fi
set -e
#######################################
# Dump des bases
#######################################
set +e
for DB in "${DBS_ARRAY[@]}"; do
FILE="${TMP_DIR}/${DB}_${TS}.dump"
DB_DUMP_OK["$DB"]=true
DB_TRANSFER_OK["$DB"]=true
DB_DETAILS["$DB"]=""
log "Dump $DB"
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"
RET=$?
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
DB_DUMP_OK["$DB"]=
DB_TRANSFER_OK["$DB"]=
DB_DETAILS["$DB"]="dump failed"
continue
fi
scp "${SSH_OPTS[@]}" "$FILE" "$IA_SSH:${REMOTE_DIR}/${DB}/"
RET=$?
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
DB_TRANSFER_OK["$DB"]=
DB_DETAILS["$DB"]="transfer failed"
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 '${REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"
RET=$?
if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for users"
else
log "Remote rotation OK for users"
fi
for DB in "${DBS_ARRAY[@]}"; do
ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"
RET=$?
if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for ${DB}"
else
log "Remote rotation OK for ${DB}"
fi
done
set -e
log "Remote rotation finished"
#######################################
# Nettoyage local
#######################################
rm -rf "$TMP_DIR"
#######################################
# Bilan final Discord
#######################################
MODE_KO=
[[ -z "${DUMPS_OK:-}" ]] && MODE_KO=true
[[ -z "${USERS_OK:-}" ]] && MODE_KO=true
if [[ -z "${MODE_KO:-}" ]]; then
discord_msg_global_ok
exit 0
fi
if [[ -n "${USERS_EXPORT_OK:-}" && -n "${USERS_TRANSFER_OK:-}" ]]; then
discord_msg_users_ok_simple
else
discord_msg_users_error "${USERS_EXPORT_OK:+true}" "${USERS_TRANSFER_OK:+true}" "$USERS_DETAILS"
fi
for DB in "${DBS_ARRAY[@]}"; do
if [[ -n "${DB_DUMP_OK[$DB]:-}" && -n "${DB_TRANSFER_OK[$DB]:-}" ]]; then
discord_msg_db_ok_simple "$DB"
else
discord_msg_db_error "$DB" "${DB_DUMP_OK[$DB]:+true}" "${DB_TRANSFER_OK[$DB]:+true}" "${DB_DETAILS[$DB]}"
fi
done
exit 2

View File

@@ -0,0 +1,222 @@
#!/usr/bin/env bash
set -uo pipefail
###############################################################################
# check-statut-recette.sh
#
# Ce script vérifie la disponibilité de plusieurs applications web définies
# dans le fichier .env.
#
# Fonctionnement global :
# 1. charge la configuration depuis le fichier .env ;
# 2. vérifie le DNS de chaque application ;
# 3. effectue une requête HTTP avec curl ;
# 4. écrit le résultat dans un fichier de log local ;
# 5. construit un message récapitulatif unique ;
# 6. envoie une seule notification Discord avec tous les statuts.
###############################################################################
#######################################
# Chargement du .env
#######################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
if [[ ! -f "$ENV_FILE" ]]; then
echo "ERROR: fichier .env introuvable : $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
#######################################
# Vérification des variables requises
#######################################
: "${ENV_NAME:?Variable ENV_NAME manquante}"
: "${APP_LOG_DIR:?Variable APP_LOG_DIR manquante}"
: "${CHECK_CONNECT_TIMEOUT:?Variable CHECK_CONNECT_TIMEOUT manquante}"
: "${CHECK_MAX_TIME:?Variable CHECK_MAX_TIME manquante}"
: "${APP_URLS:?Variable APP_URLS manquante}"
#######################################
# Sites à vérifier
#######################################
read -r -a SITES <<< "$APP_URLS"
SCHEME="http"
CONNECT_TIMEOUT="${CHECK_CONNECT_TIMEOUT}"
MAX_TIME="${CHECK_MAX_TIME}"
#######################################
# Logs
#######################################
LOG_DIR="${APP_LOG_DIR}"
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/app_health_$(date +'%Y-%m-%d').log"
#######################################
# Discord
#######################################
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
DISCORD_PING="${DISCORD_PING:-@here}"
#######################################
# Variables globales de synthèse
#######################################
SUMMARY_LINES=()
FAILURES=0
#######################################
# Logging
#######################################
log_line() {
printf "%s | %s | %s | %s\n" \
"$(date +'%Y-%m-%d %H:%M:%S')" "$1" "$2" "$3" | tee -a "$LOG_FILE"
}
#######################################
# DNS
#######################################
dns_ok() {
getent hosts "$1" >/dev/null 2>&1
}
#######################################
# Ajout au résumé Discord
#######################################
add_summary_line() {
local site="$1"
local status="$2"
local detail="$3"
local icon
if [[ "$status" == "OK" ]]; then
icon="✅"
else
icon="❌"
fi
SUMMARY_LINES+=("${icon} ${site} : ${detail}")
}
#######################################
# Envoi du message Discord récapitulatif
#######################################
send_discord_summary() {
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
local header_icon ping_prefix=""
if [[ "$FAILURES" -eq 0 ]]; then
header_icon="🟢"
else
header_icon="🔴"
ping_prefix="${DISCORD_PING} "
fi
local msg="**${ping_prefix}CHECK APP ${ENV_NAME} ${header_icon}**"$'\n'
local line
for line in "${SUMMARY_LINES[@]}"; do
msg+="${line}"$'\n'
done
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
}
#######################################
# Check application
#######################################
check_site() {
local host="$1"
local url="${SCHEME}://${host}/"
if ! dns_ok "$host"; then
log_line "DOWN" "$host" "Résolution impossible (getent hosts)"
add_summary_line "$host" "DOWN" "DOWN - DNS"
return 1
fi
local http_code curl_exit err
local stderr
stderr="$(mktemp)"
http_code="$(
curl -sS -o /dev/null \
-w '%{http_code}' \
--connect-timeout "$CONNECT_TIMEOUT" \
--max-time "$MAX_TIME" \
"$url" 2>"$stderr"
)"
curl_exit=$?
if [[ "$curl_exit" -ne 0 ]]; then
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"
add_summary_line "$host" "OK" "OK"
return 0
fi
log_line "DOWN" "$host" "HTTP $http_code (erreur appli)"
add_summary_line "$host" "DOWN" "DOWN - HTTP $http_code"
return 1
fi
log_line "DOWN" "$host" "Code HTTP inattendu: $http_code"
add_summary_line "$host" "DOWN" "DOWN - code HTTP invalide"
return 1
}
#######################################
# Main
#######################################
main() {
local failures=0
for site in "${SITES[@]}"; do
if ! check_site "$site"; then
failures=$((failures + 1))
fi
done
FAILURES="$failures"
send_discord_summary
if [[ "$failures" -gt 0 ]]; then
exit 2
fi
exit 0
}
main "$@"

View File

@@ -0,0 +1,447 @@
#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# rebuild-bdd-recette.sh
#
# Script de reconstruction d'une base PostgreSQL à partir d'un dump distant.
#
# Fonctionnement global :
# 1. charge la configuration depuis le fichier .env ;
# 2. prépare les chemins, logs et options SSH ;
# 3. installe PostgreSQL si absent ;
# 4. démarre PostgreSQL si nécessaire ;
# 5. crée le rôle PGUSER uniquement si PostgreSQL vient d'être installé ;
# 6. propose à l'utilisateur de choisir une base à reconstruire ;
# 7. teste la connexion SSH au serveur distant ;
# 8. recherche le dernier dump distant de la base choisie ;
# 9. recherche le dernier fichier SQL des rôles dans le dossier "user" ;
# 10. télécharge les fichiers nécessaires ;
# 11. restaure les rôles via psql (avec filtrage des rôles sensibles) ;
# 12. supprime puis recrée la base cible ;
# 13. restaure la base choisie via pg_restore ;
# 14. envoie une notification Discord si tout s'est bien passé.
###############################################################################
###############################################################################
# Chemins fixes du script
###############################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
###############################################################################
# Vérification du fichier .env
###############################################################################
if [[ ! -f "$ENV_FILE" ]]; then
echo "ERROR: fichier .env introuvable : $ENV_FILE" >&2
exit 1
fi
###############################################################################
# Chargement du .env
###############################################################################
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
###############################################################################
# Variables obligatoires
###############################################################################
: "${ENV_NAME:?Variable ENV_NAME manquante}"
: "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}"
: "${PGPASSWORD:?Variable PGPASSWORD manquante}"
: "${DBS:?Variable DBS manquante}"
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR manquante}"
: "${SSH_KEY:?Variable SSH_KEY manquante}"
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
###############################################################################
# Variables optionnelles
###############################################################################
LOCAL_RESTORE_DIR="${LOCAL_RESTORE_DIR:-${SCRIPT_DIR}/restore_tmp}"
REMOTE_ROLES_DIR_NAME="${REMOTE_ROLES_DIR_NAME:-user}"
SSH_CONNECT_TIMEOUT="${SSH_CONNECT_TIMEOUT:-8}"
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
###############################################################################
# Préparation des dossiers locaux
###############################################################################
mkdir -p "$BACKUP_LOG_DIR" || {
echo "ERROR: impossible de créer le dossier de logs : $BACKUP_LOG_DIR" >&2
exit 1
}
mkdir -p "$LOCAL_RESTORE_DIR" || {
echo "ERROR: impossible de créer le dossier local de restauration : $LOCAL_RESTORE_DIR" >&2
exit 1
}
TIMESTAMP="$(date '+%Y-%m-%d_%H-%M-%S')"
LOG_FILE="${BACKUP_LOG_DIR}/restore_${ENV_NAME,,}_${TIMESTAMP}.log"
touch "$LOG_FILE" || {
echo "ERROR: impossible d'écrire dans le fichier de log : $LOG_FILE" >&2
exit 1
}
###############################################################################
# Fonctions utilitaires
###############################################################################
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
fail() {
log "ERROR: $*"
exit 1
}
cleanup() {
rm -f \
"${LOCAL_DB_DUMP_FILE:-}" \
"${LOCAL_ROLES_FILE:-}" \
"${FILTERED_ROLES_FILE:-}" \
"${ROLES_CREATE_LIST:-}" \
"${ROLES_APPLY_FILE:-}"
}
trap cleanup EXIT
require_cmd() {
command -v "$1" >/dev/null 2>&1
}
###############################################################################
# Envoi Discord
#
# Envoi simple d'un message texte via webhook Discord.
# Si WEBHOOK_URL n'est pas défini, on ignore silencieusement l'envoi.
###############################################################################
send_discord_message() {
local message="$1"
local payload=""
[[ -n "$DISCORD_WEBHOOK_URL" ]] || {
log "WEBHOOK_URL non défini : notification Discord ignorée."
return 0
}
if ! require_cmd curl; then
log "curl absent : notification Discord ignorée."
return 0
fi
payload="$(python3 -c 'import json,sys; print(json.dumps({"content": sys.argv[1]}))' "$message")" || {
log "Impossible de construire le payload JSON Discord."
return 0
}
curl -sS -X POST "$DISCORD_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$payload" \
>/dev/null || log "Échec d'envoi de la notification Discord."
}
###############################################################################
# Vérifications de base
###############################################################################
[[ -f "$SSH_KEY" ]] || fail "clé SSH introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "clé SSH non lisible : $SSH_KEY"
export PGPASSWORD
SSH_OPTS=(
-i "$SSH_KEY"
-o IdentitiesOnly=yes
-o BatchMode=yes
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
-o StrictHostKeyChecking=accept-new
)
REMOTE_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
###############################################################################
# Installation PostgreSQL si absent
#
# Le rôle PGUSER est créé uniquement si PostgreSQL vient d'être installé.
###############################################################################
POSTGRES_INSTALLED=false
if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb; then
log "PostgreSQL absent : installation en cours..."
sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update"
sudo apt install -y postgresql postgresql-client postgresql-contrib \
>>"$LOG_FILE" 2>&1 || fail "échec de l'installation de PostgreSQL"
POSTGRES_INSTALLED=true
log "Installation PostgreSQL terminée."
else
log "PostgreSQL déjà installé."
fi
###############################################################################
# Démarrage PostgreSQL
###############################################################################
if ! sudo systemctl is-active --quiet postgresql; then
log "Démarrage du service PostgreSQL..."
sudo systemctl start postgresql >>"$LOG_FILE" 2>&1 || fail "impossible de démarrer PostgreSQL"
else
log "Service PostgreSQL déjà actif."
fi
###############################################################################
# Attente disponibilité PostgreSQL
###############################################################################
log "Vérification de la disponibilité de PostgreSQL..."
for _ in {1..20}; do
if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
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
fail "PostgreSQL ne répond pas correctement"
fi
###############################################################################
# Création du rôle PGUSER uniquement si PostgreSQL vient d'être installé
###############################################################################
if [[ "$POSTGRES_INSTALLED" == "true" ]]; then
log "Création du rôle PostgreSQL ${PGUSER} suite à une installation neuve..."
sudo -u postgres psql -d postgres -c \
"CREATE ROLE \"${PGUSER}\" WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${PGPASSWORD}';" \
>>"$LOG_FILE" 2>&1 || fail "échec de création du rôle ${PGUSER}"
log "Rôle PostgreSQL ${PGUSER} créé."
fi
###############################################################################
# Affichage des bases disponibles
###############################################################################
read -r -a DBS_ARRAY <<< "$DBS"
if [[ "${#DBS_ARRAY[@]}" -eq 0 ]]; then
fail "aucune base définie dans DBS"
fi
echo "Bases disponibles dans le .env :"
for i in "${!DBS_ARRAY[@]}"; do
printf ' %d) %s\n' "$((i + 1))" "${DBS_ARRAY[$i]}"
done
echo
read -r -p "Voulez-vous utiliser une base de cette liste ? (oui/non) : " USE_LIST
DB=""
if [[ "${USE_LIST,,}" == "oui" || "${USE_LIST,,}" == "o" ]]; then
read -r -p "Sélectionnez le numéro de la base à restaurer : " DB_INDEX
[[ "$DB_INDEX" =~ ^[0-9]+$ ]] || fail "numéro invalide"
(( DB_INDEX >= 1 && DB_INDEX <= ${#DBS_ARRAY[@]} )) || fail "numéro hors plage"
DB="${DBS_ARRAY[$((DB_INDEX - 1))]}"
else
read -r -p "Nom exact de la base à restaurer : " DB
[[ -n "$DB" ]] || fail "nom de base vide"
fi
log "Environnement : $ENV_NAME"
log "Base cible sélectionnée : $DB"
###############################################################################
# Test de connexion SSH
###############################################################################
log "Test de connexion SSH vers ${REMOTE_SSH}..."
SSH_TEST_OUTPUT=""
if ! SSH_TEST_OUTPUT="$(ssh "${SSH_OPTS[@]}" "$REMOTE_SSH" "exit 0" 2>&1)"; then
echo "$SSH_TEST_OUTPUT" | tee -a "$LOG_FILE" >&2
fail "connexion SSH impossible vers ${REMOTE_SSH}"
fi
###############################################################################
# Définition des chemins distants
###############################################################################
REMOTE_DB_DIR="${BACKUP_REMOTE_DIR}/${DB}"
REMOTE_ROLES_DIR="${BACKUP_REMOTE_DIR}/${REMOTE_ROLES_DIR_NAME}"
log "Recherche du dernier dump distant pour ${DB} dans : ${REMOTE_DB_DIR}"
log "Recherche du dernier fichier de rôles dans : ${REMOTE_ROLES_DIR}"
###############################################################################
# Recherche du dernier dump de base
###############################################################################
LAST_REMOTE_DB_DUMP="$(
ssh "${SSH_OPTS[@]}" "$REMOTE_SSH" \
"find '${REMOTE_DB_DIR}' -maxdepth 1 -type f -name '${DB}_*.dump' | LC_ALL=C sort | tail -n 1"
)"
if [[ -z "$LAST_REMOTE_DB_DUMP" ]]; then
fail "aucun dump trouvé pour la base ${DB} dans ${REMOTE_DB_DIR}"
fi
log "Dernier dump distant sélectionné : ${LAST_REMOTE_DB_DUMP}"
###############################################################################
# Recherche du dernier fichier SQL des rôles
###############################################################################
LAST_REMOTE_ROLES_FILE="$(
ssh "${SSH_OPTS[@]}" "$REMOTE_SSH" \
"find '${REMOTE_ROLES_DIR}' -maxdepth 1 -type f -name 'user_*.sql' | LC_ALL=C sort | tail -n 1"
)"
if [[ -n "$LAST_REMOTE_ROLES_FILE" ]]; then
log "Dernier fichier des rôles sélectionné : ${LAST_REMOTE_ROLES_FILE}"
else
log "Aucun fichier des rôles trouvé sur le serveur distant."
fi
###############################################################################
# Téléchargement du dump principal
###############################################################################
LOCAL_DB_DUMP_FILE="${LOCAL_RESTORE_DIR}/$(basename "$LAST_REMOTE_DB_DUMP")"
LOCAL_ROLES_FILE=""
log "Téléchargement du dump..."
scp "${SSH_OPTS[@]}" "${REMOTE_SSH}:${LAST_REMOTE_DB_DUMP}" "$LOCAL_DB_DUMP_FILE" \
>>"$LOG_FILE" 2>&1 || fail "échec du téléchargement du dump principal"
###############################################################################
# Téléchargement du fichier des rôles si présent
###############################################################################
if [[ -n "$LAST_REMOTE_ROLES_FILE" ]]; then
LOCAL_ROLES_FILE="${LOCAL_RESTORE_DIR}/$(basename "$LAST_REMOTE_ROLES_FILE")"
log "Téléchargement du fichier des rôles..."
scp "${SSH_OPTS[@]}" "${REMOTE_SSH}:${LAST_REMOTE_ROLES_FILE}" "$LOCAL_ROLES_FILE" \
>>"$LOG_FILE" 2>&1 || fail "échec du téléchargement du fichier des rôles"
else
log "La restauration des rôles sera ignorée."
fi
###############################################################################
# Test de connexion PostgreSQL locale avec PGUSER
###############################################################################
if ! psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -c "SELECT 1;" \
>>"$LOG_FILE" 2>&1; then
fail "connexion PostgreSQL locale impossible avec PGUSER=${PGUSER}"
fi
###############################################################################
# Demande d'écrasement si la base existe déjà
###############################################################################
DB_EXISTS="$(
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -tAc \
"SELECT 1 FROM pg_database WHERE datname='${DB}'" 2>>"$LOG_FILE" || true
)"
if [[ "$DB_EXISTS" == "1" ]]; then
read -r -p "La base '${DB}' existe déjà. Voulez-vous l'écraser ? (oui/non) : " CONFIRM_OVERWRITE
if [[ "${CONFIRM_OVERWRITE,,}" != "oui" && "${CONFIRM_OVERWRITE,,}" != "o" ]]; then
fail "restauration annulée par l'utilisateur"
fi
log "Suppression de la base existante : ${DB}"
dropdb -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" --if-exists "$DB" \
>>"$LOG_FILE" 2>&1 || fail "échec de suppression de la base ${DB}"
fi
###############################################################################
# Restauration des rôles
###############################################################################
if [[ -n "$LOCAL_ROLES_FILE" ]]; then
log "Restauration des rôles depuis : ${LOCAL_ROLES_FILE}"
FILTERED_ROLES_FILE="${LOCAL_RESTORE_DIR}/filtered_$(basename "$LOCAL_ROLES_FILE")"
ROLES_CREATE_LIST="${LOCAL_RESTORE_DIR}/roles_to_create_$(basename "$LOCAL_ROLES_FILE")"
ROLES_APPLY_FILE="${LOCAL_RESTORE_DIR}/roles_apply_$(basename "$LOCAL_ROLES_FILE")"
grep -viE '^(CREATE ROLE|ALTER ROLE) (backup_liot|postgres)\b' "$LOCAL_ROLES_FILE" \
> "$FILTERED_ROLES_FILE" || true
log "Fichier des rôles filtré généré : ${FILTERED_ROLES_FILE}"
sed -nE 's/^CREATE ROLE "?([^" ;]+)"?;$/\1/p' "$FILTERED_ROLES_FILE" \
> "$ROLES_CREATE_LIST" || true
if [[ -s "$ROLES_CREATE_LIST" ]]; then
while IFS= read -r role_name; do
[[ -z "$role_name" ]] && continue
ROLE_EXISTS="$(
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -tAc \
"SELECT 1 FROM pg_roles WHERE rolname='${role_name}'" 2>>"$LOG_FILE" || true
)"
if [[ "$ROLE_EXISTS" != "1" ]]; then
log "Création du rôle manquant : ${role_name}"
psql -v ON_ERROR_STOP=1 \
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres \
-c "CREATE ROLE \"${role_name}\";" \
>>"$LOG_FILE" 2>&1 || fail "échec de création du rôle ${role_name}"
else
log "Rôle déjà présent, création ignorée : ${role_name}"
fi
done < "$ROLES_CREATE_LIST"
fi
grep -viE '^CREATE ROLE ' "$FILTERED_ROLES_FILE" > "$ROLES_APPLY_FILE" || true
log "Application des ALTER ROLE / privilèges / memberships..."
psql \
-v ON_ERROR_STOP=1 \
-h "$PGHOST" \
-p "$PGPORT" \
-U "$PGUSER" \
-d postgres \
-f "$ROLES_APPLY_FILE" \
>>"$LOG_FILE" 2>&1 || fail "échec de restauration des rôles via psql"
else
log "Aucune restauration des rôles effectuée."
fi
###############################################################################
# Création de la base
###############################################################################
log "Création de la base : ${DB}"
createdb -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" "$DB" \
>>"$LOG_FILE" 2>&1 || fail "échec de création de la base ${DB}"
###############################################################################
# Restauration de la base principale
###############################################################################
log "Restauration de la base ${DB}..."
pg_restore \
-h "$PGHOST" \
-p "$PGPORT" \
-U "$PGUSER" \
-d "$DB" \
--clean \
--if-exists \
--no-owner \
--no-privileges \
"$LOCAL_DB_DUMP_FILE" \
>>"$LOG_FILE" 2>&1 || fail "échec de restauration de la base ${DB}"
###############################################################################
# Fin
###############################################################################
log "Restauration terminée avec succès pour la base : ${DB}"
log "Fichier de log : ${LOG_FILE}"
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"

View File

@@ -1,210 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
#######################################
# Configuration
#######################################
DBS=("sirh" "inventory" "ferme")
PGHOST="localhost"
PGPORT="5432"
PGUSER="backup_liot"
PGPASSWORD="backup_liot"
IA_SSH="malio-b@192.168.0.179"
IA_BASE_DIR="/home/malio-b/backups"
SSH_KEY="/home/malio/.ssh/id_ed25519_backup"
SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10)
LOG_DIR="/var/log/pg_backup"
mkdir -p "$LOG_DIR"
TS="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_DIR_NAME="backup_${TS}"
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
TMP_DIR="/tmp/pg_dump_${BACKUP_DIR_NAME}"
mkdir -p "$TMP_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; }
export PGPASSWORD
#######################################
# Discord (Webhook)
#######################################
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1478503102888935506/YCtJM09QZiKNMiCe5u7vCQb52VcLjHAd9wwEsKNltlJVcy7sKvoMTOJkvEKOOrk-Wpkh"
discord_ping() {
local details="${1:-}"
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
local color dumps_display users_display
if [[ -n "${DUMPS_OK:-}" && -n "${USERS_OK:-}" ]]; then
color="🟢"
else
color="🔴"
fi
dumps_display=$([[ -n "${DUMPS_OK:-}" ]] && echo "✅" || echo "❌")
users_display=$([[ -n "${USERS_OK:-}" ]] && echo "✅" || echo "❌")
local msg="**@here BACKUP BDD RECETTE ${color}**\n"
msg+="Name: ${BACKUP_DIR_NAME}\n"
msg+="Dumps transfer: ${dumps_display}\n"
msg+="Users transfer: ${users_display}\n"
[[ -n "$details" ]] && msg+="Details: $details"
curl -fsS -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" \
"$DISCORD_WEBHOOK_URL" >/dev/null || true
}
#######################################
# Statuts init
#######################################
DUMPS_OK=true
USERS_OK=true
DUMP_ERRORS=""
USER_ERRORS=""
#######################################
# Lock (évite 2 backups en même temps)
#######################################
LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
log "ERROR: Backup déjà en cours (lock: $LOCK_DIR)"
DUMPS_OK=
USERS_OK=
discord_ping "Lock exists: $LOCK_DIR"
exit 1
fi
trap 'rm -rf "$LOCK_DIR"' EXIT
#######################################
# Remote dir
#######################################
REMOTE_DIR="${IA_BASE_DIR}/${BACKUP_DIR_NAME}"
log "Starting backup process"
log "Remote directory: ${REMOTE_DIR}"
log "Creating remote directory"
if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "mkdir -p '${REMOTE_DIR}'"; then
log "ERROR: Création dossier distant impossible: ${REMOTE_DIR}"
DUMPS_OK=
USERS_OK=
discord_ping "Remote mkdir KO: ${REMOTE_DIR}"
exit 1
fi
#######################################
# Export PostgreSQL roles (no passwords)
#######################################
ROLES_FILE="${TMP_DIR}/roles_${TS}.sql"
log "Exporting PostgreSQL roles"
set +e
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -Atq <<'SQL' > "$ROLES_FILE"
SELECT
format(
'DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %L) THEN
CREATE ROLE %I;
END IF;
END $$;',
rolname, rolname
)
FROM pg_roles
WHERE rolname !~ '^pg_'
ORDER BY rolname;
SELECT
format(
'ALTER ROLE %I WITH %s%s%s%s%s%s%s%s;',
rolname,
CASE WHEN rolsuper THEN 'SUPERUSER ' ELSE 'NOSUPERUSER ' END,
CASE WHEN rolinherit THEN 'INHERIT ' ELSE 'NOINHERIT ' END,
CASE WHEN rolcreaterole THEN 'CREATEROLE ' ELSE 'NOCREATEROLE ' END,
CASE WHEN rolcreatedb THEN 'CREATEDB ' ELSE 'NOCREATEDB ' END,
CASE WHEN rolcanlogin THEN 'LOGIN ' ELSE 'NOLOGIN ' END,
CASE WHEN rolreplication THEN 'REPLICATION ' ELSE 'NOREPLICATION ' END,
CASE WHEN rolbypassrls THEN 'BYPASSRLS ' ELSE 'NOBYPASSRLS ' END,
'CONNECTION LIMIT ' || rolconnlimit
)
FROM pg_roles
WHERE rolname !~ '^pg_'
ORDER BY rolname;
SQL
RET=$?
if [[ $RET -ne 0 ]]; then
USERS_OK=
USER_ERRORS+="roles_export "
log "ERROR: Users export failed"
else
log "Roles export completed: $ROLES_FILE"
fi
set -e
log "Sending roles file to IA server"
set +e
scp "${SSH_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${REMOTE_DIR}/"
RET=$?
if [[ $RET -ne 0 ]]; then
USERS_OK=
USER_ERRORS+="roles_scp "
log "ERROR: Users transfer failed (roles file)"
else
log "Roles transfer completed"
fi
set -e
#######################################
# Dump des bases + transfert (continue même si KO)
#######################################
set +e
for DB in "${DBS[@]}"; do
FILE="${TMP_DIR}/${DB}_${TS}.dump"
log "Dumping database: $DB"
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc --no-owner --no-acl -d "$DB" -f "$FILE"
RET=$?
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
DUMP_ERRORS+="${DB} "
log "ERROR: Dump failed for $DB"
continue
fi
log "Dump completed: $FILE"
log "Sending dump to IA server"
scp "${SSH_OPTS[@]}" "$FILE" "$IA_SSH:${REMOTE_DIR}/"
RET=$?
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
DUMP_ERRORS+="${DB}(scp) "
log "ERROR: Transfer failed for $DB"
continue
fi
log "Transfer completed for $DB"
done
set -e
#######################################
# Nettoyage
#######################################
log "Cleaning temporary files"
rm -rf "$TMP_DIR"
#######################################
# Envoi message Discord (final)
#######################################
DETAILS=""
[[ -z "${DUMPS_OK:-}" ]] && DETAILS+="Dumps KO: ${DUMP_ERRORS} "
[[ -z "${USERS_OK:-}" ]] && DETAILS+="Users KO: ${USER_ERRORS} "
discord_ping "$DETAILS"
log "Backup finished"

View File

@@ -1,6 +0,0 @@
DATA_DIR=
LOCAL_BACKUP=
REMOTE_USER=
REMOTE_HOST=
REMOTE_DIR=
SSH_KEY=

View File

@@ -1,3 +0,0 @@
.env
backup.log

View File

@@ -1,116 +0,0 @@
# FONCTIONNEMENT DU SCRIPT VAULTWARDEN
Le script de backup de vaultwarden permet une sauvegard périodique des mots de passe et utilisateurs de celui-ci.
## INITIALISATION DES VARIABLES DE MANIÈRE SÉCURISÉ
1. Les informations sensibles ne sont pas stockées directement dans le script. Elles sont placées dans un fichier .env
```bash
WEBHOOK_URL=...
REMOTE_USER=...
REMOTE_HOST=...
SSH_KEY=...
DATA_DIR=...
```
2. on recupere les varibales dans le script
```bash
REMOTE_USER=$(grep -E '^REMOTE_USER=' .env | cut -d '=' -f2-)
```
Explication:
- grep recherche la variable dans le fichier .env
- cut récupère uniquement la valeur après =
- REMOTE_USER="user" Le script récupère >> "user"
Cela permet:
- daméliorer la sécurité
- déviter de modifier le script si un paramètre change
## RÉCUPÉRATION DES DONNÉES
1. Le dossier data de Vaultwarden est dupliqué puis compressé afin de créer une archive :
```bash
tar -czf "$LOCAL_BACKUP" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")"
```
2. Transfer vers le serveur de backup
```bash
scp "${SSH_OPTS[@]}" "$LOCAL_BACKUP" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
```
La sauvegarde est envoyée vers une machine dédiée grâce à SCP. Pour éviter de saisir un mot de passe à chaque fois, une clé SSH est utilisée.
Cette clé SSH est générée sur la machine de backup et autorisée sur la machine Vaultwarden.
## NOTIFICATION DISCORD
Le script envoie une notification sur un salon Discord pour informer de létat de la sauvegarde. Cela se fait grâce à un webhook Discord.
1. on défini le message
```bash
local msg="**@here Backup Vaultwarden $color**\n"
msg+="Backup: ${BACKUP_NAME}\n"
msg+="Data transfer: $dumps_display\n"
[[ -n "$details" ]] && msg+="Details: $details"
```
2. on envoie le message sur discord avec le message et le webhook
```bash
curl -fsS -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" \
"$DISCORD_WEBHOOK_URL"
```
Le message indique:
- si la sauvegarde a réussi 🟢
- si elle a échoué 🔴
- le nom du backup
- les détails de lerreur si nécessaire
## PLANIFICATION AVEC CRON
Le script est exécuté automatiquement chaque jour grâce à cron.
1. Ouvrez le crontab pour l'édition :
```bash
crontab -e
```
2. Ajoutez la ligne suivante pour exécuter le script tous les jours à 19h :
```bash
0 19 * * * /chemin/vers/le/script/check_storage.sh
```
Signification:
- 0 minute 0
- 19 19h
- * tous les jours du mois
- * tous les mois
- * tous les jours de la semaine
Tous les jours à 19h, le script est exécuté et les logs sont enregistrés dans backup.log ce qui permet danalyser les erreurs si un problème survient.
## NETTOYAGE
Une fois la sauvegarde envoyée sur la machine distante, le fichier temporaire est supprimé :
```bash
rm -f "$LOCAL_BACKUP"
```
Cela permet de garder le serveur propre et éviter de remplir le disque.
## RÉSUMÉ
Le script automatise complètement les sauvegardes Vaultwarden :
- sauvegarde du dossier data
- compression et datation
- transfert sécurisé via SSH
- notification Discord
- exécution automatique avec cron
- sécurisation des paramètres via .env
Cela permet davoir une sauvegarde quotidienne fiable et surveillée.

View File

@@ -1,147 +0,0 @@
#!/usr/bin/env bash
set -u
#######################################
# Sites à vérifier
#######################################
SITES=(
"ferme.malio-dev.fr"
"sirh.malio-dev.fr"
"inventory.malio-dev.fr"
)
SCHEME="http"
CONNECT_TIMEOUT=3
MAX_TIME=8
#######################################
# Logs
#######################################
LOG_DIR="/var/log/app_health"
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/app_health_$(date +'%Y-%m-%d').log"
#######################################
# Discord
#######################################
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1478379245842600007/tSxi3G6PbCn89pOdeqK34LR7c-GhXfT-lSCPolwBywJXcpa3ihL8rN4QRwsTjF6SS3w0"
discord_ping() {
local site="$1"
local status="$2"
local detail="$3"
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
local color icon
if [[ "$status" == "OK" ]]; then
color="🟢"
icon="✅"
else
color="🔴"
icon="❌"
fi
local msg="**CHECK APP RECETTE $color**\n"
msg+="Application: ${site}\n"
msg+="Status: ${icon}\n"
msg+="Details: ${detail}"
curl -fsS -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" \
"$DISCORD_WEBHOOK_URL" >/dev/null || true
}
#######################################
# Logging
#######################################
log_line() {
# 2026-03-04 14:12:33 | LEVEL | site | message
printf "%s | %s | %s | %s\n" \
"$(date +'%Y-%m-%d %H:%M:%S')" "$1" "$2" "$3" | tee -a "$LOG_FILE"
# Envoi Discord par application
discord_ping "$2" "$1" "$3"
}
#######################################
# DNS
#######################################
dns_ok() {
getent hosts "$1" >/dev/null 2>&1
}
#######################################
# Check application
#######################################
check_site() {
local host="$1"
local url="${SCHEME}://${host}/"
if ! dns_ok "$host"; then
log_line "DOWN" "$host" "Résolution impossible (getent hosts)"
return 1
fi
local http_code curl_exit stderr
stderr="$(mktemp)"
http_code="$(
curl -sS -o /dev/null \
-w '%{http_code}' \
--connect-timeout "$CONNECT_TIMEOUT" \
--max-time "$MAX_TIME" \
"$url" 2>"$stderr"
)"
curl_exit=$?
if [ $curl_exit -ne 0 ]; then
local err
err="$(head -n 1 "$stderr" | tr -d '\r')"
rm -f "$stderr"
log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}"
return 1
fi
rm -f "$stderr"
if [[ "$http_code" =~ ^[0-9]{3}$ ]]; then
if [ "$http_code" -ge 200 ] && [ "$http_code" -le 399 ]; then
log_line "OK" "$host" "HTTP $http_code"
return 0
fi
log_line "DOWN" "$host" "HTTP $http_code (erreur appli)"
return 1
fi
log_line "DOWN" "$host" "Code HTTP inattendu: $http_code"
return 1
}
#######################################
# Main
#######################################
main() {
local failures=0
for site in "${SITES[@]}"; do
if ! check_site "$site"; then
failures=$((failures + 1))
fi
done
if [ "$failures" -gt 0 ]; then
exit 2
fi
exit 0
}
main "$@"