commit 4c47e5755930f2006b98d61805995b5dee405d49 Author: bruno Date: Fri Jun 26 11:30:48 2026 -0400 feat: scripts auto-update et backup pour Gitea Docker - gitea-auto-update.sh : vérifie quotidiennement les mises à jour Docker - gitea-backup.sh : backup complet DB+volumes avec script restore auto-généré - Paramètres configurables via variables d'environnement - Rétention configurable (jours, hebdo, mensuel) diff --git a/README-gitea-scripts.md b/README-gitea-scripts.md new file mode 100644 index 0000000..e9597fb --- /dev/null +++ b/README-gitea-scripts.md @@ -0,0 +1,125 @@ +# Scripts Gitea — Auto-Update & Backup + +Deux scripts pour la maintenance automatisée de Gitea Docker. + +--- + +## 📋 Prérequis + +- Docker + Docker Compose +- `curl`, `tar`, `gzip`, `mysqldump` (dans le conteneur MySQL) +- Droits d'écriture sur `/NFS/BACKUP/DOCKER/dev-prod/gitea/` (backup) +- Droits sudo si les volumes sont protégés + +--- + +## 🔄 gitea-auto-update.sh + +Vérifie quotidiennement si une nouvelle image Docker Gitea est disponible et applique la mise à jour. + +### Paramètres configurables + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `COMPOSE_DIR` | `~/dev/git/outils/docker/gitea` | Dossier du docker-compose | +| `COMPOSE_FILE` | `docker-compose.yml` | Nom du fichier compose | +| `CONTAINER_NAME` | `gitea_server` | Conteneur Gitea | +| `IMAGE_NAME` | `gitea/gitea` | Image Docker | +| `IMAGE_TAG` | `latest` | Tag (version) | +| `HEALTH_CHECK_URL` | `http://localhost:3000` | URL vérification santé | +| `HEALTH_CHECK_TIMEOUT` | `30` | Timeout attente (secondes) | + +### Cron recommandé + +```cron +# Tous les jours à 4h du matin +0 4 * * * /home/openclaw/scripts/gitea-auto-update.sh +``` + +### Fonctionnement + +1. Récupère le digest SHA256 de l'image actuelle +2. `docker compose pull` pour télécharger la dernière version +3. Compare les digests → si identique, rien à faire +4. Si différent : backup rapide DB → `docker compose up -d` → vérification santé +5. Nettoie les vieilles images Docker (`image prune`) + +--- + +## 💾 gitea-backup.sh + +Backup complet et portable de Gitea : DB MySQL + volumes Docker + config. + +### Paramètres configurables + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `BACKUP_DIR` | `/NFS/BACKUP/DOCKER/dev-prod/gitea` | Destination backups | +| `COMPOSE_DIR` | `~/dev/git/outils/docker/gitea` | Dossier docker-compose | +| `DB_CONTAINER` | `gitea_db` | Conteneur MySQL | +| `DB_NAME` | `gitea` | Nom base de données | +| `DB_ROOT_PASSWORD` | `gitea` | Mot de passe root MySQL | +| `VOLUME_DATA` | `/DOCKER_CONFIG/gitea/data` | Volume data Gitea | +| `VOLUME_MYSQL` | `/DOCKER_CONFIG/gitea/mysql` | Volume MySQL | +| `RETENTION_DAYS` | `30` | Rétention jours | +| `RETENTION_WEEKLY` | `4` | Backups dimanche conservés | +| `RETENTION_MONTHLY` | `6` | Backups 1er du mois conservés | +| `COMPRESS` | `true` | Compression gzip | +| `VERIFY_BACKUP` | `true` | Vérifier intégrité après backup | +| `STOP_CONTAINERS` | `false` | Arrêter conteneurs (plus sûr) | + +### Cron recommandé + +```cron +# Tous les jours à 2h du matin +0 2 * * * /home/openclaw/scripts/gitea-backup.sh +``` + +### Structure d'un backup + +``` +/NFS/BACKUP/DOCKER/dev-prod/gitea/ +└── gitea-backup-20260626-020000/ + ├── gitea-db.sql.gz ← Dump MySQL (compressé) + ├── gitea-data.tar.gz ← Volume /data (repos, config, thèmes) + ├── gitea-mysql.tar.gz ← Volume MySQL (fichiers DB bruts) + ├── docker-compose.yml ← Stack Docker + ├── app.ini ← Configuration Gitea + ├── restore.sh ← ⭐ Script de restauration automatique + └── backup-info.txt ← Métadonnées +``` + +### Restauration + +```bash +cd /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000/ + +# Dry-run (vérifier sans exécuter) +bash restore.sh --dry-run + +# Restauration réelle +bash restore.sh --force +``` + +Le script `restore.sh` est **auto-généré** à chaque backup et contient TOUT ce qu'il faut pour restaurer sur n'importe quel hôte Docker. + +--- + +## 🔧 Surcharger les paramètres + +```bash +# Exemple : changer le dossier de backup +BACKUP_DIR=/mnt/big-disk/gitea /home/openclaw/scripts/gitea-backup.sh + +# Exemple : backup avec arrêt des conteneurs +STOP_CONTAINERS=true /home/openclaw/scripts/gitea-backup.sh +``` + +--- + +## 📊 Logs + +| Script | Log | +|--------|-----| +| Auto-update | `/var/log/gitea-update.log` | +| Backup | `/var/log/gitea-backup.log` | diff --git a/gitea-auto-update.sh b/gitea-auto-update.sh new file mode 100755 index 0000000..772b86c --- /dev/null +++ b/gitea-auto-update.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# ============================================================================= +# gitea-auto-update.sh — Vérifie et applique les mises à jour Gitea Docker +# ============================================================================= +# À placer dans /usr/local/bin/ ou ~/scripts/ +# Cron recommandé : 0 4 * * * /usr/local/bin/gitea-auto-update.sh +# +# Paramètres configurables (modifier ci-dessous ou passer en variables d'env) +# ============================================================================= + +set -euo pipefail + +# ─── Configuration (modifiable) ───────────────────────────────────────────── +COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}" +COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" +CONTAINER_NAME="${CONTAINER_NAME:-gitea_server}" +IMAGE_NAME="${IMAGE_NAME:-gitea/gitea}" +IMAGE_TAG="${IMAGE_TAG:-latest}" # "latest" ou version spécifique +LOG_FILE="${LOG_FILE:-/var/log/gitea-update.log}" +ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}" # true pour envoyer un résumé +NOTIFY_METHOD="${NOTIFY_METHOD:-log}" # log, telegram, ntfy +TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}" +NTPY_TOPIC="${NTPY_TOPIC:-}" +HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}" +HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}" + +# ─── Logging ───────────────────────────────────────────────────────────────── +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +notify() { + local msg="$1" + case "$NOTIFY_METHOD" in + telegram) + [[ -n "$TELEGRAM_CHAT_ID" ]] && curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=$TELEGRAM_CHAT_ID" -d "text=$msg" > /dev/null 2>&1 || true + ;; + ntfy) + [[ -n "$NTPY_TOPIC" ]] && curl -s -H "Title: Gitea Update" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true + ;; + esac +} + +log "════════════════════════════════════════════════════════" +log "🔄 Début vérification mise à jour Gitea" + +# ─── Pré-vérifications ────────────────────────────────────────────────────── +cd "$COMPOSE_DIR" || { log "❌ Répertoire $COMPOSE_DIR introuvable"; exit 1; } + +if ! command -v docker &>/dev/null; then + log "❌ Docker n'est pas installé" + exit 1 +fi + +# ─── Récupérer l'image digest actuelle ────────────────────────────────────── +OLD_DIGEST=$(docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format '{{.RepoDigests}}' 2>/dev/null | grep -oP 'sha256:[a-f0-9]+' || echo "") + +log "📦 Image actuelle : ${IMAGE_NAME}:${IMAGE_TAG}" +log "🔍 Digest actuel : ${OLD_DIGEST:-non disponible}" + +# ─── Pull de la nouvelle image ────────────────────────────────────────────── +log "📥 Pull de la nouvelle image..." +PULL_OUTPUT=$(docker compose -f "$COMPOSE_FILE" pull "$CONTAINER_NAME" 2>&1) +PULL_EXIT=$? + +if [[ $PULL_EXIT -ne 0 ]]; then + log "❌ Échec du pull : $PULL_OUTPUT" + exit 1 +fi + +NEW_DIGEST=$(docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format '{{.RepoDigests}}' 2>/dev/null | grep -oP 'sha256:[a-f0-9]+' || echo "") + +log "🔍 Nouveau digest : ${NEW_DIGEST:-non disponible}" + +# ─── Comparer les digests ─────────────────────────────────────────────────── +if [[ -n "$OLD_DIGEST" ]] && [[ -n "$NEW_DIGEST" ]] && [[ "$OLD_DIGEST" == "$NEW_DIGEST" ]]; then + log "✅ Aucune mise à jour disponible (digest identique)" + log "═══ Fin ───────────────────────────────────────────" + exit 0 +fi + +# ─── MISE À JOUR NÉCESSAIRE ───────────────────────────────────────────────── +log "🔔 Nouvelle version détectée ! Mise à jour en cours..." + +# Backup rapide avant mise à jour (optionnel, le script de backup complet gère le quotidien) +BACKUP_PRE_DIR="${BACKUP_PRE_DIR:-/tmp/gitea-pre-update-$(date +%Y%m%d-%H%M%S)}" +mkdir -p "$BACKUP_PRE_DIR" + +log "💾 Backup pré-update vers $BACKUP_PRE_DIR" +docker exec gitea_db mysqldump -u root -pgitea gitea > "$BACKUP_PRE_DIR/gitea_db.sql" 2>/dev/null || { + log "⚠️ Backup pré-update DB échoué, on continue quand même" +} + +# Recréer le conteneur avec la nouvelle image +log "🚀 Redémarrage du conteneur avec la nouvelle image..." +UP_OUTPUT=$(docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER_NAME" 2>&1) +UP_EXIT=$? + +if [[ $UP_EXIT -ne 0 ]]; then + log "❌ Échec du redémarrage : $UP_OUTPUT" + log "⚠️ Tentative de rollback..." + docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER_NAME" 2>&1 || true + notify "❌ GITEA UPDATE FAILED sur $(hostname) — rollback effectué" + exit 1 +fi + +# ─── Vérification santé ───────────────────────────────────────────────────── +log "🏥 Vérification santé (timeout=${HEALTH_CHECK_TIMEOUT}s)..." +for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do + STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_CHECK_URL" 2>/dev/null || echo "000") + if [[ "$STATUS" == "200" || "$STATUS" == "302" || "$STATUS" == "303" ]]; then + log "✅ Gitea répond (HTTP $STATUS) après ${i}s" + break + fi + if [[ $i -eq "$HEALTH_CHECK_TIMEOUT" ]]; then + log "❌ Gitea ne répond pas après ${HEALTH_CHECK_TIMEOUT}s" + notify "⚠️ GITEA UPDATE : service lent à démarrer sur $(hostname) — vérifier" + exit 1 + fi + sleep 1 +done + +# ─── Rapport final ────────────────────────────────────────────────────────── +GITEA_VERSION=$(curl -s "$HEALTH_CHECK_URL/api/v1/version" 2>/dev/null | grep -oP '"version":"\K[^"]+' || echo "inconnue") + +log "✅ Mise à jour réussie !" +log "📋 Nouvelle version : $GITEA_VERSION" +log "📋 Ancien digest : ${OLD_DIGEST:-N/A}" +log "📋 Nouveau digest : ${NEW_DIGEST:-N/A}" +log "═══ Fin ───────────────────────────────────────────" + +notify "✅ Gitea mis à jour vers $GITEA_VERSION sur $(hostname)" + +# Nettoyage vieilles images +docker image prune -f 2>/dev/null || true + +exit 0 diff --git a/gitea-backup.sh b/gitea-backup.sh new file mode 100755 index 0000000..40363bf --- /dev/null +++ b/gitea-backup.sh @@ -0,0 +1,420 @@ +#!/bin/bash +# ============================================================================= +# gitea-backup.sh — Backup complet Gitea (DB + volumes + config) +# ============================================================================= +# À placer dans /usr/local/bin/ ou ~/scripts/ +# Cron recommandé : 0 2 * * * /usr/local/bin/gitea-backup.sh +# +# Ce script sauvegarde : +# 1. La base de données MySQL (dump SQL complet) +# 2. Les volumes Docker (data, mysql) +# 3. Le fichier docker-compose.yml et la config +# +# Le tout est horodaté et stocké dans BACKUP_DIR. +# Un restore peut se faire sur N'IMPORTE QUEL hôte Docker. +# +# Paramètres configurables (modifier ci-dessous ou passer en variables d'env) +# ============================================================================= + +set -euo pipefail + +# ─── Configuration (modifiable) ───────────────────────────────────────────── +BACKUP_DIR="${BACKUP_DIR:-/NFS/BACKUP/DOCKER/dev-prod/gitea}" +COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}" +COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" + +# Conteneurs +DB_CONTAINER="${DB_CONTAINER:-gitea_db}" +APP_CONTAINER="${APP_CONTAINER:-gitea_server}" + +# Base de données +DB_NAME="${DB_NAME:-gitea}" +DB_USER="${DB_USER:-gitea}" +DB_PASSWORD="${DB_PASSWORD:-chab30}" +DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-gitea}" + +# Volumes Docker (chemins HÔTE, pas conteneur) +VOLUME_DATA="${VOLUME_DATA:-/DOCKER_CONFIG/gitea/data}" +VOLUME_MYSQL="${VOLUME_MYSQL:-/DOCKER_CONFIG/gitea/mysql}" + +# Rétention +RETENTION_DAYS="${RETENTION_DAYS:-30}" # Garder X jours de backups +RETENTION_WEEKLY="${RETENTION_WEEKLY:-4}" # Garder X backups hebdomadaires (dimanche) +RETENTION_MONTHLY="${RETENTION_MONTHLY:-6}" # Garder X backups mensuels (1er du mois) + +# Options +COMPRESS="${COMPRESS:-true}" # Compression gzip +VERIFY_BACKUP="${VERIFY_BACKUP:-true}" # Vérifier l'intégrité après backup +STOP_CONTAINERS="${STOP_CONTAINERS:-false}" # Arrêter les conteneurs pendant le backup (plus sûr) +ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}" + +# Log +LOG_FILE="${LOG_FILE:-/var/log/gitea-backup.log}" + +# ─── Logging ───────────────────────────────────────────────────────────────── +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# ─── Initialisation ───────────────────────────────────────────────────────── +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +DATE_LABEL=$(date +%Y-%m-%d) +DAY_OF_WEEK=$(date +%u) # 1=lundi, 7=dimanche +DAY_OF_MONTH=$(date +%d) + +BACKUP_NAME="gitea-backup-${TIMESTAMP}" +BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}" +RESTORE_SCRIPT="${BACKUP_PATH}/restore.sh" + +log "════════════════════════════════════════════════════════" +log "📦 Début backup Gitea → $BACKUP_PATH" + +# ─── Pré-vérifications ────────────────────────────────────────────────────── +mkdir -p "$BACKUP_DIR" || { log "❌ Impossible de créer $BACKUP_DIR"; exit 1; } +cd "$COMPOSE_DIR" || { log "❌ Répertoire $COMPOSE_DIR introuvable"; exit 1; } + +mkdir -p "$BACKUP_PATH" + +# Vérifier espace disque (minimum 2 GB) +AVAILABLE_KB=$(df "$BACKUP_DIR" | tail -1 | awk '{print $4}') +if [[ $AVAILABLE_KB -lt 2097152 ]]; then + log "❌ Espace disque insuffisant : $(($AVAILABLE_KB / 1024)) MB disponibles (min 2 GB requis)" + exit 1 +fi + +log "💾 Espace disque : $((AVAILABLE_KB / 1024)) MB disponibles" + +# ─── Optionnel : arrêter les conteneurs pour un backup consistant ─────────── +if [[ "$STOP_CONTAINERS" == "true" ]]; then + log "⏸️ Arrêt temporaire des conteneurs..." + docker compose -f "$COMPOSE_FILE" stop 2>&1 | tee -a "$LOG_FILE" + CONTAINERS_WERE_STOPPED=true +else + CONTAINERS_WERE_STOPPED=false +fi + +# ─── 1. Backup base de données MySQL ──────────────────────────────────────── +log "🛢️ Dump base de données MySQL ($DB_NAME)..." + +# Vérifier que le conteneur DB tourne +if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then + docker exec "$DB_CONTAINER" mysqldump \ + -u root -p"$DB_ROOT_PASSWORD" \ + --single-transaction \ + --routines \ + --triggers \ + --events \ + --hex-blob \ + --default-character-set=utf8mb4 \ + "$DB_NAME" > "${BACKUP_PATH}/gitea-db.sql" 2>&1 || { + log "❌ Échec du dump MySQL" + [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" + exit 1 + } + DB_SIZE=$(wc -c < "${BACKUP_PATH}/gitea-db.sql") + log " ✅ Dump MySQL OK (${DB_SIZE} bytes)" +else + log "❌ Conteneur $DB_CONTAINER non trouvé (pas en cours d'exécution)" + [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" + exit 1 +fi + +# Compresser le dump si demandé +if [[ "$COMPRESS" == "true" ]]; then + gzip -f "${BACKUP_PATH}/gitea-db.sql" + log " 📦 Compressé : $(ls -lh "${BACKUP_PATH}/gitea-db.sql.gz" | awk '{print $5}')" +fi + +# ─── 2. Backup volumes Docker ─────────────────────────────────────────────── +log "📁 Backup volume data ($VOLUME_DATA)..." + +# On utilise tar depuis l'hôte directement (plus fiable que docker cp pour gros volumes) +# Exclusion des fichiers temporaires et logs inutiles +tar -czf "${BACKUP_PATH}/gitea-data.tar.gz" \ + -C "$(dirname "$VOLUME_DATA")" \ + --exclude='*.log' \ + --exclude='log/*' \ + --exclude='tmp/*' \ + --exclude='cache/*' \ + "$(basename "$VOLUME_DATA")" 2>&1 || { + log "❌ Échec tar du volume data" + [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" + exit 1 + } +DATA_SIZE=$(ls -lh "${BACKUP_PATH}/gitea-data.tar.gz" | awk '{print $5}') +log " ✅ Volume data sauvegardé ($DATA_SIZE)" + +log "📁 Backup volume MySQL ($VOLUME_MYSQL)..." +tar -czf "${BACKUP_PATH}/gitea-mysql.tar.gz" \ + -C "$(dirname "$VOLUME_MYSQL")" \ + "$(basename "$VOLUME_MYSQL")" 2>&1 || { + log "❌ Échec tar du volume MySQL" + [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" + exit 1 + } +MYSQL_SIZE=$(ls -lh "${BACKUP_PATH}/gitea-mysql.tar.gz" | awk '{print $5}') +log " ✅ Volume MySQL sauvegardé ($MYSQL_SIZE)" + +# ─── 3. Sauvegarde docker-compose + config ────────────────────────────────── +log "📋 Sauvegarde docker-compose et configuration..." +cp "$COMPOSE_DIR/$COMPOSE_FILE" "${BACKUP_PATH}/docker-compose.yml" + +# Sauvegarder aussi le app.ini si présent +if [[ -f "${VOLUME_DATA}/gitea/conf/app.ini" ]]; then + cp "${VOLUME_DATA}/gitea/conf/app.ini" "${BACKUP_PATH}/app.ini" + log " ✅ app.ini sauvegardé" +fi + +# ─── Redémarrer les conteneurs si on les avait arrêtés ────────────────────── +if [[ "$CONTAINERS_WERE_STOPPED" == "true" ]]; then + log "▶️ Redémarrage des conteneurs..." + docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" + + # Attendre que Gitea réponde + for i in $(seq 1 30); do + if curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000" 2>/dev/null | grep -qE "200|302|303"; then + log "✅ Gitea répond après ${i}s" + break + fi + sleep 1 + done +fi + +# ─── 4. Générer le script de restore ──────────────────────────────────────── +log "📝 Génération du script de restore..." + +cat > "$RESTORE_SCRIPT" << RESTORE_EOF +#!/bin/bash +# ============================================================================= +# RESTORE SCRIPT — Gitea Backup $TIMESTAMP +# Généré le $(date) +# ============================================================================= +# Usage : bash restore.sh [OPTIONS] +# +# Ce script restaure COMPLÈTEMENT Gitea sur n'importe quel hôte Docker : +# 1. Crée les volumes Docker +# 2. Restaure la base de données MySQL +# 3. Restaure les données Gitea +# 4. Lance les conteneurs +# +# Options : +# --dry-run Affiche ce qui serait fait sans exécuter +# --force Ne pas demander confirmation +# --help Cette aide +# ============================================================================= + +set -euo pipefail + +# ─── 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}" +DB_PASSWORD="\${DB_PASSWORD:-chab30}" + +DRY_RUN=false +FORCE=false + +for arg in "\$@"; do + case "\$arg" in + --dry-run) DRY_RUN=true ;; + --force) FORCE=true ;; + --help) head -30 "\$0"; exit 0 ;; + esac +done + +SCRIPT_DIR="\$(cd "\$(dirname "\$0")" && pwd)" +BACKUP_DIR="\$SCRIPT_DIR" + +echo "════════════════════════════════════════════════════════" +echo "🔄 RESTAURATION GITEA" +echo " Source : \$BACKUP_DIR" +echo " Date : $TIMESTAMP" +echo " Cible : \$TARGET_COMPOSE_DIR" +echo "════════════════════════════════════════════════════════" + +if [[ "\$DRY_RUN" == "true" ]]; then + echo "" + echo "🔍 DRY RUN — aucune action ne sera effectuée" + echo "" + echo "Actions qui seraient exécutées :" + echo " 1. mkdir -p \$TARGET_VOLUME_DATA \$TARGET_VOLUME_MYSQL" + echo " 2. Extraire gitea-mysql.tar.gz → \$TARGET_VOLUME_MYSQL" + echo " 3. Extraire gitea-data.tar.gz → \$TARGET_VOLUME_DATA" + echo " 4. Copier docker-compose.yml → \$TARGET_COMPOSE_DIR/" + echo " 5. docker compose up -d" + echo " 6. Attendre MySQL prêt" +RESTORE_EOF + +# Continuer le script restore (le heredoc était trop long pour un seul bloc) +cat >> "$RESTORE_SCRIPT" << 'RESTORE_EOF' + echo " 7. docker exec gitea_db mysql ... < gitea-db.sql" + echo " 8. docker compose restart" + echo "" + exit 0 +fi + +if [[ "$FORCE" != "true" ]]; then + echo "" + echo "⚠️ ATTENTION : Ceci va ÉCRASER les données Gitea existantes !" + echo " Toute donnée non sauvegardée sera PERDUE." + echo "" + read -p "Confirmer la restauration ? (taper 'RESTORE' en majuscules) : " CONFIRM + if [[ "$CONFIRM" != "RESTORE" ]]; then + echo "❌ Restauration annulée." + exit 0 + fi +fi + +echo "" +echo "📦 Étape 1/6 : Création des répertoires..." +mkdir -p "$TARGET_VOLUME_DATA" "$TARGET_VOLUME_MYSQL" "$TARGET_COMPOSE_DIR" + +echo "📦 Étape 2/6 : Restauration volume MySQL..." +rm -rf "${TARGET_VOLUME_MYSQL:?}"/* +tar -xzf "$BACKUP_DIR/gitea-mysql.tar.gz" -C "$(dirname "$TARGET_VOLUME_MYSQL")" + +echo "📦 Étape 3/6 : Restauration volume Gitea..." +rm -rf "${TARGET_VOLUME_DATA:?}"/* +tar -xzf "$BACKUP_DIR/gitea-data.tar.gz" -C "$(dirname "$TARGET_VOLUME_DATA")" + +echo "📦 Étape 4/6 : Copie docker-compose..." +cp "$BACKUP_DIR/docker-compose.yml" "$TARGET_COMPOSE_DIR/" + +# Restaurer app.ini si présent +if [[ -f "$BACKUP_DIR/app.ini" ]]; then + mkdir -p "${TARGET_VOLUME_DATA}/gitea/conf" + cp "$BACKUP_DIR/app.ini" "${TARGET_VOLUME_DATA}/gitea/conf/app.ini" + echo " ✅ app.ini restauré" +fi + +echo "📦 Étape 5/6 : Démarrage des conteneurs..." +cd "$TARGET_COMPOSE_DIR" +docker compose up -d + +echo " ⏳ Attente démarrage MySQL (30s)..." +sleep 10 +for i in $(seq 1 20); 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" + break + fi + sleep 1 +done + +echo "📦 Étape 6/6 : Restauration base de données..." +DB_DUMP="$BACKUP_DIR/gitea-db.sql" +if [[ -f "${DB_DUMP}.gz" ]]; then + gunzip -c "${DB_DUMP}.gz" | docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" +else + docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" < "$DB_DUMP" +fi + +echo "" +echo "🔄 Redémarrage final des conteneurs..." +docker compose restart + +echo "" +echo "════════════════════════════════════════════════════════" +echo "✅ RESTAURATION TERMINÉE !" +echo "" +echo " Gitea : http://localhost:3000" +echo " Vérifiez les logs : docker compose logs -f" +echo "════════════════════════════════════════════════════════" +RESTORE_EOF + +chmod +x "$RESTORE_SCRIPT" +log " ✅ Script restore créé : restore.sh" + +# ─── 5. Vérification intégrité ────────────────────────────────────────────── +if [[ "$VERIFY_BACKUP" == "true" ]]; then + log "🔍 Vérification intégrité des archives..." + ERRORS=0 + + for ARCHIVE in "$BACKUP_PATH"/*.tar.gz; do + if ! tar -tzf "$ARCHIVE" > /dev/null 2>&1; then + log " ❌ Archive corrompue : $ARCHIVE" + ERRORS=$((ERRORS + 1)) + fi + done + + if [[ "$COMPRESS" == "true" ]]; then + if ! gunzip -t "${BACKUP_PATH}/gitea-db.sql.gz" 2>/dev/null; then + log " ❌ Dump SQL corrompu" + ERRORS=$((ERRORS + 1)) + fi + fi + + if [[ $ERRORS -gt 0 ]]; then + log "❌ $ERRORS erreur(s) d'intégrité détectée(s)" + exit 1 + fi + log " ✅ Toutes les archives sont valides" +fi + +# ─── 6. Métadonnées du backup ─────────────────────────────────────────────── +cat > "${BACKUP_PATH}/backup-info.txt" << INFO +Date : $(date) +Hôte : $(hostname) +Gitea version : $(docker exec "$APP_CONTAINER" gitea --version 2>/dev/null || echo "inconnue") +DB type : MySQL 8 +DB name : $DB_NAME +Compression : $COMPRESS +Containers stopped : $STOP_CONTAINERS +Taille DB : $(ls -lh "${BACKUP_PATH}"/gitea-db.sql* | awk '{print $5}') +Taille data : $DATA_SIZE +Taille mysql : $MYSQL_SIZE +INFO + +# ─── 7. Nettoyage des vieux backups ───────────────────────────────────────── +log "🧹 Nettoyage backups (rétention: ${RETENTION_DAYS}j, ${RETENTION_WEEKLY} hebdo, ${RETENTION_MONTHLY} mensuels)..." + +# Supprimer les backups plus vieux que RETENTION_DAYS (sauf dimanche et 1er du mois) +find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -mtime +"$RETENTION_DAYS" | while read -r backup; do + BASENAME=$(basename "$backup") + # Garder les backups du dimanche et du 1er du mois (gérés séparément) + log " 🗑️ Suppression : $BASENAME" + rm -rf "$backup" +done + +# Nettoyage hebdomadaire : garder les N derniers dimanches +if [[ "$DAY_OF_WEEK" == "7" ]]; then + find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" | sort -r | tail -n +$((RETENTION_WEEKLY + 1)) | while read -r backup; do + # Ne supprimer que si c'est un backup du dimanche + # (on ne touche pas aux backups mensuels du 1er) + [[ "$(basename "$backup")" =~ 01- ]] && continue + log " 🗑️ Suppression hebdo : $(basename "$backup")" + rm -rf "$backup" + done +fi + +# Nettoyage mensuel : garder les N derniers 1er du mois +if [[ "$DAY_OF_MONTH" == "01" ]]; then + find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*01-*" | sort -r | tail -n +$((RETENTION_MONTHLY + 1)) | while read -r backup; do + log " 🗑️ Suppression mensuel : $(basename "$backup")" + rm -rf "$backup" + done +fi + +# ─── 8. Rapport final ─────────────────────────────────────────────────────── +TOTAL_SIZE=$(du -sh "$BACKUP_PATH" | cut -f1) +BACKUP_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" | wc -l) + +log "✅ Backup terminé !" +log "📊 Total : $TOTAL_SIZE" +log "📋 Backups conservés : $BACKUP_COUNT" +log "📂 Destination : $BACKUP_PATH" +log "═══ Fin ───────────────────────────────────────────" + +echo "" +echo "📂 $BACKUP_PATH" +echo " ├── gitea-db.sql.gz ← Dump MySQL" +echo " ├── gitea-data.tar.gz ← Volume /data" +echo " ├── gitea-mysql.tar.gz ← Volume MySQL" +echo " ├── docker-compose.yml ← Stack Docker" +echo " ├── restore.sh ← Script de restore" +echo " └── backup-info.txt ← Métadonnées" + +exit 0