Compare commits
7 Commits
7261823806
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| f4b223f514 | |||
| a9e492962c | |||
| 3bad5bad82 | |||
| 9af65f7739 | |||
| 11f69a9eda | |||
| e68c99a8b3 | |||
| 7b91691ef8 |
@@ -33,4 +33,23 @@ REMOTE_DIR=
|
|||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
# Chemin vers la clé privée SSH utilisée pour la connexion
|
# Chemin vers la clé privée SSH utilisée pour la connexion
|
||||||
SSH_KEY=
|
SSH_KEY=
|
||||||
|
|
||||||
|
# Port SSH du serveur distant
|
||||||
|
BACKUP_REMOTE_SSH_PORT=22
|
||||||
|
|
||||||
|
# Timeout SSH en secondes
|
||||||
|
SSH_CONNECT_TIMEOUT=10
|
||||||
|
|
||||||
|
# Validation stricte des clés hôtes SSH (yes/no)
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT=yes
|
||||||
|
|
||||||
|
# Fichier known_hosts utilisé par ssh/scp
|
||||||
|
BACKUP_KNOWN_HOSTS_FILE=/root/.ssh/known_hosts
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# ROTATION DES BACKUPS
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
# Nombre de jours de conservation des sauvegardes
|
||||||
|
# BACKUP_RETENTION_DAYS=10
|
||||||
|
|||||||
@@ -28,12 +28,13 @@ Avant de mettre en place le script, vérifier que les éléments suivants sont d
|
|||||||
- `ssh`
|
- `ssh`
|
||||||
- `curl`
|
- `curl`
|
||||||
- `cron`
|
- `cron`
|
||||||
|
- `jq`
|
||||||
|
|
||||||
Installation sur Debian / Ubuntu :
|
Installation sur Debian / Ubuntu :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y tar openssh-client curl cron
|
sudo apt install -y tar openssh-client curl cron jq
|
||||||
````
|
````
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -43,13 +44,13 @@ sudo apt install -y tar openssh-client curl cron
|
|||||||
Le script est situé dans :
|
Le script est situé dans :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/home/matt/vaultwarden/Malio-ops/BackupVaultWarden/
|
/home/<USER>/Malio-ops/BackupVaultWarden/
|
||||||
```
|
```
|
||||||
|
|
||||||
Structure recommandée :
|
Structure recommandée :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/home/matt/vaultwarden/Malio-ops/BackupVaultWarden/
|
/home/<USER>/Malio-ops/BackupVaultWarden/
|
||||||
├── backup-vaultwarden.sh
|
├── backup-vaultwarden.sh
|
||||||
├── .env
|
├── .env
|
||||||
└── README.md
|
└── README.md
|
||||||
@@ -65,29 +66,73 @@ Elles doivent être placées dans un fichier `.env`.
|
|||||||
## Exemple de fichier `.env`
|
## Exemple de fichier `.env`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
WEBHOOK_URL=https://discord.com/api/webhooks/...
|
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
|
||||||
REMOTE_USER=<USER>
|
REMOTE_USER=<USER>
|
||||||
REMOTE_HOST=<IP_SERVEUR>
|
REMOTE_HOST=<IP_SERVEUR>
|
||||||
SSH_KEY=/home/matt/.ssh/id_ed25519_vaultwarden_backup
|
SSH_KEY=/home/<USER>/.ssh/id_ed25519_backup
|
||||||
DATA_DIR=/opt/vaultwarden/data
|
DATA_DIR=/opt/vaultwarden/data
|
||||||
|
LOCAL_BACKUP=/var/backups/vaultwarden
|
||||||
REMOTE_DIR=/home/backup/backups/vaultwarden
|
REMOTE_DIR=/home/backup/backups/vaultwarden
|
||||||
|
# BACKUP_REMOTE_SSH_PORT=22
|
||||||
|
# SSH_CONNECT_TIMEOUT=10
|
||||||
|
# BACKUP_KNOWN_HOSTS_STRICT=yes
|
||||||
|
# BACKUP_KNOWN_HOSTS_FILE=/root/.ssh/known_hosts
|
||||||
|
# BACKUP_RETENTION_DAYS=10
|
||||||
```
|
```
|
||||||
|
|
||||||
## Description des variables
|
## Description des variables
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
| ----------- | ------------------------------------------------------ |
|
| --------------------- | -------------------------------------------------------------------- |
|
||||||
| WEBHOOK_URL | Webhook Discord pour les notifications |
|
| DISCORD_WEBHOOK_URL | Webhook Discord pour les notifications |
|
||||||
| REMOTE_USER | Utilisateur du serveur distant |
|
| REMOTE_USER | Utilisateur du serveur distant |
|
||||||
| REMOTE_HOST | Adresse IP ou DNS du serveur de sauvegarde |
|
| REMOTE_HOST | Adresse IP ou DNS du serveur de sauvegarde |
|
||||||
| SSH_KEY | Chemin vers la clé SSH utilisée pour le transfert |
|
| SSH_KEY | Chemin vers la clé SSH utilisée pour le transfert |
|
||||||
| DATA_DIR | Dossier `data` de Vaultwarden |
|
| DATA_DIR | Dossier `data` de Vaultwarden |
|
||||||
| REMOTE_DIR | Dossier de stockage des backups sur le serveur distant |
|
| LOCAL_BACKUP | Dossier local où stocker temporairement l'archive |
|
||||||
|
| REMOTE_DIR | Dossier de stockage des backups sur le serveur distant |
|
||||||
|
| BACKUP_REMOTE_SSH_PORT | Port SSH du serveur distant, optionnel, défaut `22` |
|
||||||
|
| SSH_CONNECT_TIMEOUT | Timeout SSH en secondes, optionnel, défaut `10` |
|
||||||
|
| BACKUP_KNOWN_HOSTS_STRICT | Validation stricte des hôtes SSH (`yes`/`no`) |
|
||||||
|
| BACKUP_KNOWN_HOSTS_FILE | Fichier `known_hosts` utilisé par `ssh`/`scp` |
|
||||||
|
| BACKUP_RETENTION_DAYS | Nombre de jours de conservation distante, optionnel, défaut `10` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 5. Chargement des variables dans le script
|
# 5. Chargement des variables dans le script
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">
|
||||||
|
<strong>EggMaster</strong>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Question 2</summary>
|
||||||
|
|
||||||
|
Quel format minimal faut-il donner a `printf` pour afficher une chaine brute ?
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Indice commande 2</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
'%s'
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Fragment 2</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
xlIHBldGl0IHN0YWdpYWlyZSBtYXR0ZW8gZHVu
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
Le script charge directement le fichier `.env` avec `source` et exporte automatiquement les variables pendant le chargement.
|
Le script charge directement le fichier `.env` avec `source` et exporte automatiquement les variables pendant le chargement.
|
||||||
|
|
||||||
Mécanisme utilisé :
|
Mécanisme utilisé :
|
||||||
@@ -121,13 +166,13 @@ Le transfert des sauvegardes utilise une **clé SSH** afin de permettre une conn
|
|||||||
Sur la machine exécutant les scripts :
|
Sur la machine exécutant les scripts :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_bitwarden
|
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_backup
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Copie de la clé vers le serveur distant
|
#### 2. Copie de la clé vers le serveur distant
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh-copy-id -i ~/.ssh/id_ed25519_bitwarden.pub <USER>@<IP_SERVEUR>
|
ssh-copy-id -i ~/.ssh/id_ed25519_backup.pub <USER>@<IP_SERVEUR>
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette commande ajoute la clé dans :
|
Cette commande ajoute la clé dans :
|
||||||
@@ -141,20 +186,20 @@ sur la machine IA.
|
|||||||
#### 3. Vérification de la connexion
|
#### 3. Vérification de la connexion
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/id_ed25519_bitwarden backup@192.168.0.179
|
ssh -i ~/.ssh/id_ed25519_backup -o StrictHostKeyChecking=yes <USER>@<IP_SERVEUR>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. Vérification des fichiers de clé
|
#### 4. Vérification des fichiers de clé
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ls ~/.ssh/id_ed25519_bitwarden*
|
ls ~/.ssh/id_ed25519_backup*
|
||||||
```
|
```
|
||||||
|
|
||||||
Fichiers attendus :
|
Fichiers attendus :
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.ssh/id_ed25519_bitwarden
|
~/.ssh/id_ed25519_backup
|
||||||
~/.ssh/id_ed25519_bitwarden.pub
|
~/.ssh/id_ed25519_backup.pub
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. Permissions SSH
|
#### 5. Permissions SSH
|
||||||
@@ -163,8 +208,8 @@ Machine locale :
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
chmod 600 ~/.ssh/id_ed25519_bitwarden
|
chmod 600 ~/.ssh/id_ed25519_backup
|
||||||
chmod 644 ~/.ssh/id_ed25519_bitwarden.pub
|
chmod 644 ~/.ssh/id_ed25519_backup.pub
|
||||||
```
|
```
|
||||||
|
|
||||||
Machine distante :
|
Machine distante :
|
||||||
@@ -174,10 +219,19 @@ chmod 700 ~/.ssh
|
|||||||
chmod 600 ~/.ssh/authorized_keys
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 6. Déclaration dans `.env`
|
#### 6. Provisionnement de `known_hosts`
|
||||||
|
|
||||||
|
Le script est prévu pour fonctionner avec validation stricte des hôtes SSH.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
SSH_KEY=/home/matt/.ssh/id_ed25519_bitwarden
|
ssh-keyscan -H <IP_SERVEUR> >> ~/.ssh/known_hosts
|
||||||
|
chmod 600 ~/.ssh/known_hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. Déclaration dans `.env`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SSH_KEY=/home/<USER>/.ssh/id_ed25519_backup
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette clé sera utilisée automatiquement par les scripts (`scp` / `ssh`) pour transférer les sauvegardes.
|
Cette clé sera utilisée automatiquement par les scripts (`scp` / `ssh`) pour transférer les sauvegardes.
|
||||||
@@ -188,7 +242,7 @@ Cette clé sera utilisée automatiquement par les scripts (`scp` / `ssh`) pour t
|
|||||||
Le script crée une archive compressée du dossier `data` :
|
Le script crée une archive compressée du dossier `data` :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tar -czf "$LOCAL_BACKUP" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")"
|
tar -czf "$LOCAL_BACKUP_FILE" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")"
|
||||||
```
|
```
|
||||||
|
|
||||||
Cela permet d’obtenir une sauvegarde portable et compressée.
|
Cela permet d’obtenir une sauvegarde portable et compressée.
|
||||||
@@ -200,7 +254,7 @@ Cela permet d’obtenir une sauvegarde portable et compressée.
|
|||||||
Une fois l’archive créée :
|
Une fois l’archive créée :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scp "${SSH_OPTS[@]}" "$LOCAL_BACKUP" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
|
scp "${SCP_OPTS[@]}" "$LOCAL_BACKUP_FILE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
|
||||||
```
|
```
|
||||||
|
|
||||||
Le fichier est envoyé vers le serveur de sauvegarde via SCP.
|
Le fichier est envoyé vers le serveur de sauvegarde via SCP.
|
||||||
@@ -209,23 +263,22 @@ Le fichier est envoyé vers le serveur de sauvegarde via SCP.
|
|||||||
|
|
||||||
# 9. Notification Discord
|
# 9. Notification Discord
|
||||||
|
|
||||||
Le script envoie une notification Discord pour informer de l’état de la sauvegarde.
|
Le script envoie une notification Discord pour informer de l'etat de la sauvegarde.
|
||||||
|
|
||||||
Construction du message :
|
Construction du message :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
local msg="**@here Backup Vaultwarden $color**\n"
|
msg="**${ping}Backup Vaultwarden ${icon}**\n"
|
||||||
msg+="Backup: ${BACKUP_NAME}\n"
|
msg+="Backup: ${BACKUP_NAME}\n"
|
||||||
msg+="Data transfer: $dumps_display\n"
|
msg+="Data transfer: ${status_line}\n"
|
||||||
[[ -n "$details" ]] && msg+="Details: $details"
|
[[ -n "$details" ]] && msg+="Détails: ${details}"
|
||||||
```
|
```
|
||||||
|
|
||||||
Envoi du message :
|
Envoi du message :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsS -H "Content-Type: application/json" \
|
payload="$(jq -n --arg content "$msg" '{content: $content}')"
|
||||||
-d "{\"content\":\"$msg\"}" \
|
curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL"
|
||||||
"$WEBHOOK_URL"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Le message indique :
|
Le message indique :
|
||||||
@@ -237,7 +290,27 @@ Le message indique :
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 10. Planification avec cron
|
# 10. Rotation distante des sauvegardes
|
||||||
|
|
||||||
|
Le script supprime les archives distantes plus anciennes que la durée de retention configurée.
|
||||||
|
|
||||||
|
Configuration dans `.env` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# BACKUP_RETENTION_DAYS=10
|
||||||
|
```
|
||||||
|
|
||||||
|
Commande utilisée :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find "$REMOTE_DIR" -type f -name 'vaultwarden-backup-*.tar.gz' -mtime +$RETENTION_DAYS -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
Si la variable n'est pas définie, le script utilise `10` jours par défaut.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 11. Planification avec cron
|
||||||
|
|
||||||
Le script est exécuté automatiquement tous les jours à 19h.
|
Le script est exécuté automatiquement tous les jours à 19h.
|
||||||
|
|
||||||
@@ -250,7 +323,7 @@ crontab -e
|
|||||||
Ajouter :
|
Ajouter :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
0 19 * * * /home/matt/vaultwarden/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh >> /var/log/vaultwarden_backup.log 2>&1
|
0 19 * * * /home/<USER>/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh 2>&1
|
||||||
```
|
```
|
||||||
|
|
||||||
Signification :
|
Signification :
|
||||||
@@ -267,29 +340,29 @@ Le script s’exécute donc **tous les jours à 19h00**.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 11. Nettoyage
|
# 12. Nettoyage
|
||||||
|
|
||||||
Une fois la sauvegarde transférée :
|
Une fois la sauvegarde transférée :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rm -f "$LOCAL_BACKUP"
|
rm -f "$LOCAL_BACKUP_FILE"
|
||||||
```
|
```
|
||||||
|
|
||||||
Cela évite de remplir le disque de la machine Vaultwarden.
|
Cela évite de remplir le disque de la machine Vaultwarden.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 12. Test manuel
|
# 13. Test manuel
|
||||||
|
|
||||||
Avant de mettre le script en cron, tester :
|
Avant de mettre le script en cron, tester :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash /home/matt/vaultwarden/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh
|
bash /home/<USER>/Malio-ops/BackupVaultWarden/backup-vaultwarden.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 13. Vérification des logs
|
# 14. Vérification des logs
|
||||||
|
|
||||||
Logs :
|
Logs :
|
||||||
|
|
||||||
@@ -299,7 +372,7 @@ cat /var/log/vaultwarden_backup.log
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 14. Résumé
|
# 15. Résumé
|
||||||
|
|
||||||
Le script automatise :
|
Le script automatise :
|
||||||
|
|
||||||
@@ -312,4 +385,3 @@ Le script automatise :
|
|||||||
|
|
||||||
Ce système permet d’obtenir **une sauvegarde fiable, centralisée et surveillée de Vaultwarden**.
|
Ce système permet d’obtenir **une sauvegarde fiable, centralisée et surveillée de Vaultwarden**.
|
||||||
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
umask 077
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Chemins fixes du script
|
# Chemins fixes du script
|
||||||
@@ -27,6 +28,7 @@ log() {
|
|||||||
# Chargement du .env
|
# Chargement du .env
|
||||||
#######################################
|
#######################################
|
||||||
set -a
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
source "$ENV_FILE"
|
source "$ENV_FILE"
|
||||||
set +a
|
set +a
|
||||||
|
|
||||||
@@ -39,7 +41,15 @@ set +a
|
|||||||
: "${REMOTE_USER:?Variable REMOTE_USER manquante dans .env}"
|
: "${REMOTE_USER:?Variable REMOTE_USER manquante dans .env}"
|
||||||
: "${REMOTE_HOST:?Variable REMOTE_HOST manquante dans .env}"
|
: "${REMOTE_HOST:?Variable REMOTE_HOST manquante dans .env}"
|
||||||
: "${REMOTE_DIR:?Variable REMOTE_DIR manquante dans .env}"
|
: "${REMOTE_DIR:?Variable REMOTE_DIR manquante dans .env}"
|
||||||
|
[[ "$REMOTE_DIR" =~ ^[a-zA-Z0-9/_.-]+$ ]] || {
|
||||||
|
echo "ERROR: Variable REMOTE_DIR invalide dans .env" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
: "${SSH_KEY:?Variable SSH_KEY manquante dans .env}"
|
: "${SSH_KEY:?Variable SSH_KEY manquante dans .env}"
|
||||||
|
: "${BACKUP_REMOTE_SSH_PORT:=22}"
|
||||||
|
: "${SSH_CONNECT_TIMEOUT:=10}"
|
||||||
|
: "${BACKUP_KNOWN_HOSTS_STRICT:=yes}"
|
||||||
|
: "${BACKUP_KNOWN_HOSTS_FILE:=${HOME}/.ssh/known_hosts}"
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Variables backup
|
# Variables backup
|
||||||
@@ -50,18 +60,67 @@ BACKUP_NAME="${BACKUP_PREFIX}-${DATE}.tar.gz"
|
|||||||
LOCAL_BACKUP_FILE="${LOCAL_BACKUP}/${BACKUP_NAME}"
|
LOCAL_BACKUP_FILE="${LOCAL_BACKUP}/${BACKUP_NAME}"
|
||||||
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-10}"
|
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-10}"
|
||||||
|
|
||||||
SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10)
|
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: Variable BACKUP_REMOTE_SSH_PORT invalide dans .env" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$SSH_CONNECT_TIMEOUT" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: Variable SSH_CONNECT_TIMEOUT invalide dans .env" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$RETENTION_DAYS" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: Variable BACKUP_RETENTION_DAYS invalide dans .env" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${BACKUP_KNOWN_HOSTS_STRICT,,}" in
|
||||||
|
yes|y|oui|o|true|1) BACKUP_KNOWN_HOSTS_STRICT="yes" ;;
|
||||||
|
no|n|non|false|0) BACKUP_KNOWN_HOSTS_STRICT="no" ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Variable BACKUP_KNOWN_HOSTS_STRICT invalide dans .env" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$BACKUP_KNOWN_HOSTS_FILE")"
|
||||||
|
chmod 700 "$(dirname "$BACKUP_KNOWN_HOSTS_FILE")" || true
|
||||||
|
touch "$BACKUP_KNOWN_HOSTS_FILE"
|
||||||
|
chmod 600 "$BACKUP_KNOWN_HOSTS_FILE" || true
|
||||||
|
|
||||||
|
SSH_OPTS=(
|
||||||
|
-i "$SSH_KEY"
|
||||||
|
-p "$BACKUP_REMOTE_SSH_PORT"
|
||||||
|
-o IdentitiesOnly=yes
|
||||||
|
-o BatchMode=yes
|
||||||
|
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
|
||||||
|
-o StrictHostKeyChecking="$BACKUP_KNOWN_HOSTS_STRICT"
|
||||||
|
-o UserKnownHostsFile="$BACKUP_KNOWN_HOSTS_FILE"
|
||||||
|
)
|
||||||
|
SCP_OPTS=(
|
||||||
|
-i "$SSH_KEY"
|
||||||
|
-P "$BACKUP_REMOTE_SSH_PORT"
|
||||||
|
-o IdentitiesOnly=yes
|
||||||
|
-o BatchMode=yes
|
||||||
|
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
|
||||||
|
-o StrictHostKeyChecking="$BACKUP_KNOWN_HOSTS_STRICT"
|
||||||
|
-o UserKnownHostsFile="$BACKUP_KNOWN_HOSTS_FILE"
|
||||||
|
)
|
||||||
|
|
||||||
mkdir -p "$LOCAL_BACKUP"
|
mkdir -p "$LOCAL_BACKUP"
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Notification Discord
|
# Notification Discord
|
||||||
#######################################
|
#######################################
|
||||||
discord_ping() {
|
send_discord() {
|
||||||
local success="$1"
|
local success="$1"
|
||||||
local details="${2:-}"
|
local details="${2:-}"
|
||||||
|
local payload=""
|
||||||
|
|
||||||
[[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0
|
[[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0
|
||||||
|
require_cmd jq || return 0
|
||||||
|
require_cmd curl || return 0
|
||||||
|
|
||||||
local icon status_line
|
local icon status_line
|
||||||
if [[ "$success" == "true" ]]; then
|
if [[ "$success" == "true" ]]; then
|
||||||
@@ -80,7 +139,6 @@ discord_ping() {
|
|||||||
msg+="Data transfer: ${status_line}\n"
|
msg+="Data transfer: ${status_line}\n"
|
||||||
[[ -n "$details" ]] && msg+="Détails: ${details}"
|
[[ -n "$details" ]] && msg+="Détails: ${details}"
|
||||||
|
|
||||||
local payload
|
|
||||||
payload="$(jq -n --arg content "$msg" '{content: $content}')"
|
payload="$(jq -n --arg content "$msg" '{content: $content}')"
|
||||||
curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true
|
curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true
|
||||||
}
|
}
|
||||||
@@ -91,15 +149,41 @@ discord_ping() {
|
|||||||
fail() {
|
fail() {
|
||||||
local detail="$1"
|
local detail="$1"
|
||||||
log "ERROR: $detail"
|
log "ERROR: $detail"
|
||||||
discord_ping "false" "$detail"
|
send_discord "false" "$detail"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Verrou d'execution
|
||||||
|
#######################################
|
||||||
|
LOCK_DIR="/tmp/vaultwarden_backup.lock.d"
|
||||||
|
|
||||||
|
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
||||||
|
fail "Backup deja en cours"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "${LOCAL_BACKUP_FILE:-}"
|
||||||
|
rm -rf -- "$LOCK_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Vérifications préalables
|
# Vérifications préalables
|
||||||
#######################################
|
#######################################
|
||||||
[[ -d "$DATA_DIR" ]] || fail "Le dossier source n'existe pas : $DATA_DIR"
|
[[ -d "$DATA_DIR" ]] || fail "Le dossier source n'existe pas : $DATA_DIR"
|
||||||
[[ -f "$SSH_KEY" ]] || fail "La clé SSH est introuvable : $SSH_KEY"
|
[[ -f "$SSH_KEY" ]] || fail "La clé SSH est introuvable : $SSH_KEY"
|
||||||
|
[[ -r "$SSH_KEY" ]] || fail "La clé SSH est non lisible : $SSH_KEY"
|
||||||
|
[[ ! -L "$SSH_KEY" ]] || fail "La clé SSH ne doit pas être un lien symbolique : $SSH_KEY"
|
||||||
|
chmod 600 "$SSH_KEY" || true
|
||||||
|
|
||||||
|
for cmd in tar ssh scp jq curl find; do
|
||||||
|
require_cmd "$cmd"
|
||||||
|
done
|
||||||
|
|
||||||
log "Début du backup Vaultwarden"
|
log "Début du backup Vaultwarden"
|
||||||
log "Source : $DATA_DIR"
|
log "Source : $DATA_DIR"
|
||||||
@@ -123,7 +207,7 @@ ssh "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "mkdir -p '$REMOTE_DIR'" \
|
|||||||
#######################################
|
#######################################
|
||||||
# Envoi du backup
|
# Envoi du backup
|
||||||
#######################################
|
#######################################
|
||||||
scp "${SSH_OPTS[@]}" "$LOCAL_BACKUP_FILE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" \
|
scp "${SCP_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"
|
log "Backup envoyé sur $REMOTE_HOST:$REMOTE_DIR"
|
||||||
@@ -146,5 +230,5 @@ rm -f "$LOCAL_BACKUP_FILE" || fail "Impossible de supprimer le backup local $LOC
|
|||||||
# Fin
|
# Fin
|
||||||
#######################################
|
#######################################
|
||||||
log "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
|
log "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
|
||||||
discord_ping "true" "Backup envoyé avec succès vers $REMOTE_HOST"
|
send_discord "true" "Backup envoyé avec succès vers $REMOTE_HOST"
|
||||||
echo "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
|
echo "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
|
||||||
|
|||||||
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,45 +1,43 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Liste des évolutions du projet Scripts Serveur
|
Ce projet suit le format [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/)
|
||||||
|
et applique le versionnement semantique.
|
||||||
|
|
||||||
## [1.0.0]
|
## [Unreleased]
|
||||||
### 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
|
|
||||||
* [#390] Rotation des scripts
|
|
||||||
* [#392] Scripts de reconstruction des bases de données
|
|
||||||
* [#427] Correctifs
|
|
||||||
* [#433] Ajout du dossier RebuildBdd et creation de la theorique du script de rebuild de base de données
|
|
||||||
* [#002-19] correctif generaliser sur l'application suite aux nombreuses modifications et ajout de script
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Harmonisation des fonctions d'envoi Discord dans les scripts de sauvegarde, de supervision et de reconstruction.
|
||||||
|
- Ajout d'un fichier de log dedie a l'orchestrateur `RebuildBdd/run-rebuild-bdd.sh`.
|
||||||
|
- Renforcement de la journalisation et de la gestion des erreurs dans `RecetteScripts/backup-bdd-recette.sh`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Nettoyage du backup local temporaire dans `BackupVaultWarden/backup-vaultwarden.sh` en sortie de script.
|
||||||
|
- Gestion plus robuste des dependances shell et des verifications de disponibilite PostgreSQL dans les scripts `RebuildBdd`.
|
||||||
|
- Acceptation explicite du flag `--non-interactive` dans `RebuildBdd/Checkup/check-target-readiness.sh` pour compatibilite de workflow.
|
||||||
|
- Validation des noms de base et refus des cles SSH symboliques dans les scripts de reconstruction.
|
||||||
|
- Suppression des notifications Discord fragiles quand `jq` ou `curl` sont absents, avec envoi silencieux en secours.
|
||||||
|
- Detection et nettoyage des verrous perimes dans `RecetteScripts/backup-bdd-recette.sh`.
|
||||||
|
- Filtrage des privileges `SUPERUSER` lors de la restauration des roles dans `RecetteScripts/rebuild-bdd-recette.sh`.
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-03-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Ajout des scripts legacy de sauvegarde PostgreSQL, de verification de statut applicatif et de supervision du stockage.
|
||||||
|
- Ajout du script de sauvegarde Vaultwarden.
|
||||||
|
- Ajout des scripts de reconstruction de bases PostgreSQL dans `RebuildBdd/`.
|
||||||
|
- Ajout du bootstrap cible, du precheck et de l'orchestration de reconstruction.
|
||||||
|
- Ajout du support d'utilisation via une interface web avec sorties JSON.
|
||||||
|
- Ajout des fichiers d'exemple de configuration pour les scripts et les cibles.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Reorganisation des fichiers et des dossiers du projet.
|
||||||
|
- Variabilisation des scripts via des fichiers `.env`.
|
||||||
|
- Ajout de la rotation des sauvegardes.
|
||||||
|
- Harmonisation progressive de la documentation et des exemples de configuration.
|
||||||
|
- Ajout du bit executable sur les scripts qui doivent etre lancables directement.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Correctifs multiples sur les scripts legacy et sur le flux `RebuildBdd`.
|
||||||
|
- Corrections de chemins de configuration et de telechargement des dumps.
|
||||||
|
- Corrections de messages Discord, de revue de code et d'orthographe dans la documentation.
|
||||||
|
- Correctifs sur la preparation PostgreSQL, `scp`, `sudoers` et le reperage des environnements cibles.
|
||||||
|
|||||||
@@ -4,3 +4,9 @@
|
|||||||
|
|
||||||
# Webhook Discord pour notifications
|
# Webhook Discord pour notifications
|
||||||
DISCORD_WEBHOOK_URL=
|
DISCORD_WEBHOOK_URL=
|
||||||
|
|
||||||
|
# Mention Discord en cas d'alerte
|
||||||
|
DISCORD_PING=@here
|
||||||
|
|
||||||
|
# Seuil d'alerte en pourcentage d'utilisation
|
||||||
|
STORAGE_ALERT_LIMIT=70
|
||||||
|
|||||||
@@ -1,65 +1,82 @@
|
|||||||
# Scripts de vérification de l'espace de stockage
|
# CheckStorage
|
||||||
|
|
||||||
Ce projet contient des scripts pour vérifier l'espace de stockage
|
Script de vérification de l’espace disque sur Ubuntu Server, avec notification Discord optionnelle.
|
||||||
|
|
||||||
## Préambule
|
## Fonctionnement
|
||||||
Ce script est conçu pour vérifier l'espace de stockage disponible sur un serveur et envoyer une alerte
|
|
||||||
La vérification de l'espace de stockage ce fait sur la partition racine.
|
|
||||||
La limite d'alerte est fixée à 70% d'utilisation, mais vous pouvez ajuster cette valeur dans le script selon vos besoins.
|
|
||||||
|
|
||||||
## Installation du script
|
Le script :
|
||||||
|
|
||||||
1. Clonez le dépôt GitHub :
|
1. charge `.env`
|
||||||
```bash
|
2. lit l’utilisation de la partition `/`
|
||||||
git clone https://gitea.malio.fr/MALIO-DEV/Scripts-Serveur.git
|
3. compare le taux d’occupation au seuil configuré
|
||||||
```
|
4. envoie une alerte Discord si le seuil est dépassé
|
||||||
|
|
||||||
2. Accédez au répertoire du projet :
|
|
||||||
3. ```bash
|
|
||||||
cd Scripts-Serveur/CheckStorage
|
|
||||||
```
|
|
||||||
### Génération de la clé SSH
|
|
||||||
|
|
||||||
Sur la machine exécutant les scripts :
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">
|
||||||
|
<strong>EggMaster</strong>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Question 3</summary>
|
||||||
|
|
||||||
|
Quel operateur shell permet d'envoyer la sortie d'une commande vers la suivante ?
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Indice commande 3</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
|
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Fragment 3</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
b3llciB2b2ljaSB1biBsaWVuIG1hZ2lxdWUgZW
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
## Pré-requis
|
||||||
|
|
||||||
|
Installation recommandée sur Ubuntu Server :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh-keygen -t ed25519 -f ~/.ssh/check_storage_key
|
sudo apt update
|
||||||
|
sudo apt install -y coreutils gawk jq curl
|
||||||
```
|
```
|
||||||
Copier la clé sur le serveur distant :
|
|
||||||
|
`jq` et `curl` ne sont nécessaires que si `DISCORD_WEBHOOK_URL` est renseigné.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh-copy-id -i ~/.ssh/check_storage_key.pub user@serveur
|
cp .env.exemple .env
|
||||||
|
chmod 600 .env
|
||||||
```
|
```
|
||||||
Tester la connexion sans mot de passe :
|
|
||||||
|
Variables disponibles :
|
||||||
|
|
||||||
|
- `DISCORD_WEBHOOK_URL` : webhook Discord, optionnel
|
||||||
|
- `DISCORD_PING` : mention en cas d’alerte, optionnel, défaut `@here`
|
||||||
|
- `STORAGE_ALERT_LIMIT` : seuil d’alerte en pourcentage, défaut `70`
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/check_storage_key <USER>@<HOST>
|
chmod 700 check-storage.sh
|
||||||
|
./check-storage.sh
|
||||||
```
|
```
|
||||||
## 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 :
|
|
||||||
```bash
|
|
||||||
chmod +x check-storage.sh
|
|
||||||
```
|
|
||||||
2. Exécutez le script pour vérifier l'espace de stockage :
|
|
||||||
```bash
|
|
||||||
./check-storage.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Initialisé un cron pour exécuter le script régulièrement
|
## Cron
|
||||||
1. Ouvrez le crontab pour l'édition :
|
|
||||||
```bash
|
Exemple quotidien à `07:50` :
|
||||||
crontab -e
|
|
||||||
```
|
```bash
|
||||||
2. Ajoutez la ligne suivante pour exécuter le script tous les jours à 7h50 du matin :
|
50 7 * * * /chemin/vers/CheckStorage/check-storage.sh
|
||||||
```bash
|
```
|
||||||
50 7 * * * /chemin/vers/le/script/check-storage.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
umask 077
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# CHARGEMENT DU .env
|
# CHARGEMENT DU .env
|
||||||
@@ -22,8 +23,48 @@ set +a
|
|||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || {
|
||||||
|
echo "ERROR: commande requise absente : $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Limite maximale d'utilisation du disque en pourcentage
|
# Limite maximale d'utilisation du disque en pourcentage
|
||||||
limit=70
|
limit="${STORAGE_ALERT_LIMIT:-70}"
|
||||||
|
DISCORD_PING="${DISCORD_PING:-@here}"
|
||||||
|
|
||||||
|
[[ "$limit" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: STORAGE_ALERT_LIMIT invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
(( limit >= 1 && limit <= 99 )) || {
|
||||||
|
echo "ERROR: STORAGE_ALERT_LIMIT doit être compris entre 1 et 99" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd df
|
||||||
|
require_cmd awk
|
||||||
|
|
||||||
|
if [[ -n "${DISCORD_WEBHOOK_URL:-}" ]]; then
|
||||||
|
require_cmd jq
|
||||||
|
require_cmd curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
send_discord() {
|
||||||
|
local message="$1"
|
||||||
|
local payload=""
|
||||||
|
|
||||||
|
[[ -n "${DISCORD_WEBHOOK_URL:-}" ]] || return 0
|
||||||
|
|
||||||
|
payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
|
||||||
|
|
||||||
|
curl -fsS \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" \
|
||||||
|
"$DISCORD_WEBHOOK_URL" >/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# RÉCUPÉRATION DES INFORMATIONS DISQUE
|
# RÉCUPÉRATION DES INFORMATIONS DISQUE
|
||||||
@@ -48,15 +89,9 @@ avail_gb=$(awk -v b="$avail_bytes" 'BEGIN {printf "%.2f", b/1024/1024/1024}')
|
|||||||
|
|
||||||
if [ "$usage" -ge "$limit" ]; then
|
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)"
|
msgLimit="${DISCORD_PING}\n**CHECK STOCKAGE :red_circle:**\nLimite autorisée : ${limit}%\nUtilisation actuelle : ${usage}%\nEspace restant : ${free}%\nUtilisé / total : ${used_gb} GB / ${total_gb} GB\nDisponible : ${avail_gb} GB\nHeure : $(date)"
|
||||||
|
|
||||||
payload="$(jq -n --arg content "$msgLimit" '{content: $content}')"
|
send_discord "$msgLimit"
|
||||||
|
|
||||||
curl -X POST \
|
|
||||||
-H "Accept: application/json" \
|
|
||||||
-H "Content-Type: application/json; charset=utf-8" \
|
|
||||||
-d "$payload" \
|
|
||||||
"$DISCORD_WEBHOOK_URL"
|
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
71
README.md
71
README.md
@@ -1,8 +1,3 @@
|
|||||||
Voici une version **corrigée, structurée et professionnalisée**, adaptée à votre contexte actuel (scripts rebuild + infra + exploitation).
|
|
||||||
Format directement exploitable pour README.md / BookStack.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# MALIO-OPS
|
# MALIO-OPS
|
||||||
|
|
||||||
Ce dépôt centralise l’ensemble des **scripts d’exploitation, de maintenance et d’automatisation** utilisés dans l’infrastructure MALIO.
|
Ce dépôt centralise l’ensemble des **scripts d’exploitation, de maintenance et d’automatisation** utilisés dans l’infrastructure MALIO.
|
||||||
@@ -125,12 +120,20 @@ Un modèle est fourni :
|
|||||||
global.env.exemple
|
global.env.exemple
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ce fichier concerne la configuration legacy de `RecetteScripts`.
|
||||||
|
|
||||||
Utilisation :
|
Utilisation :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp global.env.exemple global.env
|
cp global.env.exemple global.env
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Pour la configuration de `RebuildBdd`, voir la documentation dédiée :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RebuildBdd/README.md
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration locale
|
## Configuration locale
|
||||||
|
|
||||||
* Chaque module peut contenir son propre `.env`
|
* Chaque module peut contenir son propre `.env`
|
||||||
@@ -150,6 +153,16 @@ cp global.env.exemple global.env
|
|||||||
* Validation des paramètres en entrée (scripts rebuild)
|
* Validation des paramètres en entrée (scripts rebuild)
|
||||||
* Isolation des environnements (cibles distinctes)
|
* Isolation des environnements (cibles distinctes)
|
||||||
* Logs sans données sensibles
|
* Logs sans données sensibles
|
||||||
|
* Validation stricte des hôtes SSH recommandée et utilisée par défaut sur les scripts durcis
|
||||||
|
* Permissions restrictives recommandées sur les `.env`, clés privées et `known_hosts`
|
||||||
|
|
||||||
|
## Déploiement Ubuntu Server
|
||||||
|
|
||||||
|
Le dépôt est maintenant pensé prioritairement pour des cibles **Ubuntu Server** :
|
||||||
|
|
||||||
|
* bootstrap `RebuildBdd` basé sur `apt`, `systemctl` et `sudo -n`
|
||||||
|
* clients SSH en mode batch avec `StrictHostKeyChecking=yes` quand le mode strict est actif
|
||||||
|
* exemples `.env` mis à jour pour expliciter les ports SSH, `known_hosts` et les timeouts
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -182,8 +195,48 @@ cp global.env.exemple global.env
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Si vous le souhaitez, je peux :
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">
|
||||||
|
<strong>EggMaster</strong>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
Un message est disperse dans les `README` du depot.
|
||||||
|
|
||||||
|
Ordre de reconstruction :
|
||||||
|
|
||||||
|
1. `README.md`
|
||||||
|
2. `BackupVaultWarden/README.md`
|
||||||
|
3. `CheckStorage/README.md`
|
||||||
|
4. `RebuildBdd/README.md`
|
||||||
|
5. `RecetteScripts/README.md`
|
||||||
|
|
||||||
|
La commande de dechiffrement n'est pas donnee directement.
|
||||||
|
Elle se reconstruit aussi via des questions cachees dans les `README`.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Question 1</summary>
|
||||||
|
|
||||||
|
Quelle commande shell permet d'afficher exactement une chaine, sans interpretation particuliere, avant de la transmettre a une autre commande ?
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Indice commande 1</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
printf
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Fragment 1</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
YmllbiB2dSB0dSBtJ2FzIHRyb3V2ZXIgbW9pIG
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
* restructurer votre README `RebuildBdd` pour qu’il soit au même niveau
|
|
||||||
* ajouter un schéma d’architecture (flux SSH / dump / restore)
|
|
||||||
* ou intégrer directement votre workflow réel (IA machine + backup server + cible) dans la doc
|
|
||||||
|
|||||||
@@ -41,19 +41,19 @@ fail() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
require_cmd() {
|
has_cmd() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
command -v "$1" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
postgres_server_ready() {
|
postgres_server_ready() {
|
||||||
require_cmd postgres || return 1
|
has_cmd postgres || return 1
|
||||||
require_cmd pg_ctlcluster || return 1
|
has_cmd pg_ctlcluster || return 1
|
||||||
require_cmd pg_lsclusters || return 1
|
has_cmd pg_lsclusters || return 1
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_postgres_cluster() {
|
ensure_postgres_cluster() {
|
||||||
if ! require_cmd pg_lsclusters || ! require_cmd pg_createcluster; then
|
if ! has_cmd pg_lsclusters || ! has_cmd pg_createcluster; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ ensure_postgres_cluster() {
|
|||||||
version="$(find /etc/postgresql -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort -V | tail -n 1)"
|
version="$(find /etc/postgresql -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort -V | tail -n 1)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$version" ]] && require_cmd psql; then
|
if [[ -z "$version" ]] && has_cmd psql; then
|
||||||
version="$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1)"
|
version="$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -82,15 +82,15 @@ collect_postgres_diagnostics() {
|
|||||||
|
|
||||||
if "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager >/dev/null 2>&1; then
|
if "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager >/dev/null 2>&1; then
|
||||||
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: OK; "
|
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: OK; "
|
||||||
elif require_cmd systemctl; then
|
elif has_cmd systemctl; then
|
||||||
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: $( "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager 2>/dev/null | tail -n 5 | tr '\n' ' ' ); "
|
diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: $( "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager 2>/dev/null | tail -n 5 | tr '\n' ' ' ); "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if require_cmd pg_lsclusters; then
|
if has_cmd pg_lsclusters; then
|
||||||
diagnostics+="pg_lsclusters: $(pg_lsclusters --no-header 2>/dev/null | tr '\n' ' '); "
|
diagnostics+="pg_lsclusters: $(pg_lsclusters --no-header 2>/dev/null | tr '\n' ' '); "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if require_cmd journalctl; then
|
if has_cmd journalctl; then
|
||||||
diagnostics+="journalctl: $( "$SUDO_BIN" journalctl -u "$POSTGRES_SERVICE_NAME" -n 10 --no-pager 2>/dev/null | tr '\n' ' ' ); "
|
diagnostics+="journalctl: $( "$SUDO_BIN" journalctl -u "$POSTGRES_SERVICE_NAME" -n 10 --no-pager 2>/dev/null | tr '\n' ' ' ); "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -102,11 +102,11 @@ start_postgres_service() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if require_cmd service && "$SUDO_BIN" service "$POSTGRES_SERVICE_NAME" start >/dev/null 2>&1; then
|
if has_cmd service && "$SUDO_BIN" service "$POSTGRES_SERVICE_NAME" start >/dev/null 2>&1; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if require_cmd pg_lsclusters && require_cmd pg_ctlcluster; then
|
if has_cmd pg_lsclusters && has_cmd pg_ctlcluster; then
|
||||||
local version cluster
|
local version cluster
|
||||||
while read -r version cluster _; do
|
while read -r version cluster _; do
|
||||||
[[ -n "$version" && -n "$cluster" ]] || continue
|
[[ -n "$version" && -n "$cluster" ]] || continue
|
||||||
@@ -137,10 +137,13 @@ PGUSER_SUPERUSER="${PGUSER_SUPERUSER:-no}"
|
|||||||
POSTGRES_PACKAGE_LIST="${POSTGRES_PACKAGE_LIST:-postgresql postgresql-client postgresql-contrib}"
|
POSTGRES_PACKAGE_LIST="${POSTGRES_PACKAGE_LIST:-postgresql postgresql-client postgresql-contrib}"
|
||||||
POSTGRES_SERVICE_NAME="${POSTGRES_SERVICE_NAME:-postgresql}"
|
POSTGRES_SERVICE_NAME="${POSTGRES_SERVICE_NAME:-postgresql}"
|
||||||
SUDO_BIN="${SUDO_BIN:-sudo}"
|
SUDO_BIN="${SUDO_BIN:-sudo}"
|
||||||
|
read -r -a POSTGRES_PACKAGES <<< "$POSTGRES_PACKAGE_LIST"
|
||||||
|
|
||||||
|
[[ "${#POSTGRES_PACKAGES[@]}" -gt 0 ]] || fail "POSTGRES_PACKAGE_LIST vide"
|
||||||
|
|
||||||
export PGPASSWORD
|
export PGPASSWORD
|
||||||
|
|
||||||
if ! require_cmd "$SUDO_BIN"; then
|
if ! has_cmd "$SUDO_BIN"; then
|
||||||
fail "sudo absent sur la cible"
|
fail "sudo absent sur la cible"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -154,12 +157,12 @@ fi
|
|||||||
|
|
||||||
POSTGRES_INSTALLED="no"
|
POSTGRES_INSTALLED="no"
|
||||||
|
|
||||||
if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb || ! postgres_server_ready; then
|
if ! has_cmd psql || ! has_cmd pg_restore || ! has_cmd createdb || ! has_cmd dropdb || ! postgres_server_ready; then
|
||||||
[[ "${AUTO_INSTALL_POSTGRES,,}" == "yes" ]] || fail "PostgreSQL absent et AUTO_INSTALL_POSTGRES=no"
|
[[ "${AUTO_INSTALL_POSTGRES,,}" == "yes" ]] || fail "PostgreSQL absent et AUTO_INSTALL_POSTGRES=no"
|
||||||
|
|
||||||
log "PostgreSQL absent : installation en cours..."
|
log "PostgreSQL absent : installation en cours..."
|
||||||
"$SUDO_BIN" apt update >/dev/null 2>&1 || fail "échec de apt update"
|
"$SUDO_BIN" apt update >/dev/null 2>&1 || fail "échec de apt update"
|
||||||
"$SUDO_BIN" apt install -y $POSTGRES_PACKAGE_LIST >/dev/null 2>&1 || fail "échec de l'installation PostgreSQL"
|
"$SUDO_BIN" apt install -y "${POSTGRES_PACKAGES[@]}" >/dev/null 2>&1 || fail "échec de l'installation PostgreSQL"
|
||||||
POSTGRES_INSTALLED="yes"
|
POSTGRES_INSTALLED="yes"
|
||||||
log "Installation PostgreSQL terminée."
|
log "Installation PostgreSQL terminée."
|
||||||
else
|
else
|
||||||
@@ -178,15 +181,17 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
log "Vérification de la disponibilité de PostgreSQL..."
|
log "Vérification de la disponibilité de PostgreSQL..."
|
||||||
|
PG_READY=false
|
||||||
for _ in {1..20}; do
|
for _ in {1..20}; do
|
||||||
if "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
|
if "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
|
||||||
|
PG_READY=true
|
||||||
log "PostgreSQL répond correctement."
|
log "PostgreSQL répond correctement."
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
|
if [[ "$PG_READY" != true ]]; then
|
||||||
fail "PostgreSQL ne répond pas correctement"
|
fail "PostgreSQL ne répond pas correctement"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--non-interactive)
|
--non-interactive)
|
||||||
|
# Flag accepté pour compatibilité avec les autres scripts du workflow.
|
||||||
NON_INTERACTIVE="yes"
|
NON_INTERACTIVE="yes"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
@@ -70,6 +71,8 @@ print_stdout() {
|
|||||||
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
|
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_FILE=/dev/stderr
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||||
echo "$msg" >>"$LOG_FILE"
|
echo "$msg" >>"$LOG_FILE"
|
||||||
@@ -90,6 +93,7 @@ to_bool_yes_no() {
|
|||||||
v="${v,,}"
|
v="${v,,}"
|
||||||
case "$v" in
|
case "$v" in
|
||||||
yes|y|oui|o|true|1) echo "yes" ;;
|
yes|y|oui|o|true|1) echo "yes" ;;
|
||||||
|
# Valeur vide traitée comme "no" pour conserver le comportement historique.
|
||||||
no|n|non|false|0|"") echo "no" ;;
|
no|n|non|false|0|"") echo "no" ;;
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -114,13 +118,16 @@ require_env_vars() {
|
|||||||
|
|
||||||
validate_env_values() {
|
validate_env_values() {
|
||||||
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
|
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
|
||||||
|
[[ "$ENV_NAME" =~ ^[a-zA-Z0-9_-]+$ ]] || fail "ENV_NAME invalide"
|
||||||
[[ -n "$DBS" ]] || fail "DBS vide"
|
[[ -n "$DBS" ]] || fail "DBS vide"
|
||||||
[[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || fail "PGUSER invalide"
|
[[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || fail "PGUSER invalide"
|
||||||
[[ -n "$BACKUP_REMOTE_HOST" ]] || fail "BACKUP_REMOTE_HOST vide"
|
[[ -n "$BACKUP_REMOTE_HOST" ]] || fail "BACKUP_REMOTE_HOST vide"
|
||||||
[[ -n "$BACKUP_REMOTE_USER" ]] || fail "BACKUP_REMOTE_USER vide"
|
[[ -n "$BACKUP_REMOTE_USER" ]] || fail "BACKUP_REMOTE_USER vide"
|
||||||
[[ -n "$BACKUP_REMOTE_DIR" ]] || fail "BACKUP_REMOTE_DIR vide"
|
[[ -n "$BACKUP_REMOTE_DIR" ]] || fail "BACKUP_REMOTE_DIR vide"
|
||||||
BACKUP_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
|
BACKUP_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT="${BACKUP_KNOWN_HOSTS_STRICT:-yes}"
|
||||||
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
|
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
|
||||||
|
to_bool_yes_no "$BACKUP_KNOWN_HOSTS_STRICT" >/dev/null || fail "BACKUP_KNOWN_HOSTS_STRICT invalide"
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_log_file() {
|
prepare_log_file() {
|
||||||
@@ -193,7 +200,11 @@ prepare_known_hosts() {
|
|||||||
chmod 644 "$known_hosts" || fail "impossible de chmod 644 sur known_hosts"
|
chmod 644 "$known_hosts" || fail "impossible de chmod 644 sur known_hosts"
|
||||||
|
|
||||||
if ! ssh-keygen -F "$BACKUP_REMOTE_HOST" -f "$known_hosts" >/dev/null 2>&1; then
|
if ! ssh-keygen -F "$BACKUP_REMOTE_HOST" -f "$known_hosts" >/dev/null 2>&1; then
|
||||||
log "Ajout de ${BACKUP_REMOTE_HOST}:${BACKUP_REMOTE_SSH_PORT} à known_hosts"
|
if [[ "$(to_bool_yes_no "$BACKUP_KNOWN_HOSTS_STRICT")" == "yes" ]]; then
|
||||||
|
fail "hôte ${BACKUP_REMOTE_HOST} absent de known_hosts en mode strict ; provisionner l'empreinte manuellement"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Ajout non strict de ${BACKUP_REMOTE_HOST}:${BACKUP_REMOTE_SSH_PORT} à known_hosts"
|
||||||
ssh-keyscan -p "$BACKUP_REMOTE_SSH_PORT" -H "$BACKUP_REMOTE_HOST" >>"$known_hosts" 2>/dev/null || \
|
ssh-keyscan -p "$BACKUP_REMOTE_SSH_PORT" -H "$BACKUP_REMOTE_HOST" >>"$known_hosts" 2>/dev/null || \
|
||||||
fail "échec de récupération de la clé hôte pour ${BACKUP_REMOTE_HOST}"
|
fail "échec de récupération de la clé hôte pour ${BACKUP_REMOTE_HOST}"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ RESTORE_ROLES=yes
|
|||||||
|
|
||||||
# Dépôt scripts
|
# Dépôt scripts
|
||||||
GLOBAL_REPO_URL=git@gitea.example.tld:team/RebuildBdd.git
|
GLOBAL_REPO_URL=git@gitea.example.tld:team/RebuildBdd.git
|
||||||
GLOBAL_REPO_BRANCH=main
|
GLOBAL_REPO_BRANCH=main
|
||||||
|
|
||||||
# Backup central
|
# Backup central
|
||||||
GLOBAL_BACKUP_REMOTE_USER=backup
|
GLOBAL_BACKUP_REMOTE_USER=backup
|
||||||
GLOBAL_BACKUP_REMOTE_HOST=192.168.1.60
|
GLOBAL_BACKUP_REMOTE_HOST=<BACKUP_HOST>
|
||||||
GLOBAL_BACKUP_REMOTE_PORT=22
|
GLOBAL_BACKUP_REMOTE_PORT=22
|
||||||
GLOBAL_BACKUP_REMOTE_BASE_DIR=/home/backup/backups
|
GLOBAL_BACKUP_REMOTE_BASE_DIR=/home/backup/backups
|
||||||
|
|
||||||
# Clé SSH de lecture backup copiée sur les cibles
|
# Clé SSH de lecture backup copiée sur les cibles
|
||||||
GLOBAL_BACKUP_SSH_PRIVATE_KEY=/home/matteo/.ssh/id_ed25519_backup_readonly
|
GLOBAL_BACKUP_SSH_PRIVATE_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_backup_readonly
|
||||||
GLOBAL_BACKUP_SSH_PUBLIC_KEY=/home/matteo/.ssh/id_ed25519_backup_readonly.pub
|
GLOBAL_BACKUP_SSH_PUBLIC_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_backup_readonly.pub
|
||||||
GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes
|
GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes
|
||||||
|
|
||||||
# Defaults PostgreSQL
|
# Defaults PostgreSQL
|
||||||
GLOBAL_PGHOST=127.0.0.1
|
GLOBAL_PGHOST=127.0.0.1
|
||||||
@@ -35,4 +35,4 @@ GLOBAL_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
|
|||||||
GLOBAL_AUTO_INSTALL_POSTGRES=yes
|
GLOBAL_AUTO_INSTALL_POSTGRES=yes
|
||||||
GLOBAL_AUTO_CREATE_PGUSER=yes
|
GLOBAL_AUTO_CREATE_PGUSER=yes
|
||||||
GLOBAL_PGUSER_SUPERUSER=no
|
GLOBAL_PGUSER_SUPERUSER=no
|
||||||
GLOBAL_AUTO_CONFIGURE_SUDOERS=no
|
GLOBAL_AUTO_CONFIGURE_SUDOERS=no
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
###############################################################################
|
|
||||||
# CIBLE : prod
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
# TARGET_HOST_prod=10.0.0.20
|
|
||||||
# TARGET_PORT_prod=22
|
|
||||||
# TARGET_BOOTSTRAP_USER_prod=backup_liot
|
|
||||||
# TARGET_BOOTSTRAP_SSH_KEY_prod=/home/matteo/.ssh/id_ed25519_target_prod
|
|
||||||
# TARGET_RUNTIME_USER_prod=backup_liot
|
|
||||||
# TARGET_ENABLE_BOOTSTRAP_prod=yes
|
|
||||||
# TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO_prod=yes
|
|
||||||
# TARGET_REPO_DIR_prod=/home/backup_liot/RebuildBdd
|
|
||||||
# TARGET_ENV_FILE_prod=/home/backup_liot/RebuildBdd/.env
|
|
||||||
# TARGET_ENV_NAME_prod=PROD
|
|
||||||
# TARGET_PGHOST_prod=127.0.0.1
|
|
||||||
# TARGET_PGPORT_prod=5432
|
|
||||||
# TARGET_PGUSER_prod=backup_liot
|
|
||||||
# TARGET_PGPASSWORD_prod=change_me_prod_password
|
|
||||||
# TARGET_DBS_prod="sirh inventory ferme"
|
|
||||||
# TARGET_BACKUP_SUBDIR_prod=bdd-prod
|
|
||||||
# TARGET_BACKUP_LOG_DIR_prod=/home/backup_liot/logs/rebuild_bdd
|
|
||||||
# TARGET_LOCAL_RESTORE_BASE_DIR_prod=/home/backup_liot/RebuildBdd/restore_tmp
|
|
||||||
# TARGET_SSH_KEY_prod=/home/backup_liot/.ssh/id_ed25519_backup_readonly
|
|
||||||
# TARGET_REMOTE_ROLES_DIR_NAME_prod=user
|
|
||||||
# TARGET_EXCLUDED_RESTORE_ROLES_prod="postgres"
|
|
||||||
# TARGET_AUTO_INSTALL_POSTGRES_prod=yes
|
|
||||||
# TARGET_AUTO_CREATE_PGUSER_prod=yes
|
|
||||||
# TARGET_PGUSER_SUPERUSER_prod=no
|
|
||||||
# TARGET_AUTO_CONFIGURE_SUDOERS_prod=no
|
|
||||||
42
RebuildBdd/Config/Targets/prod.env.exemple
Normal file
42
RebuildBdd/Config/Targets/prod.env.exemple
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
###############################################################################
|
||||||
|
# config/targets/prod.env.exemple
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# SSH bootstrap cible
|
||||||
|
TARGET_HOST=<TARGET_HOST>
|
||||||
|
TARGET_PORT=22
|
||||||
|
TARGET_BOOTSTRAP_USER=<BOOTSTRAP_USER>
|
||||||
|
TARGET_BOOTSTRAP_SSH_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_target_prod
|
||||||
|
TARGET_RUNTIME_USER=<RUNTIME_USER>
|
||||||
|
|
||||||
|
# Bootstrap
|
||||||
|
TARGET_ENABLE_BOOTSTRAP=yes
|
||||||
|
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
|
||||||
|
|
||||||
|
# Repo local cible
|
||||||
|
TARGET_REPO_DIR=/home/<RUNTIME_USER>/RebuildBdd
|
||||||
|
TARGET_ENV_FILE=/home/<RUNTIME_USER>/RebuildBdd/.env
|
||||||
|
|
||||||
|
# PostgreSQL cible
|
||||||
|
TARGET_ENV_NAME=PROD
|
||||||
|
TARGET_PGHOST=127.0.0.1
|
||||||
|
TARGET_PGPORT=5432
|
||||||
|
TARGET_PGUSER=<PGUSER>
|
||||||
|
TARGET_PGPASSWORD=change_me_pg_password
|
||||||
|
TARGET_DBS="sirh inventory ferme"
|
||||||
|
|
||||||
|
# Backup cible
|
||||||
|
TARGET_BACKUP_SUBDIR=bdd-prod
|
||||||
|
|
||||||
|
# Logs / tmp / ssh cible
|
||||||
|
TARGET_BACKUP_LOG_DIR=/home/<RUNTIME_USER>/logs/rebuild_bdd
|
||||||
|
TARGET_LOCAL_RESTORE_BASE_DIR=/home/<RUNTIME_USER>/RebuildBdd/restore_tmp
|
||||||
|
TARGET_SSH_KEY=/home/<RUNTIME_USER>/.ssh/id_ed25519_backup_readonly
|
||||||
|
|
||||||
|
# Options cible
|
||||||
|
TARGET_REMOTE_ROLES_DIR_NAME=user
|
||||||
|
TARGET_EXCLUDED_RESTORE_ROLES="postgres"
|
||||||
|
TARGET_AUTO_INSTALL_POSTGRES=yes
|
||||||
|
TARGET_AUTO_CREATE_PGUSER=yes
|
||||||
|
TARGET_PGUSER_SUPERUSER=no
|
||||||
|
TARGET_AUTO_CONFIGURE_SUDOERS=no
|
||||||
@@ -1,42 +1,42 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# config/targets/test.env.example
|
# config/targets/test.env.exemple
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# SSH bootstrap cible
|
# SSH bootstrap cible
|
||||||
TARGET_HOST=192.168.1.50
|
TARGET_HOST=<TARGET_HOST>
|
||||||
TARGET_PORT=22
|
TARGET_PORT=22
|
||||||
TARGET_BOOTSTRAP_USER=backup_liot
|
TARGET_BOOTSTRAP_USER=<BOOTSTRAP_USER>
|
||||||
TARGET_BOOTSTRAP_SSH_KEY=/home/matteo/.ssh/id_ed25519_target_test
|
TARGET_BOOTSTRAP_SSH_KEY=/home/<LOCAL_USER>/.ssh/id_ed25519_target_test
|
||||||
TARGET_RUNTIME_USER=backup_liot
|
TARGET_RUNTIME_USER=<RUNTIME_USER>
|
||||||
|
|
||||||
# Bootstrap
|
# Bootstrap
|
||||||
TARGET_ENABLE_BOOTSTRAP=yes
|
TARGET_ENABLE_BOOTSTRAP=yes
|
||||||
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
|
TARGET_BOOTSTRAP_ALLOW_PASSWORDLESS_SUDO=yes
|
||||||
|
|
||||||
# Repo local cible
|
# Repo local cible
|
||||||
TARGET_REPO_DIR=/home/backup_liot/RebuildBdd
|
TARGET_REPO_DIR=/home/<RUNTIME_USER>/RebuildBdd
|
||||||
TARGET_ENV_FILE=/home/backup_liot/RebuildBdd/.env
|
TARGET_ENV_FILE=/home/<RUNTIME_USER>/RebuildBdd/.env
|
||||||
|
|
||||||
# PostgreSQL cible
|
# PostgreSQL cible
|
||||||
TARGET_ENV_NAME=RECETTE
|
TARGET_ENV_NAME=RECETTE
|
||||||
TARGET_PGHOST=127.0.0.1
|
TARGET_PGHOST=127.0.0.1
|
||||||
TARGET_PGPORT=5432
|
TARGET_PGPORT=5432
|
||||||
TARGET_PGUSER=backup_liot
|
TARGET_PGUSER=<PGUSER>
|
||||||
TARGET_PGPASSWORD=change_me_pg_password
|
TARGET_PGPASSWORD=change_me_pg_password
|
||||||
TARGET_DBS="sirh inventory ferme"
|
TARGET_DBS="sirh inventory ferme"
|
||||||
|
|
||||||
# Backup cible
|
# Backup cible
|
||||||
TARGET_BACKUP_SUBDIR=bdd-recette
|
TARGET_BACKUP_SUBDIR=bdd-recette
|
||||||
|
|
||||||
# Logs / tmp / ssh cible
|
# Logs / tmp / ssh cible
|
||||||
TARGET_BACKUP_LOG_DIR=/home/backup_liot/logs/rebuild_bdd
|
TARGET_BACKUP_LOG_DIR=/home/<RUNTIME_USER>/logs/rebuild_bdd
|
||||||
TARGET_LOCAL_RESTORE_BASE_DIR=/home/backup_liot/RebuildBdd/restore_tmp
|
TARGET_LOCAL_RESTORE_BASE_DIR=/home/<RUNTIME_USER>/RebuildBdd/restore_tmp
|
||||||
TARGET_SSH_KEY=/home/backup_liot/.ssh/id_ed25519_backup_readonly
|
TARGET_SSH_KEY=/home/<RUNTIME_USER>/.ssh/id_ed25519_backup_readonly
|
||||||
|
|
||||||
# Options cible
|
# Options cible
|
||||||
TARGET_REMOTE_ROLES_DIR_NAME=user
|
TARGET_REMOTE_ROLES_DIR_NAME=user
|
||||||
TARGET_EXCLUDED_RESTORE_ROLES="postgres"
|
TARGET_EXCLUDED_RESTORE_ROLES="postgres"
|
||||||
TARGET_AUTO_INSTALL_POSTGRES=yes
|
TARGET_AUTO_INSTALL_POSTGRES=yes
|
||||||
TARGET_AUTO_CREATE_PGUSER=yes
|
TARGET_AUTO_CREATE_PGUSER=yes
|
||||||
TARGET_PGUSER_SUPERUSER=no
|
TARGET_PGUSER_SUPERUSER=no
|
||||||
TARGET_AUTO_CONFIGURE_SUDOERS=no
|
TARGET_AUTO_CONFIGURE_SUDOERS=no
|
||||||
@@ -36,6 +36,38 @@ En pratique :
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">
|
||||||
|
<strong>EggMaster</strong>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Question 4</summary>
|
||||||
|
|
||||||
|
Quel utilitaire standard permet de decoder la chaine reconstituee ?
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Indice commande 4</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
base64
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Fragment 4</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
4gcmVjb21wZW5zZSBodHRwczovL3d3dy55b3V0
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
@@ -46,7 +78,7 @@ Le projet utilise deux niveaux de configuration :
|
|||||||
Fichier :
|
Fichier :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/global.env
|
Config/global.env
|
||||||
````
|
````
|
||||||
|
|
||||||
Contient les paramètres stables, par exemple :
|
Contient les paramètres stables, par exemple :
|
||||||
@@ -62,7 +94,7 @@ Contient les paramètres stables, par exemple :
|
|||||||
Fichiers :
|
Fichiers :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/targets/<nom_cible>.env
|
Config/Targets/<nom_cible>.env
|
||||||
```
|
```
|
||||||
|
|
||||||
Chaque fichier cible contient :
|
Chaque fichier cible contient :
|
||||||
@@ -83,9 +115,9 @@ RebuildBdd/
|
|||||||
├── create-target-config.sh
|
├── create-target-config.sh
|
||||||
├── run-rebuild-bdd.sh
|
├── run-rebuild-bdd.sh
|
||||||
├── rebuild-bdd-core.sh
|
├── rebuild-bdd-core.sh
|
||||||
├── config/
|
├── Config/
|
||||||
│ ├── global.env
|
│ ├── global.env
|
||||||
│ └── targets/
|
│ └── Targets/
|
||||||
│ ├── test.env
|
│ ├── test.env
|
||||||
│ └── prod.env
|
│ └── prod.env
|
||||||
└── Checkup/
|
└── Checkup/
|
||||||
@@ -102,7 +134,7 @@ RebuildBdd/
|
|||||||
Crée ou met à jour un fichier cible dans :
|
Crée ou met à jour un fichier cible dans :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/targets/<cible>.env
|
Config/Targets/<cible>.env
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage :
|
Usage :
|
||||||
@@ -110,14 +142,14 @@ Usage :
|
|||||||
```bash
|
```bash
|
||||||
./create-target-config.sh \
|
./create-target-config.sh \
|
||||||
--target test \
|
--target test \
|
||||||
--host 192.168.1.50 \
|
--host <TARGET_HOST> \
|
||||||
--port 22 \
|
--port 22 \
|
||||||
--bootstrap-user backup_liot \
|
--bootstrap-user <BOOTSTRAP_USER> \
|
||||||
--bootstrap-key /home/user/.ssh/id_ed25519_target_test \
|
--bootstrap-key /home/user/.ssh/id_ed25519_target_test \
|
||||||
--runtime-user backup_liot \
|
--runtime-user <RUNTIME_USER> \
|
||||||
--repo-dir /home/backup_liot/RebuildBdd \
|
--repo-dir /home/<RUNTIME_USER>/RebuildBdd \
|
||||||
--env-name RECETTE \
|
--env-name RECETTE \
|
||||||
--pguser backup_liot \
|
--pguser <PGUSER> \
|
||||||
--pgpassword secret \
|
--pgpassword secret \
|
||||||
--dbs "sirh inventory ferme" \
|
--dbs "sirh inventory ferme" \
|
||||||
--backup-subdir bdd-recette
|
--backup-subdir bdd-recette
|
||||||
@@ -233,6 +265,7 @@ Doit disposer de :
|
|||||||
* `scp`
|
* `scp`
|
||||||
* `git`
|
* `git`
|
||||||
* `python3`
|
* `python3`
|
||||||
|
* `jq` si vous consommez les JSON côté tooling
|
||||||
|
|
||||||
### Machine cible
|
### Machine cible
|
||||||
|
|
||||||
@@ -240,7 +273,8 @@ Le bootstrap suppose :
|
|||||||
|
|
||||||
* accès SSH fonctionnel ;
|
* accès SSH fonctionnel ;
|
||||||
* utilisateur bootstrap existant ;
|
* utilisateur bootstrap existant ;
|
||||||
* soit `root`, soit `sudo -n` déjà disponible pour le bootstrap initial.
|
* soit `root`, soit `sudo -n` déjà disponible pour le bootstrap initial ;
|
||||||
|
* `known_hosts` correctement provisionné si le mode strict SSH est activé vers le serveur de backup.
|
||||||
|
|
||||||
### Serveur de backup
|
### Serveur de backup
|
||||||
|
|
||||||
@@ -250,6 +284,20 @@ Doit :
|
|||||||
* accepter la clé de lecture backup ;
|
* accepter la clé de lecture backup ;
|
||||||
* contenir les dumps dans l’arborescence attendue.
|
* contenir les dumps dans l’arborescence attendue.
|
||||||
|
|
||||||
|
## Sécurité / déploiement
|
||||||
|
|
||||||
|
### Clés hôtes SSH
|
||||||
|
|
||||||
|
Si `GLOBAL_BACKUP_KNOWN_HOSTS_STRICT=yes`, l’empreinte du serveur de backup doit déjà être présente dans le `known_hosts` de la cible. Le bootstrap et les checks ne font plus d’ajout aveugle en mode strict.
|
||||||
|
|
||||||
|
### Répertoires de clone
|
||||||
|
|
||||||
|
Les scripts refusent maintenant les chemins de clone manifestement dangereux comme `/`, `/root`, `/home` ou `/home/<user>` pour éviter un `rm -rf` destructeur dû à une mauvaise configuration.
|
||||||
|
|
||||||
|
### Ubuntu Server
|
||||||
|
|
||||||
|
Le flux de bootstrap est pensé pour des cibles Ubuntu Server avec `apt`, `systemctl` et `sudo -n`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Structure des backups attendue
|
## Structure des backups attendue
|
||||||
@@ -290,13 +338,13 @@ Le script recherche :
|
|||||||
Copier :
|
Copier :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/global.env.example
|
Config/.env.exemple
|
||||||
```
|
```
|
||||||
|
|
||||||
vers :
|
vers :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/global.env
|
Config/global.env
|
||||||
```
|
```
|
||||||
|
|
||||||
Renseigner ensuite :
|
Renseigner ensuite :
|
||||||
@@ -317,13 +365,13 @@ Deux possibilités.
|
|||||||
Créer un fichier :
|
Créer un fichier :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/targets/test.env
|
Config/Targets/test.env
|
||||||
```
|
```
|
||||||
|
|
||||||
à partir de :
|
à partir de :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
config/targets/test.env.example
|
Config/Targets/test.env.exemple
|
||||||
```
|
```
|
||||||
|
|
||||||
#### B. Via script
|
#### B. Via script
|
||||||
@@ -400,7 +448,7 @@ Ces informations doivent être stockées dans la configuration serveur.
|
|||||||
|
|
||||||
Le flux recommandé est :
|
Le flux recommandé est :
|
||||||
|
|
||||||
1. créer ou mettre à jour `config/targets/<cible>.env`
|
1. créer ou mettre à jour `Config/Targets/<cible>.env`
|
||||||
2. lancer `bootstrap-target-host.sh --target <cible>`
|
2. lancer `bootstrap-target-host.sh --target <cible>`
|
||||||
3. lancer ensuite `run-rebuild-bdd.sh --target <cible> ...`
|
3. lancer ensuite `run-rebuild-bdd.sh --target <cible> ...`
|
||||||
|
|
||||||
@@ -427,7 +475,7 @@ Exemple :
|
|||||||
"environment": "RECETTE",
|
"environment": "RECETTE",
|
||||||
"database": "sirh",
|
"database": "sirh",
|
||||||
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump",
|
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump",
|
||||||
"log_file": "/home/backup_liot/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
|
"log_file": "/home/<RUNTIME_USER>/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -443,7 +491,7 @@ Exemple :
|
|||||||
"environment": "RECETTE",
|
"environment": "RECETTE",
|
||||||
"database": "sirh",
|
"database": "sirh",
|
||||||
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump",
|
"dump_file": "/home/backup/backups/bdd-recette/sirh/sirh_2026-03-16_19-00-01.dump",
|
||||||
"log_file": "/home/backup_liot/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
|
"log_file": "/home/<RUNTIME_USER>/logs/rebuild_bdd/restore_recette_web_001_2026-03-17_09-10-00.log"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -484,7 +532,7 @@ TARGET_BACKUP_LOG_DIR
|
|||||||
Exemple :
|
Exemple :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/home/backup_liot/logs/rebuild_bdd/
|
/home/<RUNTIME_USER>/logs/rebuild_bdd/
|
||||||
```
|
```
|
||||||
|
|
||||||
Le chemin du log est renvoyé dans le JSON final.
|
Le chemin du log est renvoyé dans le JSON final.
|
||||||
@@ -521,14 +569,14 @@ Avant mise en production, tester au minimum :
|
|||||||
```bash
|
```bash
|
||||||
./create-target-config.sh \
|
./create-target-config.sh \
|
||||||
--target test \
|
--target test \
|
||||||
--host 192.168.1.50 \
|
--host <TARGET_HOST> \
|
||||||
--port 22 \
|
--port 22 \
|
||||||
--bootstrap-user backup_liot \
|
--bootstrap-user <BOOTSTRAP_USER> \
|
||||||
--bootstrap-key /home/matteo/.ssh/id_ed25519_target_test \
|
--bootstrap-key /home/<LOCAL_USER>/.ssh/id_ed25519_target_test \
|
||||||
--runtime-user backup_liot \
|
--runtime-user <RUNTIME_USER> \
|
||||||
--repo-dir /home/backup_liot/RebuildBdd \
|
--repo-dir /home/<RUNTIME_USER>/RebuildBdd \
|
||||||
--env-name RECETTE \
|
--env-name RECETTE \
|
||||||
--pguser backup_liot \
|
--pguser <PGUSER> \
|
||||||
--pgpassword secret \
|
--pgpassword secret \
|
||||||
--dbs "sirh inventory ferme" \
|
--dbs "sirh inventory ferme" \
|
||||||
--backup-subdir bdd-recette
|
--backup-subdir bdd-recette
|
||||||
@@ -562,5 +610,3 @@ Le projet permet désormais une utilisation :
|
|||||||
* intégrée au web ;
|
* intégrée au web ;
|
||||||
|
|
||||||
avec préparation des cibles, exécution non interactive et retour JSON.
|
avec préparation des cibles, exécution non interactive et retour JSON.
|
||||||
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ to_bool_yes_no() {
|
|||||||
v="${v,,}"
|
v="${v,,}"
|
||||||
case "$v" in
|
case "$v" in
|
||||||
yes|y|oui|o|true|1) echo "yes" ;;
|
yes|y|oui|o|true|1) echo "yes" ;;
|
||||||
|
# Valeur vide traitée comme "no" pour conserver le comportement historique.
|
||||||
no|n|non|false|0|"") echo "no" ;;
|
no|n|non|false|0|"") echo "no" ;;
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -221,6 +222,14 @@ if [[ -n "$TARGET_REPO_SUBDIR" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for critical_dir in "$TARGET_CLONE_DIR" "$TARGET_SCRIPT_DIR" "$TARGET_REPO_DIR"; do
|
||||||
|
[[ -n "$critical_dir" ]] || fail "répertoire critique vide"
|
||||||
|
[[ "$critical_dir" != "/" ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
[[ "$critical_dir" != "/root" ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
[[ "$critical_dir" != "/home" ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
[[ ! "$critical_dir" =~ ^/home/[^/]+$ ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
done
|
||||||
|
|
||||||
[[ -n "$TARGET_ENV_NAME_VALUE" ]] || fail "TARGET_ENV_NAME manquante"
|
[[ -n "$TARGET_ENV_NAME_VALUE" ]] || fail "TARGET_ENV_NAME manquante"
|
||||||
[[ -n "$TARGET_PGHOST_VALUE" ]] || fail "TARGET_PGHOST/GLOBAL_PGHOST manquant"
|
[[ -n "$TARGET_PGHOST_VALUE" ]] || fail "TARGET_PGHOST/GLOBAL_PGHOST manquant"
|
||||||
[[ -n "$TARGET_PGPORT_VALUE" ]] || fail "TARGET_PGPORT/GLOBAL_PGPORT manquant"
|
[[ -n "$TARGET_PGPORT_VALUE" ]] || fail "TARGET_PGPORT/GLOBAL_PGPORT manquant"
|
||||||
@@ -258,7 +267,7 @@ SSH_OPTS=(
|
|||||||
-p "$BOOTSTRAP_PORT"
|
-p "$BOOTSTRAP_PORT"
|
||||||
-o IdentitiesOnly=yes
|
-o IdentitiesOnly=yes
|
||||||
-o BatchMode=yes
|
-o BatchMode=yes
|
||||||
-o StrictHostKeyChecking=accept-new
|
-o StrictHostKeyChecking=yes
|
||||||
-o ConnectTimeout=8
|
-o ConnectTimeout=8
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -339,6 +348,7 @@ BACKUP_LOG_DIR=$(shell_quote "$TARGET_BACKUP_LOG_DIR_VALUE")
|
|||||||
LOCAL_RESTORE_BASE_DIR=$(shell_quote "$TARGET_LOCAL_RESTORE_BASE_DIR_VALUE")
|
LOCAL_RESTORE_BASE_DIR=$(shell_quote "$TARGET_LOCAL_RESTORE_BASE_DIR_VALUE")
|
||||||
REMOTE_ROLES_DIR_NAME=$(shell_quote "$TARGET_REMOTE_ROLES_DIR_NAME_VALUE")
|
REMOTE_ROLES_DIR_NAME=$(shell_quote "$TARGET_REMOTE_ROLES_DIR_NAME_VALUE")
|
||||||
SSH_KEY=$(shell_quote "$TARGET_SSH_KEY_VALUE")
|
SSH_KEY=$(shell_quote "$TARGET_SSH_KEY_VALUE")
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT=$(shell_quote "$TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE")
|
||||||
|
|
||||||
AUTO_INSTALL_POSTGRES=$(shell_quote "$TARGET_AUTO_INSTALL_POSTGRES_VALUE")
|
AUTO_INSTALL_POSTGRES=$(shell_quote "$TARGET_AUTO_INSTALL_POSTGRES_VALUE")
|
||||||
AUTO_CREATE_PGUSER=$(shell_quote "$TARGET_AUTO_CREATE_PGUSER_VALUE")
|
AUTO_CREATE_PGUSER=$(shell_quote "$TARGET_AUTO_CREATE_PGUSER_VALUE")
|
||||||
@@ -376,6 +386,13 @@ log "Correction des permissions SSH côté cible"
|
|||||||
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SSH_PERMS_CMD" \
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_SSH_PERMS_CMD" \
|
||||||
|| fail "échec de correction des permissions SSH sur la cible"
|
|| fail "échec de correction des permissions SSH sur la cible"
|
||||||
|
|
||||||
|
STRICT_OPTION="yes"
|
||||||
|
case "${TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE,,}" in
|
||||||
|
yes|y|oui|o|true|1) STRICT_OPTION="yes" ;;
|
||||||
|
no|n|non|false|0) STRICT_OPTION="no" ;;
|
||||||
|
*) fail "TARGET_BACKUP_KNOWN_HOSTS_STRICT invalide" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
REMOTE_KNOWN_HOSTS_CMD="
|
REMOTE_KNOWN_HOSTS_CMD="
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -385,6 +402,10 @@ if ! command -v ssh-keyscan >/dev/null 2>&1; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! ssh-keygen -F $(shell_quote "$TARGET_BACKUP_REMOTE_HOST_VALUE") -f $(shell_quote "$REMOTE_KNOWN_HOSTS") >/dev/null 2>&1; then
|
if ! ssh-keygen -F $(shell_quote "$TARGET_BACKUP_REMOTE_HOST_VALUE") -f $(shell_quote "$REMOTE_KNOWN_HOSTS") >/dev/null 2>&1; then
|
||||||
|
if [[ $(shell_quote "$STRICT_OPTION") == yes ]]; then
|
||||||
|
echo 'hôte backup absent de known_hosts en mode strict ; empreinte à provisionner manuellement' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
ssh-keyscan -p $(shell_quote "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE") -H $(shell_quote "$TARGET_BACKUP_REMOTE_HOST_VALUE") >> $(shell_quote "$REMOTE_KNOWN_HOSTS") 2>/dev/null
|
ssh-keyscan -p $(shell_quote "$TARGET_BACKUP_REMOTE_SSH_PORT_VALUE") -H $(shell_quote "$TARGET_BACKUP_REMOTE_HOST_VALUE") >> $(shell_quote "$REMOTE_KNOWN_HOSTS") 2>/dev/null
|
||||||
fi
|
fi
|
||||||
"
|
"
|
||||||
@@ -393,13 +414,6 @@ log "Ajout du serveur de backup dans known_hosts côté cible"
|
|||||||
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_KNOWN_HOSTS_CMD" \
|
ssh "${SSH_OPTS[@]}" "$REMOTE" "$REMOTE_KNOWN_HOSTS_CMD" \
|
||||||
|| fail "échec de préparation known_hosts sur la cible"
|
|| fail "échec de préparation known_hosts sur la cible"
|
||||||
|
|
||||||
STRICT_OPTION="yes"
|
|
||||||
case "${TARGET_BACKUP_KNOWN_HOSTS_STRICT_VALUE,,}" in
|
|
||||||
yes|y|oui|o|true|1) STRICT_OPTION="yes" ;;
|
|
||||||
no|n|non|false|0) STRICT_OPTION="no" ;;
|
|
||||||
*) fail "TARGET_BACKUP_KNOWN_HOSTS_STRICT invalide" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
REMOTE_BACKUP_TEST_CMD="
|
REMOTE_BACKUP_TEST_CMD="
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -488,6 +502,10 @@ REMOTE_REPO_CMD="
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [[ ! -d $(shell_quote "${TARGET_CLONE_DIR}/.git") ]]; then
|
if [[ ! -d $(shell_quote "${TARGET_CLONE_DIR}/.git") ]]; then
|
||||||
|
if [[ $(shell_quote "$TARGET_CLONE_DIR") == / || $(shell_quote "$TARGET_CLONE_DIR") == /root || $(shell_quote "$TARGET_CLONE_DIR") == /home || $(shell_quote "$TARGET_CLONE_DIR") =~ ^/home/[^/]+$ ]]; then
|
||||||
|
echo 'TARGET_CLONE_DIR dangereux refusé' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
rm -rf $(shell_quote "$TARGET_CLONE_DIR")
|
rm -rf $(shell_quote "$TARGET_CLONE_DIR")
|
||||||
git clone --branch $(shell_quote "$TARGET_REPO_BRANCH") --single-branch $(shell_quote "$TARGET_REPO_URL") $(shell_quote "$TARGET_CLONE_DIR")
|
git clone --branch $(shell_quote "$TARGET_REPO_BRANCH") --single-branch $(shell_quote "$TARGET_REPO_URL") $(shell_quote "$TARGET_CLONE_DIR")
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ to_bool_yes_no() {
|
|||||||
v="${v,,}"
|
v="${v,,}"
|
||||||
case "$v" in
|
case "$v" in
|
||||||
yes|y|oui|o|true|1) echo "yes" ;;
|
yes|y|oui|o|true|1) echo "yes" ;;
|
||||||
|
# Valeur vide traitée comme "no" pour conserver le comportement historique.
|
||||||
no|n|non|false|0|"") echo "no" ;;
|
no|n|non|false|0|"") echo "no" ;;
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ print_stdout() {
|
|||||||
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
|
[[ "$JSON_ONLY" == "yes" ]] || echo "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_FILE=/dev/stderr
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||||
echo "$msg" >>"$LOG_FILE"
|
echo "$msg" >>"$LOG_FILE"
|
||||||
@@ -95,6 +97,10 @@ fail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require_cmd() {
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
has_cmd() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
command -v "$1" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +125,7 @@ to_bool_yes_no() {
|
|||||||
v="${v,,}"
|
v="${v,,}"
|
||||||
case "$v" in
|
case "$v" in
|
||||||
yes|y|oui|o|true|1) echo "yes" ;;
|
yes|y|oui|o|true|1) echo "yes" ;;
|
||||||
|
# Valeur vide traitée comme "no" pour conserver le comportement historique.
|
||||||
no|n|non|false|0|"") echo "no" ;;
|
no|n|non|false|0|"") echo "no" ;;
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -134,6 +141,13 @@ sql_escape_literal() {
|
|||||||
printf "%s" "$s"
|
printf "%s" "$s"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_db_name() {
|
||||||
|
local db_name="${1:-}"
|
||||||
|
|
||||||
|
[[ -n "$db_name" ]] || return 1
|
||||||
|
[[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
build_excluded_roles_regex() {
|
build_excluded_roles_regex() {
|
||||||
local roles_string="${1:-}"
|
local roles_string="${1:-}"
|
||||||
local role
|
local role
|
||||||
@@ -165,6 +179,22 @@ build_excluded_roles_regex() {
|
|||||||
printf '%s' "$joined"
|
printf '%s' "$joined"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
send_discord() {
|
||||||
|
local message="$1"
|
||||||
|
local payload=""
|
||||||
|
|
||||||
|
[[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
|
||||||
|
has_cmd jq || return 0
|
||||||
|
has_cmd curl || return 0
|
||||||
|
|
||||||
|
payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
|
||||||
|
|
||||||
|
curl -fsS "$DISCORD_WEBHOOK_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" \
|
||||||
|
>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm -f \
|
rm -f \
|
||||||
"${LOCAL_DB_DUMP_FILE:-}" \
|
"${LOCAL_DB_DUMP_FILE:-}" \
|
||||||
@@ -220,6 +250,7 @@ RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
|
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
|
||||||
|
[[ "$ENV_NAME" =~ ^[a-zA-Z0-9_-]+$ ]] || fail "ENV_NAME invalide"
|
||||||
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
|
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
|
||||||
|
|
||||||
mkdir -p "$BACKUP_LOG_DIR" || {
|
mkdir -p "$BACKUP_LOG_DIR" || {
|
||||||
@@ -248,7 +279,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for cmd in ssh scp psql pg_restore createdb dropdb python3 grep sed find basename curl; do
|
for cmd in ssh scp psql pg_restore createdb dropdb python3 grep sed find basename curl; do
|
||||||
require_cmd "$cmd" || fail "commande requise absente : $cmd"
|
require_cmd "$cmd"
|
||||||
done
|
done
|
||||||
|
|
||||||
CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh"
|
CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh"
|
||||||
@@ -260,6 +291,7 @@ fi
|
|||||||
|
|
||||||
[[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY"
|
[[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY"
|
||||||
[[ -r "$SSH_KEY" ]] || fail "clé SSH source backup non lisible : $SSH_KEY"
|
[[ -r "$SSH_KEY" ]] || fail "clé SSH source backup non lisible : $SSH_KEY"
|
||||||
|
[[ ! -L "$SSH_KEY" ]] || fail "clé SSH source backup ne doit pas être un lien symbolique : $SSH_KEY"
|
||||||
|
|
||||||
export PGPASSWORD
|
export PGPASSWORD
|
||||||
|
|
||||||
@@ -315,7 +347,7 @@ for candidate in "${DBS_ARRAY[@]}"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
[[ -n "$DB" ]] || fail "base refusée : non présente dans DBS"
|
[[ -n "$DB" ]] || fail "base refusée : non présente dans DBS"
|
||||||
[[ "$DB" =~ ^[a-zA-Z0-9_]+$ ]] || fail "nom de base invalide"
|
validate_db_name "$DB" || fail "nom de base invalide"
|
||||||
|
|
||||||
log "Environnement : $ENV_NAME"
|
log "Environnement : $ENV_NAME"
|
||||||
log "Base cible : $DB"
|
log "Base cible : $DB"
|
||||||
@@ -475,27 +507,13 @@ pg_restore \
|
|||||||
"$LOCAL_DB_DUMP_FILE" \
|
"$LOCAL_DB_DUMP_FILE" \
|
||||||
>>"$LOG_FILE" 2>&1 || fail "échec restauration base ${DB}"
|
>>"$LOG_FILE" 2>&1 || fail "échec restauration base ${DB}"
|
||||||
|
|
||||||
send_discord_message() {
|
|
||||||
local message="$1"
|
|
||||||
local payload=""
|
|
||||||
|
|
||||||
[[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
|
|
||||||
|
|
||||||
payload="$(python3 -c 'import json,sys; print(json.dumps({"content": sys.argv[1]}))' "$message")" || return 0
|
|
||||||
|
|
||||||
curl -sS -X POST "$DISCORD_WEBHOOK_URL" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$payload" \
|
|
||||||
>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
SUCCESS_MESSAGE="✅ REBUILD BDD ${ENV_NAME}
|
SUCCESS_MESSAGE="✅ REBUILD BDD ${ENV_NAME}
|
||||||
Base restaurée : ${DB}
|
Base restaurée : ${DB}
|
||||||
Hôte PostgreSQL : ${PGHOST}:${PGPORT}
|
Hôte PostgreSQL : ${PGHOST}:${PGPORT}
|
||||||
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
|
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
|
||||||
Log : ${LOG_FILE}"
|
Log : ${LOG_FILE}"
|
||||||
|
|
||||||
send_discord_message "$SUCCESS_MESSAGE"
|
send_discord "$SUCCESS_MESSAGE"
|
||||||
|
|
||||||
log "Restauration terminée avec succès pour ${DB}"
|
log "Restauration terminée avec succès pour ${DB}"
|
||||||
print_json_and_exit "success" "restauration terminée avec succès" 0
|
print_json_and_exit "success" "restauration terminée avec succès" 0
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ to_bool_yes_no() {
|
|||||||
v="${v,,}"
|
v="${v,,}"
|
||||||
case "$v" in
|
case "$v" in
|
||||||
yes|y|oui|o|true|1) echo "yes" ;;
|
yes|y|oui|o|true|1) echo "yes" ;;
|
||||||
|
# Valeur vide traitée comme "no" pour conserver le comportement historique.
|
||||||
no|n|non|false|0|"") echo "no" ;;
|
no|n|non|false|0|"") echo "no" ;;
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -123,6 +124,10 @@ REQUESTED_DB="${CLI_DB:-${REQUESTED_DB:-}}"
|
|||||||
ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}"
|
ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}"
|
||||||
RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}"
|
RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}"
|
||||||
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-$(date '+%Y%m%d%H%M%S')_$RANDOM}}"
|
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-$(date '+%Y%m%d%H%M%S')_$RANDOM}}"
|
||||||
|
LOG_DIR="${RUN_REBUILD_BDD_LOG_DIR:-${SCRIPT_DIR}/logs}"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
LOG_FILE="${LOG_DIR}/run_rebuild_bdd_${REQUEST_ID}.log"
|
||||||
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
|
|
||||||
ALLOW_OVERWRITE="$(to_bool_yes_no "$ALLOW_OVERWRITE_RAW")" || fail "ALLOW_OVERWRITE invalide"
|
ALLOW_OVERWRITE="$(to_bool_yes_no "$ALLOW_OVERWRITE_RAW")" || fail "ALLOW_OVERWRITE invalide"
|
||||||
RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide"
|
RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide"
|
||||||
@@ -195,6 +200,14 @@ if [[ -n "$TARGET_REPO_SUBDIR" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for critical_dir in "$TARGET_CLONE_DIR" "$TARGET_SCRIPT_DIR" "$TARGET_REPO_DIR"; do
|
||||||
|
[[ -n "$critical_dir" ]] || fail "répertoire critique vide"
|
||||||
|
[[ "$critical_dir" != "/" ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
[[ "$critical_dir" != "/root" ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
[[ "$critical_dir" != "/home" ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
[[ ! "$critical_dir" =~ ^/home/[^/]+$ ]] || fail "répertoire critique dangereux refusé : $critical_dir"
|
||||||
|
done
|
||||||
|
|
||||||
TARGET_ENABLE_BOOTSTRAP="$(to_bool_yes_no "$TARGET_ENABLE_BOOTSTRAP")" || fail "TARGET_ENABLE_BOOTSTRAP invalide"
|
TARGET_ENABLE_BOOTSTRAP="$(to_bool_yes_no "$TARGET_ENABLE_BOOTSTRAP")" || fail "TARGET_ENABLE_BOOTSTRAP invalide"
|
||||||
|
|
||||||
BOOTSTRAP_SCRIPT_LOCAL="${SCRIPT_DIR}/bootstrap-target-host.sh"
|
BOOTSTRAP_SCRIPT_LOCAL="${SCRIPT_DIR}/bootstrap-target-host.sh"
|
||||||
@@ -261,7 +274,7 @@ SSH_OPTS=(
|
|||||||
-p "$TARGET_PORT"
|
-p "$TARGET_PORT"
|
||||||
-o IdentitiesOnly=yes
|
-o IdentitiesOnly=yes
|
||||||
-o BatchMode=yes
|
-o BatchMode=yes
|
||||||
-o StrictHostKeyChecking=accept-new
|
-o StrictHostKeyChecking=yes
|
||||||
-o ConnectTimeout=8
|
-o ConnectTimeout=8
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -294,6 +307,10 @@ mkdir -p \"\$(dirname \"\$CLONE_DIR\")\"
|
|||||||
mkdir -p \"\$(dirname \"\$REPO_DIR\")\"
|
mkdir -p \"\$(dirname \"\$REPO_DIR\")\"
|
||||||
|
|
||||||
if [[ ! -d \"\$CLONE_DIR/.git\" ]]; then
|
if [[ ! -d \"\$CLONE_DIR/.git\" ]]; then
|
||||||
|
if [[ \"\$CLONE_DIR\" == / || \"\$CLONE_DIR\" == /root || \"\$CLONE_DIR\" == /home || \"\$CLONE_DIR\" =~ ^/home/[^/]+$ ]]; then
|
||||||
|
echo '{\"status\":\"error\",\"message\":\"TARGET_CLONE_DIR dangereux refusé\"}'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
rm -rf \"\$CLONE_DIR\"
|
rm -rf \"\$CLONE_DIR\"
|
||||||
git clone --branch \"\$REPO_BRANCH\" --single-branch \"\$REPO_URL\" \"\$CLONE_DIR\" >/dev/null 2>&1
|
git clone --branch \"\$REPO_BRANCH\" --single-branch \"\$REPO_URL\" \"\$CLONE_DIR\" >/dev/null 2>&1
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -47,14 +47,13 @@ Les scripts fonctionnent indépendamment mais utilisent le même principe :
|
|||||||
|
|
||||||
Environnement Linux recommandé.
|
Environnement Linux recommandé.
|
||||||
|
|
||||||
Packages nécessaires :
|
Packages nécessaires sur Ubuntu Server :
|
||||||
|
|
||||||
```
|
```
|
||||||
postgresql-client
|
postgresql-client
|
||||||
curl
|
curl
|
||||||
jq
|
jq
|
||||||
ssh
|
openssh-client
|
||||||
scp
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Commandes PostgreSQL requises :
|
Commandes PostgreSQL requises :
|
||||||
@@ -116,11 +115,18 @@ ssh-copy-id -i ~/.ssh/id_backup_postgres.pub backup@192.168.1.50
|
|||||||
Tester la connexion sans mot de passe :
|
Tester la connexion sans mot de passe :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/id_backup_postgres backup@192.168.1.50
|
ssh -i ~/.ssh/id_backup_postgres -o StrictHostKeyChecking=yes backup@192.168.1.50
|
||||||
```
|
```
|
||||||
|
|
||||||
La connexion doit fonctionner **sans demander de mot de passe**.
|
La connexion doit fonctionner **sans demander de mot de passe**.
|
||||||
|
|
||||||
|
Provisionner aussi `known_hosts` avant le premier run :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh-keyscan -H 192.168.1.50 >> ~/.ssh/known_hosts
|
||||||
|
chmod 600 ~/.ssh/known_hosts
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Sécuriser les permissions
|
### Sécuriser les permissions
|
||||||
@@ -153,13 +159,18 @@ cp backup.env.exemple .env
|
|||||||
|
|
||||||
Puis modifier les variables.
|
Puis modifier les variables.
|
||||||
|
|
||||||
|
Variables SSH supplémentaires désormais supportées :
|
||||||
|
|
||||||
|
```
|
||||||
|
BACKUP_REMOTE_SSH_PORT
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT
|
||||||
|
BACKUP_KNOWN_HOSTS_FILE
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 5. Script : backup-bdd-recette.sh
|
# 5. Script : backup-bdd-recette.sh
|
||||||
|
|
||||||
Script :
|
|
||||||
|
|
||||||
|
|
||||||
## Objectif
|
## Objectif
|
||||||
|
|
||||||
Sauvegarder plusieurs bases PostgreSQL et transférer les dumps vers un serveur distant.
|
Sauvegarder plusieurs bases PostgreSQL et transférer les dumps vers un serveur distant.
|
||||||
@@ -205,6 +216,8 @@ Suppression des sauvegardes plus anciennes que :
|
|||||||
10 jours
|
10 jours
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Le script utilise maintenant des options SSH strictes et refuse les clés privées symboliques.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Exécution
|
## Exécution
|
||||||
@@ -217,9 +230,6 @@ Suppression des sauvegardes plus anciennes que :
|
|||||||
|
|
||||||
# 6. Script : check-statut-recette.sh
|
# 6. Script : check-statut-recette.sh
|
||||||
|
|
||||||
Script :
|
|
||||||
|
|
||||||
|
|
||||||
## Objectif
|
## Objectif
|
||||||
|
|
||||||
Vérifier la disponibilité des applications web.
|
Vérifier la disponibilité des applications web.
|
||||||
@@ -279,12 +289,40 @@ CHECK APP RECETTE 🟢
|
|||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">
|
||||||
|
<strong>EggMaster</strong>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Question 5</summary>
|
||||||
|
|
||||||
|
Quelle option demande explicitement un decodage plutot qu'un encodage ?
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Indice commande 5</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
-d
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary style="list-style: none; cursor: pointer;">Fragment 5</summary>
|
||||||
|
|
||||||
|
```text
|
||||||
|
dWJlLmNvbS93YXRjaD92PWRRdzR3OVdnWGNR
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
# 7. Script : rebuild-bdd-recette.sh
|
# 7. Script : rebuild-bdd-recette.sh
|
||||||
|
|
||||||
Script :
|
|
||||||
|
|
||||||
|
|
||||||
## Objectif
|
## Objectif
|
||||||
|
|
||||||
Restaurer une base PostgreSQL à partir d’un dump distant.
|
Restaurer une base PostgreSQL à partir d’un dump distant.
|
||||||
@@ -306,6 +344,8 @@ Le script :
|
|||||||
9. restaure la base via `pg_restore`
|
9. restaure la base via `pg_restore`
|
||||||
10. envoie une notification Discord
|
10. envoie une notification Discord
|
||||||
|
|
||||||
|
Le script supporte désormais le port SSH distant, un fichier `known_hosts` dédié et l’exclusion configurable des rôles via `EXCLUDED_RESTORE_ROLES`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sélection de la base
|
## Sélection de la base
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
umask 077
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# backup-bdd-recette.sh
|
# backup-bdd-recette.sh
|
||||||
@@ -17,7 +18,8 @@ set -euo pipefail
|
|||||||
# 6. exporte les rôles PostgreSQL ;
|
# 6. exporte les rôles PostgreSQL ;
|
||||||
# 7. dump chaque base au format personnalisé PostgreSQL ;
|
# 7. dump chaque base au format personnalisé PostgreSQL ;
|
||||||
# 8. transfère chaque fichier vers le serveur distant ;
|
# 8. transfère chaque fichier vers le serveur distant ;
|
||||||
# 9. applique une rotation distante sur 10 jours ;
|
# 9. applique une rotation distante selon BACKUP_RETENTION_DAYS
|
||||||
|
# (10 jours par défaut) ;
|
||||||
# 10. envoie un bilan sur Discord :
|
# 10. envoie un bilan sur Discord :
|
||||||
# - 1 message global si tout est OK ;
|
# - 1 message global si tout est OK ;
|
||||||
# - en cas d’erreur partielle :
|
# - en cas d’erreur partielle :
|
||||||
@@ -49,6 +51,10 @@ set +a
|
|||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
: "${ENV_NAME:?Variable ENV_NAME manquante}"
|
: "${ENV_NAME:?Variable ENV_NAME manquante}"
|
||||||
|
[[ "$ENV_NAME" =~ ^[a-zA-Z0-9_-]+$ ]] || {
|
||||||
|
echo "Variable ENV_NAME invalide : $ENV_NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
: "${PGHOST:?Variable PGHOST manquante}"
|
: "${PGHOST:?Variable PGHOST manquante}"
|
||||||
: "${PGPORT:?Variable PGPORT manquante}"
|
: "${PGPORT:?Variable PGPORT manquante}"
|
||||||
: "${PGUSER:?Variable PGUSER manquante}"
|
: "${PGUSER:?Variable PGUSER manquante}"
|
||||||
@@ -57,6 +63,10 @@ set +a
|
|||||||
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
|
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
|
||||||
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
|
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
|
||||||
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR manquante}"
|
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR manquante}"
|
||||||
|
[[ "$BACKUP_REMOTE_DIR" =~ ^[a-zA-Z0-9/_.-]+$ ]] || {
|
||||||
|
echo "Variable BACKUP_REMOTE_DIR invalide : $BACKUP_REMOTE_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
: "${SSH_KEY:?Variable SSH_KEY manquante}"
|
: "${SSH_KEY:?Variable SSH_KEY manquante}"
|
||||||
: "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}"
|
: "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}"
|
||||||
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
|
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
|
||||||
@@ -67,15 +77,82 @@ set +a
|
|||||||
|
|
||||||
read -r -a DBS_ARRAY <<< "$DBS"
|
read -r -a DBS_ARRAY <<< "$DBS"
|
||||||
|
|
||||||
|
validate_db_name() {
|
||||||
|
local db_name="$1"
|
||||||
|
|
||||||
|
[[ -n "$db_name" ]] || {
|
||||||
|
echo "ERROR: nom de base vide dans DBS" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || {
|
||||||
|
echo "ERROR: nom de base invalide dans DBS : $db_name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for DB in "${DBS_ARRAY[@]}"; do
|
||||||
|
validate_db_name "$DB"
|
||||||
|
done
|
||||||
|
|
||||||
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
|
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
|
||||||
IA_BASE_DIR="${BACKUP_REMOTE_DIR}"
|
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-10}"
|
||||||
RETENTION_DAYS=10
|
BACKUP_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT="${BACKUP_KNOWN_HOSTS_STRICT:-yes}"
|
||||||
|
BACKUP_KNOWN_HOSTS_FILE="${BACKUP_KNOWN_HOSTS_FILE:-${HOME}/.ssh/known_hosts}"
|
||||||
|
|
||||||
|
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: BACKUP_REMOTE_SSH_PORT invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$PGPORT" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: PGPORT invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$SSH_TIMEOUT" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: SSH_TIMEOUT invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || {
|
||||||
|
echo "ERROR: PGUSER invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${BACKUP_KNOWN_HOSTS_STRICT,,}" in
|
||||||
|
yes|y|oui|o|true|1) BACKUP_KNOWN_HOSTS_STRICT="yes" ;;
|
||||||
|
no|n|non|false|0) BACKUP_KNOWN_HOSTS_STRICT="no" ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: BACKUP_KNOWN_HOSTS_STRICT invalide" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$BACKUP_KNOWN_HOSTS_FILE")"
|
||||||
|
chmod 700 "$(dirname "$BACKUP_KNOWN_HOSTS_FILE")" || true
|
||||||
|
touch "$BACKUP_KNOWN_HOSTS_FILE"
|
||||||
|
chmod 600 "$BACKUP_KNOWN_HOSTS_FILE" || true
|
||||||
|
|
||||||
SSH_OPTS=(
|
SSH_OPTS=(
|
||||||
-i "$SSH_KEY"
|
-i "$SSH_KEY"
|
||||||
|
-p "$BACKUP_REMOTE_SSH_PORT"
|
||||||
-o IdentitiesOnly=yes
|
-o IdentitiesOnly=yes
|
||||||
-o BatchMode=yes
|
-o BatchMode=yes
|
||||||
-o ConnectTimeout="${SSH_TIMEOUT}"
|
-o ConnectTimeout="${SSH_TIMEOUT}"
|
||||||
|
-o StrictHostKeyChecking="${BACKUP_KNOWN_HOSTS_STRICT}"
|
||||||
|
-o UserKnownHostsFile="${BACKUP_KNOWN_HOSTS_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
SCP_OPTS=(
|
||||||
|
-i "$SSH_KEY"
|
||||||
|
-P "$BACKUP_REMOTE_SSH_PORT"
|
||||||
|
-o IdentitiesOnly=yes
|
||||||
|
-o BatchMode=yes
|
||||||
|
-o ConnectTimeout="${SSH_TIMEOUT}"
|
||||||
|
-o StrictHostKeyChecking="${BACKUP_KNOWN_HOSTS_STRICT}"
|
||||||
|
-o UserKnownHostsFile="${BACKUP_KNOWN_HOSTS_FILE}"
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG_DIR="${BACKUP_LOG_DIR}"
|
LOG_DIR="${BACKUP_LOG_DIR}"
|
||||||
@@ -85,15 +162,32 @@ TS="$(date +'%Y-%m-%d_%H-%M-%S')"
|
|||||||
BACKUP_DIR_NAME="backup_${TS}"
|
BACKUP_DIR_NAME="backup_${TS}"
|
||||||
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
|
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
|
||||||
|
|
||||||
TMP_DIR="/tmp/pg_dump_${BACKUP_DIR_NAME}"
|
|
||||||
mkdir -p "$TMP_DIR"
|
|
||||||
|
|
||||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
|
|
||||||
log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; }
|
TMP_DIR="$(mktemp -d /tmp/pg_dump_XXXXXX)" || {
|
||||||
|
echo "ERROR: impossible de créer le dossier temporaire" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
log "ERROR: $*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
require_cmd() {
|
require_cmd() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
command -v "$1" >/dev/null 2>&1 || fail "commande manquante : $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_remove_dir() {
|
||||||
|
local dir="${1:-}"
|
||||||
|
[[ -n "$dir" ]] || return 0
|
||||||
|
[[ "$dir" == /tmp/pg_dump_* ]] || {
|
||||||
|
log "WARNING: suppression refusée pour le chemin inattendu : $dir"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
rm -rf -- "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
export PGPASSWORD
|
export PGPASSWORD
|
||||||
@@ -102,13 +196,27 @@ export PGPASSWORD
|
|||||||
# Vérification dépendances minimales
|
# Vérification dépendances minimales
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
for cmd in ssh scp curl jq pg_dump pg_dumpall; do
|
for cmd in ssh scp curl jq pg_dump pg_dumpall mktemp; do
|
||||||
require_cmd "$cmd" || {
|
require_cmd "$cmd"
|
||||||
echo "ERROR: commande manquante : $cmd" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
done
|
done
|
||||||
|
|
||||||
|
[[ -f "$SSH_KEY" ]] || {
|
||||||
|
echo "ERROR: clé SSH introuvable : $SSH_KEY" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -r "$SSH_KEY" ]] || {
|
||||||
|
echo "ERROR: clé SSH non lisible : $SSH_KEY" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ ! -L "$SSH_KEY" ]] || {
|
||||||
|
echo "ERROR: la clé SSH ne doit pas être un lien symbolique : $SSH_KEY" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
chmod 600 "$SSH_KEY" || true
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Configuration Discord
|
# Configuration Discord
|
||||||
#######################################
|
#######################################
|
||||||
@@ -116,14 +224,14 @@ done
|
|||||||
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
|
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
|
||||||
DISCORD_PING="${DISCORD_PING:-@here}"
|
DISCORD_PING="${DISCORD_PING:-@here}"
|
||||||
|
|
||||||
discord_send() {
|
send_discord() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
|
local payload
|
||||||
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
|
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
|
||||||
|
|
||||||
local payload
|
|
||||||
payload="$(jq -n --arg content "$msg" '{content: $content}')" || {
|
payload="$(jq -n --arg content "$msg" '{content: $content}')" || {
|
||||||
log "ERROR: impossible de construire le payload JSON Discord"
|
log "ERROR: impossible de construire le payload JSON Discord"
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
curl -fsS \
|
curl -fsS \
|
||||||
@@ -145,7 +253,7 @@ Dumps transfer: ✅
|
|||||||
Users transfer: ✅
|
Users transfer: ✅
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
discord_send "$msg"
|
send_discord "$msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
@@ -159,7 +267,7 @@ discord_msg_users_ok_simple() {
|
|||||||
Users backup validé
|
Users backup validé
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
discord_send "$msg"
|
send_discord "$msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
discord_msg_users_error() {
|
discord_msg_users_error() {
|
||||||
@@ -191,7 +299,7 @@ EOF
|
|||||||
)"
|
)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
discord_send "$msg"
|
send_discord "$msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
@@ -206,7 +314,7 @@ discord_msg_db_ok_simple() {
|
|||||||
Backup validé : ${db}
|
Backup validé : ${db}
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
discord_send "$msg"
|
send_discord "$msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
discord_msg_db_error() {
|
discord_msg_db_error() {
|
||||||
@@ -241,7 +349,7 @@ EOF
|
|||||||
)"
|
)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
discord_send "$msg"
|
send_discord "$msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
@@ -264,26 +372,53 @@ declare -A DB_DETAILS
|
|||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d"
|
LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d"
|
||||||
|
LOCK_PID_FILE="${LOCK_DIR}/pid"
|
||||||
|
|
||||||
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
||||||
log "ERROR: Backup déjà en cours"
|
stale_lock="no"
|
||||||
discord_msg_users_error "" "" "Lock already exists"
|
existing_pid=""
|
||||||
exit 1
|
|
||||||
|
if [[ -f "$LOCK_PID_FILE" ]]; then
|
||||||
|
existing_pid="$(<"$LOCK_PID_FILE")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$existing_pid" =~ ^[0-9]+$ ]] && kill -0 "$existing_pid" 2>/dev/null; then
|
||||||
|
log "ERROR: Backup déjà en cours (PID ${existing_pid})"
|
||||||
|
discord_msg_users_error "" "" "Lock already exists (PID ${existing_pid})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
stale_lock="yes"
|
||||||
|
log "WARNING: lock périmé détecté, nettoyage en cours"
|
||||||
|
rm -rf -- "$LOCK_DIR"
|
||||||
|
|
||||||
|
mkdir "$LOCK_DIR" 2>/dev/null || fail "impossible de recréer le lock après nettoyage"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
trap 'rm -rf "$LOCK_DIR" "$TMP_DIR"' EXIT
|
echo $$ > "$LOCK_PID_FILE" || {
|
||||||
|
rm -rf -- "$LOCK_DIR"
|
||||||
|
fail "impossible d'écrire le PID du lock"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${stale_lock:-no}" == "yes" ]]; then
|
||||||
|
log "Lock périmé nettoyé."
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf -- "$LOCK_DIR"
|
||||||
|
safe_remove_dir "$TMP_DIR" || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Préparation du dossier distant
|
# Préparation du dossier distant
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
REMOTE_DIR="${IA_BASE_DIR}"
|
|
||||||
|
|
||||||
log "Creating remote directories"
|
log "Creating remote directories"
|
||||||
|
|
||||||
MKDIR_CMD="mkdir -p '${REMOTE_DIR}/user'"
|
MKDIR_CMD="mkdir -p '${BACKUP_REMOTE_DIR}/user'"
|
||||||
for DB in "${DBS_ARRAY[@]}"; do
|
for DB in "${DBS_ARRAY[@]}"; do
|
||||||
MKDIR_CMD+=" '${REMOTE_DIR}/${DB}'"
|
MKDIR_CMD+=" '${BACKUP_REMOTE_DIR}/${DB}'"
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "$MKDIR_CMD"; then
|
if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "$MKDIR_CMD"; then
|
||||||
@@ -298,18 +433,18 @@ fi
|
|||||||
|
|
||||||
ROLES_FILE="${TMP_DIR}/user_${TS}.sql"
|
ROLES_FILE="${TMP_DIR}/user_${TS}.sql"
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
log "Export des rôles PostgreSQL"
|
log "Export des rôles PostgreSQL"
|
||||||
|
|
||||||
pg_dumpall \
|
if pg_dumpall \
|
||||||
-h "$PGHOST" \
|
-h "$PGHOST" \
|
||||||
-p "$PGPORT" \
|
-p "$PGPORT" \
|
||||||
-U "$PGUSER" \
|
-U "$PGUSER" \
|
||||||
--globals-only \
|
--globals-only \
|
||||||
> "$ROLES_FILE"
|
> "$ROLES_FILE"; then
|
||||||
|
RET=0
|
||||||
RET=$?
|
else
|
||||||
|
RET=$?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $RET -ne 0 ]]; then
|
if [[ $RET -ne 0 ]]; then
|
||||||
USERS_OK=
|
USERS_OK=
|
||||||
@@ -320,8 +455,11 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
|
if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
|
||||||
scp "${SSH_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${REMOTE_DIR}/user/"
|
if scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/"; then
|
||||||
RET=$?
|
RET=0
|
||||||
|
else
|
||||||
|
RET=$?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $RET -ne 0 ]]; then
|
if [[ $RET -ne 0 ]]; then
|
||||||
USERS_OK=
|
USERS_OK=
|
||||||
@@ -336,14 +474,10 @@ if [[ -n "${USERS_EXPORT_OK:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Dump des bases
|
# Dump des bases
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
for DB in "${DBS_ARRAY[@]}"; do
|
for DB in "${DBS_ARRAY[@]}"; do
|
||||||
FILE="${TMP_DIR}/${DB}_${TS}.dump"
|
FILE="${TMP_DIR}/${DB}_${TS}.dump"
|
||||||
|
|
||||||
@@ -353,8 +487,11 @@ for DB in "${DBS_ARRAY[@]}"; do
|
|||||||
|
|
||||||
log "Dump $DB"
|
log "Dump $DB"
|
||||||
|
|
||||||
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"
|
if pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"; then
|
||||||
RET=$?
|
RET=0
|
||||||
|
else
|
||||||
|
RET=$?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $RET -ne 0 ]]; then
|
if [[ $RET -ne 0 ]]; then
|
||||||
DUMPS_OK=
|
DUMPS_OK=
|
||||||
@@ -364,8 +501,11 @@ for DB in "${DBS_ARRAY[@]}"; do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
scp "${SSH_OPTS[@]}" "$FILE" "$IA_SSH:${REMOTE_DIR}/${DB}/"
|
if scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/"; then
|
||||||
RET=$?
|
RET=0
|
||||||
|
else
|
||||||
|
RET=$?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $RET -ne 0 ]]; then
|
if [[ $RET -ne 0 ]]; then
|
||||||
DUMPS_OK=
|
DUMPS_OK=
|
||||||
@@ -374,18 +514,17 @@ for DB in "${DBS_ARRAY[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Rotation distante
|
# Rotation distante
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
log "Starting remote rotation: delete backups older than ${RETENTION_DAYS} days"
|
log "Starting remote rotation: delete backups older than ${RETENTION_DAYS} days"
|
||||||
|
|
||||||
set +e
|
if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"; then
|
||||||
|
RET=0
|
||||||
ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"
|
else
|
||||||
RET=$?
|
RET=$?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $RET -ne 0 ]]; then
|
if [[ $RET -ne 0 ]]; then
|
||||||
log "ERROR: remote rotation failed for users"
|
log "ERROR: remote rotation failed for users"
|
||||||
@@ -394,8 +533,11 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for DB in "${DBS_ARRAY[@]}"; do
|
for DB in "${DBS_ARRAY[@]}"; do
|
||||||
ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"
|
if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"; then
|
||||||
RET=$?
|
RET=0
|
||||||
|
else
|
||||||
|
RET=$?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $RET -ne 0 ]]; then
|
if [[ $RET -ne 0 ]]; then
|
||||||
log "ERROR: remote rotation failed for ${DB}"
|
log "ERROR: remote rotation failed for ${DB}"
|
||||||
@@ -404,15 +546,13 @@ for DB in "${DBS_ARRAY[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
log "Remote rotation finished"
|
log "Remote rotation finished"
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Nettoyage local
|
# Nettoyage local
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
rm -rf "$TMP_DIR"
|
safe_remove_dir "$TMP_DIR" || true
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Bilan final Discord
|
# Bilan final Discord
|
||||||
@@ -442,4 +582,4 @@ for DB in "${DBS_ARRAY[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
exit 2
|
exit 2
|
||||||
|
|||||||
@@ -44,9 +44,18 @@ BACKUP_REMOTE_DIR=/home/.../backups/bdd-recette
|
|||||||
# Clé SSH utilisée pour se connecter au serveur distant
|
# Clé SSH utilisée pour se connecter au serveur distant
|
||||||
SSH_KEY=/home/.../.ssh/id_ed25519_backup
|
SSH_KEY=/home/.../.ssh/id_ed25519_backup
|
||||||
|
|
||||||
|
# Port SSH du serveur de backup
|
||||||
|
BACKUP_REMOTE_SSH_PORT=22
|
||||||
|
|
||||||
# Timeout de connexion SSH (secondes)
|
# Timeout de connexion SSH (secondes)
|
||||||
SSH_TIMEOUT=10
|
SSH_TIMEOUT=10
|
||||||
|
|
||||||
|
# Validation stricte des clés hôtes SSH (yes/no)
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT=yes
|
||||||
|
|
||||||
|
# Fichier known_hosts utilisé par ssh/scp
|
||||||
|
BACKUP_KNOWN_HOSTS_FILE=/home/.../.ssh/known_hosts
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# LOGS
|
# LOGS
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -62,4 +71,4 @@ BACKUP_LOG_DIR=/var/log/script/
|
|||||||
DISCORD_WEBHOOK_URL=
|
DISCORD_WEBHOOK_URL=
|
||||||
|
|
||||||
# Mention envoyée en cas d'erreur
|
# Mention envoyée en cas d'erreur
|
||||||
DISCORD_PING=@here
|
DISCORD_PING=@here
|
||||||
|
|||||||
@@ -44,13 +44,23 @@ set +a
|
|||||||
: "${CHECK_MAX_TIME:?Variable CHECK_MAX_TIME manquante}"
|
: "${CHECK_MAX_TIME:?Variable CHECK_MAX_TIME manquante}"
|
||||||
: "${APP_URLS:?Variable APP_URLS manquante}"
|
: "${APP_URLS:?Variable APP_URLS manquante}"
|
||||||
|
|
||||||
|
[[ "$CHECK_CONNECT_TIMEOUT" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: Variable CHECK_CONNECT_TIMEOUT invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$CHECK_MAX_TIME" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "ERROR: Variable CHECK_MAX_TIME invalide" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Sites à vérifier
|
# Sites à vérifier
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
read -r -a SITES <<< "$APP_URLS"
|
read -r -a SITES <<< "$APP_URLS"
|
||||||
|
|
||||||
SCHEME="http"
|
SCHEME="${APP_SCHEME:-http}"
|
||||||
CONNECT_TIMEOUT="${CHECK_CONNECT_TIMEOUT}"
|
CONNECT_TIMEOUT="${CHECK_CONNECT_TIMEOUT}"
|
||||||
MAX_TIME="${CHECK_MAX_TIME}"
|
MAX_TIME="${CHECK_MAX_TIME}"
|
||||||
|
|
||||||
@@ -75,6 +85,16 @@ DISCORD_PING="${DISCORD_PING:-@here}"
|
|||||||
|
|
||||||
SUMMARY_LINES=()
|
SUMMARY_LINES=()
|
||||||
FAILURES=0
|
FAILURES=0
|
||||||
|
TMPFILES=()
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local tmpfile
|
||||||
|
for tmpfile in "${TMPFILES[@]}"; do
|
||||||
|
[[ -n "$tmpfile" ]] || continue
|
||||||
|
rm -f -- "$tmpfile"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Logging
|
# Logging
|
||||||
@@ -115,8 +135,21 @@ add_summary_line() {
|
|||||||
#######################################
|
#######################################
|
||||||
# Envoi du message Discord récapitulatif
|
# Envoi du message Discord récapitulatif
|
||||||
#######################################
|
#######################################
|
||||||
send_discord_summary() {
|
should_send_discord() {
|
||||||
|
if [[ "$FAILURES" -gt 0 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current_hour current_minute
|
||||||
|
current_hour="$(date +'%H')"
|
||||||
|
current_minute="$(date +'%M')"
|
||||||
|
|
||||||
|
[[ "$current_hour" == "19" && "$current_minute" -ge 0 && "$current_minute" -le 4 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
send_discord() {
|
||||||
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
|
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0
|
||||||
|
should_send_discord || return 0
|
||||||
|
|
||||||
local header_icon ping_prefix=""
|
local header_icon ping_prefix=""
|
||||||
if [[ "$FAILURES" -eq 0 ]]; then
|
if [[ "$FAILURES" -eq 0 ]]; then
|
||||||
@@ -134,7 +167,7 @@ send_discord_summary() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
local payload
|
local payload
|
||||||
payload="$(jq -n --arg content "$msg" '{content: $content}')"
|
payload="$(jq -n --arg content "$msg" '{content: $content}')" || return 0
|
||||||
|
|
||||||
curl -fsS -H "Content-Type: application/json" \
|
curl -fsS -H "Content-Type: application/json" \
|
||||||
-d "$payload" \
|
-d "$payload" \
|
||||||
@@ -158,6 +191,7 @@ check_site() {
|
|||||||
local http_code curl_exit err
|
local http_code curl_exit err
|
||||||
local stderr
|
local stderr
|
||||||
stderr="$(mktemp)"
|
stderr="$(mktemp)"
|
||||||
|
TMPFILES+=("$stderr")
|
||||||
|
|
||||||
http_code="$(
|
http_code="$(
|
||||||
curl -sS -o /dev/null \
|
curl -sS -o /dev/null \
|
||||||
@@ -170,15 +204,12 @@ check_site() {
|
|||||||
|
|
||||||
if [[ "$curl_exit" -ne 0 ]]; then
|
if [[ "$curl_exit" -ne 0 ]]; then
|
||||||
err="$(head -n 1 "$stderr" | tr -d '\r')"
|
err="$(head -n 1 "$stderr" | tr -d '\r')"
|
||||||
rm -f "$stderr"
|
|
||||||
|
|
||||||
log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}"
|
log_line "DOWN" "$host" "curl exit=$curl_exit : ${err:-"(aucun)"}"
|
||||||
add_summary_line "$host" "DOWN" "DOWN - curl"
|
add_summary_line "$host" "DOWN" "DOWN - curl"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$stderr"
|
|
||||||
|
|
||||||
if [[ "$http_code" =~ ^[0-9]{3}$ ]]; then
|
if [[ "$http_code" =~ ^[0-9]{3}$ ]]; then
|
||||||
if [[ "$http_code" -ge 200 && "$http_code" -le 399 ]]; then
|
if [[ "$http_code" -ge 200 && "$http_code" -le 399 ]]; then
|
||||||
log_line "OK" "$host" "HTTP $http_code"
|
log_line "OK" "$host" "HTTP $http_code"
|
||||||
@@ -201,8 +232,6 @@ check_site() {
|
|||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
trap '[[ -n "$STDERR_TMP" ]] && rm -f "$STDERR_TMP"' EXIT
|
|
||||||
|
|
||||||
local failures=0
|
local failures=0
|
||||||
|
|
||||||
for site in "${SITES[@]}"; do
|
for site in "${SITES[@]}"; do
|
||||||
@@ -212,7 +241,7 @@ main() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
FAILURES="$failures"
|
FAILURES="$failures"
|
||||||
send_discord_summary
|
send_discord
|
||||||
|
|
||||||
if [[ "$failures" -gt 0 ]]; then
|
if [[ "$failures" -gt 0 ]]; then
|
||||||
exit 2
|
exit 2
|
||||||
@@ -221,4 +250,4 @@ main() {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
umask 077
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# rebuild-bdd-recette.sh
|
# rebuild-bdd-recette.sh
|
||||||
@@ -49,6 +50,10 @@ set +a
|
|||||||
# Variables obligatoires
|
# Variables obligatoires
|
||||||
###############################################################################
|
###############################################################################
|
||||||
: "${ENV_NAME:?Variable ENV_NAME manquante}"
|
: "${ENV_NAME:?Variable ENV_NAME manquante}"
|
||||||
|
[[ "$ENV_NAME" =~ ^[a-zA-Z0-9_-]+$ ]] || {
|
||||||
|
echo "Variable ENV_NAME invalide : $ENV_NAME" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
: "${PGHOST:?Variable PGHOST manquante}"
|
: "${PGHOST:?Variable PGHOST manquante}"
|
||||||
: "${PGPORT:?Variable PGPORT manquante}"
|
: "${PGPORT:?Variable PGPORT manquante}"
|
||||||
: "${PGUSER:?Variable PGUSER manquante}"
|
: "${PGUSER:?Variable PGUSER manquante}"
|
||||||
@@ -65,8 +70,10 @@ set +a
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
LOCAL_RESTORE_DIR="${LOCAL_RESTORE_DIR:-${SCRIPT_DIR}/restore_tmp}"
|
LOCAL_RESTORE_DIR="${LOCAL_RESTORE_DIR:-${SCRIPT_DIR}/restore_tmp}"
|
||||||
REMOTE_ROLES_DIR_NAME="${REMOTE_ROLES_DIR_NAME:-user}"
|
REMOTE_ROLES_DIR_NAME="${REMOTE_ROLES_DIR_NAME:-user}"
|
||||||
|
BACKUP_REMOTE_SSH_PORT="${BACKUP_REMOTE_SSH_PORT:-22}"
|
||||||
SSH_CONNECT_TIMEOUT="${SSH_CONNECT_TIMEOUT:-8}"
|
SSH_CONNECT_TIMEOUT="${SSH_CONNECT_TIMEOUT:-8}"
|
||||||
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
|
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
|
||||||
|
EXCLUDED_RESTORE_ROLES="${EXCLUDED_RESTORE_ROLES:-postgres}"
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Préparation des dossiers locaux
|
# Préparation des dossiers locaux
|
||||||
@@ -108,42 +115,66 @@ cleanup() {
|
|||||||
"${FILTERED_ROLES_FILE:-}" \
|
"${FILTERED_ROLES_FILE:-}" \
|
||||||
"${ROLES_CREATE_LIST:-}" \
|
"${ROLES_CREATE_LIST:-}" \
|
||||||
"${ROLES_APPLY_FILE:-}"
|
"${ROLES_APPLY_FILE:-}"
|
||||||
|
rm -rf "${LOCAL_RESTORE_DIR:-}" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
require_cmd() {
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
has_cmd() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
command -v "$1" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sql_escape_literal() {
|
||||||
|
local s="${1:-}"
|
||||||
|
s="${s//\'/\'\'}"
|
||||||
|
printf "%s" "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_db_name() {
|
||||||
|
local db_name="${1:-}"
|
||||||
|
|
||||||
|
[[ -n "$db_name" ]] || return 1
|
||||||
|
[[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
build_excluded_roles_regex() {
|
||||||
|
local role regex=""
|
||||||
|
|
||||||
|
for role in $EXCLUDED_RESTORE_ROLES; do
|
||||||
|
[[ -z "$role" ]] && continue
|
||||||
|
[[ "$role" =~ ^[a-zA-Z_][a-zA-Z0-9_-]*$ ]] || fail "rôle exclu invalide : ${role}"
|
||||||
|
if [[ -n "$regex" ]]; then
|
||||||
|
regex+="|"
|
||||||
|
fi
|
||||||
|
regex+="$role"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s' "$regex"
|
||||||
|
}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Envoi Discord
|
# Envoi Discord
|
||||||
#
|
#
|
||||||
# Envoi simple d'un message texte via webhook Discord.
|
# Envoi simple d'un message texte via webhook Discord.
|
||||||
# Si WEBHOOK_URL n'est pas défini, on ignore silencieusement l'envoi.
|
# Si DISCORD_WEBHOOK_URL n'est pas défini, on ignore silencieusement l'envoi.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
send_discord_message() {
|
send_discord() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
local payload=""
|
local payload=""
|
||||||
|
|
||||||
[[ -n "$DISCORD_WEBHOOK_URL" ]] || {
|
[[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0
|
||||||
log "WEBHOOK_URL non défini : notification Discord ignorée."
|
has_cmd jq || return 0
|
||||||
return 0
|
has_cmd curl || return 0
|
||||||
}
|
|
||||||
|
|
||||||
if ! require_cmd curl; then
|
payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0
|
||||||
log "curl absent : notification Discord ignorée."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
payload="$(jq -n --arg content "$message" '{content: $content}')" || {
|
curl -fsS "$DISCORD_WEBHOOK_URL" \
|
||||||
log "Impossible de construire le payload JSON Discord."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
curl -sS -X POST "$DISCORD_WEBHOOK_URL" \
|
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$payload" \
|
-d "$payload" \
|
||||||
>/dev/null || log "Échec d'envoi de la notification Discord."
|
>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -151,15 +182,29 @@ send_discord_message() {
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
[[ -f "$SSH_KEY" ]] || fail "clé SSH introuvable : $SSH_KEY"
|
[[ -f "$SSH_KEY" ]] || fail "clé SSH introuvable : $SSH_KEY"
|
||||||
[[ -r "$SSH_KEY" ]] || fail "clé SSH non lisible : $SSH_KEY"
|
[[ -r "$SSH_KEY" ]] || fail "clé SSH non lisible : $SSH_KEY"
|
||||||
|
[[ ! -L "$SSH_KEY" ]] || fail "clé SSH ne doit pas être un lien symbolique : $SSH_KEY"
|
||||||
|
[[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide"
|
||||||
|
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide"
|
||||||
|
[[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || fail "PGUSER invalide"
|
||||||
|
|
||||||
export PGPASSWORD
|
export PGPASSWORD
|
||||||
|
|
||||||
SSH_OPTS=(
|
SSH_OPTS=(
|
||||||
-i "$SSH_KEY"
|
-i "$SSH_KEY"
|
||||||
|
-p "$BACKUP_REMOTE_SSH_PORT"
|
||||||
-o IdentitiesOnly=yes
|
-o IdentitiesOnly=yes
|
||||||
-o BatchMode=yes
|
-o BatchMode=yes
|
||||||
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
|
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
|
||||||
-o StrictHostKeyChecking=accept-new
|
-o StrictHostKeyChecking=yes
|
||||||
|
)
|
||||||
|
|
||||||
|
SCP_OPTS=(
|
||||||
|
-i "$SSH_KEY"
|
||||||
|
-P "$BACKUP_REMOTE_SSH_PORT"
|
||||||
|
-o IdentitiesOnly=yes
|
||||||
|
-o BatchMode=yes
|
||||||
|
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
|
||||||
|
-o StrictHostKeyChecking=yes
|
||||||
)
|
)
|
||||||
|
|
||||||
REMOTE_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
|
REMOTE_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
|
||||||
@@ -171,7 +216,7 @@ REMOTE_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
POSTGRES_INSTALLED=false
|
POSTGRES_INSTALLED=false
|
||||||
|
|
||||||
if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb; then
|
if ! has_cmd psql || ! has_cmd pg_restore || ! has_cmd createdb || ! has_cmd dropdb; then
|
||||||
log "PostgreSQL absent : installation en cours..."
|
log "PostgreSQL absent : installation en cours..."
|
||||||
|
|
||||||
sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update"
|
sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update"
|
||||||
@@ -198,15 +243,17 @@ fi
|
|||||||
# Attente disponibilité PostgreSQL
|
# Attente disponibilité PostgreSQL
|
||||||
###############################################################################
|
###############################################################################
|
||||||
log "Vérification de la disponibilité de PostgreSQL..."
|
log "Vérification de la disponibilité de PostgreSQL..."
|
||||||
|
PG_READY=false
|
||||||
for _ in {1..20}; do
|
for _ in {1..20}; do
|
||||||
if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
|
if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
|
||||||
|
PG_READY=true
|
||||||
log "PostgreSQL répond correctement."
|
log "PostgreSQL répond correctement."
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
|
if [[ "$PG_READY" != true ]]; then
|
||||||
fail "PostgreSQL ne répond pas correctement"
|
fail "PostgreSQL ne répond pas correctement"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -217,7 +264,7 @@ if [[ "$POSTGRES_INSTALLED" == "true" ]]; then
|
|||||||
log "Création du rôle PostgreSQL ${PGUSER} suite à une installation neuve..."
|
log "Création du rôle PostgreSQL ${PGUSER} suite à une installation neuve..."
|
||||||
|
|
||||||
sudo -u postgres psql -d postgres -c \
|
sudo -u postgres psql -d postgres -c \
|
||||||
"CREATE ROLE \"${PGUSER}\" WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${PGPASSWORD}';" \
|
"CREATE ROLE \"${PGUSER}\" WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '$(sql_escape_literal "$PGPASSWORD")';" \
|
||||||
>>"$LOG_FILE" 2>&1 || fail "échec de création du rôle ${PGUSER}"
|
>>"$LOG_FILE" 2>&1 || fail "échec de création du rôle ${PGUSER}"
|
||||||
|
|
||||||
log "Rôle PostgreSQL ${PGUSER} créé."
|
log "Rôle PostgreSQL ${PGUSER} créé."
|
||||||
@@ -251,9 +298,10 @@ if [[ "${USE_LIST,,}" == "oui" || "${USE_LIST,,}" == "o" ]]; then
|
|||||||
DB="${DBS_ARRAY[$((DB_INDEX - 1))]}"
|
DB="${DBS_ARRAY[$((DB_INDEX - 1))]}"
|
||||||
else
|
else
|
||||||
read -r -p "Nom exact de la base à restaurer : " DB
|
read -r -p "Nom exact de la base à restaurer : " DB
|
||||||
[[ -n "$DB" ]] || fail "nom de base vide"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
validate_db_name "$DB" || fail "nom de base invalide"
|
||||||
|
|
||||||
log "Environnement : $ENV_NAME"
|
log "Environnement : $ENV_NAME"
|
||||||
log "Base cible sélectionnée : $DB"
|
log "Base cible sélectionnée : $DB"
|
||||||
|
|
||||||
@@ -312,7 +360,7 @@ LOCAL_DB_DUMP_FILE="${LOCAL_RESTORE_DIR}/$(basename "$LAST_REMOTE_DB_DUMP")"
|
|||||||
LOCAL_ROLES_FILE=""
|
LOCAL_ROLES_FILE=""
|
||||||
|
|
||||||
log "Téléchargement du dump..."
|
log "Téléchargement du dump..."
|
||||||
scp "${SSH_OPTS[@]}" "${REMOTE_SSH}:${LAST_REMOTE_DB_DUMP}" "$LOCAL_DB_DUMP_FILE" \
|
scp "${SCP_OPTS[@]}" "${REMOTE_SSH}:${LAST_REMOTE_DB_DUMP}" "$LOCAL_DB_DUMP_FILE" \
|
||||||
>>"$LOG_FILE" 2>&1 || fail "échec du téléchargement du dump principal"
|
>>"$LOG_FILE" 2>&1 || fail "échec du téléchargement du dump principal"
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -322,7 +370,7 @@ if [[ -n "$LAST_REMOTE_ROLES_FILE" ]]; then
|
|||||||
LOCAL_ROLES_FILE="${LOCAL_RESTORE_DIR}/$(basename "$LAST_REMOTE_ROLES_FILE")"
|
LOCAL_ROLES_FILE="${LOCAL_RESTORE_DIR}/$(basename "$LAST_REMOTE_ROLES_FILE")"
|
||||||
|
|
||||||
log "Téléchargement du fichier des rôles..."
|
log "Téléchargement du fichier des rôles..."
|
||||||
scp "${SSH_OPTS[@]}" "${REMOTE_SSH}:${LAST_REMOTE_ROLES_FILE}" "$LOCAL_ROLES_FILE" \
|
scp "${SCP_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"
|
>>"$LOG_FILE" 2>&1 || fail "échec du téléchargement du fichier des rôles"
|
||||||
else
|
else
|
||||||
log "La restauration des rôles sera ignorée."
|
log "La restauration des rôles sera ignorée."
|
||||||
@@ -341,7 +389,7 @@ fi
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
DB_EXISTS="$(
|
DB_EXISTS="$(
|
||||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -tAc \
|
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -tAc \
|
||||||
"SELECT 1 FROM pg_database WHERE datname='${DB}'" 2>>"$LOG_FILE" || true
|
"SELECT 1 FROM pg_database WHERE datname='$(sql_escape_literal "$DB")'" 2>>"$LOG_FILE" || true
|
||||||
)"
|
)"
|
||||||
|
|
||||||
if [[ "$DB_EXISTS" == "1" ]]; then
|
if [[ "$DB_EXISTS" == "1" ]]; then
|
||||||
@@ -364,9 +412,16 @@ if [[ -n "$LOCAL_ROLES_FILE" ]]; then
|
|||||||
FILTERED_ROLES_FILE="${LOCAL_RESTORE_DIR}/filtered_$(basename "$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_CREATE_LIST="${LOCAL_RESTORE_DIR}/roles_to_create_$(basename "$LOCAL_ROLES_FILE")"
|
||||||
ROLES_APPLY_FILE="${LOCAL_RESTORE_DIR}/roles_apply_$(basename "$LOCAL_ROLES_FILE")"
|
ROLES_APPLY_FILE="${LOCAL_RESTORE_DIR}/roles_apply_$(basename "$LOCAL_ROLES_FILE")"
|
||||||
|
EXCLUDED_ROLES_REGEX="$(build_excluded_roles_regex)"
|
||||||
|
|
||||||
grep -viE '^(CREATE ROLE|ALTER ROLE) (backup_liot|postgres)\b' "$LOCAL_ROLES_FILE" \
|
if [[ -n "$EXCLUDED_ROLES_REGEX" ]]; then
|
||||||
> "$FILTERED_ROLES_FILE" || true
|
grep -viE "^(CREATE ROLE|ALTER ROLE) (${EXCLUDED_ROLES_REGEX})\\b" "$LOCAL_ROLES_FILE" \
|
||||||
|
> "$FILTERED_ROLES_FILE" || true
|
||||||
|
else
|
||||||
|
cp "$LOCAL_ROLES_FILE" "$FILTERED_ROLES_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -i -E '/^ALTER ROLE .* (NO)?SUPERUSER\b/d' "$FILTERED_ROLES_FILE"
|
||||||
|
|
||||||
log "Fichier des rôles filtré généré : ${FILTERED_ROLES_FILE}"
|
log "Fichier des rôles filtré généré : ${FILTERED_ROLES_FILE}"
|
||||||
|
|
||||||
@@ -383,7 +438,7 @@ if [[ -n "$LOCAL_ROLES_FILE" ]]; then
|
|||||||
|
|
||||||
ROLE_EXISTS="$(
|
ROLE_EXISTS="$(
|
||||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -tAc \
|
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -tAc \
|
||||||
"SELECT 1 FROM pg_roles WHERE rolname='${role_name}'" 2>>"$LOG_FILE" || true
|
"SELECT 1 FROM pg_roles WHERE rolname='$(sql_escape_literal "$role_name")'" 2>>"$LOG_FILE" || true
|
||||||
)"
|
)"
|
||||||
|
|
||||||
if [[ "$ROLE_EXISTS" != "1" ]]; then
|
if [[ "$ROLE_EXISTS" != "1" ]]; then
|
||||||
@@ -448,4 +503,4 @@ Hôte PostgreSQL : ${PGHOST}:${PGPORT}
|
|||||||
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
|
Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP")
|
||||||
Log : ${LOG_FILE}"
|
Log : ${LOG_FILE}"
|
||||||
|
|
||||||
send_discord_message "$SUCCESS_MESSAGE"
|
send_discord "$SUCCESS_MESSAGE"
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ BACKUP_REMOTE_HOST=
|
|||||||
# Répertoire racine distant :
|
# Répertoire racine distant :
|
||||||
BACKUP_REMOTE_DIR=/home/.../backups/bdd-recette
|
BACKUP_REMOTE_DIR=/home/.../backups/bdd-recette
|
||||||
|
|
||||||
|
# Port SSH du serveur de backup
|
||||||
|
BACKUP_REMOTE_SSH_PORT=22
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# SSH
|
# SSH
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -50,6 +53,12 @@ SSH_KEY=/home/.../.ssh/id_ed25519_backup
|
|||||||
# Variable optionnelle dans le script, mais utile ici comme valeur par défaut
|
# Variable optionnelle dans le script, mais utile ici comme valeur par défaut
|
||||||
SSH_CONNECT_TIMEOUT=8
|
SSH_CONNECT_TIMEOUT=8
|
||||||
|
|
||||||
|
# Validation stricte des clés hôtes SSH (yes/no)
|
||||||
|
BACKUP_KNOWN_HOSTS_STRICT=yes
|
||||||
|
|
||||||
|
# Fichier known_hosts utilisé par ssh/scp
|
||||||
|
BACKUP_KNOWN_HOSTS_FILE=/home/.../.ssh/known_hosts
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# LOGS
|
# LOGS
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -72,9 +81,12 @@ LOCAL_RESTORE_DIR=/tmp/rebuild-bdd-recette
|
|||||||
# Nom du dossier distant contenant les exports SQL des rôles
|
# Nom du dossier distant contenant les exports SQL des rôles
|
||||||
REMOTE_ROLES_DIR_NAME=user
|
REMOTE_ROLES_DIR_NAME=user
|
||||||
|
|
||||||
|
# Rôles PostgreSQL à exclure lors de la restauration
|
||||||
|
EXCLUDED_RESTORE_ROLES="postgres"
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# DISCORD
|
# DISCORD
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# Webhook Discord pour notifier le succès de la restauration
|
# Webhook Discord pour notifier le succès de la restauration
|
||||||
DISCORD_WEBHOOK_URL=
|
DISCORD_WEBHOOK_URL=
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ BACKUP_REMOTE_DIR=/home/.../backups/bdd-recette
|
|||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
# Clé SSH utilisée pour se connecter au serveur distant
|
# Clé SSH utilisée pour se connecter au serveur distant
|
||||||
SSH_KEY=/home/.../.ssh/id_ed25519_backup
|
SSH_KEY=/home/<USER>/.ssh/id_ed25519_backup
|
||||||
|
|
||||||
# Timeout SSH (secondes)
|
# Timeout SSH (secondes)
|
||||||
SSH_TIMEOUT=10
|
SSH_TIMEOUT=10
|
||||||
@@ -89,6 +89,7 @@ SSH_TIMEOUT=10
|
|||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
# Nombre de jours de conservation des sauvegardes
|
# Nombre de jours de conservation des sauvegardes
|
||||||
|
# Utilisé par backup-bdd-recette.sh et backup-vaultwarden.sh
|
||||||
BACKUP_RETENTION_DAYS=10
|
BACKUP_RETENTION_DAYS=10
|
||||||
|
|
||||||
|
|
||||||
@@ -96,12 +97,11 @@ BACKUP_RETENTION_DAYS=10
|
|||||||
# APPLICATIONS À SURVEILLER
|
# APPLICATIONS À SURVEILLER
|
||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
# Liste des applications à vérifier
|
# Liste des applications à vérifier (séparées par espace)
|
||||||
APPS="
|
APP_URLS="ferme.malio-dev.fr inventory.malio-dev.fr sirh.malio-dev.fr"
|
||||||
ferme.malio-dev.fr
|
|
||||||
inventory.malio-dev.fr
|
# Schéma utilisé pour les applications surveillées
|
||||||
sirh.malio-dev.fr
|
APP_SCHEME="http"
|
||||||
"
|
|
||||||
|
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
|
|||||||
Reference in New Issue
Block a user