diff --git a/README.md b/README.md index 1577838..ca0740e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Docker Scripts — Auto-Update, Backup, Restore & Déploiement Cron -Solution complète et générique pour la maintenance automatisée d'applications +Solution complète et **générique** pour la maintenance automatisée d'applications Docker (Gitea, ou toute autre app MySQL + volumes) : backup, mise à jour, restauration et déploiement. @@ -147,7 +147,7 @@ bash setup.sh --dry-run # Aperçu sans rien installer --- -### 💾 `gitea-backup.sh` — Backup complet +### 💾 `docker-backup.sh` — Backup complet Sauvegarde complète et portable : DB MySQL + volumes Docker + configuration. @@ -155,8 +155,8 @@ Sauvegarde complète et portable : DB MySQL + volumes Docker + configuration. | Variable | Défaut | Description | |----------|--------|-------------| -| `BACKUP_DIR` | `/NFS/BACKUP/DOCKER/dev-prod/gitea` | Destination backups | -| `COMPOSE_DIR` | `~/dev/git/outils/docker/gitea` | Dossier docker-compose | +| `BACKUP_DIR` | `/NFS/BACKUP/DOCKER/$HOSTNAME/gitea` | Destination backups | +| `COMPOSE_DIR` | `~/dev/git/outils/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 | @@ -174,7 +174,7 @@ Sauvegarde complète et portable : DB MySQL + volumes Docker + configuration. ```cron # Tous les jours à 2h du matin -0 2 * * * /home/openclaw/scripts/gitea-backup.sh +0 2 * * * /home/openclaw/scripts/docker-backup.sh ``` #### Structure d'un backup @@ -193,7 +193,7 @@ Sauvegarde complète et portable : DB MySQL + volumes Docker + configuration. #### Backup distant (optionnel) -Configurer dans `gitea-scripts.conf` : +Configurer dans `docker-scripts.conf` : ```bash REMOTE_BACKUP_ENABLED=true @@ -206,7 +206,7 @@ REMOTE_BACKUP_SSH_KEY=/home/user/.ssh/id_rsa --- -### 🔄 `gitea-auto-update.sh` — Mise à jour automatique +### 🔄 `docker-auto-update.sh` — Mise à jour automatique Vérifie quotidiennement si une nouvelle image Docker Gitea est disponible. @@ -220,6 +220,7 @@ Vérifie quotidiennement si une nouvelle image Docker Gitea est disponible. | `IMAGE_TAG` | `latest` | Tag (version) | | `HEALTH_CHECK_URL` | `http://localhost:3000` | URL vérification santé | | `HEALTH_CHECK_TIMEOUT` | `30` | Timeout attente (secondes) | +| `VERSION_ENDPOINT` | *(vide)* | Endpoint version (ex: `/api/v1/version`) | #### Fonctionnement @@ -240,12 +241,12 @@ Vérifie quotidiennement si une nouvelle image Docker Gitea est disponible. # Telegram ENABLE_NOTIFICATIONS=true NOTIFY_METHOD=telegram \ TELEGRAM_BOT_TOKEN=xxx TELEGRAM_CHAT_ID=xxx \ - bash gitea-auto-update.sh + bash docker-auto-update.sh # ntfy.sh ENABLE_NOTIFICATIONS=true NOTIFY_METHOD=ntfy \ NTPY_TOPIC=mon-topic \ - bash gitea-auto-update.sh + bash docker-auto-update.sh ``` --- @@ -293,8 +294,8 @@ Le script gère un **bloc marqué** dans la crontab : ```cron # >>> GITEA SCRIPTS (managed by install_cron.sh) <<< -0 2 * * * /home/bruno/scripts/gitea-backup.sh >> /var/log/gitea-backup-cron.log 2>&1 -0 4 * * * /home/bruno/scripts/gitea-auto-update.sh >> /var/log/gitea-update-cron.log 2>&1 +0 2 * * * /home/bruno/scripts/docker-backup.sh >> /var/log/gitea-backup-cron.log 2>&1 +0 4 * * * /home/bruno/scripts/docker-auto-update.sh >> /var/log/gitea-update-cron.log 2>&1 # >>> END GITEA SCRIPTS <<< ``` @@ -304,7 +305,7 @@ Le `--uninstall` retire uniquement ce bloc, sans toucher au reste de la crontab. ## ⚙️ Configuration centralisée -Tous les paramètres sont dans `gitea-scripts.conf`. Les scripts chargent ce +Tous les paramètres sont dans `docker-scripts.conf`. Les scripts chargent ce fichier automatiquement s'il est présent dans le même répertoire. Les variables d'environnement **surchargent** les valeurs du fichier de @@ -312,13 +313,13 @@ configuration. ```bash # Exemple : changer le dossier de backup -BACKUP_DIR=/mnt/big-disk/gitea bash gitea-backup.sh +BACKUP_DIR=/mnt/big-disk/gitea bash docker-backup.sh # Exemple : backup avec arrêt des conteneurs -STOP_CONTAINERS=true bash gitea-backup.sh +STOP_CONTAINERS=true bash docker-backup.sh # Exemple : backup + sync distante -REMOTE_BACKUP_ENABLED=true bash gitea-backup.sh +REMOTE_BACKUP_ENABLED=true bash docker-backup.sh ``` --- @@ -342,8 +343,8 @@ logrotate (rotation hebdomadaire, 12 semaines de rétention, compression). Pour éviter les exécutions concurrentes (backup + update simultanés), chaque script utilise un lock file dans `/tmp` : -- `/tmp/gitea-backup.lock` -- `/tmp/gitea-auto-update.lock` +- `/tmp/${DOCKER_APP_NAME}-backup.lock` +- `/tmp/${DOCKER_APP_NAME}-auto-update.lock` Si un lock stale est détecté (PID inexistant), il est automatiquement nettoyé. @@ -387,7 +388,7 @@ ls -t /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-* | tail -n +10 | xargs rm tail -50 /var/log/gitea-update.log # Tester manuellement -bash ~/scripts/gitea-auto-update.sh +bash ~/scripts/docker-auto-update.sh ``` ### Restauration @@ -403,11 +404,11 @@ bash ~/scripts/restore.sh $(ls -td /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backu ## 🔐 Sécurité -⚠️ Les mots de passe dans `gitea-scripts.conf` et `docker-compose.yml` sont +⚠️ Les mots de passe dans `docker-scripts.conf` et `docker-compose.yml` sont des valeurs par défaut. **Changez-les avant la mise en production :** ```bash -# Dans gitea-scripts.conf +# Dans docker-scripts.conf DB_ROOT_PASSWORD=votre-mot-de-passe-securise DB_PASSWORD=votre-autre-mot-de-passe diff --git a/backup-docker.sh b/backup-docker.sh deleted file mode 100644 index 45a34f1..0000000 --- a/backup-docker.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -euo pipefail - -BACKUP_DIR="/NFS/BACKUPS" -DATE=$(date +%Y%m%d) -mkdir -p $BACKUP_DIR - -docker run --rm -v jellyfin_data:/source -v $BACKUP_DIR:/backup alpine \ - tar czf /backup/jellyfin-$DATE.tar.gz /source -docker run --rm -v shaarli_data:/source -v $BACKUP_DIR:/backup alpine \ - tar czf /backup/shaarli-$DATE.tar.gz /source - -# Vérification -ls -lh $BACKUP_DIR | grep -E "jellyfin|shaarli" \ No newline at end of file diff --git a/docker-auto-update.sh b/docker-auto-update.sh index ebc65fc..7616871 100644 --- a/docker-auto-update.sh +++ b/docker-auto-update.sh @@ -50,6 +50,7 @@ NTPY_TOPIC="${NTPY_TOPIC:-}" # Santé HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}" HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}" +VERSION_ENDPOINT="${VERSION_ENDPOINT:-}" # Lock file LOCK_DIR="${LOCK_DIR:-/tmp}" @@ -252,7 +253,13 @@ for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do done # ─── Rapport final ────────────────────────────────────────────────────────── -APP_VERSION=$(curl -s "$HEALTH_CHECK_URL/api/v1/version" 2>/dev/null | grep -oE '"version":"[^"]+"' | cut -d'"' -f4 || echo "inconnue") +APP_VERSION="inconnue" +if [[ -n "$VERSION_ENDPOINT" ]]; then + APP_VERSION=$(curl -s "${HEALTH_CHECK_URL}${VERSION_ENDPOINT}" 2>/dev/null | grep -oE '"version":"[^"]+"' | cut -d'"' -f4 || echo "inconnue") +else + # Essayer de détecter la version via l'image Docker + APP_VERSION=$(docker image inspect "$FULL_IMAGE" --format '{{.Id}}' 2>/dev/null | cut -c8-19 || echo "inconnue") +fi log "✅ Mise à jour réussie !" log "📋 Nouvelle version : $APP_VERSION" diff --git a/docker-backup.sh b/docker-backup.sh index 556f8c9..adcb90b 100644 --- a/docker-backup.sh +++ b/docker-backup.sh @@ -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 diff --git a/docker-scripts.conf b/docker-scripts.conf index b66cb70..f3c455e 100644 --- a/docker-scripts.conf +++ b/docker-scripts.conf @@ -45,6 +45,8 @@ VOLUME_MYSQL="${VOLUME_MYSQL:-/DOCKER_CONFIG/$DOCKER_APP_NAME/mysql}" # ─── URLs ───────────────────────────────────────────────────────────────────── HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}" HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}" +# Endpoint version (ex: /api/v1/version pour Gitea). Laisser vide pour détection auto. +VERSION_ENDPOINT="${VERSION_ENDPOINT:-}" # ─── Logs (dérivés de DOCKER_APP_NAME) ──────────────────────────────────────── LOG_DIR="${LOG_DIR:-/var/log}" diff --git a/restore.sh b/restore.sh index af2af45..f29cce0 100644 --- a/restore.sh +++ b/restore.sh @@ -394,7 +394,7 @@ $DOCKER_COMPOSE restart 2>&1 || true echo "" echo "🏥 Vérification que ${DOCKER_APP_NAME} 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") + STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${HEALTH_CHECK_URL:-http://localhost:3000}" 2>/dev/null || echo "000") if [[ "$STATUS" == "200" || "$STATUS" == "302" || "$STATUS" == "303" ]]; then ok "${DOCKER_APP_NAME} répond (HTTP $STATUS) après ${i}s" break @@ -416,7 +416,7 @@ echo "" echo " Durée : ${DURATION}s" echo " Backup : $BACKUP_NAME" echo " Cible : $TARGET_COMPOSE_DIR" -echo " ${DOCKER_APP_NAME} : http://localhost:3000" +echo " ${DOCKER_APP_NAME} : ${HEALTH_CHECK_URL:-http://localhost:3000}" echo "" echo " Logs : $DOCKER_COMPOSE logs -f" echo " Status : $DOCKER_COMPOSE ps"