- Corrections de bugs (14): grep portable, df robuste, rollback reel, retention intelligente, nettoyage cible, lock anti-concurrence, etc. - Nouveaux fichiers: setup.sh (installateur tout-en-un), restore.sh (restauration standalone), gitea-scripts.conf (config unifiee) - Features: backup distant (rsync/SCP), lock files, logrotate, dry-run, healthchecks Docker, notifications Telegram/ntfy.sh - Documentation enrichie avec diagrammes d'architecture mermaid
415 lines
17 KiB
Bash
415 lines
17 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# restore.sh — Restauration Gitea depuis un backup
|
|
# =============================================================================
|
|
# Usage :
|
|
# bash restore.sh <chemin_backup> → Restauration interactive
|
|
# bash restore.sh <chemin_backup> --force → Sans confirmation
|
|
# bash restore.sh <chemin_backup> --dry-run → Vérification sans exécuter
|
|
#
|
|
# Exemples :
|
|
# bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000
|
|
# bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000 --dry-run
|
|
# =============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
# ─── Couleurs ───────────────────────────────────────────────────────────────
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
ok() { echo -e " ${GREEN}✅${NC} $1"; }
|
|
warn() { echo -e " ${YELLOW}⚠️${NC} $1"; }
|
|
err() { echo -e " ${RED}❌${NC} $1"; }
|
|
info() { echo -e " ${CYAN}📋${NC} $1"; }
|
|
|
|
# ─── Usage ──────────────────────────────────────────────────────────────────
|
|
usage() {
|
|
cat << EOF
|
|
Usage: $0 <chemin_backup> [OPTIONS]
|
|
|
|
Arguments :
|
|
chemin_backup Dossier du backup à restaurer (ex: gitea-backup-YYYYMMDD-HHMMSS)
|
|
|
|
Options :
|
|
--force Ne pas demander confirmation
|
|
--dry-run Affiche ce qui serait fait sans exécuter
|
|
--help Cette aide
|
|
|
|
Variables d'environnement :
|
|
TARGET_COMPOSE_DIR Dossier docker-compose cible (défaut: ~/dev/git/outils/docker/gitea)
|
|
TARGET_VOLUME_DATA Volume data cible (défaut: /DOCKER_CONFIG/gitea/data)
|
|
TARGET_VOLUME_MYSQL Volume MySQL cible (défaut: /DOCKER_CONFIG/gitea/mysql)
|
|
DB_ROOT_PASSWORD Mot de passe root MySQL (défaut: gitea)
|
|
DB_NAME Nom base de données (défaut: gitea)
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# ─── Vérifications ──────────────────────────────────────────────────────────
|
|
check_prerequisites() {
|
|
local missing=0
|
|
|
|
if ! command -v docker &>/dev/null; then
|
|
err "Docker n'est pas installé"
|
|
missing=1
|
|
fi
|
|
|
|
if ! command -v tar &>/dev/null; then
|
|
err "tar n'est pas installé"
|
|
missing=1
|
|
fi
|
|
|
|
if docker compose version &>/dev/null; then
|
|
DOCKER_COMPOSE="docker compose"
|
|
elif command -v docker-compose &>/dev/null; then
|
|
DOCKER_COMPOSE="docker-compose"
|
|
else
|
|
err "Ni 'docker compose' ni 'docker-compose' n'est disponible"
|
|
missing=1
|
|
fi
|
|
|
|
return $missing
|
|
}
|
|
|
|
# ─── Lister les backups disponibles ─────────────────────────────────────────
|
|
list_backups() {
|
|
local dir="${1:-.}"
|
|
echo ""
|
|
echo "Backups disponibles :"
|
|
echo "─────────────────────"
|
|
local count=0
|
|
for d in "$dir"/gitea-backup-*; do
|
|
if [[ -d "$d" ]]; then
|
|
count=$((count + 1))
|
|
local info_file="$d/backup-info.txt"
|
|
local date_info=""
|
|
if [[ -f "$info_file" ]]; then
|
|
date_info=$(grep "^Date" "$info_file" 2>/dev/null | sed 's/Date[[:space:]]*:[[:space:]]*//' || echo "inconnue")
|
|
fi
|
|
echo " $count. $(basename "$d") ($date_info)"
|
|
fi
|
|
done
|
|
|
|
if [[ $count -eq 0 ]]; then
|
|
echo " Aucun backup trouvé dans $dir"
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Utilisez : $0 <chemin_backup>"
|
|
return 0
|
|
}
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
|
|
FORCE=false
|
|
DRY_RUN=false
|
|
BACKUP_PATH=""
|
|
|
|
# Détection docker compose (par défaut, sera surchargé après check)
|
|
DOCKER_COMPOSE="docker compose"
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--force) FORCE=true ;;
|
|
--dry-run) DRY_RUN=true ;;
|
|
--help|-h) usage ;;
|
|
--*) echo "Option inconnue : $arg"; usage ;;
|
|
*)
|
|
if [[ -z "$BACKUP_PATH" ]]; then
|
|
BACKUP_PATH="$arg"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Si aucun backup spécifié, lister les backups et suggérer
|
|
if [[ -z "$BACKUP_PATH" ]]; then
|
|
# Chercher dans les emplacements courants
|
|
FOUND=false
|
|
for search_dir in "/NFS/BACKUP/DOCKER/dev-prod/gitea" "$HOME/backups/gitea" "."; do
|
|
if [[ -d "$search_dir" ]]; then
|
|
list_backups "$search_dir" && FOUND=true && break
|
|
fi
|
|
done
|
|
if [[ "$FOUND" != "true" ]]; then
|
|
echo ""
|
|
echo "Usage: $0 <chemin_backup> [OPTIONS]"
|
|
echo " $0 --help pour plus d'informations"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Résoudre le chemin absolu
|
|
BACKUP_PATH="$(cd "$(dirname "$BACKUP_PATH")" 2>/dev/null && pwd)/$(basename "$BACKUP_PATH")" || BACKUP_PATH="$(realpath "$BACKUP_PATH" 2>/dev/null || echo "$BACKUP_PATH")"
|
|
|
|
# Vérifier que le dossier de backup existe et est valide
|
|
if [[ ! -d "$BACKUP_PATH" ]]; then
|
|
err "Dossier de backup introuvable : $BACKUP_PATH"
|
|
echo ""
|
|
echo "Recherche de backups dans les emplacements standards..."
|
|
for search_dir in "/NFS/BACKUP/DOCKER/dev-prod/gitea" "$HOME/backups/gitea" "."; do
|
|
if [[ -d "$search_dir" ]]; then
|
|
list_backups "$search_dir"
|
|
break
|
|
fi
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
# Vérifier les fichiers requis
|
|
MISSING_FILES=()
|
|
for required_file in "gitea-mysql.tar.gz" "gitea-data.tar.gz" "docker-compose.yml"; do
|
|
if [[ ! -f "$BACKUP_PATH/$required_file" ]]; then
|
|
MISSING_FILES+=("$required_file")
|
|
fi
|
|
done
|
|
|
|
# Vérifier le dump SQL (compressé ou non)
|
|
if [[ ! -f "$BACKUP_PATH/gitea-db.sql.gz" ]] && [[ ! -f "$BACKUP_PATH/gitea-db.sql" ]]; then
|
|
MISSING_FILES+=("gitea-db.sql ou gitea-db.sql.gz")
|
|
fi
|
|
|
|
if [[ ${#MISSING_FILES[@]} -gt 0 ]]; then
|
|
err "Fichiers requis manquants dans le backup :"
|
|
for f in "${MISSING_FILES[@]}"; do
|
|
err " - $f"
|
|
done
|
|
echo ""
|
|
warn "Ce dossier ne semble pas être un backup Gitea valide."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "════════════════════════════════════════════════════════"
|
|
echo "🔄 RESTAURATION GITEA"
|
|
echo "════════════════════════════════════════════════════════"
|
|
echo ""
|
|
|
|
# ─── Configuration cible ────────────────────────────────────────────────────
|
|
TARGET_COMPOSE_DIR="${TARGET_COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}"
|
|
TARGET_VOLUME_DATA="${TARGET_VOLUME_DATA:-/DOCKER_CONFIG/gitea/data}"
|
|
TARGET_VOLUME_MYSQL="${TARGET_VOLUME_MYSQL:-/DOCKER_CONFIG/gitea/mysql}"
|
|
DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-gitea}"
|
|
DB_NAME="${DB_NAME:-gitea}"
|
|
DB_USER="${DB_USER:-gitea}"
|
|
|
|
# ─── Afficher les infos du backup ────────────────────────────────────────────
|
|
BACKUP_NAME=$(basename "$BACKUP_PATH")
|
|
BACKUP_DATE=${BACKUP_NAME#gitea-backup-}
|
|
|
|
if [[ -f "$BACKUP_PATH/backup-info.txt" ]]; then
|
|
info "Informations du backup :"
|
|
while IFS= read -r line; do
|
|
echo " $line"
|
|
done < "$BACKUP_PATH/backup-info.txt"
|
|
echo ""
|
|
else
|
|
info "Backup : $BACKUP_NAME"
|
|
echo ""
|
|
fi
|
|
|
|
info "Configuration cible :"
|
|
echo " Compose dir : $TARGET_COMPOSE_DIR"
|
|
echo " Volume data : $TARGET_VOLUME_DATA"
|
|
echo " Volume MySQL: $TARGET_VOLUME_MYSQL"
|
|
echo " DB name : $DB_NAME"
|
|
echo ""
|
|
|
|
# ─── Vérifier les prérequis ─────────────────────────────────────────────────
|
|
check_prerequisites || {
|
|
echo ""
|
|
err "Prérequis manquants — impossible de continuer"
|
|
exit 1
|
|
}
|
|
|
|
# ─── Dry-run ────────────────────────────────────────────────────────────────
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo "🔍 DRY RUN — aucune action ne sera effectuée"
|
|
echo ""
|
|
echo "Actions qui seraient exécutées :"
|
|
echo ""
|
|
echo " 1. Arrêter les conteneurs Gitea existants"
|
|
echo " cd $TARGET_COMPOSE_DIR && $DOCKER_COMPOSE down"
|
|
echo ""
|
|
echo " 2. Créer les répertoires cibles"
|
|
echo " mkdir -p $TARGET_VOLUME_DATA $TARGET_VOLUME_MYSQL"
|
|
echo ""
|
|
echo " 3. Restaurer le volume MySQL"
|
|
echo " rm -rf ${TARGET_VOLUME_MYSQL}/*"
|
|
echo " tar -xzf $BACKUP_PATH/gitea-mysql.tar.gz -C $(dirname "$TARGET_VOLUME_MYSQL")"
|
|
echo ""
|
|
echo " 4. Restaurer le volume Gitea"
|
|
echo " rm -rf ${TARGET_VOLUME_DATA}/*"
|
|
echo " tar -xzf $BACKUP_PATH/gitea-data.tar.gz -C $(dirname "$TARGET_VOLUME_DATA")"
|
|
echo ""
|
|
echo " 5. Copier docker-compose.yml"
|
|
echo " cp $BACKUP_PATH/docker-compose.yml $TARGET_COMPOSE_DIR/"
|
|
echo ""
|
|
if [[ -f "$BACKUP_PATH/app.ini" ]]; then
|
|
echo " 6. Restaurer app.ini"
|
|
echo " mkdir -p ${TARGET_VOLUME_DATA}/gitea/conf"
|
|
echo " cp $BACKUP_PATH/app.ini ${TARGET_VOLUME_DATA}/gitea/conf/"
|
|
echo ""
|
|
fi
|
|
echo " 7. Démarrer les conteneurs"
|
|
echo " cd $TARGET_COMPOSE_DIR && $DOCKER_COMPOSE up -d"
|
|
echo ""
|
|
echo " 8. Restaurer la base de données"
|
|
echo " docker exec -i gitea_db mysql -u root -p$DB_ROOT_PASSWORD $DB_NAME < dump"
|
|
echo ""
|
|
echo " 9. Redémarrer"
|
|
echo " $DOCKER_COMPOSE restart"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
# ─── Confirmation ───────────────────────────────────────────────────────────
|
|
if [[ "$FORCE" != "true" ]]; then
|
|
echo "⚠️ ATTENTION : Cette opération va ÉCRASER l'installation Gitea existante !"
|
|
echo " Toute donnée non sauvegardée sera PERDUE."
|
|
echo ""
|
|
echo " Les conteneurs seront arrêtés et les volumes remplacés."
|
|
echo ""
|
|
read -p "Taper 'RESTORE' (en majuscules) pour confirmer : " CONFIRM
|
|
if [[ "$CONFIRM" != "RESTORE" ]]; then
|
|
echo "❌ Restauration annulée."
|
|
exit 0
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# RESTAURATION
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
|
|
TIMING_START=$(date +%s)
|
|
|
|
# ─── Étape 1 : Arrêter les conteneurs existants ─────────────────────────────
|
|
echo "📦 Étape 1/7 : Arrêt des conteneurs existants..."
|
|
if [[ -f "$TARGET_COMPOSE_DIR/docker-compose.yml" ]] || [[ -f "$TARGET_COMPOSE_DIR/$COMPOSE_FILE" ]]; then
|
|
cd "$TARGET_COMPOSE_DIR" 2>/dev/null && $DOCKER_COMPOSE down 2>&1 || true
|
|
fi
|
|
ok "Conteneurs arrêtés"
|
|
|
|
# ─── Étape 2 : Créer les répertoires ────────────────────────────────────────
|
|
echo "📦 Étape 2/7 : Création des répertoires cibles..."
|
|
mkdir -p "$TARGET_VOLUME_DATA" "$TARGET_VOLUME_MYSQL" "$TARGET_COMPOSE_DIR"
|
|
ok "Répertoires créés"
|
|
|
|
# ─── Étape 3 : Restaurer volume MySQL ───────────────────────────────────────
|
|
echo "📦 Étape 3/7 : Restauration volume MySQL..."
|
|
rm -rf "${TARGET_VOLUME_MYSQL:?}"/*
|
|
if ! tar -xzf "$BACKUP_PATH/gitea-mysql.tar.gz" -C "$(dirname "$TARGET_VOLUME_MYSQL")"; then
|
|
err "Échec de l'extraction du volume MySQL"
|
|
exit 1
|
|
fi
|
|
ok "Volume MySQL restauré"
|
|
|
|
# ─── Étape 4 : Restaurer volume Gitea ───────────────────────────────────────
|
|
echo "📦 Étape 4/7 : Restauration volume Gitea..."
|
|
rm -rf "${TARGET_VOLUME_DATA:?}"/*
|
|
if ! tar -xzf "$BACKUP_PATH/gitea-data.tar.gz" -C "$(dirname "$TARGET_VOLUME_DATA")"; then
|
|
err "Échec de l'extraction du volume Gitea"
|
|
exit 1
|
|
fi
|
|
ok "Volume Gitea restauré"
|
|
|
|
# ─── Étape 5 : Copier docker-compose ────────────────────────────────────────
|
|
echo "📦 Étape 5/7 : Copie docker-compose et configuration..."
|
|
cp "$BACKUP_PATH/docker-compose.yml" "$TARGET_COMPOSE_DIR/"
|
|
ok "docker-compose.yml copié"
|
|
|
|
if [[ -f "$BACKUP_PATH/app.ini" ]]; then
|
|
mkdir -p "${TARGET_VOLUME_DATA}/gitea/conf"
|
|
cp "$BACKUP_PATH/app.ini" "${TARGET_VOLUME_DATA}/gitea/conf/app.ini"
|
|
ok "app.ini restauré"
|
|
fi
|
|
|
|
# ─── Étape 6 : Démarrer les conteneurs ──────────────────────────────────────
|
|
echo "📦 Étape 6/7 : Démarrage des conteneurs..."
|
|
cd "$TARGET_COMPOSE_DIR"
|
|
if ! $DOCKER_COMPOSE up -d; then
|
|
err "Échec du démarrage des conteneurs"
|
|
warn "Vérifiez les logs : $DOCKER_COMPOSE logs"
|
|
exit 1
|
|
fi
|
|
ok "Conteneurs démarrés"
|
|
|
|
# Attendre que MySQL soit prêt
|
|
echo " ⏳ Attente démarrage MySQL..."
|
|
MYSQL_READY=false
|
|
sleep 10
|
|
for i in $(seq 1 30); do
|
|
if docker exec gitea_db mysqladmin ping -u root -p"$DB_ROOT_PASSWORD" --silent 2>/dev/null; then
|
|
echo " ✅ MySQL prêt après $((10 + i))s"
|
|
MYSQL_READY=true
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if [[ "$MYSQL_READY" != "true" ]]; then
|
|
warn "MySQL n'a pas répondu — on tente la restauration DB quand même..."
|
|
fi
|
|
|
|
# ─── Étape 7 : Restaurer la base de données ─────────────────────────────────
|
|
echo "📦 Étape 7/7 : Restauration base de données..."
|
|
|
|
# Créer la base de données si elle n'existe pas
|
|
docker exec gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" -e "CREATE DATABASE IF NOT EXISTS \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" 2>/dev/null || true
|
|
|
|
# Restaurer le dump
|
|
if [[ -f "$BACKUP_PATH/gitea-db.sql.gz" ]]; then
|
|
gunzip -c "$BACKUP_PATH/gitea-db.sql.gz" | docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" 2>&1 || {
|
|
err "Échec de la restauration du dump SQL (compressé)"
|
|
exit 1
|
|
}
|
|
elif [[ -f "$BACKUP_PATH/gitea-db.sql" ]]; then
|
|
docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" < "$BACKUP_PATH/gitea-db.sql" 2>&1 || {
|
|
err "Échec de la restauration du dump SQL"
|
|
exit 1
|
|
}
|
|
fi
|
|
ok "Base de données restaurée"
|
|
|
|
# ─── Redémarrage final ──────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "🔄 Redémarrage final des conteneurs..."
|
|
$DOCKER_COMPOSE restart 2>&1 || true
|
|
|
|
# ─── Vérification santé ─────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "🏥 Vérification que Gitea répond..."
|
|
for i in $(seq 1 30); do
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000" 2>/dev/null || echo "000")
|
|
if [[ "$STATUS" == "200" || "$STATUS" == "302" || "$STATUS" == "303" ]]; then
|
|
ok "Gitea répond (HTTP $STATUS) après ${i}s"
|
|
break
|
|
fi
|
|
if [[ $i -eq 30 ]]; then
|
|
warn "Gitea ne répond pas encore — vérifiez les logs"
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# ─── Résumé ─────────────────────────────────────────────────────────────────
|
|
TIMING_END=$(date +%s)
|
|
DURATION=$((TIMING_END - TIMING_START))
|
|
|
|
echo ""
|
|
echo "════════════════════════════════════════════════════════"
|
|
echo "✅ RESTAURATION TERMINÉE !"
|
|
echo ""
|
|
echo " Durée : ${DURATION}s"
|
|
echo " Backup : $BACKUP_NAME"
|
|
echo " Cible : $TARGET_COMPOSE_DIR"
|
|
echo " Gitea : http://localhost:3000"
|
|
echo ""
|
|
echo " Logs : $DOCKER_COMPOSE logs -f"
|
|
echo " Status : $DOCKER_COMPOSE ps"
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
exit 0
|