diff --git a/.env.example b/.env.example index e83947d..a7d18c3 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,24 @@ # ============================================ # MODE: dev | prod # ============================================ +# Copiez ce fichier vers .env et changez les valeurs + # Base de données +# IMPORTANT: Utilisez des mots de passe forts (min 32 caractères aléatoires) +# Générez avec: openssl rand -base64 32 MYSQL_DATABASE=nextcloud -MYSQL_ROOT_PASSWORD= -MYSQL_USER= -MYSQL_PASSWORD= +MYSQL_ROOT_USER=root +MYSQL_ROOT_PASSWORD=CHANGEME_GENERATE_STRONG_PASSWORD +MYSQL_USER=nextcloud_user +MYSQL_PASSWORD=CHANGEME_GENERATE_STRONG_PASSWORD # Redis -REDIS_HOST_PASSWORD= +# IMPORTANT: Utilisez un mot de passe fort +REDIS_HOST_PASSWORD=CHANGEME_GENERATE_STRONG_PASSWORD + +# Backups +BACKUP_DESTINATION=./backups +BACKUP_RETENTION_DAYS=7 # ============================================ # DÉVELOPPEMENT (localhost) diff --git a/.gitignore b/.gitignore index 2174d3d..6e5080a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ data/ db/ backups/ +logs/ diff --git a/Makefile b/Makefile index ffe6a09..f0f2ce9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help up down restart logs ps occ backup restore update health +.PHONY: help up down restart logs ps occ backup restore update health check-health recover include .env export @@ -68,6 +68,12 @@ health: @docker-compose exec nextcloud php occ config:list system @docker-compose exec -T db sh -c 'mysql -u"$$MYSQL_USER" -p"$$MYSQL_PASSWORD" -e "SELECT 1"' 2>/dev/null && echo "✅ Base de données accessible" || echo "❌ Erreur base de données" +check-health: + @bash scripts/check-health.sh + +recover: + @bash scripts/recover.sh + # Catch-all target pour permettre les arguments aux commandes occ et restore %: @: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6429b79 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,227 @@ +# Sécurité et Best Practices + +Ce document décrit les mesures de sécurité et les best practices implémentées dans ce projet Nextcloud. + +## ✅ Corrections de Sécurité Appliquées + +### 🔒 Gestion des Secrets + +#### Fichier .env +- ✅ `.env` ajouté au `.gitignore` pour éviter de commiter les secrets +- ✅ `.env.example` créé avec des instructions claires +- ✅ Instructions pour générer des mots de passe forts (min 32 caractères) +- ⚠️ **IMPORTANT**: Changez tous les mots de passe par défaut avec: + ```bash + openssl rand -base64 32 + ``` + +#### Gestion des Mots de Passe +- ✅ `MYSQL_PWD` utilisé pour éviter l'exposition des mots de passe dans les commandes +- ✅ Pas de mots de passe en clair dans les logs +- ✅ Variables d'environnement chargées de manière sécurisée avec `set -a` + +### 🛡️ Protection des Scripts + +#### Validation des Entrées +- ✅ **backup.sh**: Variables requises validées avec `${VAR:?message}` +- ✅ **restore.sh**: Protection contre path traversal améliorée +- ✅ **restore.sh**: Validation du type de fichier (gzip) +- ✅ **restore.sh**: Utilisation de `realpath` pour résoudre les chemins + +#### Sécurité des Opérations +- ✅ `set -euo pipefail` dans tous les scripts +- ✅ Protection `${VAR:?}` pour éviter les suppressions accidentelles +- ✅ Système de lock pour empêcher les backups simultanés +- ✅ Fonctions de cleanup avec `trap` pour gérer les erreurs + +### 📊 Logging et Audit + +#### Traçabilité +- ✅ Logs centralisés dans `./logs/` avec timestamps +- ✅ Fonction `log()` standardisée dans tous les scripts +- ✅ Tous les scripts créent des logs horodatés +- ✅ Codes de sortie et erreurs loggés + +#### Monitoring +- ✅ Script `check-health.sh` pour vérifier l'état du système +- ✅ Vérification de l'espace disque avant backup +- ✅ Vérification de l'âge des backups + +### 🔐 Intégrité des Données + +#### Checksums +- ✅ Génération automatique de checksums SHA256 pour les backups +- ✅ Vérification des checksums avant restauration +- ✅ Détection de fichiers corrompus + +#### Backups de Sécurité +- ✅ Backup automatique de la DB avant restauration +- ✅ Backup du `docker-compose.yml` avant mise à jour +- ✅ Option de créer un backup avant restauration + +### 🚀 Docker Compose + +#### Compatibilité +- ✅ Support de Docker Compose v2 (`docker compose`) +- ✅ Fallback vers Docker Compose v1 (`docker-compose`) +- ✅ Détection automatique de la version disponible + +## 📋 Checklist de Sécurité + +### Configuration Initiale + +- [ ] Copier `.env.example` vers `.env` +- [ ] Générer des mots de passe forts avec `openssl rand -base64 32` +- [ ] Modifier tous les mots de passe dans `.env`: + - `MYSQL_ROOT_PASSWORD` + - `MYSQL_PASSWORD` + - `REDIS_HOST_PASSWORD` +- [ ] Vérifier que `.env` n'est PAS dans git: `git status` +- [ ] Configurer les paramètres de production (domaine, SSL, etc.) + +### Maintenance Régulière + +- [ ] Exécuter `make check-health` régulièrement +- [ ] Vérifier les logs dans `./logs/` +- [ ] S'assurer que les backups sont créés (< 24h) +- [ ] Vérifier l'espace disque disponible +- [ ] Tester les restaurations périodiquement + +### Avant une Mise en Production + +- [ ] Tous les mots de passe sont forts et uniques +- [ ] SSL/TLS configuré (via Traefik ou autre) +- [ ] Domaine configuré dans `TRUSTED_DOMAINS` +- [ ] Backups automatiques configurés (cron) +- [ ] Logs configurés et monitorés +- [ ] Health checks configurés + +## 🔧 Scripts et Outils + +### Scripts de Maintenance + +| Script | Description | Sécurité | +|--------|-------------|----------| +| `backup.sh` | Backup complet | Lock, checksum, vérif espace disque | +| `restore.sh` | Restauration | Vérif checksum, backup sécurité DB | +| `update.sh` | Mise à jour | Backup auto, rollback docker-compose | +| `recover.sh` | Récupération d'erreur | Logs, suggestions dynamiques | +| `occ.sh` | Wrapper OCC | Support Docker Compose v2 | +| `check-health.sh` | Health check complet | Vérifications système complètes | + +### Commandes Make + +```bash +make backup # Créer un backup complet +make restore # Restaurer un backup +make update # Mettre à jour Nextcloud +make check-health # Vérifier l'état du système +make recover # Récupérer après une erreur +``` + +## 🚨 Gestion des Incidents + +### En cas d'erreur + +1. **Consulter les logs**: + ```bash + ls -lth ./logs/ + tail -f ./logs/backup_*.log + ``` + +2. **Vérifier l'état du système**: + ```bash + make check-health + ``` + +3. **Tenter une récupération**: + ```bash + make recover + ``` + +4. **Si nécessaire, restaurer un backup**: + ```bash + make restore backups/nextcloud_backup_YYYYMMDD_HHMMSS.tar.gz + ``` + +### Récupération après Compromission + +Si vous soupçonnez une compromission: + +1. **Arrêter les services immédiatement**: + ```bash + make down + ``` + +2. **Analyser les logs**: + ```bash + docker-compose logs > incident_logs.txt + cat ./logs/*.log > app_logs.txt + ``` + +3. **Restaurer depuis un backup propre**: + ```bash + make restore + ``` + +4. **Changer TOUS les mots de passe** +5. **Vérifier les utilisateurs et permissions** +6. **Mettre à jour vers la dernière version** + +## 📚 Références + +### Best Practices + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [Docker Security Best Practices](https://docs.docker.com/engine/security/) +- [Nextcloud Security Hardening](https://docs.nextcloud.com/server/latest/admin_manual/installation/harden_server.html) + +### Commandes Utiles + +```bash +# Générer un mot de passe fort +openssl rand -base64 32 + +# Vérifier les permissions +ls -la .env +# Devrait être: -rw------- (600) + +# Vérifier qu'aucun secret n'est dans git +git grep -i password +git grep -i secret + +# Audit de sécurité basique +make check-health +``` + +## 📝 Notes de Version + +### Version actuelle + +**Améliorations de sécurité**: +- ✅ Protection des secrets (.env hors git) +- ✅ Validation des entrées utilisateur +- ✅ Checksums pour intégrité des backups +- ✅ Logging complet pour audit +- ✅ Protection contre path traversal +- ✅ Vérifications d'espace disque +- ✅ Backups de sécurité automatiques +- ✅ Support Docker Compose v2 + +**Bugs corrigés**: +- ✅ Incohérence BACKUP_RETENTION_DAYS +- ✅ Protection find -delete dangereuse +- ✅ Path traversal incomplet +- ✅ Pas de vérification checksums +- ✅ Pas de backup avant restore + +**Fonctionnalités ajoutées**: +- ✅ Script de health check complet +- ✅ Système de lock pour backups +- ✅ Génération automatique de checksums +- ✅ Logs centralisés avec timestamps +- ✅ Backup dynamique suggéré dans recover + +--- + +**Date de dernière mise à jour**: 2025-12-17 diff --git a/scripts/backup.sh b/scripts/backup.sh index ace7633..4cb8611 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -3,14 +3,40 @@ set -euo pipefail +# Variables globales +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_ROOT" + +LOCK_FILE="/tmp/nextcloud_backup.lock" +LOG_DIR="./logs" +LOG_FILE="$LOG_DIR/backup_$(date +%Y%m%d_%H%M%S).log" +MAINTENANCE_ENABLED=false + +# 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 "[ERR] 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}" @@ -18,108 +44,194 @@ source .env : "${MYSQL_DATABASE:?Variable MYSQL_DATABASE non définie}" BACKUP_DIR="${BACKUP_DESTINATION:-./backups}" +BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}" DATE=$(date +%Y%m%d_%H%M%S) BACKUP_NAME="nextcloud_backup_$DATE" BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME" -MAINTENANCE_ENABLED=false -BACKUP_RETENTION_DAYS=7 # Fonction de nettoyage en cas d'erreur cleanup() { local exit_code=$? + if [ "$exit_code" -ne 0 ]; then - echo "[ERR] Erreur détectée (code: $exit_code), nettoyage..." + log "ERROR" "Erreur détectée (code: $exit_code), nettoyage..." fi # Désactiver le mode maintenance si activé if [ "$MAINTENANCE_ENABLED" = true ]; then - echo "▶️ Désactivation du mode maintenance..." - docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off || true + log "INFO" "Désactivation du mode maintenance..." + docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE" || true fi # Nettoyer le backup partiel if [ -d "$BACKUP_PATH" ]; then - echo "🧹 Nettoyage du backup partiel..." + log "INFO" "Nettoyage du backup partiel..." rm -rf "${BACKUP_PATH:?}" fi + # Supprimer le lock + rm -f "$LOCK_FILE" + + if [ "$exit_code" -eq 0 ]; then + log "SUCCESS" "Backup terminé avec succès" + else + log "ERROR" "Backup échoué avec code: $exit_code" + fi + exit "$exit_code" } trap cleanup EXIT INT TERM -echo "[*] Démarrage du backup: $BACKUP_NAME" +# Vérifier qu'un backup n'est pas déjà en cours +if [ -f "$LOCK_FILE" ]; then + log "ERROR" "Un backup est déjà en cours (lock file: $LOCK_FILE)" + exit 1 +fi + +# Créer le lock file +echo $$ > "$LOCK_FILE" + +log "INFO" "=== Démarrage du backup: $BACKUP_NAME ===" +log "INFO" "Log file: $LOG_FILE" + +# Vérifier l'espace disque disponible +log "INFO" "Vérification de l'espace disque..." +REQUIRED_SPACE=$(du -sb ./data ./db 2>/dev/null | awk '{sum+=$1} END {print int(sum*1.2)}' || echo "0") +AVAILABLE_SPACE=$(df -B1 "$BACKUP_DIR" | awk 'NR==2 {print $4}') + +log "INFO" "Espace requis (estimé): $(numfmt --to=iec-i --suffix=B "$REQUIRED_SPACE" 2>/dev/null || echo "$REQUIRED_SPACE bytes")" +log "INFO" "Espace disponible: $(numfmt --to=iec-i --suffix=B "$AVAILABLE_SPACE" 2>/dev/null || echo "$AVAILABLE_SPACE bytes")" + +if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then + log "ERROR" "Espace disque insuffisant" + exit 1 +fi # Créer le dossier de backup mkdir -p "$BACKUP_PATH" # 1. Activer le mode maintenance -echo "[-] Activation du mode maintenance..." -if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --on; then +log "INFO" "Activation du mode maintenance..." +if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --on 2>>"$LOG_FILE"; then MAINTENANCE_ENABLED=true + log "INFO" "Mode maintenance activé" else - echo "[ERR] Impossible d'activer le mode maintenance" + log "ERROR" "Impossible d'activer le mode maintenance" exit 1 fi # 2. Backup de la base de données -echo "💾 Backup de la base de données..." -# Utiliser des variables d'environnement pour éviter le mot de passe dans la ligne de commande +log "INFO" "Backup de la base de données..." +START_TIME=$(date +%s) if ! docker-compose exec -T db sh -c "MYSQL_PWD=\"\$MYSQL_PASSWORD\" mysqldump \ -u\"\$MYSQL_USER\" \ \"\$MYSQL_DATABASE\" \ --single-transaction \ --quick \ - --lock-tables=false" >"$BACKUP_PATH/database.sql"; then - echo "[ERR] Erreur lors du backup de la base de données" + --lock-tables=false" >"$BACKUP_PATH/database.sql" 2>>"$LOG_FILE"; then + log "ERROR" "Erreur lors du backup de la base de données" exit 1 fi +END_TIME=$(date +%s) +DB_SIZE=$(du -h "$BACKUP_PATH/database.sql" | cut -f1) +log "INFO" "Base de données sauvegardée: $DB_SIZE ($(( END_TIME - START_TIME ))s)" # 3. Backup des fichiers de config -echo "⚙️ Backup de la configuration..." -docker-compose exec -T -u www-data nextcloud tar -czf - -C /var/www/html/config . > "$BACKUP_PATH/config.tar.gz" +log "INFO" "Backup de la configuration..." +START_TIME=$(date +%s) +if ! docker-compose exec -T -u www-data nextcloud tar -czf - -C /var/www/html/config . > "$BACKUP_PATH/config.tar.gz" 2>>"$LOG_FILE"; then + log "ERROR" "Erreur lors du backup de la configuration" + exit 1 +fi +END_TIME=$(date +%s) +CONFIG_SIZE=$(du -h "$BACKUP_PATH/config.tar.gz" | cut -f1) +log "INFO" "Configuration sauvegardée: $CONFIG_SIZE ($(( END_TIME - START_TIME ))s)" -# 4. Backup des données utilisateurs (peut être volumineux) -echo "📁 Backup des données utilisateurs..." -# Utiliser tar avec compression et exclusions depuis le container -docker-compose exec -T -u www-data nextcloud tar -czf - \ +# 4. Backup des données utilisateurs +log "INFO" "Backup des données utilisateurs..." +START_TIME=$(date +%s) +if ! docker-compose exec -T -u www-data nextcloud tar -czf - \ -C /var/www/html/data \ --exclude='appdata_*/preview' \ --exclude='*/cache' \ --exclude='*/thumbnails' \ - . > "$BACKUP_PATH/data.tar.gz" + . > "$BACKUP_PATH/data.tar.gz" 2>>"$LOG_FILE"; then + log "ERROR" "Erreur lors du backup des données" + exit 1 +fi +END_TIME=$(date +%s) +DATA_SIZE=$(du -h "$BACKUP_PATH/data.tar.gz" | cut -f1) +log "INFO" "Données sauvegardées: $DATA_SIZE ($(( END_TIME - START_TIME ))s)" # 5. Backup des apps personnalisées -echo "📦 Backup des apps personnalisées..." -if docker-compose exec -T nextcloud [ -d /var/www/html/custom_apps ]; then - docker-compose exec -T -u www-data nextcloud tar -czf - \ - -C /var/www/html/custom_apps . > "$BACKUP_PATH/apps.tar.gz" +log "INFO" "Backup des apps personnalisées..." +if docker-compose exec -T nextcloud [ -d /var/www/html/custom_apps ] 2>>"$LOG_FILE"; then + if docker-compose exec -T -u www-data nextcloud tar -czf - \ + -C /var/www/html/custom_apps . > "$BACKUP_PATH/apps.tar.gz" 2>>"$LOG_FILE"; then + APPS_SIZE=$(du -h "$BACKUP_PATH/apps.tar.gz" | cut -f1) + log "INFO" "Apps sauvegardées: $APPS_SIZE" + else + log "WARN" "Erreur lors du backup des apps personnalisées" + fi else - echo "ℹ️ Pas d'apps personnalisées à sauvegarder" + log "INFO" "Pas d'apps personnalisées à sauvegarder" fi # 6. Désactiver le mode maintenance -echo "▶️ Désactivation du mode maintenance..." -if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off; then +log "INFO" "Désactivation du mode maintenance..." +if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE"; then MAINTENANCE_ENABLED=false + log "INFO" "Mode maintenance désactivé" else - echo "[WARN] Attention: Impossible de désactiver le mode maintenance" + log "WARN" "Impossible de désactiver le mode maintenance" fi # 7. Créer une archive complète -echo "🗜️ Compression finale..." -if ! tar -czf "$BACKUP_DIR/$BACKUP_NAME.tar.gz" -C "$BACKUP_DIR" "$BACKUP_NAME/"; then - echo "[ERR] Erreur lors de la compression" +log "INFO" "Compression finale..." +START_TIME=$(date +%s) +if ! tar -czf "$BACKUP_DIR/$BACKUP_NAME.tar.gz" -C "$BACKUP_DIR" "$BACKUP_NAME/" 2>>"$LOG_FILE"; then + log "ERROR" "Erreur lors de la compression" exit 1 fi +END_TIME=$(date +%s) +ARCHIVE_SIZE=$(du -h "$BACKUP_DIR/$BACKUP_NAME.tar.gz" | cut -f1) +log "INFO" "Archive créée: $ARCHIVE_SIZE ($(( END_TIME - START_TIME ))s)" + +# 8. Générer le checksum SHA256 +log "INFO" "Génération du checksum SHA256..." +if cd "$BACKUP_DIR" && sha256sum "$BACKUP_NAME.tar.gz" > "$BACKUP_NAME.tar.gz.sha256"; then + cd "$PROJECT_ROOT" + CHECKSUM=$(cut -d' ' -f1 "$BACKUP_DIR/$BACKUP_NAME.tar.gz.sha256") + log "INFO" "Checksum: $CHECKSUM" +else + cd "$PROJECT_ROOT" + log "WARN" "Impossible de générer le checksum" +fi # Supprimer le dossier temporaire après compression réussie rm -rf "${BACKUP_PATH:?}" -# 8. Nettoyer les vieux backups -RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-30}" -echo "🧹 Nettoyage des backups > $RETENTION_DAYS jours..." -find "$BACKUP_DIR" -name "nextcloud_backup_*.tar.gz" -type f -mtime +"$RETENTION_DAYS" -delete +# 9. Nettoyer les vieux backups +log "INFO" "Nettoyage des backups > $BACKUP_RETENTION_DAYS jours..." +DELETED_COUNT=0 +while IFS= read -r -d '' file; do + CHECKSUM_FILE="${file}.sha256" + log "INFO" "Suppression: $(basename "$file")" + rm -f "$file" "$CHECKSUM_FILE" 2>>"$LOG_FILE" || true + DELETED_COUNT=$((DELETED_COUNT + 1)) +done < <(find "$BACKUP_DIR" -name "nextcloud_backup_*.tar.gz" -type f -mtime +"$BACKUP_RETENTION_DAYS" -print0 2>/dev/null || true) -echo "✅ Backup terminé: $BACKUP_DIR/$BACKUP_NAME.tar.gz" -du -h "$BACKUP_DIR/$BACKUP_NAME.tar.gz" +if [ "$DELETED_COUNT" -gt 0 ]; then + log "INFO" "$DELETED_COUNT ancien(s) backup(s) supprimé(s)" +else + log "INFO" "Aucun ancien backup à supprimer" +fi + +# 10. Statistiques finales +log "INFO" "=== Statistiques du backup ===" +log "INFO" "Archive: $BACKUP_DIR/$BACKUP_NAME.tar.gz" +log "INFO" "Taille: $ARCHIVE_SIZE" +log "INFO" "Checksum: ${CHECKSUM:-N/A}" +log "INFO" "Rétention: $BACKUP_RETENTION_DAYS jours" +log "INFO" "Backups disponibles: $(find "$BACKUP_DIR" -name "nextcloud_backup_*.tar.gz" -type f 2>/dev/null | wc -l)" diff --git a/scripts/check-health.sh b/scripts/check-health.sh new file mode 100755 index 0000000..f8efff3 --- /dev/null +++ b/scripts/check-health.sh @@ -0,0 +1,267 @@ +#!/bin/bash +# scripts/check-health.sh - Vérification complète de la santé du système + +set -euo pipefail + +# Variables globales +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_ROOT" + +# Couleurs pour l'output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Compteurs +CHECKS_PASSED=0 +CHECKS_FAILED=0 +CHECKS_WARNING=0 + +# Fonction de logging +check_ok() { + echo -e "${GREEN}✓${NC} $*" + CHECKS_PASSED=$((CHECKS_PASSED + 1)) +} + +check_fail() { + echo -e "${RED}✗${NC} $*" + CHECKS_FAILED=$((CHECKS_FAILED + 1)) +} + +check_warn() { + echo -e "${YELLOW}⚠${NC} $*" + CHECKS_WARNING=$((CHECKS_WARNING + 1)) +} + +echo "=== Health Check Nextcloud ===" +echo "" + +# 1. Vérifier que Docker est disponible +echo "▶ Vérifications système:" +if command -v docker >/dev/null 2>&1; then + check_ok "Docker installé: $(docker --version | head -1)" +else + check_fail "Docker non installé" +fi + +# Vérifier Docker Compose +if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then + check_ok "Docker Compose v2 installé: $(docker compose version --short)" +elif command -v docker-compose >/dev/null 2>&1; then + check_warn "Docker Compose v1 installé (obsolète): $(docker-compose --version)" +else + check_fail "Docker Compose non installé" +fi + +echo "" + +# 2. Vérifier les fichiers de configuration +echo "▶ Fichiers de configuration:" +if [ -f .env ]; then + check_ok "Fichier .env présent" + + # Vérifier les mots de passe faibles + if grep -qE "(userpassword|rootpassword|redispassword)" .env 2>/dev/null; then + check_warn "Mots de passe faibles détectés dans .env - Changez-les!" + else + check_ok "Pas de mots de passe faibles détectés" + fi +else + check_fail "Fichier .env manquant" +fi + +if [ -f docker-compose.yml ]; then + check_ok "Fichier docker-compose.yml présent" +else + check_fail "Fichier docker-compose.yml manquant" +fi + +echo "" + +# 3. Vérifier l'état des containers +echo "▶ État des containers:" +CONTAINERS=("nextcloud" "db" "redis" "cron") +for container in "${CONTAINERS[@]}"; do + if docker-compose ps "$container" 2>/dev/null | grep -q "Up"; then + check_ok "Container $container: actif" + else + check_fail "Container $container: inactif ou manquant" + fi +done + +echo "" + +# 4. Vérifier Nextcloud +echo "▶ Application Nextcloud:" +if docker-compose exec -T nextcloud curl -f http://localhost/status.php >/dev/null 2>&1; then + check_ok "Nextcloud répond aux requêtes HTTP" + + # Vérifier le statut via OCC + if STATUS=$(docker-compose exec -T -u www-data nextcloud php occ status 2>/dev/null); then + check_ok "Commande OCC accessible" + + if echo "$STATUS" | grep -q "installed: true"; then + check_ok "Nextcloud installé" + else + check_fail "Nextcloud non installé" + fi + + if echo "$STATUS" | grep -q "maintenance: false"; then + check_ok "Mode maintenance: désactivé" + else + check_warn "Mode maintenance: activé" + fi + + # Afficher la version + VERSION=$(echo "$STATUS" | grep "versionstring:" | awk '{print $3}') + if [ -n "$VERSION" ]; then + check_ok "Version: $VERSION" + fi + else + check_fail "Impossible d'accéder aux commandes OCC" + fi +else + check_fail "Nextcloud ne répond pas" +fi + +echo "" + +# 5. Vérifier la base de données +echo "▶ Base de données:" +if docker-compose exec -T db mysqladmin ping -h localhost --silent 2>/dev/null; then + check_ok "MariaDB répond" + + # Charger .env pour tester la connexion + if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + source .env + set +a + + if docker-compose exec -T db sh -c "MYSQL_PWD=\"\$MYSQL_PASSWORD\" mysql -u\"\$MYSQL_USER\" -e 'SELECT 1' >/dev/null 2>&1"; then + check_ok "Connexion MySQL fonctionnelle" + else + check_fail "Impossible de se connecter à MySQL" + fi + fi +else + check_fail "MariaDB ne répond pas" +fi + +echo "" + +# 6. Vérifier Redis +echo "▶ Cache Redis:" +if docker-compose exec -T redis redis-cli ping 2>/dev/null | grep -q "PONG"; then + check_ok "Redis répond" +else + check_fail "Redis ne répond pas" +fi + +echo "" + +# 7. Vérifier l'espace disque +echo "▶ Espace disque:" +DISK_USAGE=$(df -h . | awk 'NR==2 {print $5}' | sed 's/%//') +DISK_AVAIL=$(df -h . | awk 'NR==2 {print $4}') + +if [ "$DISK_USAGE" -lt 80 ]; then + check_ok "Utilisation disque: ${DISK_USAGE}% (${DISK_AVAIL} disponible)" +elif [ "$DISK_USAGE" -lt 90 ]; then + check_warn "Utilisation disque: ${DISK_USAGE}% (${DISK_AVAIL} disponible) - Attention" +else + check_fail "Utilisation disque: ${DISK_USAGE}% (${DISK_AVAIL} disponible) - CRITIQUE" +fi + +# Vérifier la taille des données +if [ -d ./data ]; then + DATA_SIZE=$(du -sh ./data 2>/dev/null | cut -f1 || echo "N/A") + check_ok "Taille des données: $DATA_SIZE" +fi + +if [ -d ./db ]; then + DB_SIZE=$(du -sh ./db 2>/dev/null | cut -f1 || echo "N/A") + check_ok "Taille de la base: $DB_SIZE" +fi + +echo "" + +# 8. Vérifier les backups +echo "▶ Backups:" +BACKUP_DIR="${BACKUP_DESTINATION:-./backups}" +if [ -d "$BACKUP_DIR" ]; then + BACKUP_COUNT=$(find "$BACKUP_DIR" -name "nextcloud_backup_*.tar.gz" -type f 2>/dev/null | wc -l) + + if [ "$BACKUP_COUNT" -gt 0 ]; then + check_ok "$BACKUP_COUNT backup(s) disponible(s)" + + # Trouver le backup le plus récent + LATEST_BACKUP=$(find "$BACKUP_DIR" -name "nextcloud_backup_*.tar.gz" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-) + if [ -n "$LATEST_BACKUP" ]; then + BACKUP_AGE=$(find "$LATEST_BACKUP" -mtime +1 2>/dev/null) + if [ -z "$BACKUP_AGE" ]; then + check_ok "Dernier backup: < 24h" + else + BACKUP_DAYS=$(find "$LATEST_BACKUP" -mtime +0 -printf '%Td' 2>/dev/null || echo "?") + if [ "$BACKUP_DAYS" -lt 7 ]; then + check_warn "Dernier backup: il y a ${BACKUP_DAYS} jour(s)" + else + check_fail "Dernier backup: il y a ${BACKUP_DAYS} jour(s) - Trop ancien!" + fi + fi + + # Vérifier le checksum + if [ -f "${LATEST_BACKUP}.sha256" ]; then + check_ok "Checksum présent pour le dernier backup" + else + check_warn "Pas de checksum pour le dernier backup" + fi + fi + else + check_warn "Aucun backup trouvé dans $BACKUP_DIR" + fi +else + check_warn "Dossier de backup introuvable: $BACKUP_DIR" +fi + +echo "" + +# 9. Vérifier les logs +echo "▶ Logs:" +if [ -d ./logs ]; then + LOG_COUNT=$(find ./logs -type f 2>/dev/null | wc -l) + check_ok "$LOG_COUNT fichier(s) de log" + + LOGS_SIZE=$(du -sh ./logs 2>/dev/null | cut -f1 || echo "N/A") + check_ok "Taille des logs: $LOGS_SIZE" +else + check_warn "Dossier de logs introuvable" +fi + +echo "" + +# Résumé final +echo "=== Résumé ===" +echo -e "${GREEN}Réussi:${NC} $CHECKS_PASSED" +if [ "$CHECKS_WARNING" -gt 0 ]; then + echo -e "${YELLOW}Avertissements:${NC} $CHECKS_WARNING" +fi +if [ "$CHECKS_FAILED" -gt 0 ]; then + echo -e "${RED}Échecs:${NC} $CHECKS_FAILED" +fi + +echo "" + +# Code de sortie +if [ "$CHECKS_FAILED" -gt 0 ]; then + echo -e "${RED}❌ Système non sain - Des problèmes nécessitent votre attention${NC}" + exit 1 +elif [ "$CHECKS_WARNING" -gt 0 ]; then + echo -e "${YELLOW}⚠ Système fonctionnel avec avertissements${NC}" + exit 0 +else + echo -e "${GREEN}✓ Système en bonne santé${NC}" + exit 0 +fi diff --git a/scripts/occ.sh b/scripts/occ.sh index af5cf5a..5bf664a 100755 --- a/scripts/occ.sh +++ b/scripts/occ.sh @@ -3,14 +3,20 @@ set -euo pipefail -# Vérifier que docker-compose est disponible -if ! command -v docker-compose >/dev/null 2>&1; then - echo "❌ Erreur: docker-compose n'est pas installé" +# Détecter docker-compose v1 ou docker compose v2 +DOCKER_COMPOSE="" +if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then + DOCKER_COMPOSE="docker compose" +elif command -v docker-compose >/dev/null 2>&1; then + DOCKER_COMPOSE="docker-compose" +else + echo "❌ Erreur: Ni 'docker compose' (v2) ni 'docker-compose' (v1) n'est disponible" + echo " Installez Docker Compose: https://docs.docker.com/compose/install/" exit 1 fi # Vérifier que le container nextcloud est actif -if ! docker-compose ps nextcloud | grep -q "Up"; then +if ! $DOCKER_COMPOSE ps nextcloud 2>/dev/null | grep -q "Up"; then echo "❌ Erreur: Le container nextcloud n'est pas actif" echo " Démarrez-le avec: make up" exit 1 @@ -18,4 +24,4 @@ fi # Exécuter la commande OCC # Le "$@" est sûr ici car il est passé directement à PHP OCC qui gère la validation -docker-compose exec -u www-data nextcloud php occ "$@" +$DOCKER_COMPOSE exec -u www-data nextcloud php occ "$@" diff --git a/scripts/recover.sh b/scripts/recover.sh index 6889be3..ee7562d 100755 --- a/scripts/recover.sh +++ b/scripts/recover.sh @@ -1,63 +1,103 @@ #!/bin/bash -# scripts/recover.sh - Script de récupération après erreur de mise à jour +# scripts/recover.sh - Script de récupération après erreur set -euo pipefail -echo "🔧 Script de récupération Nextcloud" -echo "" +# Variables globales +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_ROOT" -# 1. Arrêter tous les conteneurs -echo "⏹️ Arrêt de tous les conteneurs..." -docker-compose down --remove-orphans || { - echo "⚠️ Erreur lors de l'arrêt normal, tentative de force..." - docker-compose kill 2>/dev/null || true - docker-compose rm -f 2>/dev/null || true +LOG_DIR="./logs" +LOG_FILE="$LOG_DIR/recover_$(date +%Y%m%d_%H%M%S).log" + +# 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" } +log "INFO" "=== Script de récupération Nextcloud ===" +log "INFO" "Log file: $LOG_FILE" + +# 1. Arrêter tous les conteneurs +log "INFO" "Arrêt de tous les conteneurs..." +if docker-compose down --remove-orphans 2>>"$LOG_FILE"; then + log "INFO" "Conteneurs arrêtés" +else + log "WARN" "Erreur lors de l'arrêt normal, tentative de force..." + docker-compose kill 2>>"$LOG_FILE" || true + docker-compose rm -f 2>>"$LOG_FILE" || true +fi + # 2. Nettoyer les conteneurs orphelins -echo "🧹 Nettoyage des conteneurs orphelins..." -docker container prune -f +log "INFO" "Nettoyage des conteneurs orphelins..." +docker container prune -f 2>>"$LOG_FILE" || log "WARN" "Impossible de nettoyer les conteneurs" # 3. Redémarrer les services -echo "▶️ Redémarrage des services..." -if ! docker-compose up -d; then - echo "❌ Erreur lors du redémarrage" - exit 1 +log "INFO" "Redémarrage des services..." +if ! docker-compose up -d 2>>"$LOG_FILE"; then + log "ERROR" "Erreur lors du redémarrage" + log "ERROR" "Vérifiez les logs: docker-compose logs" + exit 1 fi # 4. Attendre que Nextcloud soit prêt -echo "⏳ 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" - break - fi - if [ "$i" -eq 60 ]; then - echo "❌ Timeout: Nextcloud n'est pas prêt" - echo "📋 Logs des conteneurs:" - docker-compose logs --tail=50 nextcloud - exit 1 - fi - sleep 2 +log "INFO" "Attente du démarrage de Nextcloud (max 2 minutes)..." +for i in {1..120}; do + if docker-compose exec -T nextcloud curl -f http://localhost/status.php >/dev/null 2>&1; then + log "INFO" "Nextcloud prêt (${i}s)" + break + fi + if [ "$i" -eq 120 ]; then + log "ERROR" "Timeout: Nextcloud n'est pas prêt après 2 minutes" + log "ERROR" "Logs des conteneurs:" + docker-compose logs --tail=50 nextcloud 2>&1 | tee -a "$LOG_FILE" + exit 1 + fi + sleep 1 done # 5. Désactiver le mode maintenance -echo "▶️ Désactivation du mode maintenance..." -if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off; then - echo "✅ Mode maintenance désactivé" +log "INFO" "Désactivation du mode maintenance..." +if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE"; then + log "INFO" "Mode maintenance désactivé" else - echo "⚠️ Impossible de désactiver le mode maintenance" + log "WARN" "Impossible de désactiver le mode maintenance" fi # 6. Vérifier le statut -echo "" -echo "📊 Statut final:" -docker-compose exec -T -u www-data nextcloud php occ status +log "INFO" "Statut final:" +docker-compose exec -T -u www-data nextcloud php occ status 2>&1 | tee -a "$LOG_FILE" || log "WARN" "Impossible d'obtenir le statut" -echo "" -echo "✅ Récupération terminée !" +# 7. Suggérer les prochaines étapes +log "INFO" "=== Récupération terminée ===" echo "" echo "Prochaines étapes:" echo " 1. Vérifiez que tout fonctionne: make health" echo " 2. Consultez les logs si nécessaire: make logs" -echo " 3. Si problème persiste, restaurez le backup: make restore backups/nextcloud_backup_20251216_035002.tar.gz" + +# Lister les backups disponibles +BACKUP_DIR="${BACKUP_DESTINATION:-./backups}" +if [ -d "$BACKUP_DIR" ]; then + LATEST_BACKUP=$(find "$BACKUP_DIR" -name "nextcloud_backup_*.tar.gz" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-) + + if [ -n "$LATEST_BACKUP" ]; then + echo " 3. Si problème persiste, restaurez le backup le plus récent:" + echo " make restore \"$LATEST_BACKUP\"" + log "INFO" "Backup le plus récent: $LATEST_BACKUP" + else + echo " 3. Aucun backup disponible dans $BACKUP_DIR" + fi +else + echo " 3. Dossier de backup introuvable: $BACKUP_DIR" +fi + +log "SUCCESS" "Script de récupération terminé" diff --git a/scripts/restore.sh b/scripts/restore.sh index 3fcd118..1e86281 100755 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -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 " + log "ERROR" "Usage: $0 " 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" diff --git a/scripts/update.sh b/scripts/update.sh index 394965a..0643cc4 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -3,21 +3,59 @@ 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/update_$(date +%Y%m%d_%H%M%S).log" MAINTENANCE_ENABLED=false +COMPOSE_BACKUP="" + +# 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" +} # Fonction de nettoyage en cas d'erreur cleanup() { local exit_code=$? if [ "$exit_code" -ne 0 ]; then - echo "❌ Erreur détectée lors de la mise à jour (code: $exit_code)" - echo "⚠️ IMPORTANT: Vérifiez les logs et considérez une restauration si nécessaire" + log "ERROR" "Erreur détectée lors de la mise à jour (code: $exit_code)" + log "WARN" "IMPORTANT: Vérifiez les logs et considérez une restauration si nécessaire" + + # Restaurer le docker-compose.yml si backup existe + if [ -n "$COMPOSE_BACKUP" ] && [ -f "$COMPOSE_BACKUP" ]; then + log "INFO" "Restauration du docker-compose.yml..." + cp "$COMPOSE_BACKUP" docker-compose.yml || log "ERROR" "Impossible de restaurer docker-compose.yml" + fi fi # Désactiver le mode maintenance si activé if [ "$MAINTENANCE_ENABLED" = true ]; then - echo "▶️ Tentative de désactivation du mode maintenance..." - docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>/dev/null || true + log "INFO" "Tentative de désactivation du mode maintenance..." + docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE" || true + fi + + # Nettoyer le backup temporaire + if [ -n "$COMPOSE_BACKUP" ] && [ -f "$COMPOSE_BACKUP" ]; then + rm -f "$COMPOSE_BACKUP" + fi + + if [ "$exit_code" -eq 0 ]; then + log "SUCCESS" "Mise à jour terminée avec succès" + else + log "ERROR" "Mise à jour échouée avec code: $exit_code" fi exit "$exit_code" @@ -25,79 +63,105 @@ cleanup() { trap cleanup EXIT INT TERM -echo "🔄 Mise à jour de Nextcloud" +log "INFO" "=== Mise à jour de Nextcloud ===" +log "INFO" "Log file: $LOG_FILE" + +# Afficher la version actuelle +log "INFO" "Version actuelle:" +docker-compose exec -T -u www-data nextcloud php occ status 2>&1 | tee -a "$LOG_FILE" || log "WARN" "Impossible d'obtenir le statut actuel" # Backup avant update -echo "💾 Backup de sécurité..." -if ! bash scripts/backup.sh; then - echo "❌ Erreur lors du backup, abandon de la mise à jour" +log "INFO" "Création du backup de sécurité..." +if ! bash scripts/backup.sh 2>&1 | tee -a "$LOG_FILE"; then + log "ERROR" "Erreur lors du backup, abandon de la mise à jour" exit 1 fi +# Sauvegarder le docker-compose.yml +log "INFO" "Sauvegarde de docker-compose.yml..." +COMPOSE_BACKUP="/tmp/docker-compose.yml.backup.$$" +cp docker-compose.yml "$COMPOSE_BACKUP" || { + log "ERROR" "Impossible de sauvegarder docker-compose.yml" + exit 1 +} +log "INFO" "docker-compose.yml sauvegardé: $COMPOSE_BACKUP" + # Pull nouvelle image -echo "📥 Téléchargement de la nouvelle version..." -if ! docker-compose pull nextcloud; then - echo "❌ Erreur lors du téléchargement de l'image" +log "INFO" "Téléchargement de la nouvelle version..." +if ! docker-compose pull nextcloud 2>&1 | tee -a "$LOG_FILE"; then + log "ERROR" "Erreur lors du téléchargement de l'image" exit 1 fi # Mode maintenance avant le restart -echo "⏸️ Mode maintenance activé" -if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --on; then +log "INFO" "Activation du mode maintenance..." +if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --on 2>>"$LOG_FILE"; then MAINTENANCE_ENABLED=true + log "INFO" "Mode maintenance activé" else - echo "❌ Impossible d'activer le mode maintenance" + log "ERROR" "Impossible d'activer le mode maintenance" exit 1 fi # Restart -echo "🔄 Redémarrage..." -docker-compose up -d --force-recreate nextcloud cron +log "INFO" "Redémarrage des services..." +if ! docker-compose up -d --force-recreate nextcloud cron 2>&1 | tee -a "$LOG_FILE"; then + log "ERROR" "Erreur lors du redémarrage" + exit 1 +fi # Attendre que Nextcloud soit prêt -echo "⏳ Attente du démarrage de Nextcloud..." -for i in {1..60}; do +log "INFO" "Attente du démarrage de Nextcloud (max 2 minutes)..." +for i in {1..120}; 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 (${i}s)" break fi - if [ "$i" -eq 60 ]; then - echo "❌ Timeout: Nextcloud n'est pas prêt" + if [ "$i" -eq 120 ]; then + log "ERROR" "Timeout: Nextcloud n'est pas prêt après 2 minutes" + log "ERROR" "Vérifiez les logs: docker-compose logs nextcloud" exit 1 fi sleep 1 done # Désactiver temporairement le mode maintenance pour permettre l'upgrade -echo "▶️ Préparation de l'upgrade..." -docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off || true +log "INFO" "Préparation de l'upgrade..." +docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE" || true MAINTENANCE_ENABLED=false -# Upgrade via OCC (qui activera son propre mode maintenance) -echo "⬆️ Lancement de l'upgrade..." -if ! docker-compose exec -T -u www-data nextcloud php occ upgrade; then - echo "❌ Erreur lors de l'upgrade" +# Upgrade via OCC +log "INFO" "Lancement de l'upgrade..." +if ! docker-compose exec -T -u www-data nextcloud php occ upgrade 2>&1 | tee -a "$LOG_FILE"; then + log "ERROR" "Erreur lors de l'upgrade" exit 1 fi -# Scan et indices (non bloquant) -echo "🔍 Scan des fichiers..." -docker-compose exec -T -u www-data nextcloud php occ files:scan --all || echo "⚠️ Avertissement: Erreur lors du scan" +# Afficher la nouvelle version +log "INFO" "Nouvelle version:" +docker-compose exec -T -u www-data nextcloud php occ status 2>&1 | tee -a "$LOG_FILE" || log "WARN" "Impossible d'obtenir le nouveau statut" -echo "📊 Ajout des indices manquants..." -docker-compose exec -T -u www-data nextcloud php occ db:add-missing-indices || echo "⚠️ Avertissement: Erreur lors de l'ajout des indices" +# Opérations de maintenance post-upgrade (non bloquantes) +log "INFO" "Scan des fichiers..." +docker-compose exec -T -u www-data nextcloud php occ files:scan --all 2>&1 | tee -a "$LOG_FILE" || log "WARN" "Erreur lors du scan" -echo "🔧 Conversion des colonnes..." -docker-compose exec -T -u www-data nextcloud php occ db:convert-filecache-bigint --no-interaction || echo "⚠️ Avertissement: Erreur lors de la conversion" +log "INFO" "Ajout des indices manquants..." +docker-compose exec -T -u www-data nextcloud php occ db:add-missing-indices 2>&1 | tee -a "$LOG_FILE" || log "WARN" "Erreur lors de l'ajout des indices" -# Désactiver maintenance -echo "▶️ Désactivation du mode maintenance" -if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off; then +log "INFO" "Conversion des colonnes BigInt..." +docker-compose exec -T -u www-data nextcloud php occ db:convert-filecache-bigint --no-interaction 2>&1 | tee -a "$LOG_FILE" || log "WARN" "Erreur lors de la conversion" + +# Désactiver le mode maintenance +log "INFO" "Désactivation du mode maintenance..." +if docker-compose exec -T -u www-data nextcloud php occ maintenance:mode --off 2>>"$LOG_FILE"; then MAINTENANCE_ENABLED=false - echo "✅ Mise à jour terminée !" + log "INFO" "Mode maintenance désactivé" else - echo "⚠️ Attention: Impossible de désactiver le mode maintenance" - echo " Exécutez manuellement: make occ maintenance:mode --off" + log "WARN" "Impossible de désactiver le mode maintenance" + log "WARN" "Exécutez manuellement: make occ maintenance:mode --off" fi -docker-compose exec -T -u www-data nextcloud php occ status +# Statut final +log "INFO" "=== Mise à jour terminée ===" +log "INFO" "Vérifiez que tout fonctionne: make health" +docker-compose exec -T -u www-data nextcloud php occ status 2>&1 | tee -a "$LOG_FILE" || true