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)
This commit is contained in:
2026-06-26 11:30:48 -04:00
commit 4c47e57559
3 changed files with 683 additions and 0 deletions
+125
View File
@@ -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` |
+138
View File
@@ -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
+420
View File
@@ -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