Apply critical security fixes and major improvements to all scripts

Security (CRITICAL):
- Add .env.example with strong password generation instructions
- Fix path traversal validation in restore.sh (now detects all .. patterns)
- Secure .env loading with set -a/set +a in all scripts
- Add logs/ to .gitignore to prevent credential leaks

Backup & Restore (IMPORTANT):
- Add file locking system to prevent concurrent backups
- Add disk space verification before backup operations
- Generate SHA256 checksums for all backups
- Verify checksums before restoration
- Create safety database backup before restore
- Implement comprehensive logging to ./logs/ directory
- Fix BACKUP_RETENTION_DAYS inconsistency
- Replace dangerous find -delete with safe iteration

Update & Recovery:
- Backup docker-compose.yml before updates with auto-rollback
- Add version display before/after updates
- Increase timeouts to 120s for slow containers
- Dynamic backup suggestion in recover.sh

Compatibility:
- Add Docker Compose v2 support with v1 fallback in all scripts
- Standardized log() function across all scripts

New Features:
- Add check-health.sh: comprehensive system health monitoring
- Add SECURITY.md: complete security documentation
- Update Makefile with check-health and recover commands
- Centralized logging with timestamps and levels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
BeauTroll
2025-12-17 18:27:00 +01:00
parent 701513ce15
commit c6de550329
10 changed files with 1037 additions and 201 deletions

View File

@@ -3,178 +3,281 @@
set -euo pipefail
# Variables globales
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$PROJECT_ROOT"
LOG_DIR="./logs"
LOG_FILE="$LOG_DIR/restore_$(date +%Y%m%d_%H%M%S).log"
TEMP_DIR=""
# Créer le dossier de logs
mkdir -p "$LOG_DIR"
# Fonction de logging
log() {
local level="$1"
shift
local message="$*"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
# Charger les variables d'environnement
if [ ! -f .env ]; then
echo "❌ Erreur: Fichier .env introuvable"
log "ERROR" "Fichier .env introuvable"
exit 1
fi
# Charger .env de manière sécurisée
set -a
# shellcheck disable=SC1091
source .env
set +a
# Vérifier les variables requises
: "${MYSQL_USER:?Variable MYSQL_USER non définie}"
: "${MYSQL_PASSWORD:?Variable MYSQL_PASSWORD non définie}"
: "${MYSQL_DATABASE:?Variable MYSQL_DATABASE non définie}"
# Vérifier les arguments
if [ -z "${1:-}" ]; then
echo "Usage: $0 <backup_file.tar.gz>"
log "ERROR" "Usage: $0 <backup_file.tar.gz>"
exit 1
fi
BACKUP_FILE="$1"
# Valider le chemin du fichier (éviter path traversal)
if [[ "$BACKUP_FILE" =~ \.\./\.\. ]]; then
echo "Chemin de fichier invalide"
if [[ "$BACKUP_FILE" =~ \.\. ]] || [[ "$BACKUP_FILE" == /* && ! "$BACKUP_FILE" =~ ^/home/ && ! "$BACKUP_FILE" =~ ^/tmp/ ]]; then
log "ERROR" "Chemin de fichier invalide ou non autorisé"
exit 1
fi
# Résoudre le chemin absolu
BACKUP_FILE=$(realpath "$BACKUP_FILE" 2>/dev/null) || {
log "ERROR" "Impossible de résoudre le chemin: $1"
exit 1
}
if [ ! -f "$BACKUP_FILE" ]; then
echo "Fichier introuvable: $BACKUP_FILE"
log "ERROR" "Fichier introuvable: $BACKUP_FILE"
exit 1
fi
# Vérifier que c'est bien un fichier tar.gz
if ! file "$BACKUP_FILE" | grep -q "gzip compressed"; then
echo "Le fichier n'est pas une archive gzip valide"
log "ERROR" "Le fichier n'est pas une archive gzip valide"
exit 1
fi
echo "⚠️ ATTENTION: Cette opération va écraser les données actuelles !"
read -r -p "Continuer? (yes/no): " confirm
# Vérifier le checksum si disponible
CHECKSUM_FILE="${BACKUP_FILE}.sha256"
if [ -f "$CHECKSUM_FILE" ]; then
log "INFO" "Vérification du checksum..."
BACKUP_DIR=$(dirname "$BACKUP_FILE")
BACKUP_NAME=$(basename "$BACKUP_FILE")
if [ "$confirm" != "yes" ]; then
echo "Annulé."
exit 0
if cd "$BACKUP_DIR" && sha256sum -c "$BACKUP_NAME.sha256" 2>>"$LOG_FILE"; then
cd "$PROJECT_ROOT"
log "INFO" "Checksum valide"
else
cd "$PROJECT_ROOT"
log "ERROR" "Checksum invalide! Le fichier pourrait être corrompu"
exit 1
fi
else
log "WARN" "Pas de fichier checksum trouvé, impossible de vérifier l'intégrité"
fi
# Extraire le backup
TEMP_DIR=$(mktemp -d)
log "INFO" "=== Restauration depuis: $BACKUP_FILE ==="
log "INFO" "Log file: $LOG_FILE"
echo ""
log "WARN" "ATTENTION: Cette opération va écraser les données actuelles!"
read -r -p "Voulez-vous créer un backup de sécurité avant de continuer? (yes/no): " create_backup
if [ "$create_backup" = "yes" ]; then
log "INFO" "Création du backup de sécurité..."
if bash scripts/backup.sh 2>&1 | tee -a "$LOG_FILE"; then
log "INFO" "Backup de sécurité créé avec succès"
else
log "ERROR" "Échec du backup de sécurité"
read -r -p "Continuer quand même? (yes/no): " force_continue
if [ "$force_continue" != "yes" ]; then
log "INFO" "Restauration annulée"
exit 0
fi
fi
fi
echo ""
read -r -p "Continuer la restauration? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
log "INFO" "Restauration annulée"
exit 0
fi
# Fonction de nettoyage
cleanup() {
local exit_code=$?
if [ -d "$TEMP_DIR" ]; then
echo "🧹 Nettoyage du répertoire temporaire..."
if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
log "INFO" "Nettoyage du répertoire temporaire..."
rm -rf "${TEMP_DIR:?}"
fi
if [ "$exit_code" -eq 0 ]; then
log "SUCCESS" "Restauration terminée avec succès"
else
log "ERROR" "Restauration échouée avec code: $exit_code"
log "ERROR" "Pour récupérer, utilisez: bash scripts/recover.sh"
fi
exit "$exit_code"
}
trap cleanup EXIT INT TERM
echo "📂 Extraction vers $TEMP_DIR..."
if ! tar -xzf "$BACKUP_FILE" -C "$TEMP_DIR"; then
echo "❌ Erreur lors de l'extraction"
# Extraire le backup
TEMP_DIR=$(mktemp -d)
log "INFO" "Extraction vers $TEMP_DIR..."
if ! tar -xzf "$BACKUP_FILE" -C "$TEMP_DIR" 2>>"$LOG_FILE"; then
log "ERROR" "Erreur lors de l'extraction"
exit 1
fi
# Trouver le répertoire de backup de manière sécurisée
# Trouver le répertoire de backup
BACKUP_DIR=$(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
if [ -z "$BACKUP_DIR" ]; then
echo "Aucun répertoire trouvé dans l'archive"
log "ERROR" "Aucun répertoire trouvé dans l'archive"
exit 1
fi
BACKUP_DIR=$(basename "$BACKUP_DIR")
# Arrêter les services
echo "⏹️ Arrêt des services..."
docker-compose down
log "INFO" "Backup extrait: $(basename "$BACKUP_DIR")"
# Vérifier que les fichiers requis existent
if [ ! -f "$TEMP_DIR/$BACKUP_DIR/database.sql" ]; then
echo "Fichier database.sql manquant dans l'archive"
if [ ! -f "$BACKUP_DIR/database.sql" ]; then
log "ERROR" "Fichier database.sql manquant dans l'archive"
exit 1
fi
if [ ! -f "$TEMP_DIR/$BACKUP_DIR/config.tar.gz" ]; then
echo "Fichier config.tar.gz manquant dans l'archive"
if [ ! -f "$BACKUP_DIR/config.tar.gz" ]; then
log "ERROR" "Fichier config.tar.gz manquant dans l'archive"
exit 1
fi
# Restaurer la base de données
echo "💾 Restauration de la base de données..."
docker-compose up -d db
# Arrêter les services
log "INFO" "Arrêt des services..."
docker-compose down 2>>"$LOG_FILE"
# Démarrer uniquement la base de données
log "INFO" "Démarrage de la base de données..."
docker-compose up -d db 2>>"$LOG_FILE"
# Attendre que la base de données soit prête
echo "⏳ Attente de la base de données..."
log "INFO" "Attente de la base de données..."
for i in {1..30}; do
if docker-compose exec -T db mysqladmin ping -h localhost --silent 2>/dev/null; then
echo "✅ Base de données prête"
if docker-compose exec -T db mysqladmin ping -h localhost --silent 2>>"$LOG_FILE"; then
log "INFO" "Base de données prête"
break
fi
if [ "$i" -eq 30 ]; then
echo "Timeout: La base de données n'est pas prête"
log "ERROR" "Timeout: La base de données n'est pas prête"
exit 1
fi
sleep 1
done
# Restaurer avec MYSQL_PWD pour éviter le mot de passe dans la commande
if ! docker-compose exec -T db sh -c "MYSQL_PWD=\"\$MYSQL_PASSWORD\" mysql \
# Backup de sécurité de la DB actuelle
log "INFO" "Backup de sécurité de la base de données actuelle..."
SAFETY_BACKUP="$TEMP_DIR/safety_db_backup.sql"
if docker-compose exec -T db sh -c "MYSQL_PWD=\"\$MYSQL_PASSWORD\" mysqldump \
-u\"\$MYSQL_USER\" \
\"\$MYSQL_DATABASE\"" < "$TEMP_DIR/$BACKUP_DIR/database.sql"; then
echo "❌ Erreur lors de la restauration de la base de données"
exit 1
\"\$MYSQL_DATABASE\" \
--single-transaction \
--quick" > "$SAFETY_BACKUP" 2>>"$LOG_FILE"; then
log "INFO" "Backup de sécurité créé: $SAFETY_BACKUP"
else
log "WARN" "Impossible de créer un backup de sécurité de la DB"
fi
# Redémarrer tous les services d'abord
echo "▶️ Démarrage des services..."
docker-compose up -d
# Restaurer la base de données
log "INFO" "Restauration de la base de données..."
if ! docker-compose exec -T db sh -c "MYSQL_PWD=\"\$MYSQL_PASSWORD\" mysql \
-u\"\$MYSQL_USER\" \
\"\$MYSQL_DATABASE\"" < "$BACKUP_DIR/database.sql" 2>>"$LOG_FILE"; then
log "ERROR" "Erreur lors de la restauration de la base de données"
log "ERROR" "Backup de sécurité disponible: $SAFETY_BACKUP"
exit 1
fi
log "INFO" "Base de données restaurée"
# Démarrer tous les services
log "INFO" "Démarrage de tous les services..."
docker-compose up -d 2>>"$LOG_FILE"
# Attendre que Nextcloud soit prêt
echo "⏳ Attente du démarrage de Nextcloud..."
log "INFO" "Attente du démarrage de Nextcloud..."
for i in {1..60}; do
if docker-compose exec -T nextcloud curl -f http://localhost/status.php >/dev/null 2>&1; then
echo "✅ Nextcloud prêt"
log "INFO" "Nextcloud prêt"
break
fi
if [ "$i" -eq 60 ]; then
echo "Timeout: Nextcloud n'est pas prêt"
log "ERROR" "Timeout: Nextcloud n'est pas prêt"
log "ERROR" "Vérifiez les logs: docker-compose logs nextcloud"
exit 1
fi
sleep 1
done
# Restaurer les fichiers via le container pour éviter les problèmes de permissions
echo "📁 Restauration des fichiers..."
# Restaurer la configuration
if ! docker-compose exec -T -u www-data nextcloud tar -xzf - -C /var/www/html/config < "$TEMP_DIR/$BACKUP_DIR/config.tar.gz"; then
echo "❌ Erreur lors de la restauration de la configuration"
log "INFO" "Restauration de la configuration..."
if ! docker-compose exec -T -u www-data nextcloud tar -xzf - -C /var/www/html/config < "$BACKUP_DIR/config.tar.gz" 2>>"$LOG_FILE"; then
log "ERROR" "Erreur lors de la restauration de la configuration"
exit 1
fi
log "INFO" "Configuration restaurée"
# Restaurer les données
if [ -f "$TEMP_DIR/$BACKUP_DIR/data.tar.gz" ]; then
echo "📦 Restauration des données utilisateurs..."
if ! docker-compose exec -T -u www-data nextcloud tar -xzf - -C /var/www/html/data < "$TEMP_DIR/$BACKUP_DIR/data.tar.gz"; then
echo "Erreur lors de la restauration des données"
if [ -f "$BACKUP_DIR/data.tar.gz" ]; then
log "INFO" "Restauration des données utilisateurs..."
if ! docker-compose exec -T -u www-data nextcloud tar -xzf - -C /var/www/html/data < "$BACKUP_DIR/data.tar.gz" 2>>"$LOG_FILE"; then
log "ERROR" "Erreur lors de la restauration des données"
exit 1
fi
elif [ -d "$TEMP_DIR/$BACKUP_DIR/data" ]; then
echo "⚠️ Format de backup rsync détecté, copie manuelle nécessaire"
echo " Utilisez: docker cp pour copier $TEMP_DIR/$BACKUP_DIR/data/ vers le container"
log "INFO" "Données restaurées"
else
log "WARN" "Pas de fichier data.tar.gz trouvé"
fi
# Restaurer les apps personnalisées si présentes
if [ -f "$TEMP_DIR/$BACKUP_DIR/apps.tar.gz" ]; then
echo "📦 Restauration des apps personnalisées..."
docker-compose exec -T -u www-data nextcloud tar -xzf - -C /var/www/html/custom_apps < "$TEMP_DIR/$BACKUP_DIR/apps.tar.gz" || echo " Pas d'apps à restaurer"
# Restaurer les apps personnalisées
if [ -f "$BACKUP_DIR/apps.tar.gz" ]; then
log "INFO" "Restauration des apps personnalisées..."
if docker-compose exec -T -u www-data nextcloud tar -xzf - -C /var/www/html/custom_apps < "$BACKUP_DIR/apps.tar.gz" 2>>"$LOG_FILE"; then
log "INFO" "Apps restaurées"
else
log "WARN" "Erreur lors de la restauration des apps"
fi
else
log "INFO" "Pas d'apps personnalisées à restaurer"
fi
# Réparer
echo "🔧 Réparation..."
docker-compose exec -T -u www-data nextcloud php occ maintenance:repair || echo "⚠️ Erreur lors de la réparation"
# Désactiver le mode maintenance
log "INFO" "Désactivation du mode maintenance..."
docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE" || log "WARN" "Impossible de désactiver le mode maintenance"
# Désactiver le mode maintenance avant le scan
echo "▶️ Désactivation du mode maintenance..."
docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off || echo "⚠️ Impossible de désactiver le mode maintenance"
# Réparer et scanner
log "INFO" "Réparation de l'installation..."
docker-compose exec -T -u www-data nextcloud php occ maintenance:repair 2>>"$LOG_FILE" || log "WARN" "Erreur lors de la réparation"
# Scanner les fichiers
echo "🔍 Scan des fichiers..."
docker-compose exec -T -u www-data nextcloud php occ files:scan --all || echo "⚠️ Erreur lors du scan"
log "INFO" "Scan des fichiers..."
docker-compose exec -T -u www-data nextcloud php occ files:scan --all 2>>"$LOG_FILE" || log "WARN" "Erreur lors du scan"
echo " Restauration terminée !"
log "INFO" "=== Restauration terminée ==="
log "INFO" "Vérifiez que tout fonctionne: make health"