diff --git a/README-gitea-scripts.md b/README-gitea-scripts.md
index afbf0b7..f908e5c 100644
--- a/README-gitea-scripts.md
+++ b/README-gitea-scripts.md
@@ -1,71 +1,151 @@
-# Scripts Gitea — Auto-Update, Backup & Déploiement Cron
+# Scripts Gitea — Auto-Update, Backup, Restore & Déploiement Cron
-Trois scripts pour la maintenance automatisée de Gitea Docker.
+Solution complète pour la maintenance automatisée de Gitea Docker :
+backup, mise à jour, restauration et déploiement.
+
+---
+
+## 📂 Structure du projet
+
+```
+gitea-scripts/
+├── setup.sh ← 🚀 Installateur tout-en-un
+├── gitea-scripts.conf ← ⚙️ Configuration unifiée
+├── gitea-backup.sh ← 💾 Backup complet (DB + volumes + config)
+├── gitea-auto-update.sh ← 🔄 Mise à jour automatique
+├── restore.sh ← ♻️ Restauration standalone
+├── install_cron.sh ← 🕐 Gestion des crons
+└── docker-compose.yml ← 🐳 Stack Docker de référence
+```
+
+### Architecture
+
+```mermaid
+flowchart TB
+ subgraph Config["⚙️ Configuration"]
+ CONF["gitea-scripts.conf
Configuration unifiée"]
+ end
+
+ subgraph Core["🛠️ Scripts core"]
+ BACKUP["gitea-backup.sh
💾 Backup quotidien"]
+ UPDATE["gitea-auto-update.sh
🔄 Mise à jour auto"]
+ RESTORE["restore.sh
♻️ Restauration"]
+ end
+
+ subgraph Deploy["🚀 Déploiement"]
+ SETUP["setup.sh
Installateur complet"]
+ CRON["install_cron.sh
Gestion crontab"]
+ end
+
+ subgraph Runtime["⚡ Runtime"]
+ CRONTAB["Crontab
Backup 2h → Update 4h"]
+ DOCKER["Docker Compose
Gitea + MySQL"]
+ end
+
+ subgraph Storage["💾 Stockage"]
+ LOCAL["Backups locaux
/NFS/BACKUP/..."]
+ REMOTE["Backup distant
rsync / SCP"]
+ end
+
+ CONF --> BACKUP
+ CONF --> UPDATE
+ CONF --> RESTORE
+ CONF --> CRON
+
+ SETUP --> CRON
+ SETUP --> DOCKER
+ CRON --> CRONTAB
+ CRONTAB --> BACKUP
+ CRONTAB --> UPDATE
+
+ BACKUP --> LOCAL
+ BACKUP --> REMOTE
+ RESTORE --> LOCAL
+ RESTORE --> DOCKER
+ UPDATE --> DOCKER
+```
+
+```mermaid
+flowchart LR
+ subgraph Daily["📅 Quotidien (cron)"]
+ direction TB
+ A["🕐 2h00"] --> B["gitea-backup.sh"]
+ B --> C["💾 Backup DB + volumes"]
+ C --> D["📝 Génère restore.sh"]
+ C --> E["☁️ Sync distante"]
+ C --> F["🧹 Nettoie vieux backups"]
+
+ G["🕐 4h00"] --> H["gitea-auto-update.sh"]
+ H --> I{"Nouvelle image ?"}
+ I -->|"Non"| J["✅ Rien à faire"]
+ I -->|"Oui"| K["💾 Backup DB rapide"]
+ K --> L["🚀 docker compose up -d"]
+ L --> M{"Santé OK ?"}
+ M -->|"Oui"| N["✅ Mise à jour réussie"]
+ M -->|"Non"| O["🔄 Rollback auto"]
+ end
+
+ subgraph Manual["🖐️ Manuel"]
+ direction TB
+ P["setup.sh"] --> Q["Installation complète"]
+ R["restore.sh"] --> S["Restauration backup"]
+ T["setup.sh --update"] --> U["Màj scripts (git pull)"]
+ end
+```
---
## 🚀 Installation rapide
```bash
-# Cloner les scripts
+# Cloner et installer en une commande
git clone https://git.dracodev.net/Outils/gitea-scripts.git ~/scripts
-
-# Installer dans le cron (interactif)
-bash ~/scripts/install_cron.sh
+bash ~/scripts/setup.sh
# Ou forcer sans confirmation
-bash ~/scripts/install_cron.sh --force
+bash ~/scripts/setup.sh --force
```
---
## 📋 Prérequis
-- Docker + Docker Compose
-- `curl`, `tar`, `gzip`, `mysqldump` (dans le conteneur MySQL)
-- Droits d'écriture sur `/NFS/BACKUP/DOCKER/dev-prod/gitea/` (backup)
+- Docker + Docker Compose (plugin ou standalone)
+- `git`, `curl`, `tar`, `gzip`, `crontab`
+- `mysqldump` (dans le conteneur MySQL)
+- Droits d'écriture sur le répertoire de backup
- Droits sudo si les volumes sont protégés
---
-## 🔄 gitea-auto-update.sh
+## 🛠️ Scripts
-Vérifie quotidiennement si une nouvelle image Docker Gitea est disponible et applique la mise à jour.
+### 🚀 `setup.sh` — Installation complète
-### Paramètres configurables
+Installateur tout-en-un qui configure l'ensemble du système :
-| 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
+```bash
+bash setup.sh # Installation interactive
+bash setup.sh --force # Sans confirmation
+bash setup.sh --update # Mise à jour des scripts (git pull)
+bash setup.sh --dry-run # Aperçu sans rien installer
```
-### 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`)
+**Étapes exécutées :**
+1. Vérification des prérequis système
+2. Clone/mise à jour depuis Git
+3. Permissions d'exécution
+4. Configuration des logs
+5. Configuration logrotate (rotation automatique des logs)
+6. Installation des tâches cron
---
-## 💾 gitea-backup.sh
+### 💾 `gitea-backup.sh` — Backup complet
-Backup complet et portable de Gitea : DB MySQL + volumes Docker + config.
+Sauvegarde complète et portable : DB MySQL + volumes Docker + configuration.
-### Paramètres configurables
+#### Paramètres configurables
| Variable | Défaut | Description |
|----------|--------|-------------|
@@ -82,15 +162,16 @@ Backup complet et portable de Gitea : DB MySQL + volumes Docker + config.
| `COMPRESS` | `true` | Compression gzip |
| `VERIFY_BACKUP` | `true` | Vérifier intégrité après backup |
| `STOP_CONTAINERS` | `false` | Arrêter conteneurs (plus sûr) |
+| `REMOTE_BACKUP_ENABLED` | `false` | Activer backup distant (rsync/SCP) |
-### Cron recommandé
+#### Cron recommandé
```cron
# Tous les jours à 2h du matin
0 2 * * * /home/openclaw/scripts/gitea-backup.sh
```
-### Structure d'un backup
+#### Structure d'un backup
```
/NFS/BACKUP/DOCKER/dev-prod/gitea/
@@ -104,48 +185,105 @@ Backup complet et portable de Gitea : DB MySQL + volumes Docker + config.
└── backup-info.txt ← Métadonnées
```
-### Restauration
+#### Backup distant (optionnel)
+
+Configurer dans `gitea-scripts.conf` :
```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
+REMOTE_BACKUP_ENABLED=true
+REMOTE_BACKUP_METHOD=rsync # ou scp
+REMOTE_BACKUP_HOST=backup.example.com
+REMOTE_BACKUP_USER=backup
+REMOTE_BACKUP_PATH=/backups/gitea
+REMOTE_BACKUP_SSH_KEY=/home/user/.ssh/id_rsa
```
-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.
-
---
-## 🚀 install_cron.sh — Déploiement automatisé
+### 🔄 `gitea-auto-update.sh` — Mise à jour automatique
-Installe (ou désinstalle) les crons d'un seul coup.
+Vérifie quotidiennement si une nouvelle image Docker Gitea est disponible.
-### Commandes
+#### Paramètres configurables
+
+| Variable | Défaut | Description |
+|----------|--------|-------------|
+| `COMPOSE_DIR` | `~/dev/git/outils/docker/gitea` | Dossier du docker-compose |
+| `APP_SERVICE` | `gitea_server` | Nom du service dans compose |
+| `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) |
+
+#### 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 de la DB
+ - Sauvegarde de l'image actuelle pour rollback
+ - `docker compose up -d` avec la nouvelle image
+ - Vérification santé
+ - **Rollback automatique** en cas d'échec
+5. Nettoyage ciblé des anciennes images Gitea
+
+#### Notifications (optionnel)
+
+```bash
+# Telegram
+ENABLE_NOTIFICATIONS=true NOTIFY_METHOD=telegram \
+ TELEGRAM_BOT_TOKEN=xxx TELEGRAM_CHAT_ID=xxx \
+ bash gitea-auto-update.sh
+
+# ntfy.sh
+ENABLE_NOTIFICATIONS=true NOTIFY_METHOD=ntfy \
+ NTPY_TOPIC=mon-topic \
+ bash gitea-auto-update.sh
+```
+
+---
+
+### ♻️ `restore.sh` — Restauration standalone
+
+Restaure un backup Gitea sur n'importe quel hôte Docker.
+
+```bash
+# Lister les backups disponibles
+bash restore.sh
+
+# Restauration interactive
+bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000
+
+# Dry-run (vérifier sans exécuter)
+bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000 --dry-run
+
+# Restauration forcée (sans confirmation)
+bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000 --force
+```
+
+**Étapes de restauration :**
+1. Arrêt des conteneurs existants
+2. Création des répertoires cibles
+3. Restauration du volume MySQL
+4. Restauration du volume Gitea
+5. Copie du docker-compose.yml et app.ini
+6. Démarrage des conteneurs
+7. Restauration de la base de données + redémarrage final
+
+---
+
+### 🕐 `install_cron.sh` — Gestion des crons
```bash
bash install_cron.sh # Installation interactive
bash install_cron.sh --force # Sans confirmation
+bash install_cron.sh --dry-run # Aperçu sans rien installer
bash install_cron.sh --uninstall # Retirer du cron
bash install_cron.sh --status # Vérifier l'état
```
-### Paramètres configurables
-
-| Variable | Défaut | Description |
-|----------|--------|-------------|
-| `SCRIPTS_DIR` | `$HOME/scripts` | Dossier des scripts |
-| `CRON_BACKUP_SCHEDULE` | `0 2 * * *` | Horaire backup |
-| `CRON_UPDATE_SCHEDULE` | `0 4 * * *` | Horaire update |
-| `BACKUP_LOG` | `/var/log/gitea-backup-cron.log` | Log backup |
-| `UPDATE_LOG` | `/var/log/gitea-update-cron.log` | Log update |
-
-### Gestion propre
-
-Le script ajoute un **bloc marqué** dans la crontab :
+Le script gère un **bloc marqué** dans la crontab :
```cron
# >>> GITEA SCRIPTS (managed by install_cron.sh) <<<
@@ -158,14 +296,23 @@ Le `--uninstall` retire uniquement ce bloc, sans toucher au reste de la crontab.
---
-## 🔧 Surcharger les paramètres
+## ⚙️ Configuration centralisée
+
+Tous les paramètres sont dans `gitea-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
+configuration.
```bash
# Exemple : changer le dossier de backup
-BACKUP_DIR=/mnt/big-disk/gitea /home/openclaw/scripts/gitea-backup.sh
+BACKUP_DIR=/mnt/big-disk/gitea bash gitea-backup.sh
# Exemple : backup avec arrêt des conteneurs
-STOP_CONTAINERS=true /home/openclaw/scripts/gitea-backup.sh
+STOP_CONTAINERS=true bash gitea-backup.sh
+
+# Exemple : backup + sync distante
+REMOTE_BACKUP_ENABLED=true bash gitea-backup.sh
```
---
@@ -176,3 +323,90 @@ STOP_CONTAINERS=true /home/openclaw/scripts/gitea-backup.sh
|--------|-----|
| Auto-update | `/var/log/gitea-update.log` |
| Backup | `/var/log/gitea-backup.log` |
+| Cron backup | `~/gitea-backup-cron.log` |
+| Cron update | `~/gitea-update-cron.log` |
+
+La rotation des logs est configurée automatiquement par `setup.sh` via
+logrotate (rotation hebdomadaire, 12 semaines de rétention, compression).
+
+---
+
+## 🔒 Lock files
+
+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`
+
+Si un lock stale est détecté (PID inexistant), il est automatiquement nettoyé.
+
+---
+
+## 🔄 Mise à jour périodique des scripts
+
+```bash
+# Mise à jour manuelle
+bash ~/scripts/setup.sh --update
+
+# Ajouter au cron pour mise à jour automatique (optionnel)
+# 0 3 * * 0 bash ~/scripts/setup.sh --update --force
+```
+
+---
+
+## 🐳 Docker Compose
+
+Le fichier `docker-compose.yml` fourni inclut :
+- **Healthchecks** sur les deux services (MySQL + Gitea)
+- `depends_on` avec `condition: service_healthy` pour MySQL
+- Timezone mapping pour les logs horodatés correctement
+
+---
+
+## 🔧 Dépannage
+
+### Le backup échoue par manque d'espace
+```bash
+# Vérifier l'espace disponible
+df -h /NFS/BACKUP/DOCKER/dev-prod/gitea
+
+# Nettoyer les vieux backups manuellement
+ls -t /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-* | tail -n +10 | xargs rm -rf
+```
+
+### L'auto-update échoue
+```bash
+# Vérifier les logs
+tail -50 /var/log/gitea-update.log
+
+# Tester manuellement
+bash ~/scripts/gitea-auto-update.sh
+```
+
+### Restauration
+```bash
+# Lister les backups disponibles
+bash ~/scripts/restore.sh
+
+# Restaurer le plus récent
+bash ~/scripts/restore.sh $(ls -td /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-* | head -1)
+```
+
+---
+
+## 🔐 Sécurité
+
+⚠️ Les mots de passe dans `gitea-scripts.conf` et `docker-compose.yml` sont
+des valeurs par défaut. **Changez-les avant la mise en production :**
+
+```bash
+# Dans gitea-scripts.conf
+DB_ROOT_PASSWORD=votre-mot-de-passe-securise
+DB_PASSWORD=votre-autre-mot-de-passe
+
+# Dans docker-compose.yml
+MYSQL_ROOT_PASSWORD=votre-mot-de-passe-securise
+MYSQL_PASSWORD=votre-autre-mot-de-passe
+GITEA__database__PASSWD=votre-autre-mot-de-passe
+```
diff --git a/docker-compose.yml b/docker-compose.yml
index 82af5f3..be5f5d2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -28,7 +28,14 @@ services:
- "3000:3000"
- "222:22"
depends_on:
- - gitea_db
+ gitea_db:
+ condition: service_healthy
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:3000"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 30s
gitea_db:
image: mysql:8
@@ -43,3 +50,19 @@ services:
- gitea
volumes:
- /DOCKER_CONFIG/gitea/mysql:/var/lib/mysql
+ healthcheck:
+ test:
+ [
+ "CMD",
+ "mysqladmin",
+ "ping",
+ "-h",
+ "localhost",
+ "-u",
+ "root",
+ "-p${MYSQL_ROOT_PASSWORD:-gitea}",
+ ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
diff --git a/gitea-auto-update.sh b/gitea-auto-update.sh
index d2d74e6..0b7467f 100755
--- a/gitea-auto-update.sh
+++ b/gitea-auto-update.sh
@@ -5,43 +5,127 @@
# À 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)
+# Paramètres configurables : voir gitea-scripts.conf
# =============================================================================
set -euo pipefail
-# ─── Configuration (modifiable) ─────────────────────────────────────────────
-COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/gitea}"
+# ─── Charger la configuration commune ─────────────────────────────────────────
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/gitea-scripts.conf}"
+
+if [[ -f "$CONFIG_FILE" ]]; then
+ # shellcheck source=/dev/null
+ source "$CONFIG_FILE"
+fi
+
+# ─── Configuration locale (après chargement conf, permet surcharge env) ──────
+# Chemins et conteneurs
+COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
-CONTAINER_NAME="${CONTAINER_NAME:-gitea_server}"
+APP_SERVICE="${APP_SERVICE:-gitea_server}"
+APP_CONTAINER="${APP_CONTAINER:-gitea_server}"
+DB_CONTAINER="${DB_CONTAINER:-gitea_db}"
IMAGE_NAME="${IMAGE_NAME:-gitea/gitea}"
-IMAGE_TAG="${IMAGE_TAG:-latest}" # "latest" ou version spécifique
+IMAGE_TAG="${IMAGE_TAG:-latest}"
+
+# Base de données
+DB_NAME="${DB_NAME:-gitea}"
+DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-gitea}"
+
+# Logs
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
+LOG_DIR="$(dirname "$LOG_FILE")"
+
+# Notifications
+ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
+NOTIFY_METHOD="${NOTIFY_METHOD:-log}"
+TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
NTPY_TOPIC="${NTPY_TOPIC:-}"
+
+# Santé
HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}"
HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}"
+# Lock file
+LOCK_DIR="${LOCK_DIR:-/tmp}"
+LOCK_FILE="${LOCK_DIR}/gitea-auto-update.lock"
+
+# ─── Prérequis : s'assurer que le dossier de log existe ──────────────────────
+mkdir -p "$LOG_DIR" 2>/dev/null || true
+
# ─── Logging ─────────────────────────────────────────────────────────────────
log() {
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
+ local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
+ echo "$msg"
+ echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}
+# ─── Notification ────────────────────────────────────────────────────────────
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
+ if [[ -n "$TELEGRAM_BOT_TOKEN" ]] && [[ -n "$TELEGRAM_CHAT_ID" ]]; then
+ 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
+ fi
;;
ntfy)
- [[ -n "$NTPY_TOPIC" ]] && curl -s -H "Title: Gitea Update" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
+ if [[ -n "$NTPY_TOPIC" ]]; then
+ curl -s -H "Title: Gitea Update" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
+ fi
;;
esac
}
+# ─── Lock file ───────────────────────────────────────────────────────────────
+acquire_lock() {
+ if [[ -f "$LOCK_FILE" ]]; then
+ local pid
+ pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
+ # Vérifier si le processus existe encore
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
+ log "⚠️ Une autre instance (PID $pid) est déjà en cours d'exécution — arrêt"
+ exit 0
+ fi
+ # Stale lock, on le nettoie
+ log "⚠️ Lock stale détecté (PID $pid n'existe plus), on continue"
+ rm -f "$LOCK_FILE"
+ fi
+ echo $$ > "$LOCK_FILE"
+}
+
+release_lock() {
+ rm -f "$LOCK_FILE"
+}
+
+# ─── Nettoyage en cas d'erreur ───────────────────────────────────────────────
+cleanup() {
+ release_lock
+}
+trap cleanup EXIT
+
+# ─── Récupération du digest (compatible POSIX grep) ──────────────────────────
+get_image_digest() {
+ local image="$1"
+ local digest
+ # Essayer d'abord avec -E (ERE, disponible presque partout)
+ digest=$(docker image inspect "$image" --format '{{.RepoDigests}}' 2>/dev/null | grep -oE 'sha256:[a-f0-9]{64}' | head -1 || echo "")
+ if [[ -z "$digest" ]]; then
+ # Fallback : utiliser awk
+ digest=$(docker image inspect "$image" --format '{{.RepoDigests}}' 2>/dev/null | grep -o 'sha256:[a-f0-9]\{64\}' | head -1 || echo "")
+ fi
+ echo "$digest"
+}
+
+# ═════════════════════════════════════════════════════════════════════════════
+# MAIN
+# ═════════════════════════════════════════════════════════════════════════════
+
+acquire_lock
+
log "════════════════════════════════════════════════════════"
log "🔄 Début vérification mise à jour Gitea"
@@ -53,23 +137,32 @@ if ! command -v docker &>/dev/null; then
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 "")
+# Vérifier si docker compose (plugin) ou docker-compose (standalone) est dispo
+if docker compose version &>/dev/null; then
+ DOCKER_COMPOSE="docker compose"
+elif command -v docker-compose &>/dev/null; then
+ DOCKER_COMPOSE="docker-compose"
+else
+ log "❌ Ni 'docker compose' ni 'docker-compose' n'est disponible"
+ exit 1
+fi
-log "📦 Image actuelle : ${IMAGE_NAME}:${IMAGE_TAG}"
+# ─── Récupérer l'image digest actuelle ──────────────────────────────────────
+FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
+OLD_DIGEST=$(get_image_digest "$FULL_IMAGE")
+
+log "📦 Image actuelle : $FULL_IMAGE"
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"
+PULL_OUTPUT=$($DOCKER_COMPOSE -f "$COMPOSE_FILE" pull "$APP_SERVICE" 2>&1) || {
+ PULL_EXIT=$?
+ log "❌ Échec du pull (code $PULL_EXIT) : $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 "")
+NEW_DIGEST=$(get_image_digest "$FULL_IMAGE")
log "🔍 Nouveau digest : ${NEW_DIGEST:-non disponible}"
@@ -83,34 +176,68 @@ 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)
+# Sauvegarder l'image actuelle pour rollback éventuel
+log "💾 Sauvegarde de l'image actuelle pour rollback..."
+OLD_IMAGE_ID=$(docker image inspect "$FULL_IMAGE" --format '{{.ID}}' 2>/dev/null || echo "")
+ROLLBACK_TAG="${IMAGE_NAME}:rollback-$(date +%Y%m%d-%H%M%S)"
+if [[ -n "$OLD_IMAGE_ID" ]]; then
+ docker tag "$OLD_IMAGE_ID" "$ROLLBACK_TAG" 2>/dev/null || {
+ log "⚠️ Impossible de tagger l'image pour rollback (on continue)"
+ ROLLBACK_TAG=""
+ }
+else
+ ROLLBACK_TAG=""
+fi
+
+# Backup rapide DB avant mise à jour
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"
-}
+log "💾 Backup DB pré-update vers $BACKUP_PRE_DIR"
+if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
+ docker exec "$DB_CONTAINER" mysqldump \
+ -u root -p"$DB_ROOT_PASSWORD" \
+ --single-transaction \
+ "$DB_NAME" > "$BACKUP_PRE_DIR/gitea_db.sql" 2>/dev/null || {
+ log "⚠️ Backup pré-update DB échoué, on continue quand même"
+ }
+else
+ log "⚠️ Conteneur $DB_CONTAINER non trouvé — pas de backup pré-update"
+fi
# 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=$?
+UP_OUTPUT=$($DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1) || {
+ UP_EXIT=$?
+ log "❌ Échec du redémarrage (code $UP_EXIT) : $UP_OUTPUT"
-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
+ # Rollback réel : restaurer l'ancienne image
+ if [[ -n "$ROLLBACK_TAG" ]]; then
+ log "🔄 ROLLBACK : restauration de l'image $ROLLBACK_TAG..."
+ docker tag "$ROLLBACK_TAG" "$FULL_IMAGE" 2>/dev/null || true
+ $DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1 || true
+ docker rmi "$ROLLBACK_TAG" 2>/dev/null || true
+ else
+ log "⚠️ Rollback impossible (pas d'image sauvegardée)"
+ $DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1 || true
+ fi
notify "❌ GITEA UPDATE FAILED sur $(hostname) — rollback effectué"
exit 1
+}
+
+# Nettoyer le tag de rollback si tout va bien
+if [[ -n "$ROLLBACK_TAG" ]]; then
+ docker rmi "$ROLLBACK_TAG" 2>/dev/null || true
fi
# ─── Vérification santé ─────────────────────────────────────────────────────
log "🏥 Vérification santé (timeout=${HEALTH_CHECK_TIMEOUT}s)..."
+HEALTH_OK=false
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"
+ HEALTH_OK=true
break
fi
if [[ $i -eq "$HEALTH_CHECK_TIMEOUT" ]]; then
@@ -122,7 +249,7 @@ for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do
done
# ─── Rapport final ──────────────────────────────────────────────────────────
-GITEA_VERSION=$(curl -s "$HEALTH_CHECK_URL/api/v1/version" 2>/dev/null | grep -oP '"version":"\K[^"]+' || echo "inconnue")
+GITEA_VERSION=$(curl -s "$HEALTH_CHECK_URL/api/v1/version" 2>/dev/null | grep -oE '"version":"[^"]+"' | cut -d'"' -f4 || echo "inconnue")
log "✅ Mise à jour réussie !"
log "📋 Nouvelle version : $GITEA_VERSION"
@@ -132,7 +259,20 @@ log "═══ Fin ────────────────────
notify "✅ Gitea mis à jour vers $GITEA_VERSION sur $(hostname)"
-# Nettoyage vieilles images
+# ─── Nettoyage ciblé : seulement les vieilles images Gitea ──────────────────
+log "🧹 Nettoyage des anciennes images Gitea..."
+# Trouver toutes les images gitea/gitea sauf la dernière utilisée
+CURRENT_IMAGE_ID=$(docker image inspect "$FULL_IMAGE" --format '{{.ID}}' 2>/dev/null || echo "")
+if [[ -n "$CURRENT_IMAGE_ID" ]]; then
+ # Lister les images Gitea non taggées ou anciennes
+ docker images "$IMAGE_NAME" --format '{{.ID}} {{.Tag}}' 2>/dev/null | while read -r img_id img_tag; do
+ if [[ "$img_id" != "$CURRENT_IMAGE_ID" ]] && [[ "$img_tag" != "$IMAGE_TAG" ]]; then
+ log " 🗑️ Suppression ancienne image : $img_id ($img_tag)"
+ docker rmi "$img_id" 2>/dev/null || true
+ fi
+ done
+fi
+# Nettoyer aussi les images dangling (sans tag)
docker image prune -f 2>/dev/null || true
exit 0
diff --git a/gitea-backup.sh b/gitea-backup.sh
index 40363bf..556f8c9 100755
--- a/gitea-backup.sh
+++ b/gitea-backup.sh
@@ -9,16 +9,23 @@
# 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
+# 4. Génère un script restore.sh autonome
#
-# 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)
+# Paramètres configurables : voir gitea-scripts.conf
# =============================================================================
set -euo pipefail
-# ─── Configuration (modifiable) ─────────────────────────────────────────────
+# ─── Charger la configuration commune ─────────────────────────────────────────
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/gitea-scripts.conf}"
+
+if [[ -f "$CONFIG_FILE" ]]; then
+ # shellcheck source=/dev/null
+ source "$CONFIG_FILE"
+fi
+
+# ─── 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}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
@@ -26,6 +33,8 @@ 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}"
# Base de données
DB_NAME="${DB_NAME:-gitea}"
@@ -38,25 +47,176 @@ 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)
+RETENTION_DAYS="${RETENTION_DAYS:-30}"
+RETENTION_WEEKLY="${RETENTION_WEEKLY:-4}"
+RETENTION_MONTHLY="${RETENTION_MONTHLY:-6}"
# 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)
+COMPRESS="${COMPRESS:-true}"
+VERIFY_BACKUP="${VERIFY_BACKUP:-true}"
+STOP_CONTAINERS="${STOP_CONTAINERS:-false}"
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
# Log
LOG_FILE="${LOG_FILE:-/var/log/gitea-backup.log}"
+LOG_DIR="$(dirname "$LOG_FILE")"
+
+# Lock file
+LOCK_DIR="${LOCK_DIR:-/tmp}"
+LOCK_FILE="${LOCK_DIR}/gitea-backup.lock"
+
+# Backup distant
+REMOTE_BACKUP_ENABLED="${REMOTE_BACKUP_ENABLED:-false}"
+REMOTE_BACKUP_METHOD="${REMOTE_BACKUP_METHOD:-rsync}"
+REMOTE_BACKUP_HOST="${REMOTE_BACKUP_HOST:-}"
+REMOTE_BACKUP_USER="${REMOTE_BACKUP_USER:-}"
+REMOTE_BACKUP_PATH="${REMOTE_BACKUP_PATH:-}"
+REMOTE_BACKUP_SSH_KEY="${REMOTE_BACKUP_SSH_KEY:-$HOME/.ssh/id_rsa}"
+
+# Notifications
+NOTIFY_METHOD="${NOTIFY_METHOD:-log}"
+TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
+TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
+NTPY_TOPIC="${NTPY_TOPIC:-}"
+
+# ─── Prérequis : s'assurer que le dossier de log existe ──────────────────────
+mkdir -p "$LOG_DIR" 2>/dev/null || true
# ─── Logging ─────────────────────────────────────────────────────────────────
log() {
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
+ local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
+ echo "$msg"
+ echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}
-# ─── Initialisation ─────────────────────────────────────────────────────────
+# ─── Notification ────────────────────────────────────────────────────────────
+notify() {
+ local msg="$1"
+ case "$NOTIFY_METHOD" in
+ telegram)
+ if [[ -n "$TELEGRAM_BOT_TOKEN" ]] && [[ -n "$TELEGRAM_CHAT_ID" ]]; then
+ 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
+ fi
+ ;;
+ ntfy)
+ if [[ -n "$NTPY_TOPIC" ]]; then
+ curl -s -H "Title: Gitea Backup" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
+ fi
+ ;;
+ esac
+}
+
+# ─── Lock file ───────────────────────────────────────────────────────────────
+acquire_lock() {
+ if [[ -f "$LOCK_FILE" ]]; then
+ local pid
+ pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
+ log "⚠️ Une autre instance (PID $pid) est déjà en cours d'exécution — arrêt"
+ exit 0
+ fi
+ log "⚠️ Lock stale détecté (PID $pid n'existe plus), on continue"
+ rm -f "$LOCK_FILE"
+ fi
+ echo $$ > "$LOCK_FILE"
+}
+
+release_lock() {
+ rm -f "$LOCK_FILE"
+}
+
+cleanup() {
+ release_lock
+}
+trap cleanup EXIT
+
+# ─── Détection docker compose ────────────────────────────────────────────────
+detect_compose() {
+ if docker compose version &>/dev/null; then
+ echo "docker compose"
+ elif command -v docker-compose &>/dev/null; then
+ echo "docker-compose"
+ else
+ echo ""
+ fi
+}
+
+# ─── Vérification espace disque (robuste) ────────────────────────────────────
+check_disk_space() {
+ local path="$1"
+ local min_kb="${2:-2097152}" # 2 GB par défaut
+ local available_kb
+
+ # Méthode robuste : utiliser df -P (format POSIX, pas de wrapping)
+ available_kb=$(df -P "$path" 2>/dev/null | tail -1 | awk '{print $4}')
+ if [[ -z "$available_kb" || "$available_kb" -le 0 ]]; then
+ # Fallback avec --output (GNU df)
+ available_kb=$(df --output=avail "$path" 2>/dev/null | tail -1 | tr -d ' ' || echo "0")
+ fi
+
+ if [[ "$available_kb" -lt "$min_kb" ]]; then
+ return 1
+ fi
+ echo "$available_kb"
+ return 0
+}
+
+# ─── Backup distant ──────────────────────────────────────────────────────────
+sync_remote() {
+ local backup_path="$1"
+ if [[ "$REMOTE_BACKUP_ENABLED" != "true" ]]; then
+ return 0
+ fi
+ if [[ -z "$REMOTE_BACKUP_HOST" ]] || [[ -z "$REMOTE_BACKUP_USER" ]] || [[ -z "$REMOTE_BACKUP_PATH" ]]; then
+ log "⚠️ Backup distant : configuration incomplète, ignoré"
+ return 0
+ fi
+
+ log "☁️ Synchronisation distante ($REMOTE_BACKUP_METHOD) vers ${REMOTE_BACKUP_USER}@${REMOTE_BACKUP_HOST}:${REMOTE_BACKUP_PATH}..."
+
+ local ssh_opts="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
+ if [[ -f "$REMOTE_BACKUP_SSH_KEY" ]]; then
+ ssh_opts="$ssh_opts -i $REMOTE_BACKUP_SSH_KEY"
+ fi
+
+ case "$REMOTE_BACKUP_METHOD" in
+ rsync)
+ if command -v rsync &>/dev/null; then
+ rsync -avz -e "ssh $ssh_opts" \
+ "$backup_path" \
+ "${REMOTE_BACKUP_USER}@${REMOTE_BACKUP_HOST}:${REMOTE_BACKUP_PATH}/" 2>&1 || {
+ log "⚠️ Échec rsync distant"
+ return 1
+ }
+ log " ✅ Synchronisation rsync réussie"
+ else
+ log "⚠️ rsync non installé, backup distant ignoré"
+ return 1
+ fi
+ ;;
+ scp)
+ scp $ssh_opts -r "$backup_path" \
+ "${REMOTE_BACKUP_USER}@${REMOTE_BACKUP_HOST}:${REMOTE_BACKUP_PATH}/" 2>&1 || {
+ log "⚠️ Échec SCP distant"
+ return 1
+ }
+ log " ✅ Synchronisation SCP réussie"
+ ;;
+ *)
+ log "⚠️ Méthode de backup distant inconnue : $REMOTE_BACKUP_METHOD"
+ return 1
+ ;;
+ esac
+ return 0
+}
+
+# ═════════════════════════════════════════════════════════════════════════════
+# MAIN
+# ═════════════════════════════════════════════════════════════════════════════
+
+acquire_lock
+
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DATE_LABEL=$(date +%Y-%m-%d)
DAY_OF_WEEK=$(date +%u) # 1=lundi, 7=dimanche
@@ -66,6 +226,8 @@ BACKUP_NAME="gitea-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"
@@ -73,30 +235,31 @@ log "📦 Début backup Gitea → $BACKUP_PATH"
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)"
+if [[ -z "$DOCKER_COMPOSE" ]]; then
+ log "❌ Ni 'docker compose' ni 'docker-compose' n'est disponible"
exit 1
fi
+mkdir -p "$BACKUP_PATH"
+
+# Vérifier espace disque (minimum 2 GB)
+AVAILABLE_KB=$(check_disk_space "$BACKUP_DIR" 2097152) || {
+ log "❌ Espace disque insuffisant (moins de 2 GB disponibles sur $BACKUP_DIR)"
+ exit 1
+}
log "💾 Espace disque : $((AVAILABLE_KB / 1024)) MB disponibles"
# ─── Optionnel : arrêter les conteneurs pour un backup consistant ───────────
+CONTAINERS_WERE_STOPPED=false
if [[ "$STOP_CONTAINERS" == "true" ]]; then
log "⏸️ Arrêt temporaire des conteneurs..."
- docker compose -f "$COMPOSE_FILE" stop 2>&1 | tee -a "$LOG_FILE"
+ $DOCKER_COMPOSE -f "$COMPOSE_FILE" stop 2>&1 | tee -a "$LOG_FILE" || true
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" \
@@ -106,30 +269,30 @@ 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>&1 || {
+ "$DB_NAME" > "${BACKUP_PATH}/gitea-db.sql" 2>"${BACKUP_PATH}/mysqldump.err" || {
log "❌ Échec du dump MySQL"
- [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE"
+ 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
}
- DB_SIZE=$(wc -c < "${BACKUP_PATH}/gitea-db.sql")
+ rm -f "${BACKUP_PATH}/mysqldump.err"
+ DB_SIZE=$(wc -c < "${BACKUP_PATH}/gitea-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)"
- [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && docker compose -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE"
+ [[ "$CONTAINERS_WERE_STOPPED" == "true" ]] && $DOCKER_COMPOSE -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" || true
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}')"
+ log " 📦 Compressé : $(ls -lh "${BACKUP_PATH}/gitea-db.sql.gz" 2>/dev/null | 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' \
@@ -138,10 +301,10 @@ tar -czf "${BACKUP_PATH}/gitea-data.tar.gz" \
--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"
+ [[ "$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" | awk '{print $5}')
+DATA_SIZE=$(ls -lh "${BACKUP_PATH}/gitea-data.tar.gz" 2>/dev/null | awk '{print $5}')
log " ✅ Volume data sauvegardé ($DATA_SIZE)"
log "📁 Backup volume MySQL ($VOLUME_MYSQL)..."
@@ -149,10 +312,10 @@ 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"
+ [[ "$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" | awk '{print $5}')
+MYSQL_SIZE=$(ls -lh "${BACKUP_PATH}/gitea-mysql.tar.gz" 2>/dev/null | awk '{print $5}')
log " ✅ Volume MySQL sauvegardé ($MYSQL_SIZE)"
# ─── 3. Sauvegarde docker-compose + config ──────────────────────────────────
@@ -163,13 +326,15 @@ cp "$COMPOSE_DIR/$COMPOSE_FILE" "${BACKUP_PATH}/docker-compose.yml"
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/"
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"
-
+ $DOCKER_COMPOSE -f "$COMPOSE_FILE" start 2>&1 | tee -a "$LOG_FILE" || true
+
# 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
@@ -183,6 +348,9 @@ 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"
+
cat > "$RESTORE_SCRIPT" << RESTORE_EOF
#!/bin/bash
# =============================================================================
@@ -191,12 +359,6 @@ cat > "$RESTORE_SCRIPT" << RESTORE_EOF
# =============================================================================
# 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
@@ -221,7 +383,7 @@ for arg in "\$@"; do
case "\$arg" in
--dry-run) DRY_RUN=true ;;
--force) FORCE=true ;;
- --help) head -30 "\$0"; exit 0 ;;
+ --help|-h) head -50 "\$0"; exit 0 ;;
esac
done
@@ -246,23 +408,19 @@ if [[ "\$DRY_RUN" == "true" ]]; then
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 " 7. docker exec gitea_db 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 " 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
@@ -270,58 +428,77 @@ 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/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")"
+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/"
+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}/gitea/conf"
+ cp "\$BACKUP_DIR/app.ini" "\${TARGET_VOLUME_DATA}/gitea/conf/app.ini"
echo " ✅ app.ini restauré"
fi
+# Détecter docker compose ou docker-compose
+if docker compose version &>/dev/null; then
+ DC="docker compose"
+elif command -v docker-compose &>/dev/null; then
+ DC="docker-compose"
+else
+ echo "❌ Ni 'docker compose' ni 'docker-compose' n'est disponible"
+ exit 1
+fi
+
echo "📦 Étape 5/6 : Démarrage des conteneurs..."
-cd "$TARGET_COMPOSE_DIR"
-docker compose up -d
+cd "\$TARGET_COMPOSE_DIR"
+\$DC 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"
+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"
+ MYSQL_READY=true
break
fi
sleep 1
done
+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/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"
+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"
else
- docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" < "$DB_DUMP"
+ echo "❌ Fichier dump introuvable : \$DB_DUMP"
+ exit 1
fi
echo ""
echo "🔄 Redémarrage final des conteneurs..."
-docker compose restart
+\$DC restart
echo ""
echo "════════════════════════════════════════════════════════"
echo "✅ RESTAURATION TERMINÉE !"
echo ""
echo " Gitea : http://localhost:3000"
-echo " Vérifiez les logs : docker compose logs -f"
+echo " Vérifiez les logs : \$DC logs -f"
echo "════════════════════════════════════════════════════════"
RESTORE_EOF
@@ -332,21 +509,24 @@ log " ✅ Script restore créé : restore.sh"
if [[ "$VERIFY_BACKUP" == "true" ]]; then
log "🔍 Vérification intégrité des archives..."
ERRORS=0
-
+
+ # Vérifier chaque archive .tar.gz (avec gestion du glob vide)
+ shopt -s nullglob
for ARCHIVE in "$BACKUP_PATH"/*.tar.gz; do
if ! tar -tzf "$ARCHIVE" > /dev/null 2>&1; then
- log " ❌ Archive corrompue : $ARCHIVE"
+ log " ❌ Archive corrompue : $(basename "$ARCHIVE")"
ERRORS=$((ERRORS + 1))
fi
done
-
+ shopt -u nullglob
+
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
@@ -355,55 +535,121 @@ if [[ "$VERIFY_BACKUP" == "true" ]]; then
fi
# ─── 6. Métadonnées du backup ───────────────────────────────────────────────
+GITEA_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")
+fi
+
+DB_DISPLAY_SIZE=$(ls -lh "${BACKUP_PATH}"/gitea-db.sql* 2>/dev/null | awk '{print $5}' | head -1)
+
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")
+Gitea version : $GITEA_VERSION
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
+Containers stopped : $CONTAINERS_WERE_STOPPED
+Taille DB : ${DB_DISPLAY_SIZE:-N/A}
+Taille data : ${DATA_SIZE:-N/A}
+Taille mysql : ${MYSQL_SIZE:-N/A}
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"
+# 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
+ 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
+ local_date=$(echo "$backup_name" | grep -oE '[0-9]{8}' | head -1)
+ if [[ -z "$local_date" ]]; then
+ continue
+ fi
+
+ local_day=${local_date:6:2} # Jour du mois (DD)
+ local_month=${local_date:4:2}
+ local_year=${local_date:0:4}
+
+ # Déterminer le jour de la semaine (1=lundi, 7=dimanche)
+ local_dow=$(date -d "${local_year}-${local_month}-${local_day}" +%u 2>/dev/null || echo "0")
+
+ # Âge en jours
+ backup_ts=$(date -d "${local_year}-${local_month}-${local_day}" +%s 2>/dev/null || echo "0")
+ now_ts=$(date +%s)
+ age_days=$(( (now_ts - backup_ts) / 86400 ))
+
+ # Décisions de rétention
+ KEEP=false
+ REASON=""
+
+ # Toujours garder : backups récents
+ if [[ "$age_days" -le "$RETENTION_DAYS" ]]; then
+ KEEP=true
+ REASON="récent (${age_days}j <= ${RETENTION_DAYS}j)"
+ fi
+
+ # Toujours garder : backups du dimanche (hebdomadaire) dans la limite RETENTION_WEEKLY
+ if [[ "$local_dow" == "7" ]]; then
+ # 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
+ 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
+ echo "$wb"
+ fi
+ 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
+ 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
+ echo "$wb"
+ fi
+ done | sort -r | head -n "$RETENTION_WEEKLY")
+
+ if echo "$weekly_list" | grep -qF "$backup_name"; then
+ KEEP=true
+ REASON="hebdomadaire (dimanche, dans les ${RETENTION_WEEKLY} derniers)"
+ fi
+ fi
+
+ # 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
+ mb_date=$(echo "$mb" | grep -oE '[0-9]{8}')
+ mb_day=${mb_date:6:2}
+ if [[ "$mb_day" == "01" ]]; then
+ echo "$mb"
+ fi
+ done | sort -r | head -n "$RETENTION_MONTHLY")
+
+ if echo "$monthly_list" | grep -qF "$backup_name"; then
+ KEEP=true
+ REASON="mensuel (1er du mois, dans les ${RETENTION_MONTHLY} derniers)"
+ fi
+ fi
+
+ if [[ "$KEEP" == "true" ]]; then
+ log " 💾 Gardé : $backup_name ($REASON)"
+ else
+ log " 🗑️ Suppression : $backup_name"
+ rm -rf "$backup_path"
+ fi
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
+# ─── 8. Backup distant ──────────────────────────────────────────────────────
+sync_remote "$BACKUP_PATH" || true
-# 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)
+# ─── 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)
log "✅ Backup terminé !"
-log "📊 Total : $TOTAL_SIZE"
+log "📊 Total : ${TOTAL_SIZE:-N/A}"
log "📋 Backups conservés : $BACKUP_COUNT"
log "📂 Destination : $BACKUP_PATH"
log "═══ Fin ───────────────────────────────────────────"
@@ -414,7 +660,10 @@ 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 " ├── app.ini ← Configuration Gitea"
echo " ├── restore.sh ← Script de restore"
echo " └── backup-info.txt ← Métadonnées"
+notify "✅ Backup Gitea terminé sur $(hostname) — $TOTAL_SIZE — $BACKUP_PATH"
+
exit 0
diff --git a/gitea-scripts.conf b/gitea-scripts.conf
new file mode 100644
index 0000000..1faa861
--- /dev/null
+++ b/gitea-scripts.conf
@@ -0,0 +1,75 @@
+# =============================================================================
+# gitea-scripts.conf — Configuration unifiée pour tous les scripts Gitea
+# =============================================================================
+# Ce fichier est sourcé par tous les scripts (auto-update, backup, install_cron).
+# Les valeurs peuvent être surchargées par des variables d'environnement.
+# =============================================================================
+
+# ─── Chemins ──────────────────────────────────────────────────────────────────
+SCRIPTS_DIR="${SCRIPTS_DIR:-$HOME/scripts}"
+COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}"
+COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
+BACKUP_DIR="${BACKUP_DIR:-/NFS/BACKUP/DOCKER/dev-prod/gitea}"
+LOCK_DIR="${LOCK_DIR:-/tmp}"
+
+# ─── Conteneurs ───────────────────────────────────────────────────────────────
+APP_SERVICE="${APP_SERVICE:-gitea_server}"
+APP_CONTAINER="${APP_CONTAINER:-gitea_server}"
+DB_SERVICE="${DB_SERVICE:-gitea_db}"
+DB_CONTAINER="${DB_CONTAINER:-gitea_db}"
+
+# ─── Image Docker ─────────────────────────────────────────────────────────────
+IMAGE_NAME="${IMAGE_NAME:-gitea/gitea}"
+IMAGE_TAG="${IMAGE_TAG:-latest}"
+
+# ─── 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) ────────────────────────────────────────────
+VOLUME_DATA="${VOLUME_DATA:-/DOCKER_CONFIG/gitea/data}"
+VOLUME_MYSQL="${VOLUME_MYSQL:-/DOCKER_CONFIG/gitea/mysql}"
+
+# ─── URLs ─────────────────────────────────────────────────────────────────────
+HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}"
+HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}"
+
+# ─── Logs ─────────────────────────────────────────────────────────────────────
+LOG_DIR="${LOG_DIR:-/var/log}"
+UPDATE_LOG="${UPDATE_LOG:-$LOG_DIR/gitea-update.log}"
+BACKUP_LOG="${BACKUP_LOG:-$LOG_DIR/gitea-backup.log}"
+BACKUP_CRON_LOG="${BACKUP_CRON_LOG:-$LOG_DIR/gitea-backup-cron.log}"
+UPDATE_CRON_LOG="${UPDATE_CRON_LOG:-$LOG_DIR/gitea-update-cron.log}"
+LOG_RETENTION_DAYS="${LOG_RETENTION_DAYS:-90}"
+
+# ─── Rétention backups ────────────────────────────────────────────────────────
+RETENTION_DAYS="${RETENTION_DAYS:-30}"
+RETENTION_WEEKLY="${RETENTION_WEEKLY:-4}"
+RETENTION_MONTHLY="${RETENTION_MONTHLY:-6}"
+
+# ─── Options backup ───────────────────────────────────────────────────────────
+COMPRESS="${COMPRESS:-true}"
+VERIFY_BACKUP="${VERIFY_BACKUP:-true}"
+STOP_CONTAINERS="${STOP_CONTAINERS:-false}"
+ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
+
+# ─── Notifications ────────────────────────────────────────────────────────────
+NOTIFY_METHOD="${NOTIFY_METHOD:-log}"
+TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
+TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
+NTPY_TOPIC="${NTPY_TOPIC:-}"
+
+# ─── Backup distant (optionnel) ───────────────────────────────────────────────
+REMOTE_BACKUP_ENABLED="${REMOTE_BACKUP_ENABLED:-false}"
+REMOTE_BACKUP_METHOD="${REMOTE_BACKUP_METHOD:-rsync}"
+REMOTE_BACKUP_HOST="${REMOTE_BACKUP_HOST:-}"
+REMOTE_BACKUP_USER="${REMOTE_BACKUP_USER:-}"
+REMOTE_BACKUP_PATH="${REMOTE_BACKUP_PATH:-}"
+REMOTE_BACKUP_SSH_KEY="${REMOTE_BACKUP_SSH_KEY:-$HOME/.ssh/id_rsa}"
+
+# ─── Cron ─────────────────────────────────────────────────────────────────────
+CRON_BACKUP_SCHEDULE="${CRON_BACKUP_SCHEDULE:-0 2 * * *}"
+CRON_UPDATE_SCHEDULE="${CRON_UPDATE_SCHEDULE:-0 4 * * *}"
+CRON_MARKER="${CRON_MARKER:-# >>> GITEA SCRIPTS (managed by install_cron.sh) <<<}"
diff --git a/install_cron.sh b/install_cron.sh
index 5a7a4b1..97d1c41 100755
--- a/install_cron.sh
+++ b/install_cron.sh
@@ -7,13 +7,23 @@
# bash install_cron.sh --force → Installation sans confirmation
# bash install_cron.sh --uninstall → Retire les entrées du cron
# bash install_cron.sh --status → Vérifie si les crons sont actifs
+# bash install_cron.sh --dry-run → Affiche ce qui serait installé
#
-# Paramètres configurables (modifier ci-dessous ou passer en variables d'env)
+# Paramètres configurables : voir gitea-scripts.conf
# =============================================================================
set -euo pipefail
-# ─── Configuration ──────────────────────────────────────────────────────────
+# ─── Charger la configuration commune ─────────────────────────────────────────
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/gitea-scripts.conf}"
+
+if [[ -f "$CONFIG_FILE" ]]; then
+ # shellcheck source=/dev/null
+ source "$CONFIG_FILE"
+fi
+
+# ─── Configuration locale ────────────────────────────────────────────────────
SCRIPTS_DIR="${SCRIPTS_DIR:-$HOME/scripts}"
CRON_BACKUP_SCHEDULE="${CRON_BACKUP_SCHEDULE:-0 2 * * *}"
CRON_UPDATE_SCHEDULE="${CRON_UPDATE_SCHEDULE:-0 4 * * *}"
@@ -42,6 +52,7 @@ Usage: $0 [OPTION]
Options :
--force Installation sans confirmation
+ --dry-run Affiche ce qui serait installé sans rien faire
--uninstall Retire les scripts du cron
--status Vérifie l'état des crons Gitea
--help Ce message
@@ -73,9 +84,28 @@ check_prerequisites() {
missing=1
fi
+ # Vérifier aussi la présence du fichier de config (optionnel mais recommandé)
+ if [[ ! -f "$SCRIPTS_DIR/gitea-scripts.conf" ]]; then
+ warn "Fichier de configuration introuvable : $SCRIPTS_DIR/gitea-scripts.conf"
+ info "Les scripts utiliseront leurs valeurs par défaut"
+ fi
+
return $missing
}
+# ─── Construire le bloc cron ────────────────────────────────────────────────
+build_cron_block() {
+ cat << CRON_BLOCK
+
+$CRON_MARKER
+# Backup quotidien Gitea (DB + volumes)
+$CRON_BACKUP_SCHEDULE $SCRIPTS_DIR/$BACKUP_SCRIPT >> $BACKUP_LOG 2>&1
+# Vérification mise à jour Gitea Docker
+$CRON_UPDATE_SCHEDULE $SCRIPTS_DIR/$UPDATE_SCRIPT >> $UPDATE_LOG 2>&1
+# >>> END GITEA SCRIPTS <<<
+CRON_BLOCK
+}
+
# ─── Status ─────────────────────────────────────────────────────────────────
show_status() {
echo ""
@@ -93,10 +123,15 @@ show_status() {
echo ""
echo "Entrées actives :"
- echo "$current_cron" | grep -A1 "$CRON_MARKER" | grep -v "$CRON_MARKER" | grep -v '^$' | while read -r line; do
- local schedule cmd rest
+ # Extraire toutes les lignes entre les marqueurs
+ echo "$current_cron" | sed -n "/$CRON_MARKER/,/>>> END GITEA SCRIPTS <<