|
|
|
@@ -1,9 +1,9 @@
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# gitea-backup.sh — Backup complet Gitea (DB + volumes + config)
|
|
|
|
|
# docker-backup.sh — Backup complet d'une app Docker (DB + volumes + config)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# À placer dans /usr/local/bin/ ou ~/scripts/
|
|
|
|
|
# Cron recommandé : 0 2 * * * /usr/local/bin/gitea-backup.sh
|
|
|
|
|
# Cron recommandé : 0 2 * * * /usr/local/bin/docker-backup.sh
|
|
|
|
|
#
|
|
|
|
|
# Ce script sauvegarde :
|
|
|
|
|
# 1. La base de données MySQL (dump SQL complet)
|
|
|
|
@@ -11,40 +11,44 @@
|
|
|
|
|
# 3. Le fichier docker-compose.yml et la config
|
|
|
|
|
# 4. Génère un script restore.sh autonome
|
|
|
|
|
#
|
|
|
|
|
# Paramètres configurables : voir gitea-scripts.conf
|
|
|
|
|
# Paramètres configurables : voir docker-scripts.conf
|
|
|
|
|
# Utilisation avec autre app : DOCKER_APP_NAME=monapp bash docker-backup.sh
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
# ─── Charger la configuration commune ─────────────────────────────────────────
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/gitea-scripts.conf}"
|
|
|
|
|
CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/docker-scripts.conf}"
|
|
|
|
|
|
|
|
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
|
|
|
# shellcheck source=/dev/null
|
|
|
|
|
source "$CONFIG_FILE"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ─── Nom de l'application (défaut si pas dans la config) ─────────────────────
|
|
|
|
|
DOCKER_APP_NAME="${DOCKER_APP_NAME:-gitea}"
|
|
|
|
|
|
|
|
|
|
# ─── Configuration locale (après chargement conf, permet surcharge env) ──────
|
|
|
|
|
BACKUP_DIR="${BACKUP_DIR:-/NFS/BACKUP/DOCKER/dev-prod/gitea}"
|
|
|
|
|
COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}"
|
|
|
|
|
BACKUP_DIR="${BACKUP_DIR:-/NFS/BACKUP/DOCKER/$HOSTNAME/$DOCKER_APP_NAME}"
|
|
|
|
|
COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/$DOCKER_APP_NAME}"
|
|
|
|
|
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
|
|
|
|
|
|
|
|
|
|
# Conteneurs
|
|
|
|
|
DB_CONTAINER="${DB_CONTAINER:-gitea_db}"
|
|
|
|
|
APP_CONTAINER="${APP_CONTAINER:-gitea_server}"
|
|
|
|
|
DB_SERVICE="${DB_SERVICE:-gitea_db}"
|
|
|
|
|
APP_SERVICE="${APP_SERVICE:-gitea_server}"
|
|
|
|
|
# Conteneurs (dérivés de DOCKER_APP_NAME)
|
|
|
|
|
DB_CONTAINER="${DB_CONTAINER:-${DOCKER_APP_NAME}_db}"
|
|
|
|
|
APP_CONTAINER="${APP_CONTAINER:-${DOCKER_APP_NAME}_server}"
|
|
|
|
|
DB_SERVICE="${DB_SERVICE:-${DOCKER_APP_NAME}_db}"
|
|
|
|
|
APP_SERVICE="${APP_SERVICE:-${DOCKER_APP_NAME}_server}"
|
|
|
|
|
|
|
|
|
|
# Base de données
|
|
|
|
|
DB_NAME="${DB_NAME:-gitea}"
|
|
|
|
|
DB_USER="${DB_USER:-gitea}"
|
|
|
|
|
# Base de données (dérivés de DOCKER_APP_NAME)
|
|
|
|
|
DB_NAME="${DB_NAME:-$DOCKER_APP_NAME}"
|
|
|
|
|
DB_USER="${DB_USER:-$DOCKER_APP_NAME}"
|
|
|
|
|
DB_PASSWORD="${DB_PASSWORD:-chab30}"
|
|
|
|
|
DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-gitea}"
|
|
|
|
|
DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-$DOCKER_APP_NAME}"
|
|
|
|
|
|
|
|
|
|
# Volumes Docker (chemins HÔTE, pas conteneur)
|
|
|
|
|
VOLUME_DATA="${VOLUME_DATA:-/DOCKER_CONFIG/gitea/data}"
|
|
|
|
|
VOLUME_MYSQL="${VOLUME_MYSQL:-/DOCKER_CONFIG/gitea/mysql}"
|
|
|
|
|
# Volumes Docker - chemins HÔTE (dérivés de DOCKER_APP_NAME)
|
|
|
|
|
VOLUME_DATA="${VOLUME_DATA:-/DOCKER_CONFIG/$DOCKER_APP_NAME/data}"
|
|
|
|
|
VOLUME_MYSQL="${VOLUME_MYSQL:-/DOCKER_CONFIG/$DOCKER_APP_NAME/mysql}"
|
|
|
|
|
|
|
|
|
|
# Rétention
|
|
|
|
|
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
|
|
|
@@ -58,12 +62,12 @@ STOP_CONTAINERS="${STOP_CONTAINERS:-false}"
|
|
|
|
|
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
|
|
|
|
|
|
|
|
|
|
# Log
|
|
|
|
|
LOG_FILE="${LOG_FILE:-/var/log/gitea-backup.log}"
|
|
|
|
|
LOG_FILE="${LOG_FILE:-/var/log/${DOCKER_APP_NAME}-backup.log}"
|
|
|
|
|
LOG_DIR="$(dirname "$LOG_FILE")"
|
|
|
|
|
|
|
|
|
|
# Lock file
|
|
|
|
|
LOCK_DIR="${LOCK_DIR:-/tmp}"
|
|
|
|
|
LOCK_FILE="${LOCK_DIR}/gitea-backup.lock"
|
|
|
|
|
LOCK_FILE="${LOCK_DIR}/${DOCKER_APP_NAME}-backup.lock"
|
|
|
|
|
|
|
|
|
|
# Backup distant
|
|
|
|
|
REMOTE_BACKUP_ENABLED="${REMOTE_BACKUP_ENABLED:-false}"
|
|
|
|
@@ -101,7 +105,7 @@ notify() {
|
|
|
|
|
;;
|
|
|
|
|
ntfy)
|
|
|
|
|
if [[ -n "$NTPY_TOPIC" ]]; then
|
|
|
|
|
curl -s -H "Title: Gitea Backup" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
|
|
|
|
|
curl -s -H "Title: ${DOCKER_APP_NAME^} Backup" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
|
|
|
|
|
fi
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
@@ -222,14 +226,14 @@ 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_NAME="${DOCKER_APP_NAME}-backup-${TIMESTAMP}"
|
|
|
|
|
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
|
|
|
|
|
RESTORE_SCRIPT="${BACKUP_PATH}/restore.sh"
|
|
|
|
|
|
|
|
|
|
DOCKER_COMPOSE=$(detect_compose)
|
|
|
|
|
|
|
|
|
|
log "════════════════════════════════════════════════════════"
|
|
|
|
|
log "📦 Début backup Gitea → $BACKUP_PATH"
|
|
|
|
|
log "📦 Début backup ${DOCKER_APP_NAME^} → $BACKUP_PATH"
|
|
|
|
|
|
|
|
|
|
# ─── Pré-vérifications ──────────────────────────────────────────────────────
|
|
|
|
|
mkdir -p "$BACKUP_DIR" || { log "❌ Impossible de créer $BACKUP_DIR"; exit 1; }
|
|
|
|
@@ -269,14 +273,14 @@ if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
|
|
|
|
|
--events \
|
|
|
|
|
--hex-blob \
|
|
|
|
|
--default-character-set=utf8mb4 \
|
|
|
|
|
"$DB_NAME" > "${BACKUP_PATH}/gitea-db.sql" 2>"${BACKUP_PATH}/mysqldump.err" || {
|
|
|
|
|
"$DB_NAME" > "${BACKUP_PATH}/${DOCKER_APP_NAME}-db.sql" 2>"${BACKUP_PATH}/mysqldump.err" || {
|
|
|
|
|
log "❌ Échec du dump MySQL"
|
|
|
|
|
cat "${BACKUP_PATH}/mysqldump.err" >> "$LOG_FILE" 2>/dev/null || true
|
|
|
|
|
[[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && $DOCKER_COMPOSE -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" || true
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
rm -f "${BACKUP_PATH}/mysqldump.err"
|
|
|
|
|
DB_SIZE=$(wc -c < "${BACKUP_PATH}/gitea-db.sql" 2>/dev/null || echo "0")
|
|
|
|
|
DB_SIZE=$(wc -c < "${BACKUP_PATH}/${DOCKER_APP_NAME}-db.sql" 2>/dev/null || echo "0")
|
|
|
|
|
log " ✅ Dump MySQL OK (${DB_SIZE} bytes)"
|
|
|
|
|
else
|
|
|
|
|
log "❌ Conteneur $DB_CONTAINER non trouvé (pas en cours d'exécution)"
|
|
|
|
@@ -286,14 +290,14 @@ 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" 2>/dev/null | awk '{print $5}')"
|
|
|
|
|
gzip -f "${BACKUP_PATH}/${DOCKER_APP_NAME}-db.sql"
|
|
|
|
|
log " 📦 Compressé : $(ls -lh "${BACKUP_PATH}/${DOCKER_APP_NAME}-db.sql.gz" 2>/dev/null | awk '{print $5}')"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ─── 2. Backup volumes Docker ───────────────────────────────────────────────
|
|
|
|
|
log "📁 Backup volume data ($VOLUME_DATA)..."
|
|
|
|
|
|
|
|
|
|
tar -czf "${BACKUP_PATH}/gitea-data.tar.gz" \
|
|
|
|
|
tar -czf "${BACKUP_PATH}/${DOCKER_APP_NAME}-data.tar.gz" \
|
|
|
|
|
-C "$(dirname "$VOLUME_DATA")" \
|
|
|
|
|
--exclude='*.log' \
|
|
|
|
|
--exclude='log/*' \
|
|
|
|
@@ -304,30 +308,36 @@ tar -czf "${BACKUP_PATH}/gitea-data.tar.gz" \
|
|
|
|
|
[[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && $DOCKER_COMPOSE -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" || true
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
DATA_SIZE=$(ls -lh "${BACKUP_PATH}/gitea-data.tar.gz" 2>/dev/null | awk '{print $5}')
|
|
|
|
|
DATA_SIZE=$(ls -lh "${BACKUP_PATH}/${DOCKER_APP_NAME}-data.tar.gz" 2>/dev/null | awk '{print $5}')
|
|
|
|
|
log " ✅ Volume data sauvegardé ($DATA_SIZE)"
|
|
|
|
|
|
|
|
|
|
log "📁 Backup volume MySQL ($VOLUME_MYSQL)..."
|
|
|
|
|
tar -czf "${BACKUP_PATH}/gitea-mysql.tar.gz" \
|
|
|
|
|
tar -czf "${BACKUP_PATH}/${DOCKER_APP_NAME}-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" || true
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
MYSQL_SIZE=$(ls -lh "${BACKUP_PATH}/gitea-mysql.tar.gz" 2>/dev/null | awk '{print $5}')
|
|
|
|
|
MYSQL_SIZE=$(ls -lh "${BACKUP_PATH}/${DOCKER_APP_NAME}-mysql.tar.gz" 2>/dev/null | 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é"
|
|
|
|
|
else
|
|
|
|
|
log " ⚠️ app.ini non trouvé dans ${VOLUME_DATA}/gitea/conf/"
|
|
|
|
|
# Sauvegarder la config applicative si présente (app.ini ou équivalent)
|
|
|
|
|
APP_CONFIG_FOUND=false
|
|
|
|
|
for config_path in "${VOLUME_DATA}/${DOCKER_APP_NAME}/conf/app.ini" "${VOLUME_DATA}/conf/app.ini" "${VOLUME_DATA}/app.ini"; do
|
|
|
|
|
if [[ -f "$config_path" ]]; then
|
|
|
|
|
cp "$config_path" "${BACKUP_PATH}/app.ini"
|
|
|
|
|
log " ✅ app.ini sauvegardé depuis $config_path"
|
|
|
|
|
APP_CONFIG_FOUND=true
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
if [[ "$APP_CONFIG_FOUND" != "true" ]]; then
|
|
|
|
|
log " ⚠️ app.ini non trouvé (cherché dans ${VOLUME_DATA}/)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ─── Redémarrer les conteneurs si on les avait arrêtés ──────────────────────
|
|
|
|
@@ -335,10 +345,10 @@ if [[ "$CONTAINERS_WERE_STOPPED" == "true" ]]; then
|
|
|
|
|
log "▶️ Redémarrage des conteneurs..."
|
|
|
|
|
$DOCKER_COMPOSE -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" || true
|
|
|
|
|
|
|
|
|
|
# Attendre que Gitea réponde
|
|
|
|
|
# Attendre que l'app 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"
|
|
|
|
|
if curl -s -o /dev/null -w "%{http_code}" "${HEALTH_CHECK_URL:-http://localhost:3000}" 2>/dev/null | grep -qE "200|302|303"; then
|
|
|
|
|
log "✅ ${DOCKER_APP_NAME^} répond après ${i}s"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
sleep 1
|
|
|
|
@@ -348,14 +358,14 @@ fi
|
|
|
|
|
# ─── 4. Générer le script de restore ────────────────────────────────────────
|
|
|
|
|
log "📝 Génération du script de restore..."
|
|
|
|
|
|
|
|
|
|
DB_DUMP_FILE="gitea-db.sql"
|
|
|
|
|
[[ "$COMPRESS" == "true" ]] && DB_DUMP_FILE="gitea-db.sql.gz"
|
|
|
|
|
DB_DUMP_FILE="${DOCKER_APP_NAME}-db.sql"
|
|
|
|
|
[[ "$COMPRESS" == "true" ]] && DB_DUMP_FILE="${DOCKER_APP_NAME}-db.sql.gz"
|
|
|
|
|
|
|
|
|
|
cat > "$RESTORE_SCRIPT" << RESTORE_EOF
|
|
|
|
|
cat > "$RESTORE_SCRIPT" << 'RESTORE_SCRIPT_EOF'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# RESTORE SCRIPT — Gitea Backup $TIMESTAMP
|
|
|
|
|
# Généré le $(date)
|
|
|
|
|
# RESTORE SCRIPT — Backup __DOCKER_APP_NAME__ __TIMESTAMP__
|
|
|
|
|
# Généré le __GEN_DATE__
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Usage : bash restore.sh [OPTIONS]
|
|
|
|
|
#
|
|
|
|
@@ -368,59 +378,60 @@ cat > "$RESTORE_SCRIPT" << RESTORE_EOF
|
|
|
|
|
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}"
|
|
|
|
|
DOCKER_APP_NAME="${DOCKER_APP_NAME:-__DOCKER_APP_NAME__}"
|
|
|
|
|
TARGET_COMPOSE_DIR="${TARGET_COMPOSE_DIR:-$HOME/dev/git/outils/$DOCKER_APP_NAME}"
|
|
|
|
|
TARGET_VOLUME_DATA="${TARGET_VOLUME_DATA:-/DOCKER_CONFIG/$DOCKER_APP_NAME/data}"
|
|
|
|
|
TARGET_VOLUME_MYSQL="${TARGET_VOLUME_MYSQL:-/DOCKER_CONFIG/$DOCKER_APP_NAME/mysql}"
|
|
|
|
|
DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-$DOCKER_APP_NAME}"
|
|
|
|
|
DB_NAME="${DB_NAME:-$DOCKER_APP_NAME}"
|
|
|
|
|
DB_USER="${DB_USER:-$DOCKER_APP_NAME}"
|
|
|
|
|
|
|
|
|
|
DRY_RUN=false
|
|
|
|
|
FORCE=false
|
|
|
|
|
|
|
|
|
|
for arg in "\$@"; do
|
|
|
|
|
case "\$arg" in
|
|
|
|
|
for arg in "$@"; do
|
|
|
|
|
case "$arg" in
|
|
|
|
|
--dry-run) DRY_RUN=true ;;
|
|
|
|
|
--force) FORCE=true ;;
|
|
|
|
|
--help|-h) head -50 "\$0"; exit 0 ;;
|
|
|
|
|
--help|-h) head -50 "$0"; exit 0 ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="\$(cd "\$(dirname "\$0")" && pwd)"
|
|
|
|
|
BACKUP_DIR="\$SCRIPT_DIR"
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
BACKUP_DIR="$SCRIPT_DIR"
|
|
|
|
|
DB_CONTAINER="${DOCKER_APP_NAME}_db"
|
|
|
|
|
|
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
|
echo "🔄 RESTAURATION GITEA"
|
|
|
|
|
echo " Source : \$BACKUP_DIR"
|
|
|
|
|
echo " Date : $TIMESTAMP"
|
|
|
|
|
echo " Cible : \$TARGET_COMPOSE_DIR"
|
|
|
|
|
echo "🔄 RESTAURATION ${DOCKER_APP_NAME^^}"
|
|
|
|
|
echo " Source : $BACKUP_DIR"
|
|
|
|
|
echo " Date : __TIMESTAMP__"
|
|
|
|
|
echo " Cible : $TARGET_COMPOSE_DIR"
|
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
|
|
|
|
|
|
if [[ "\$DRY_RUN" == "true" ]]; then
|
|
|
|
|
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 " 1. mkdir -p $TARGET_VOLUME_DATA $TARGET_VOLUME_MYSQL"
|
|
|
|
|
echo " 2. Extraire ${DOCKER_APP_NAME}-mysql.tar.gz → $TARGET_VOLUME_MYSQL"
|
|
|
|
|
echo " 3. Extraire ${DOCKER_APP_NAME}-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"
|
|
|
|
|
echo " 7. docker exec gitea_db mysql ... < $DB_DUMP_FILE"
|
|
|
|
|
echo " 7. docker exec ${DB_CONTAINER} mysql ... < __DB_DUMP_FILE__"
|
|
|
|
|
echo " 8. docker compose restart"
|
|
|
|
|
echo ""
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ "\$FORCE" != "true" ]]; then
|
|
|
|
|
if [[ "$FORCE" != "true" ]]; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo "⚠️ ATTENTION : Ceci va ÉCRASER les données Gitea existantes !"
|
|
|
|
|
echo "⚠️ ATTENTION : Ceci va ÉCRASER les données ${DOCKER_APP_NAME} 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
|
|
|
|
|
if [[ "$CONFIRM" != "RESTORE" ]]; then
|
|
|
|
|
echo "❌ Restauration annulée."
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
@@ -428,23 +439,23 @@ fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "📦 Étape 1/6 : Création des répertoires..."
|
|
|
|
|
mkdir -p "\$TARGET_VOLUME_DATA" "\$TARGET_VOLUME_MYSQL" "\$TARGET_COMPOSE_DIR"
|
|
|
|
|
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")"
|
|
|
|
|
rm -rf "${TARGET_VOLUME_MYSQL:?}"/*
|
|
|
|
|
tar -xzf "$BACKUP_DIR/${DOCKER_APP_NAME}-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 3/6 : Restauration volume ${DOCKER_APP_NAME}..."
|
|
|
|
|
rm -rf "${TARGET_VOLUME_DATA:?}"/*
|
|
|
|
|
tar -xzf "$BACKUP_DIR/${DOCKER_APP_NAME}-data.tar.gz" -C "$(dirname "$TARGET_VOLUME_DATA")"
|
|
|
|
|
|
|
|
|
|
echo "📦 Étape 4/6 : Copie docker-compose..."
|
|
|
|
|
cp "\$BACKUP_DIR/docker-compose.yml" "\$TARGET_COMPOSE_DIR/"
|
|
|
|
|
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"
|
|
|
|
|
if [[ -f "$BACKUP_DIR/app.ini" ]]; then
|
|
|
|
|
mkdir -p "${TARGET_VOLUME_DATA}/${DOCKER_APP_NAME}/conf"
|
|
|
|
|
cp "$BACKUP_DIR/app.ini" "${TARGET_VOLUME_DATA}/${DOCKER_APP_NAME}/conf/app.ini"
|
|
|
|
|
echo " ✅ app.ini restauré"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
@@ -459,48 +470,59 @@ else
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "📦 Étape 5/6 : Démarrage des conteneurs..."
|
|
|
|
|
cd "\$TARGET_COMPOSE_DIR"
|
|
|
|
|
\$DC up -d
|
|
|
|
|
cd "$TARGET_COMPOSE_DIR"
|
|
|
|
|
$DC up -d
|
|
|
|
|
|
|
|
|
|
echo " ⏳ Attente démarrage MySQL (30s)..."
|
|
|
|
|
sleep 10
|
|
|
|
|
MYSQL_READY=false
|
|
|
|
|
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"
|
|
|
|
|
for i in $(seq 1 20); do
|
|
|
|
|
if docker exec "$DB_CONTAINER" 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
|
|
|
|
|
if [[ "$MYSQL_READY" != "true" ]]; then
|
|
|
|
|
echo "⚠️ MySQL n'a pas répondu dans le temps imparti, tentative de restore DB quand même..."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "📦 Étape 6/6 : Restauration base de données..."
|
|
|
|
|
DB_DUMP="\$BACKUP_DIR/$DB_DUMP_FILE"
|
|
|
|
|
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"
|
|
|
|
|
elif [[ -f "\$DB_DUMP" ]]; then
|
|
|
|
|
docker exec -i gitea_db mysql -u root -p"\$DB_ROOT_PASSWORD" "\$DB_NAME" < "\$DB_DUMP"
|
|
|
|
|
# Créer la base si elle n'existe pas
|
|
|
|
|
docker exec "$DB_CONTAINER" 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
|
|
|
|
|
|
|
|
|
|
DB_DUMP="$BACKUP_DIR/__DB_DUMP_FILE__"
|
|
|
|
|
if [[ -f "${DB_DUMP}" ]]; then
|
|
|
|
|
if [[ "$DB_DUMP" == *.gz ]]; then
|
|
|
|
|
gunzip -c "$DB_DUMP" | docker exec -i "$DB_CONTAINER" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME"
|
|
|
|
|
else
|
|
|
|
|
docker exec -i "$DB_CONTAINER" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" < "$DB_DUMP"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
echo "❌ Fichier dump introuvable : \$DB_DUMP"
|
|
|
|
|
echo "❌ Fichier dump introuvable : $DB_DUMP"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "🔄 Redémarrage final des conteneurs..."
|
|
|
|
|
\$DC restart
|
|
|
|
|
$DC restart
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
|
echo "✅ RESTAURATION TERMINÉE !"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " Gitea : http://localhost:3000"
|
|
|
|
|
echo " Vérifiez les logs : \$DC logs -f"
|
|
|
|
|
echo " ${DOCKER_APP_NAME^} est maintenant opérationnel"
|
|
|
|
|
echo " Vérifiez les logs : $DC logs -f"
|
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
|
RESTORE_EOF
|
|
|
|
|
RESTORE_SCRIPT_EOF
|
|
|
|
|
|
|
|
|
|
# Remplacer les placeholders dans le script généré
|
|
|
|
|
sed -i "s/__DOCKER_APP_NAME__/$DOCKER_APP_NAME/g" "$RESTORE_SCRIPT"
|
|
|
|
|
sed -i "s/__TIMESTAMP__/$TIMESTAMP/g" "$RESTORE_SCRIPT"
|
|
|
|
|
sed -i "s/__GEN_DATE__/$(date)/g" "$RESTORE_SCRIPT"
|
|
|
|
|
sed -i "s/__DB_DUMP_FILE__/$DB_DUMP_FILE/g" "$RESTORE_SCRIPT"
|
|
|
|
|
|
|
|
|
|
chmod +x "$RESTORE_SCRIPT"
|
|
|
|
|
log " ✅ Script restore créé : restore.sh"
|
|
|
|
@@ -521,7 +543,7 @@ if [[ "$VERIFY_BACKUP" == "true" ]]; then
|
|
|
|
|
shopt -u nullglob
|
|
|
|
|
|
|
|
|
|
if [[ "$COMPRESS" == "true" ]]; then
|
|
|
|
|
if ! gunzip -t "${BACKUP_PATH}/gitea-db.sql.gz" 2>/dev/null; then
|
|
|
|
|
if ! gunzip -t "${BACKUP_PATH}/${DOCKER_APP_NAME}-db.sql.gz" 2>/dev/null; then
|
|
|
|
|
log " ❌ Dump SQL corrompu"
|
|
|
|
|
ERRORS=$((ERRORS + 1))
|
|
|
|
|
fi
|
|
|
|
@@ -535,18 +557,19 @@ if [[ "$VERIFY_BACKUP" == "true" ]]; then
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ─── 6. Métadonnées du backup ───────────────────────────────────────────────
|
|
|
|
|
GITEA_VERSION="inconnue"
|
|
|
|
|
APP_VERSION="inconnue"
|
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${APP_CONTAINER}$"; then
|
|
|
|
|
GITEA_VERSION=$(docker exec "$APP_CONTAINER" gitea --version 2>/dev/null || echo "inconnue")
|
|
|
|
|
APP_VERSION=$(docker inspect "$APP_CONTAINER" --format '{{.Config.Image}}' 2>/dev/null || echo "inconnue")
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
DB_DISPLAY_SIZE=$(ls -lh "${BACKUP_PATH}"/gitea-db.sql* 2>/dev/null | awk '{print $5}' | head -1)
|
|
|
|
|
DB_DISPLAY_SIZE=$(ls -lh "${BACKUP_PATH}"/${DOCKER_APP_NAME}-db.sql* 2>/dev/null | awk '{print $5}' | head -1)
|
|
|
|
|
|
|
|
|
|
cat > "${BACKUP_PATH}/backup-info.txt" << INFO
|
|
|
|
|
Date : $(date)
|
|
|
|
|
Hôte : $(hostname)
|
|
|
|
|
Gitea version : $GITEA_VERSION
|
|
|
|
|
DB type : MySQL 8
|
|
|
|
|
Application : ${DOCKER_APP_NAME^}
|
|
|
|
|
App version : $APP_VERSION
|
|
|
|
|
DB type : MySQL
|
|
|
|
|
DB name : $DB_NAME
|
|
|
|
|
Compression : $COMPRESS
|
|
|
|
|
Containers stopped : $CONTAINERS_WERE_STOPPED
|
|
|
|
@@ -559,11 +582,11 @@ INFO
|
|
|
|
|
log "🧹 Nettoyage backups (rétention: ${RETENTION_DAYS}j, ${RETENTION_WEEKLY} hebdo, ${RETENTION_MONTHLY} mensuels)..."
|
|
|
|
|
|
|
|
|
|
# Pour chaque backup, déterminer s'il doit être gardé
|
|
|
|
|
find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/dev/null | sort -r | while read -r backup_name; do
|
|
|
|
|
find "$BACKUP_DIR" -maxdepth 1 -type d -name "${DOCKER_APP_NAME}-backup-*" -printf '%f\n' 2>/dev/null | sort -r | while read -r backup_name; do
|
|
|
|
|
backup_path="$BACKUP_DIR/$backup_name"
|
|
|
|
|
|
|
|
|
|
# Déterminer le jour du mois et le jour de la semaine à partir du nom
|
|
|
|
|
# Format: gitea-backup-YYYYMMDD-HHMMSS
|
|
|
|
|
# Format: APP-backup-YYYYMMDD-HHMMSS
|
|
|
|
|
local_date=$(echo "$backup_name" | grep -oE '[0-9]{8}' | head -1)
|
|
|
|
|
if [[ -z "$local_date" ]]; then
|
|
|
|
|
continue
|
|
|
|
@@ -593,8 +616,9 @@ find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/
|
|
|
|
|
|
|
|
|
|
# Toujours garder : backups du dimanche (hebdomadaire) dans la limite RETENTION_WEEKLY
|
|
|
|
|
if [[ "$local_dow" == "7" ]]; then
|
|
|
|
|
WEEKLY_PATTERN="^${DOCKER_APP_NAME}-backup-[0-9]{8}"
|
|
|
|
|
# Compter combien de backups du dimanche on garde déjà
|
|
|
|
|
weekly_count=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/dev/null | grep -oE '^gitea-backup-[0-9]{8}' | while read -r wb; do
|
|
|
|
|
weekly_count=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "${DOCKER_APP_NAME}-backup-*" -printf '%f\n' 2>/dev/null | grep -oE "$WEEKLY_PATTERN" | while read -r wb; do
|
|
|
|
|
wb_date=$(echo "$wb" | grep -oE '[0-9]{8}')
|
|
|
|
|
wb_dow=$(date -d "${wb_date:0:4}-${wb_date:4:2}-${wb_date:6:2}" +%u 2>/dev/null || echo "0")
|
|
|
|
|
if [[ "$wb_dow" == "7" ]]; then
|
|
|
|
@@ -603,7 +627,7 @@ find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/
|
|
|
|
|
done | sort -r | head -n "$RETENTION_WEEKLY" | wc -l)
|
|
|
|
|
|
|
|
|
|
# Si ce backup fait partie des N derniers dimanches, on garde
|
|
|
|
|
weekly_list=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/dev/null | grep -oE '^gitea-backup-[0-9]{8}' | while read -r wb; do
|
|
|
|
|
weekly_list=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "${DOCKER_APP_NAME}-backup-*" -printf '%f\n' 2>/dev/null | grep -oE "$WEEKLY_PATTERN" | while read -r wb; do
|
|
|
|
|
wb_date=$(echo "$wb" | grep -oE '[0-9]{8}')
|
|
|
|
|
wb_dow=$(date -d "${wb_date:0:4}-${wb_date:4:2}-${wb_date:6:2}" +%u 2>/dev/null || echo "0")
|
|
|
|
|
if [[ "$wb_dow" == "7" ]]; then
|
|
|
|
@@ -619,7 +643,8 @@ find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/
|
|
|
|
|
|
|
|
|
|
# Toujours garder : backups du 1er du mois (mensuel) dans la limite RETENTION_MONTHLY
|
|
|
|
|
if [[ "$local_day" == "01" ]]; then
|
|
|
|
|
monthly_list=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/dev/null | grep -oE '^gitea-backup-[0-9]{8}' | while read -r mb; do
|
|
|
|
|
MONTHLY_PATTERN="^${DOCKER_APP_NAME}-backup-[0-9]{8}"
|
|
|
|
|
monthly_list=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "${DOCKER_APP_NAME}-backup-*" -printf '%f\n' 2>/dev/null | grep -oE "$MONTHLY_PATTERN" | while read -r mb; do
|
|
|
|
|
mb_date=$(echo "$mb" | grep -oE '[0-9]{8}')
|
|
|
|
|
mb_day=${mb_date:6:2}
|
|
|
|
|
if [[ "$mb_day" == "01" ]]; then
|
|
|
|
@@ -646,7 +671,7 @@ sync_remote "$BACKUP_PATH" || true
|
|
|
|
|
|
|
|
|
|
# ─── 9. Rapport final ───────────────────────────────────────────────────────
|
|
|
|
|
TOTAL_SIZE=$(du -sh "$BACKUP_PATH" 2>/dev/null | cut -f1)
|
|
|
|
|
BACKUP_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" 2>/dev/null | wc -l)
|
|
|
|
|
BACKUP_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "${DOCKER_APP_NAME}-backup-*" 2>/dev/null | wc -l)
|
|
|
|
|
|
|
|
|
|
log "✅ Backup terminé !"
|
|
|
|
|
log "📊 Total : ${TOTAL_SIZE:-N/A}"
|
|
|
|
@@ -656,14 +681,14 @@ 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_APP_NAME}-db.sql.gz ← Dump MySQL"
|
|
|
|
|
echo " ├── ${DOCKER_APP_NAME}-data.tar.gz ← Volume /data"
|
|
|
|
|
echo " ├── ${DOCKER_APP_NAME}-mysql.tar.gz ← Volume MySQL"
|
|
|
|
|
echo " ├── docker-compose.yml ← Stack Docker"
|
|
|
|
|
echo " ├── app.ini ← Configuration Gitea"
|
|
|
|
|
echo " ├── app.ini ← Configuration ${DOCKER_APP_NAME^}"
|
|
|
|
|
echo " ├── restore.sh ← Script de restore"
|
|
|
|
|
echo " └── backup-info.txt ← Métadonnées"
|
|
|
|
|
|
|
|
|
|
notify "✅ Backup Gitea terminé sur $(hostname) — $TOTAL_SIZE — $BACKUP_PATH"
|
|
|
|
|
notify "✅ Backup ${DOCKER_APP_NAME^} terminé sur $(hostname) — $TOTAL_SIZE — $BACKUP_PATH"
|
|
|
|
|
|
|
|
|
|
exit 0
|
|
|
|
|