Correction et enrichissement complet des scripts Gitea

- Corrections de bugs (14): grep portable, df robuste, rollback reel,
  retention intelligente, nettoyage cible, lock anti-concurrence, etc.
- Nouveaux fichiers: setup.sh (installateur tout-en-un), restore.sh
  (restauration standalone), gitea-scripts.conf (config unifiee)
- Features: backup distant (rsync/SCP), lock files, logrotate, dry-run,
  healthchecks Docker, notifications Telegram/ntfy.sh
- Documentation enrichie avec diagrammes d'architecture mermaid
This commit is contained in:
2026-06-26 11:53:28 -04:00
parent 33d6c5d266
commit 1c6506e319
8 changed files with 1927 additions and 240 deletions
+303 -69
View File
@@ -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<br/>Configuration unifiée"]
end
subgraph Core["🛠️ Scripts core"]
BACKUP["gitea-backup.sh<br/>💾 Backup quotidien"]
UPDATE["gitea-auto-update.sh<br/>🔄 Mise à jour auto"]
RESTORE["restore.sh<br/>♻️ Restauration"]
end
subgraph Deploy["🚀 Déploiement"]
SETUP["setup.sh<br/>Installateur complet"]
CRON["install_cron.sh<br/>Gestion crontab"]
end
subgraph Runtime["⚡ Runtime"]
CRONTAB["Crontab<br/>Backup 2h → Update 4h"]
DOCKER["Docker Compose<br/>Gitea + MySQL"]
end
subgraph Storage["💾 Stockage"]
LOCAL["Backups locaux<br/>/NFS/BACKUP/..."]
REMOTE["Backup distant<br/>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 ## 🚀 Installation rapide
```bash ```bash
# Cloner les scripts # Cloner et installer en une commande
git clone https://git.dracodev.net/Outils/gitea-scripts.git ~/scripts git clone https://git.dracodev.net/Outils/gitea-scripts.git ~/scripts
bash ~/scripts/setup.sh
# Installer dans le cron (interactif)
bash ~/scripts/install_cron.sh
# Ou forcer sans confirmation # Ou forcer sans confirmation
bash ~/scripts/install_cron.sh --force bash ~/scripts/setup.sh --force
``` ```
--- ---
## 📋 Prérequis ## 📋 Prérequis
- Docker + Docker Compose - Docker + Docker Compose (plugin ou standalone)
- `curl`, `tar`, `gzip`, `mysqldump` (dans le conteneur MySQL) - `git`, `curl`, `tar`, `gzip`, `crontab`
- Droits d'écriture sur `/NFS/BACKUP/DOCKER/dev-prod/gitea/` (backup) - `mysqldump` (dans le conteneur MySQL)
- Droits d'écriture sur le répertoire de backup
- Droits sudo si les volumes sont protégés - 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 | ```bash
|----------|--------|-------------| bash setup.sh # Installation interactive
| `COMPOSE_DIR` | `~/dev/git/outils/docker/gitea` | Dossier du docker-compose | bash setup.sh --force # Sans confirmation
| `COMPOSE_FILE` | `docker-compose.yml` | Nom du fichier compose | bash setup.sh --update # Mise à jour des scripts (git pull)
| `CONTAINER_NAME` | `gitea_server` | Conteneur Gitea | bash setup.sh --dry-run # Aperçu sans rien installer
| `IMAGE_NAME` | `gitea/gitea` | Image Docker |
| `IMAGE_TAG` | `latest` | Tag (version) |
| `HEALTH_CHECK_URL` | `http://localhost:3000` | URL vérification santé |
| `HEALTH_CHECK_TIMEOUT` | `30` | Timeout attente (secondes) |
### Cron recommandé
```cron
# Tous les jours à 4h du matin
0 4 * * * /home/openclaw/scripts/gitea-auto-update.sh
``` ```
### Fonctionnement **Étapes exécutées :**
1. Vérification des prérequis système
1. Récupère le digest SHA256 de l'image actuelle 2. Clone/mise à jour depuis Git
2. `docker compose pull` pour télécharger la dernière version 3. Permissions d'exécution
3. Compare les digests → si identique, rien à faire 4. Configuration des logs
4. Si différent : backup rapide DB → `docker compose up -d` → vérification santé 5. Configuration logrotate (rotation automatique des logs)
5. Nettoie les vieilles images Docker (`image prune`) 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 | | Variable | Défaut | Description |
|----------|--------|-------------| |----------|--------|-------------|
@@ -82,15 +162,16 @@ Backup complet et portable de Gitea : DB MySQL + volumes Docker + config.
| `COMPRESS` | `true` | Compression gzip | | `COMPRESS` | `true` | Compression gzip |
| `VERIFY_BACKUP` | `true` | Vérifier intégrité après backup | | `VERIFY_BACKUP` | `true` | Vérifier intégrité après backup |
| `STOP_CONTAINERS` | `false` | Arrêter conteneurs (plus sûr) | | `STOP_CONTAINERS` | `false` | Arrêter conteneurs (plus sûr) |
| `REMOTE_BACKUP_ENABLED` | `false` | Activer backup distant (rsync/SCP) |
### Cron recommandé #### Cron recommandé
```cron ```cron
# Tous les jours à 2h du matin # Tous les jours à 2h du matin
0 2 * * * /home/openclaw/scripts/gitea-backup.sh 0 2 * * * /home/openclaw/scripts/gitea-backup.sh
``` ```
### Structure d'un backup #### Structure d'un backup
``` ```
/NFS/BACKUP/DOCKER/dev-prod/gitea/ /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 └── backup-info.txt ← Métadonnées
``` ```
### Restauration #### Backup distant (optionnel)
Configurer dans `gitea-scripts.conf` :
```bash ```bash
cd /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000/ REMOTE_BACKUP_ENABLED=true
REMOTE_BACKUP_METHOD=rsync # ou scp
# Dry-run (vérifier sans exécuter) REMOTE_BACKUP_HOST=backup.example.com
bash restore.sh --dry-run REMOTE_BACKUP_USER=backup
REMOTE_BACKUP_PATH=/backups/gitea
# Restauration réelle REMOTE_BACKUP_SSH_KEY=/home/user/.ssh/id_rsa
bash restore.sh --force
``` ```
Le script `restore.sh` est **auto-généré** à chaque backup et contient TOUT ce qu'il faut pour restaurer sur n'importe quel hôte Docker.
--- ---
## 🚀 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
bash install_cron.sh # Installation interactive bash install_cron.sh # Installation interactive
bash install_cron.sh --force # Sans confirmation 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 --uninstall # Retirer du cron
bash install_cron.sh --status # Vérifier l'état bash install_cron.sh --status # Vérifier l'état
``` ```
### Paramètres configurables Le script gère un **bloc marqué** dans la crontab :
| 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 :
```cron ```cron
# >>> GITEA SCRIPTS (managed by install_cron.sh) <<< # >>> 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 ```bash
# Exemple : changer le dossier de backup # 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 # 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` | | Auto-update | `/var/log/gitea-update.log` |
| Backup | `/var/log/gitea-backup.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
```
+24 -1
View File
@@ -28,7 +28,14 @@ services:
- "3000:3000" - "3000:3000"
- "222:22" - "222:22"
depends_on: 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: gitea_db:
image: mysql:8 image: mysql:8
@@ -43,3 +50,19 @@ services:
- gitea - gitea
volumes: volumes:
- /DOCKER_CONFIG/gitea/mysql:/var/lib/mysql - /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
+174 -34
View File
@@ -5,43 +5,127 @@
# À placer dans /usr/local/bin/ ou ~/scripts/ # À placer dans /usr/local/bin/ ou ~/scripts/
# Cron recommandé : 0 4 * * * /usr/local/bin/gitea-auto-update.sh # 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 set -euo pipefail
# ─── Configuration (modifiable) ───────────────────────────────────────────── # ─── Charger la configuration commune ─────────────────────────────────────────
COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/gitea}" 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}" 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_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}" LOG_FILE="${LOG_FILE:-/var/log/gitea-update.log}"
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}" # true pour envoyer un résumé LOG_DIR="$(dirname "$LOG_FILE")"
NOTIFY_METHOD="${NOTIFY_METHOD:-log}" # log, telegram, ntfy
# Notifications
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
NOTIFY_METHOD="${NOTIFY_METHOD:-log}"
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}" TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
NTPY_TOPIC="${NTPY_TOPIC:-}" NTPY_TOPIC="${NTPY_TOPIC:-}"
# Santé
HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}" HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}"
HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}" 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 ───────────────────────────────────────────────────────────────── # ─── Logging ─────────────────────────────────────────────────────────────────
log() { 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() { notify() {
local msg="$1" local msg="$1"
case "$NOTIFY_METHOD" in case "$NOTIFY_METHOD" in
telegram) telegram)
[[ -n "$TELEGRAM_CHAT_ID" ]] && curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ if [[ -n "$TELEGRAM_BOT_TOKEN" ]] && [[ -n "$TELEGRAM_CHAT_ID" ]]; then
-d "chat_id=$TELEGRAM_CHAT_ID" -d "text=$msg" > /dev/null 2>&1 || true 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) 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 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 "════════════════════════════════════════════════════════"
log "🔄 Début vérification mise à jour Gitea" log "🔄 Début vérification mise à jour Gitea"
@@ -53,23 +137,32 @@ if ! command -v docker &>/dev/null; then
exit 1 exit 1
fi fi
# ─── Récupérer l'image digest actuelle ────────────────────────────────────── # Vérifier si docker compose (plugin) ou docker-compose (standalone) est dispo
OLD_DIGEST=$(docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format '{{.RepoDigests}}' 2>/dev/null | grep -oP 'sha256:[a-f0-9]+' || echo "") 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}" log "🔍 Digest actuel : ${OLD_DIGEST:-non disponible}"
# ─── Pull de la nouvelle image ────────────────────────────────────────────── # ─── Pull de la nouvelle image ──────────────────────────────────────────────
log "📥 Pull de la nouvelle image..." log "📥 Pull de la nouvelle image..."
PULL_OUTPUT=$(docker compose -f "$COMPOSE_FILE" pull "$CONTAINER_NAME" 2>&1) PULL_OUTPUT=$($DOCKER_COMPOSE -f "$COMPOSE_FILE" pull "$APP_SERVICE" 2>&1) || {
PULL_EXIT=$? PULL_EXIT=$?
log "❌ Échec du pull (code $PULL_EXIT) : $PULL_OUTPUT"
if [[ $PULL_EXIT -ne 0 ]]; then
log "❌ Échec du pull : $PULL_OUTPUT"
exit 1 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}" log "🔍 Nouveau digest : ${NEW_DIGEST:-non disponible}"
@@ -83,34 +176,68 @@ fi
# ─── MISE À JOUR NÉCESSAIRE ───────────────────────────────────────────────── # ─── MISE À JOUR NÉCESSAIRE ─────────────────────────────────────────────────
log "🔔 Nouvelle version détectée ! Mise à jour en cours..." 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)}" BACKUP_PRE_DIR="${BACKUP_PRE_DIR:-/tmp/gitea-pre-update-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$BACKUP_PRE_DIR" mkdir -p "$BACKUP_PRE_DIR"
log "💾 Backup pré-update vers $BACKUP_PRE_DIR" log "💾 Backup DB pré-update vers $BACKUP_PRE_DIR"
docker exec gitea_db mysqldump -u root -pgitea gitea > "$BACKUP_PRE_DIR/gitea_db.sql" 2>/dev/null || { if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
log "⚠️ Backup pré-update DB échoué, on continue quand même" 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 # Recréer le conteneur avec la nouvelle image
log "🚀 Redémarrage du 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_OUTPUT=$($DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1) || {
UP_EXIT=$? UP_EXIT=$?
log "❌ Échec du redémarrage (code $UP_EXIT) : $UP_OUTPUT"
if [[ $UP_EXIT -ne 0 ]]; then # Rollback réel : restaurer l'ancienne image
log "❌ Échec du redémarrage : $UP_OUTPUT" if [[ -n "$ROLLBACK_TAG" ]]; then
log "⚠️ Tentative de rollback..." log "🔄 ROLLBACK : restauration de l'image $ROLLBACK_TAG..."
docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER_NAME" 2>&1 || true 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é" notify "❌ GITEA UPDATE FAILED sur $(hostname) — rollback effectué"
exit 1 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 fi
# ─── Vérification santé ───────────────────────────────────────────────────── # ─── Vérification santé ─────────────────────────────────────────────────────
log "🏥 Vérification santé (timeout=${HEALTH_CHECK_TIMEOUT}s)..." log "🏥 Vérification santé (timeout=${HEALTH_CHECK_TIMEOUT}s)..."
HEALTH_OK=false
for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do 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") 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 if [[ "$STATUS" == "200" || "$STATUS" == "302" || "$STATUS" == "303" ]]; then
log "✅ Gitea répond (HTTP $STATUS) après ${i}s" log "✅ Gitea répond (HTTP $STATUS) après ${i}s"
HEALTH_OK=true
break break
fi fi
if [[ $i -eq "$HEALTH_CHECK_TIMEOUT" ]]; then if [[ $i -eq "$HEALTH_CHECK_TIMEOUT" ]]; then
@@ -122,7 +249,7 @@ for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do
done done
# ─── Rapport final ────────────────────────────────────────────────────────── # ─── 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 "✅ Mise à jour réussie !"
log "📋 Nouvelle version : $GITEA_VERSION" log "📋 Nouvelle version : $GITEA_VERSION"
@@ -132,7 +259,20 @@ log "═══ Fin ────────────────────
notify "✅ Gitea mis à jour vers $GITEA_VERSION sur $(hostname)" 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 docker image prune -f 2>/dev/null || true
exit 0 exit 0
+356 -107
View File
@@ -9,16 +9,23 @@
# 1. La base de données MySQL (dump SQL complet) # 1. La base de données MySQL (dump SQL complet)
# 2. Les volumes Docker (data, mysql) # 2. Les volumes Docker (data, mysql)
# 3. Le fichier docker-compose.yml et la config # 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. # Paramètres configurables : voir gitea-scripts.conf
# Un restore peut se faire sur N'IMPORTE QUEL hôte Docker.
#
# Paramètres configurables (modifier ci-dessous ou passer en variables d'env)
# ============================================================================= # =============================================================================
set -euo pipefail 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}" BACKUP_DIR="${BACKUP_DIR:-/NFS/BACKUP/DOCKER/dev-prod/gitea}"
COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}" COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
@@ -26,6 +33,8 @@ COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
# Conteneurs # Conteneurs
DB_CONTAINER="${DB_CONTAINER:-gitea_db}" DB_CONTAINER="${DB_CONTAINER:-gitea_db}"
APP_CONTAINER="${APP_CONTAINER:-gitea_server}" APP_CONTAINER="${APP_CONTAINER:-gitea_server}"
DB_SERVICE="${DB_SERVICE:-gitea_db}"
APP_SERVICE="${APP_SERVICE:-gitea_server}"
# Base de données # Base de données
DB_NAME="${DB_NAME:-gitea}" 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}" VOLUME_MYSQL="${VOLUME_MYSQL:-/DOCKER_CONFIG/gitea/mysql}"
# Rétention # Rétention
RETENTION_DAYS="${RETENTION_DAYS:-30}" # Garder X jours de backups RETENTION_DAYS="${RETENTION_DAYS:-30}"
RETENTION_WEEKLY="${RETENTION_WEEKLY:-4}" # Garder X backups hebdomadaires (dimanche) RETENTION_WEEKLY="${RETENTION_WEEKLY:-4}"
RETENTION_MONTHLY="${RETENTION_MONTHLY:-6}" # Garder X backups mensuels (1er du mois) RETENTION_MONTHLY="${RETENTION_MONTHLY:-6}"
# Options # Options
COMPRESS="${COMPRESS:-true}" # Compression gzip COMPRESS="${COMPRESS:-true}"
VERIFY_BACKUP="${VERIFY_BACKUP:-true}" # Vérifier l'intégrité après backup VERIFY_BACKUP="${VERIFY_BACKUP:-true}"
STOP_CONTAINERS="${STOP_CONTAINERS:-false}" # Arrêter les conteneurs pendant le backup (plus sûr) STOP_CONTAINERS="${STOP_CONTAINERS:-false}"
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}" ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
# Log # Log
LOG_FILE="${LOG_FILE:-/var/log/gitea-backup.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 ───────────────────────────────────────────────────────────────── # ─── Logging ─────────────────────────────────────────────────────────────────
log() { 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) TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DATE_LABEL=$(date +%Y-%m-%d) DATE_LABEL=$(date +%Y-%m-%d)
DAY_OF_WEEK=$(date +%u) # 1=lundi, 7=dimanche DAY_OF_WEEK=$(date +%u) # 1=lundi, 7=dimanche
@@ -66,6 +226,8 @@ BACKUP_NAME="gitea-backup-${TIMESTAMP}"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}" BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
RESTORE_SCRIPT="${BACKUP_PATH}/restore.sh" RESTORE_SCRIPT="${BACKUP_PATH}/restore.sh"
DOCKER_COMPOSE=$(detect_compose)
log "════════════════════════════════════════════════════════" log "════════════════════════════════════════════════════════"
log "📦 Début backup Gitea → $BACKUP_PATH" 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; } mkdir -p "$BACKUP_DIR" || { log "❌ Impossible de créer $BACKUP_DIR"; exit 1; }
cd "$COMPOSE_DIR" || { log "❌ Répertoire $COMPOSE_DIR introuvable"; exit 1; } cd "$COMPOSE_DIR" || { log "❌ Répertoire $COMPOSE_DIR introuvable"; exit 1; }
mkdir -p "$BACKUP_PATH" if [[ -z "$DOCKER_COMPOSE" ]]; then
log "❌ Ni 'docker compose' ni 'docker-compose' n'est disponible"
# Vérifier espace disque (minimum 2 GB)
AVAILABLE_KB=$(df "$BACKUP_DIR" | tail -1 | awk '{print $4}')
if [[ $AVAILABLE_KB -lt 2097152 ]]; then
log "❌ Espace disque insuffisant : $(($AVAILABLE_KB / 1024)) MB disponibles (min 2 GB requis)"
exit 1 exit 1
fi 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" log "💾 Espace disque : $((AVAILABLE_KB / 1024)) MB disponibles"
# ─── Optionnel : arrêter les conteneurs pour un backup consistant ─────────── # ─── Optionnel : arrêter les conteneurs pour un backup consistant ───────────
CONTAINERS_WERE_STOPPED=false
if [[ "$STOP_CONTAINERS" == "true" ]]; then if [[ "$STOP_CONTAINERS" == "true" ]]; then
log "⏸️ Arrêt temporaire des conteneurs..." 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 CONTAINERS_WERE_STOPPED=true
else
CONTAINERS_WERE_STOPPED=false
fi fi
# ─── 1. Backup base de données MySQL ──────────────────────────────────────── # ─── 1. Backup base de données MySQL ────────────────────────────────────────
log "🛢️ Dump base de données MySQL ($DB_NAME)..." 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 if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
docker exec "$DB_CONTAINER" mysqldump \ docker exec "$DB_CONTAINER" mysqldump \
-u root -p"$DB_ROOT_PASSWORD" \ -u root -p"$DB_ROOT_PASSWORD" \
@@ -106,30 +269,30 @@ if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
--events \ --events \
--hex-blob \ --hex-blob \
--default-character-set=utf8mb4 \ --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" 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 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)" log " ✅ Dump MySQL OK (${DB_SIZE} bytes)"
else else
log "❌ Conteneur $DB_CONTAINER non trouvé (pas en cours d'exécution)" 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 exit 1
fi fi
# Compresser le dump si demandé # Compresser le dump si demandé
if [[ "$COMPRESS" == "true" ]]; then if [[ "$COMPRESS" == "true" ]]; then
gzip -f "${BACKUP_PATH}/gitea-db.sql" 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 fi
# ─── 2. Backup volumes Docker ─────────────────────────────────────────────── # ─── 2. Backup volumes Docker ───────────────────────────────────────────────
log "📁 Backup volume data ($VOLUME_DATA)..." 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" \ tar -czf "${BACKUP_PATH}/gitea-data.tar.gz" \
-C "$(dirname "$VOLUME_DATA")" \ -C "$(dirname "$VOLUME_DATA")" \
--exclude='*.log' \ --exclude='*.log' \
@@ -138,10 +301,10 @@ tar -czf "${BACKUP_PATH}/gitea-data.tar.gz" \
--exclude='cache/*' \ --exclude='cache/*' \
"$(basename "$VOLUME_DATA")" 2>&1 || { "$(basename "$VOLUME_DATA")" 2>&1 || {
log "❌ Échec tar du volume data" 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 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 " ✅ Volume data sauvegardé ($DATA_SIZE)"
log "📁 Backup volume MySQL ($VOLUME_MYSQL)..." log "📁 Backup volume MySQL ($VOLUME_MYSQL)..."
@@ -149,10 +312,10 @@ tar -czf "${BACKUP_PATH}/gitea-mysql.tar.gz" \
-C "$(dirname "$VOLUME_MYSQL")" \ -C "$(dirname "$VOLUME_MYSQL")" \
"$(basename "$VOLUME_MYSQL")" 2>&1 || { "$(basename "$VOLUME_MYSQL")" 2>&1 || {
log "❌ Échec tar du volume MySQL" 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 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)" log " ✅ Volume MySQL sauvegardé ($MYSQL_SIZE)"
# ─── 3. Sauvegarde docker-compose + config ────────────────────────────────── # ─── 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 if [[ -f "${VOLUME_DATA}/gitea/conf/app.ini" ]]; then
cp "${VOLUME_DATA}/gitea/conf/app.ini" "${BACKUP_PATH}/app.ini" cp "${VOLUME_DATA}/gitea/conf/app.ini" "${BACKUP_PATH}/app.ini"
log " ✅ app.ini sauvegardé" log " ✅ app.ini sauvegardé"
else
log " ⚠️ app.ini non trouvé dans ${VOLUME_DATA}/gitea/conf/"
fi fi
# ─── Redémarrer les conteneurs si on les avait arrêtés ────────────────────── # ─── Redémarrer les conteneurs si on les avait arrêtés ──────────────────────
if [[ "$CONTAINERS_WERE_STOPPED" == "true" ]]; then if [[ "$CONTAINERS_WERE_STOPPED" == "true" ]]; then
log "▶️ Redémarrage des conteneurs..." 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 # Attendre que Gitea réponde
for i in $(seq 1 30); do 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 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 ──────────────────────────────────────── # ─── 4. Générer le script de restore ────────────────────────────────────────
log "📝 Génération du 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 cat > "$RESTORE_SCRIPT" << RESTORE_EOF
#!/bin/bash #!/bin/bash
# ============================================================================= # =============================================================================
@@ -191,12 +359,6 @@ cat > "$RESTORE_SCRIPT" << RESTORE_EOF
# ============================================================================= # =============================================================================
# Usage : bash restore.sh [OPTIONS] # 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 : # Options :
# --dry-run Affiche ce qui serait fait sans exécuter # --dry-run Affiche ce qui serait fait sans exécuter
# --force Ne pas demander confirmation # --force Ne pas demander confirmation
@@ -221,7 +383,7 @@ for arg in "\$@"; do
case "\$arg" in case "\$arg" in
--dry-run) DRY_RUN=true ;; --dry-run) DRY_RUN=true ;;
--force) FORCE=true ;; --force) FORCE=true ;;
--help) head -30 "\$0"; exit 0 ;; --help|-h) head -50 "\$0"; exit 0 ;;
esac esac
done done
@@ -246,23 +408,19 @@ if [[ "\$DRY_RUN" == "true" ]]; then
echo " 4. Copier docker-compose.yml → \$TARGET_COMPOSE_DIR/" echo " 4. Copier docker-compose.yml → \$TARGET_COMPOSE_DIR/"
echo " 5. docker compose up -d" echo " 5. docker compose up -d"
echo " 6. Attendre MySQL prêt" echo " 6. Attendre MySQL prêt"
RESTORE_EOF echo " 7. docker exec gitea_db mysql ... < $DB_DUMP_FILE"
# Continuer le script restore (le heredoc était trop long pour un seul bloc)
cat >> "$RESTORE_SCRIPT" << 'RESTORE_EOF'
echo " 7. docker exec gitea_db mysql ... < gitea-db.sql"
echo " 8. docker compose restart" echo " 8. docker compose restart"
echo "" echo ""
exit 0 exit 0
fi fi
if [[ "$FORCE" != "true" ]]; then if [[ "\$FORCE" != "true" ]]; then
echo "" echo ""
echo "⚠️ ATTENTION : Ceci va ÉCRASER les données Gitea existantes !" echo "⚠️ ATTENTION : Ceci va ÉCRASER les données Gitea existantes !"
echo " Toute donnée non sauvegardée sera PERDUE." echo " Toute donnée non sauvegardée sera PERDUE."
echo "" echo ""
read -p "Confirmer la restauration ? (taper 'RESTORE' en majuscules) : " CONFIRM read -p "Confirmer la restauration ? (taper 'RESTORE' en majuscules) : " CONFIRM
if [[ "$CONFIRM" != "RESTORE" ]]; then if [[ "\$CONFIRM" != "RESTORE" ]]; then
echo "❌ Restauration annulée." echo "❌ Restauration annulée."
exit 0 exit 0
fi fi
@@ -270,58 +428,77 @@ fi
echo "" echo ""
echo "📦 Étape 1/6 : Création des répertoires..." 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..." echo "📦 Étape 2/6 : Restauration volume MySQL..."
rm -rf "${TARGET_VOLUME_MYSQL:?}"/* rm -rf "\${TARGET_VOLUME_MYSQL:?}"/*
tar -xzf "$BACKUP_DIR/gitea-mysql.tar.gz" -C "$(dirname "$TARGET_VOLUME_MYSQL")" tar -xzf "\$BACKUP_DIR/gitea-mysql.tar.gz" -C "\$(dirname "\$TARGET_VOLUME_MYSQL")"
echo "📦 Étape 3/6 : Restauration volume Gitea..." echo "📦 Étape 3/6 : Restauration volume Gitea..."
rm -rf "${TARGET_VOLUME_DATA:?}"/* rm -rf "\${TARGET_VOLUME_DATA:?}"/*
tar -xzf "$BACKUP_DIR/gitea-data.tar.gz" -C "$(dirname "$TARGET_VOLUME_DATA")" tar -xzf "\$BACKUP_DIR/gitea-data.tar.gz" -C "\$(dirname "\$TARGET_VOLUME_DATA")"
echo "📦 Étape 4/6 : Copie docker-compose..." 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 # Restaurer app.ini si présent
if [[ -f "$BACKUP_DIR/app.ini" ]]; then if [[ -f "\$BACKUP_DIR/app.ini" ]]; then
mkdir -p "${TARGET_VOLUME_DATA}/gitea/conf" mkdir -p "\${TARGET_VOLUME_DATA}/gitea/conf"
cp "$BACKUP_DIR/app.ini" "${TARGET_VOLUME_DATA}/gitea/conf/app.ini" cp "\$BACKUP_DIR/app.ini" "\${TARGET_VOLUME_DATA}/gitea/conf/app.ini"
echo " ✅ app.ini restauré" echo " ✅ app.ini restauré"
fi 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..." echo "📦 Étape 5/6 : Démarrage des conteneurs..."
cd "$TARGET_COMPOSE_DIR" cd "\$TARGET_COMPOSE_DIR"
docker compose up -d \$DC up -d
echo " ⏳ Attente démarrage MySQL (30s)..." echo " ⏳ Attente démarrage MySQL (30s)..."
sleep 10 sleep 10
for i in $(seq 1 20); do MYSQL_READY=false
if docker exec gitea_db mysqladmin ping -u root -p"$DB_ROOT_PASSWORD" --silent 2>/dev/null; then for i in \$(seq 1 20); do
echo " ✅ MySQL prêt après $((10 + i))s" 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 break
fi fi
sleep 1 sleep 1
done 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..." echo "📦 Étape 6/6 : Restauration base de données..."
DB_DUMP="$BACKUP_DIR/gitea-db.sql" DB_DUMP="\$BACKUP_DIR/$DB_DUMP_FILE"
if [[ -f "${DB_DUMP}.gz" ]]; then 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" 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 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 fi
echo "" echo ""
echo "🔄 Redémarrage final des conteneurs..." echo "🔄 Redémarrage final des conteneurs..."
docker compose restart \$DC restart
echo "" echo ""
echo "════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════"
echo "✅ RESTAURATION TERMINÉE !" echo "✅ RESTAURATION TERMINÉE !"
echo "" echo ""
echo " Gitea : http://localhost:3000" echo " Gitea : http://localhost:3000"
echo " Vérifiez les logs : docker compose logs -f" echo " Vérifiez les logs : \$DC logs -f"
echo "════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════"
RESTORE_EOF RESTORE_EOF
@@ -332,21 +509,24 @@ log " ✅ Script restore créé : restore.sh"
if [[ "$VERIFY_BACKUP" == "true" ]]; then if [[ "$VERIFY_BACKUP" == "true" ]]; then
log "🔍 Vérification intégrité des archives..." log "🔍 Vérification intégrité des archives..."
ERRORS=0 ERRORS=0
# Vérifier chaque archive .tar.gz (avec gestion du glob vide)
shopt -s nullglob
for ARCHIVE in "$BACKUP_PATH"/*.tar.gz; do for ARCHIVE in "$BACKUP_PATH"/*.tar.gz; do
if ! tar -tzf "$ARCHIVE" > /dev/null 2>&1; then if ! tar -tzf "$ARCHIVE" > /dev/null 2>&1; then
log " ❌ Archive corrompue : $ARCHIVE" log " ❌ Archive corrompue : $(basename "$ARCHIVE")"
ERRORS=$((ERRORS + 1)) ERRORS=$((ERRORS + 1))
fi fi
done done
shopt -u nullglob
if [[ "$COMPRESS" == "true" ]]; then if [[ "$COMPRESS" == "true" ]]; then
if ! gunzip -t "${BACKUP_PATH}/gitea-db.sql.gz" 2>/dev/null; then if ! gunzip -t "${BACKUP_PATH}/gitea-db.sql.gz" 2>/dev/null; then
log " ❌ Dump SQL corrompu" log " ❌ Dump SQL corrompu"
ERRORS=$((ERRORS + 1)) ERRORS=$((ERRORS + 1))
fi fi
fi fi
if [[ $ERRORS -gt 0 ]]; then if [[ $ERRORS -gt 0 ]]; then
log "❌ $ERRORS erreur(s) d'intégrité détectée(s)" log "❌ $ERRORS erreur(s) d'intégrité détectée(s)"
exit 1 exit 1
@@ -355,55 +535,121 @@ if [[ "$VERIFY_BACKUP" == "true" ]]; then
fi fi
# ─── 6. Métadonnées du backup ─────────────────────────────────────────────── # ─── 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 cat > "${BACKUP_PATH}/backup-info.txt" << INFO
Date : $(date) Date : $(date)
Hôte : $(hostname) 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 type : MySQL 8
DB name : $DB_NAME DB name : $DB_NAME
Compression : $COMPRESS Compression : $COMPRESS
Containers stopped : $STOP_CONTAINERS Containers stopped : $CONTAINERS_WERE_STOPPED
Taille DB : $(ls -lh "${BACKUP_PATH}"/gitea-db.sql* | awk '{print $5}') Taille DB : ${DB_DISPLAY_SIZE:-N/A}
Taille data : $DATA_SIZE Taille data : ${DATA_SIZE:-N/A}
Taille mysql : $MYSQL_SIZE Taille mysql : ${MYSQL_SIZE:-N/A}
INFO INFO
# ─── 7. Nettoyage des vieux backups ───────────────────────────────────────── # ─── 7. Nettoyage des vieux backups ─────────────────────────────────────────
log "🧹 Nettoyage backups (rétention: ${RETENTION_DAYS}j, ${RETENTION_WEEKLY} hebdo, ${RETENTION_MONTHLY} mensuels)..." 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) # Pour chaque backup, déterminer s'il doit être gardé
find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -mtime +"$RETENTION_DAYS" | while read -r backup; do find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" -printf '%f\n' 2>/dev/null | sort -r | while read -r backup_name; do
BASENAME=$(basename "$backup") backup_path="$BACKUP_DIR/$backup_name"
# Garder les backups du dimanche et du 1er du mois (gérés séparément)
log " 🗑️ Suppression : $BASENAME" # Déterminer le jour du mois et le jour de la semaine à partir du nom
rm -rf "$backup" # 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 done
# Nettoyage hebdomadaire : garder les N derniers dimanches # ─── 8. Backup distant ──────────────────────────────────────────────────────
if [[ "$DAY_OF_WEEK" == "7" ]]; then sync_remote "$BACKUP_PATH" || true
find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" | sort -r | tail -n +$((RETENTION_WEEKLY + 1)) | while read -r backup; do
# Ne supprimer que si c'est un backup du dimanche
# (on ne touche pas aux backups mensuels du 1er)
[[ "$(basename "$backup")" =~ 01- ]] && continue
log " 🗑️ Suppression hebdo : $(basename "$backup")"
rm -rf "$backup"
done
fi
# Nettoyage mensuel : garder les N derniers 1er du mois # ─── 9. Rapport final ───────────────────────────────────────────────────────
if [[ "$DAY_OF_MONTH" == "01" ]]; then TOTAL_SIZE=$(du -sh "$BACKUP_PATH" 2>/dev/null | cut -f1)
find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*01-*" | sort -r | tail -n +$((RETENTION_MONTHLY + 1)) | while read -r backup; do BACKUP_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" 2>/dev/null | wc -l)
log " 🗑️ Suppression mensuel : $(basename "$backup")"
rm -rf "$backup"
done
fi
# ─── 8. Rapport final ───────────────────────────────────────────────────────
TOTAL_SIZE=$(du -sh "$BACKUP_PATH" | cut -f1)
BACKUP_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "gitea-backup-*" | wc -l)
log "✅ Backup terminé !" log "✅ Backup terminé !"
log "📊 Total : $TOTAL_SIZE" log "📊 Total : ${TOTAL_SIZE:-N/A}"
log "📋 Backups conservés : $BACKUP_COUNT" log "📋 Backups conservés : $BACKUP_COUNT"
log "📂 Destination : $BACKUP_PATH" log "📂 Destination : $BACKUP_PATH"
log "═══ Fin ───────────────────────────────────────────" log "═══ Fin ───────────────────────────────────────────"
@@ -414,7 +660,10 @@ echo " ├── gitea-db.sql.gz ← Dump MySQL"
echo " ├── gitea-data.tar.gz ← Volume /data" echo " ├── gitea-data.tar.gz ← Volume /data"
echo " ├── gitea-mysql.tar.gz ← Volume MySQL" echo " ├── gitea-mysql.tar.gz ← Volume MySQL"
echo " ├── docker-compose.yml ← Stack Docker" echo " ├── docker-compose.yml ← Stack Docker"
echo " ├── app.ini ← Configuration Gitea"
echo " ├── restore.sh ← Script de restore" echo " ├── restore.sh ← Script de restore"
echo " └── backup-info.txt ← Métadonnées" echo " └── backup-info.txt ← Métadonnées"
notify "✅ Backup Gitea terminé sur $(hostname) — $TOTAL_SIZE — $BACKUP_PATH"
exit 0 exit 0
+75
View File
@@ -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) <<<}"
+114 -29
View File
@@ -7,13 +7,23 @@
# bash install_cron.sh --force → Installation sans confirmation # bash install_cron.sh --force → Installation sans confirmation
# bash install_cron.sh --uninstall → Retire les entrées du cron # 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 --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 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}" SCRIPTS_DIR="${SCRIPTS_DIR:-$HOME/scripts}"
CRON_BACKUP_SCHEDULE="${CRON_BACKUP_SCHEDULE:-0 2 * * *}" CRON_BACKUP_SCHEDULE="${CRON_BACKUP_SCHEDULE:-0 2 * * *}"
CRON_UPDATE_SCHEDULE="${CRON_UPDATE_SCHEDULE:-0 4 * * *}" CRON_UPDATE_SCHEDULE="${CRON_UPDATE_SCHEDULE:-0 4 * * *}"
@@ -42,6 +52,7 @@ Usage: $0 [OPTION]
Options : Options :
--force Installation sans confirmation --force Installation sans confirmation
--dry-run Affiche ce qui serait installé sans rien faire
--uninstall Retire les scripts du cron --uninstall Retire les scripts du cron
--status Vérifie l'état des crons Gitea --status Vérifie l'état des crons Gitea
--help Ce message --help Ce message
@@ -73,9 +84,28 @@ check_prerequisites() {
missing=1 missing=1
fi 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 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 ───────────────────────────────────────────────────────────────── # ─── Status ─────────────────────────────────────────────────────────────────
show_status() { show_status() {
echo "" echo ""
@@ -93,10 +123,15 @@ show_status() {
echo "" echo ""
echo "Entrées actives :" echo "Entrées actives :"
echo "$current_cron" | grep -A1 "$CRON_MARKER" | grep -v "$CRON_MARKER" | grep -v '^$' | while read -r line; do # Extraire toutes les lignes entre les marqueurs
local schedule cmd rest echo "$current_cron" | sed -n "/$CRON_MARKER/,/>>> END GITEA SCRIPTS <<</p" | while read -r line; do
# Ignorer les marqueurs et les commentaires
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "$line" ]] && continue
# Afficher la ligne de cron
local schedule cmd
schedule=$(echo "$line" | awk '{print $1" "$2" "$3" "$4" "$5}') schedule=$(echo "$line" | awk '{print $1" "$2" "$3" "$4" "$5}')
cmd=$(echo "$line" | awk '{print $6}') cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')
echo " 🕐 $schedule → $cmd" echo " 🕐 $schedule → $cmd"
done done
else else
@@ -108,14 +143,25 @@ show_status() {
# Vérifier l'existence des scripts # Vérifier l'existence des scripts
if [[ -x "$SCRIPTS_DIR/$BACKUP_SCRIPT" ]]; then if [[ -x "$SCRIPTS_DIR/$BACKUP_SCRIPT" ]]; then
ok "Script backup : $SCRIPTS_DIR/$BACKUP_SCRIPT" ok "Script backup : $SCRIPTS_DIR/$BACKUP_SCRIPT"
elif [[ -f "$SCRIPTS_DIR/$BACKUP_SCRIPT" ]]; then
warn "Script backup : $SCRIPTS_DIR/$BACKUP_SCRIPT (non exécutable)"
else else
err "Script backup : $SCRIPTS_DIR/$BACKUP_SCRIPT (manquant ou non exécutable)" err "Script backup : $SCRIPTS_DIR/$BACKUP_SCRIPT (manquant)"
fi fi
if [[ -x "$SCRIPTS_DIR/$UPDATE_SCRIPT" ]]; then if [[ -x "$SCRIPTS_DIR/$UPDATE_SCRIPT" ]]; then
ok "Script update : $SCRIPTS_DIR/$UPDATE_SCRIPT" ok "Script update : $SCRIPTS_DIR/$UPDATE_SCRIPT"
elif [[ -f "$SCRIPTS_DIR/$UPDATE_SCRIPT" ]]; then
warn "Script update : $SCRIPTS_DIR/$UPDATE_SCRIPT (non exécutable)"
else else
err "Script update : $SCRIPTS_DIR/$UPDATE_SCRIPT (manquant ou non exécutable)" err "Script update : $SCRIPTS_DIR/$UPDATE_SCRIPT (manquant)"
fi
# Vérifier le fichier de config
if [[ -f "$SCRIPTS_DIR/gitea-scripts.conf" ]]; then
ok "Config : $SCRIPTS_DIR/gitea-scripts.conf"
else
warn "Config : $SCRIPTS_DIR/gitea-scripts.conf (absent)"
fi fi
echo "" echo ""
@@ -123,9 +169,9 @@ show_status() {
# Vérifier les logs # Vérifier les logs
if [[ -f "$BACKUP_LOG" ]]; then if [[ -f "$BACKUP_LOG" ]]; then
local log_size log_lines log_date local log_size log_lines log_date
log_size=$(du -h "$BACKUP_LOG" | cut -f1) log_size=$(du -h "$BACKUP_LOG" 2>/dev/null | cut -f1)
log_lines=$(wc -l < "$BACKUP_LOG") log_lines=$(wc -l < "$BACKUP_LOG" 2>/dev/null || echo "0")
log_date=$(tail -1 "$BACKUP_LOG" 2>/dev/null | cut -c1-19) log_date=$(tail -1 "$BACKUP_LOG" 2>/dev/null | cut -c1-19 || echo "N/A")
ok "Log backup : $BACKUP_LOG ($log_size, $log_lines lignes, dernier: $log_date)" ok "Log backup : $BACKUP_LOG ($log_size, $log_lines lignes, dernier: $log_date)"
else else
warn "Log backup : $BACKUP_LOG (pas encore créé)" warn "Log backup : $BACKUP_LOG (pas encore créé)"
@@ -133,9 +179,9 @@ show_status() {
if [[ -f "$UPDATE_LOG" ]]; then if [[ -f "$UPDATE_LOG" ]]; then
local log_size2 log_lines2 log_date2 local log_size2 log_lines2 log_date2
log_size2=$(du -h "$UPDATE_LOG" | cut -f1) log_size2=$(du -h "$UPDATE_LOG" 2>/dev/null | cut -f1)
log_lines2=$(wc -l < "$UPDATE_LOG") log_lines2=$(wc -l < "$UPDATE_LOG" 2>/dev/null || echo "0")
log_date2=$(tail -1 "$UPDATE_LOG" 2>/dev/null | cut -c1-19) log_date2=$(tail -1 "$UPDATE_LOG" 2>/dev/null | cut -c1-19 || echo "N/A")
ok "Log update : $UPDATE_LOG ($log_size2, $log_lines2 lignes, dernier: $log_date2)" ok "Log update : $UPDATE_LOG ($log_size2, $log_lines2 lignes, dernier: $log_date2)"
else else
warn "Log update : $UPDATE_LOG (pas encore créé)" warn "Log update : $UPDATE_LOG (pas encore créé)"
@@ -173,7 +219,13 @@ do_install() {
ok "Scripts rendus exécutables" ok "Scripts rendus exécutables"
# Créer les dossiers de logs si nécessaire # Créer les dossiers de logs si nécessaire
if [[ "$LOG_DIR" != "$HOME"* ]] && [[ ! -w "$LOG_DIR" ]]; then if [[ ! -d "$LOG_DIR" ]]; then
mkdir -p "$LOG_DIR" 2>/dev/null || {
warn "Impossible de créer $LOG_DIR — les logs iront dans \$HOME"
BACKUP_LOG="$HOME/gitea-backup-cron.log"
UPDATE_LOG="$HOME/gitea-update-cron.log"
}
elif [[ ! -w "$LOG_DIR" ]]; then
warn "Pas de droit d'écriture sur $LOG_DIR — les logs iront dans \$HOME" warn "Pas de droit d'écriture sur $LOG_DIR — les logs iront dans \$HOME"
BACKUP_LOG="$HOME/gitea-backup-cron.log" BACKUP_LOG="$HOME/gitea-backup-cron.log"
UPDATE_LOG="$HOME/gitea-update-cron.log" UPDATE_LOG="$HOME/gitea-update-cron.log"
@@ -193,19 +245,14 @@ do_install() {
# Construire le bloc cron # Construire le bloc cron
local cron_block local cron_block
cron_block=$(cat << CRON_BLOCK cron_block=$(build_cron_block)
$CRON_MARKER # Appliquer : gérer le cas où la crontab est vide
# Backup quotidien Gitea (DB + volumes) if [[ -z "$current_cron" ]]; then
$CRON_BACKUP_SCHEDULE $SCRIPTS_DIR/$BACKUP_SCRIPT >> $BACKUP_LOG 2>&1 echo "$cron_block" | crontab -
# Vérification mise à jour Gitea Docker else
$CRON_UPDATE_SCHEDULE $SCRIPTS_DIR/$UPDATE_SCRIPT >> $UPDATE_LOG 2>&1 printf "%s\n%s\n" "$current_cron" "$cron_block" | crontab -
# >>> END GITEA SCRIPTS <<< fi
CRON_BLOCK
)
# Appliquer
printf "%s\n" "$current_cron" "$cron_block" | crontab -
ok "Crons installés dans la crontab de $CRON_USER" ok "Crons installés dans la crontab de $CRON_USER"
echo "" echo ""
@@ -226,6 +273,32 @@ CRON_BLOCK
echo "════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════"
} }
# ─── Dry run ────────────────────────────────────────────────────────────────
do_dry_run() {
echo ""
echo "════════════════════════════════════════════════════════"
echo "🔍 DRY RUN — APERÇU DE L'INSTALLATION"
echo "════════════════════════════════════════════════════════"
echo ""
info "Utilisateur : $CRON_USER"
info "Dossier scripts : $SCRIPTS_DIR"
info "Horaire backup : $CRON_BACKUP_SCHEDULE"
info "Horaire update : $CRON_UPDATE_SCHEDULE"
echo ""
check_prerequisites || true
echo ""
echo "Bloc qui serait ajouté à la crontab :"
echo "────────────────────────────────────────"
build_cron_block
echo "────────────────────────────────────────"
echo ""
echo "⚠️ Aucune modification n'a été effectuée."
echo " Utilisez --force pour installer sans confirmation."
echo ""
}
# ─── Désinstallation ──────────────────────────────────────────────────────── # ─── Désinstallation ────────────────────────────────────────────────────────
do_uninstall() { do_uninstall() {
echo "" echo ""
@@ -246,13 +319,18 @@ do_uninstall() {
local new_cron local new_cron
new_cron=$(echo "$current_cron" | sed "/$CRON_MARKER/,/>>> END GITEA SCRIPTS <<</d") new_cron=$(echo "$current_cron" | sed "/$CRON_MARKER/,/>>> END GITEA SCRIPTS <<</d")
if [[ -z "$new_cron" || "$new_cron" == "$current_cron" ]]; then # Supprimer les lignes vides en trop
echo "$new_cron" | crontab - 2>/dev/null || crontab -r 2>/dev/null || true new_cron=$(echo "$new_cron" | sed '/^$/N;/^\n$/d')
if [[ -z "${new_cron// }" ]]; then
# Crontab complètement vide après suppression
crontab -r 2>/dev/null || true
ok "Crontab vidée et crons Gitea retirés"
else else
echo "$new_cron" | crontab - echo "$new_cron" | crontab -
ok "Crons Gitea retirés de la crontab de $CRON_USER"
fi fi
ok "Crons Gitea retirés de la crontab de $CRON_USER"
echo "" echo ""
echo " Les scripts eux-mêmes n'ont PAS été supprimés." echo " Les scripts eux-mêmes n'ont PAS été supprimés."
echo " Ils restent dans $SCRIPTS_DIR/" echo " Ils restent dans $SCRIPTS_DIR/"
@@ -264,12 +342,14 @@ do_uninstall() {
FORCE=false FORCE=false
UNINSTALL=false UNINSTALL=false
STATUS=false STATUS=false
DRY_RUN=false
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--force) FORCE=true ;; --force) FORCE=true ;;
--uninstall) UNINSTALL=true ;; --uninstall) UNINSTALL=true ;;
--status) STATUS=true ;; --status) STATUS=true ;;
--dry-run) DRY_RUN=true ;;
--help|-h) usage ;; --help|-h) usage ;;
*) echo "Option inconnue : $arg"; usage ;; *) echo "Option inconnue : $arg"; usage ;;
esac esac
@@ -285,6 +365,11 @@ if [[ "$UNINSTALL" == "true" ]]; then
exit 0 exit 0
fi fi
if [[ "$DRY_RUN" == "true" ]]; then
do_dry_run
exit 0
fi
# Installation # Installation
if [[ "$FORCE" != "true" ]]; then if [[ "$FORCE" != "true" ]]; then
echo "" echo ""
+414
View File
@@ -0,0 +1,414 @@
#!/bin/bash
# =============================================================================
# restore.sh — Restauration Gitea depuis un backup
# =============================================================================
# Usage :
# bash restore.sh <chemin_backup> → Restauration interactive
# bash restore.sh <chemin_backup> --force → Sans confirmation
# bash restore.sh <chemin_backup> --dry-run → Vérification sans exécuter
#
# Exemples :
# bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000
# bash restore.sh /NFS/BACKUP/DOCKER/dev-prod/gitea/gitea-backup-20260626-020000 --dry-run
# =============================================================================
set -euo pipefail
# ─── Couleurs ───────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ok() { echo -e " ${GREEN}✅${NC} $1"; }
warn() { echo -e " ${YELLOW}⚠️${NC} $1"; }
err() { echo -e " ${RED}❌${NC} $1"; }
info() { echo -e " ${CYAN}📋${NC} $1"; }
# ─── Usage ──────────────────────────────────────────────────────────────────
usage() {
cat << EOF
Usage: $0 <chemin_backup> [OPTIONS]
Arguments :
chemin_backup Dossier du backup à restaurer (ex: gitea-backup-YYYYMMDD-HHMMSS)
Options :
--force Ne pas demander confirmation
--dry-run Affiche ce qui serait fait sans exécuter
--help Cette aide
Variables d'environnement :
TARGET_COMPOSE_DIR Dossier docker-compose cible (défaut: ~/dev/git/outils/docker/gitea)
TARGET_VOLUME_DATA Volume data cible (défaut: /DOCKER_CONFIG/gitea/data)
TARGET_VOLUME_MYSQL Volume MySQL cible (défaut: /DOCKER_CONFIG/gitea/mysql)
DB_ROOT_PASSWORD Mot de passe root MySQL (défaut: gitea)
DB_NAME Nom base de données (défaut: gitea)
EOF
exit 0
}
# ─── Vérifications ──────────────────────────────────────────────────────────
check_prerequisites() {
local missing=0
if ! command -v docker &>/dev/null; then
err "Docker n'est pas installé"
missing=1
fi
if ! command -v tar &>/dev/null; then
err "tar n'est pas installé"
missing=1
fi
if docker compose version &>/dev/null; then
DOCKER_COMPOSE="docker compose"
elif command -v docker-compose &>/dev/null; then
DOCKER_COMPOSE="docker-compose"
else
err "Ni 'docker compose' ni 'docker-compose' n'est disponible"
missing=1
fi
return $missing
}
# ─── Lister les backups disponibles ─────────────────────────────────────────
list_backups() {
local dir="${1:-.}"
echo ""
echo "Backups disponibles :"
echo "─────────────────────"
local count=0
for d in "$dir"/gitea-backup-*; do
if [[ -d "$d" ]]; then
count=$((count + 1))
local info_file="$d/backup-info.txt"
local date_info=""
if [[ -f "$info_file" ]]; then
date_info=$(grep "^Date" "$info_file" 2>/dev/null | sed 's/Date[[:space:]]*:[[:space:]]*//' || echo "inconnue")
fi
echo " $count. $(basename "$d") ($date_info)"
fi
done
if [[ $count -eq 0 ]]; then
echo " Aucun backup trouvé dans $dir"
return 1
fi
echo ""
echo "Utilisez : $0 <chemin_backup>"
return 0
}
# ═════════════════════════════════════════════════════════════════════════════
# MAIN
# ═════════════════════════════════════════════════════════════════════════════
FORCE=false
DRY_RUN=false
BACKUP_PATH=""
# Détection docker compose (par défaut, sera surchargé après check)
DOCKER_COMPOSE="docker compose"
for arg in "$@"; do
case "$arg" in
--force) FORCE=true ;;
--dry-run) DRY_RUN=true ;;
--help|-h) usage ;;
--*) echo "Option inconnue : $arg"; usage ;;
*)
if [[ -z "$BACKUP_PATH" ]]; then
BACKUP_PATH="$arg"
fi
;;
esac
done
# Si aucun backup spécifié, lister les backups et suggérer
if [[ -z "$BACKUP_PATH" ]]; then
# Chercher dans les emplacements courants
FOUND=false
for search_dir in "/NFS/BACKUP/DOCKER/dev-prod/gitea" "$HOME/backups/gitea" "."; do
if [[ -d "$search_dir" ]]; then
list_backups "$search_dir" && FOUND=true && break
fi
done
if [[ "$FOUND" != "true" ]]; then
echo ""
echo "Usage: $0 <chemin_backup> [OPTIONS]"
echo " $0 --help pour plus d'informations"
fi
exit 1
fi
# Résoudre le chemin absolu
BACKUP_PATH="$(cd "$(dirname "$BACKUP_PATH")" 2>/dev/null && pwd)/$(basename "$BACKUP_PATH")" || BACKUP_PATH="$(realpath "$BACKUP_PATH" 2>/dev/null || echo "$BACKUP_PATH")"
# Vérifier que le dossier de backup existe et est valide
if [[ ! -d "$BACKUP_PATH" ]]; then
err "Dossier de backup introuvable : $BACKUP_PATH"
echo ""
echo "Recherche de backups dans les emplacements standards..."
for search_dir in "/NFS/BACKUP/DOCKER/dev-prod/gitea" "$HOME/backups/gitea" "."; do
if [[ -d "$search_dir" ]]; then
list_backups "$search_dir"
break
fi
done
exit 1
fi
# Vérifier les fichiers requis
MISSING_FILES=()
for required_file in "gitea-mysql.tar.gz" "gitea-data.tar.gz" "docker-compose.yml"; do
if [[ ! -f "$BACKUP_PATH/$required_file" ]]; then
MISSING_FILES+=("$required_file")
fi
done
# Vérifier le dump SQL (compressé ou non)
if [[ ! -f "$BACKUP_PATH/gitea-db.sql.gz" ]] && [[ ! -f "$BACKUP_PATH/gitea-db.sql" ]]; then
MISSING_FILES+=("gitea-db.sql ou gitea-db.sql.gz")
fi
if [[ ${#MISSING_FILES[@]} -gt 0 ]]; then
err "Fichiers requis manquants dans le backup :"
for f in "${MISSING_FILES[@]}"; do
err " - $f"
done
echo ""
warn "Ce dossier ne semble pas être un backup Gitea valide."
exit 1
fi
echo ""
echo "════════════════════════════════════════════════════════"
echo "🔄 RESTAURATION GITEA"
echo "════════════════════════════════════════════════════════"
echo ""
# ─── 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}"
# ─── Afficher les infos du backup ────────────────────────────────────────────
BACKUP_NAME=$(basename "$BACKUP_PATH")
BACKUP_DATE=${BACKUP_NAME#gitea-backup-}
if [[ -f "$BACKUP_PATH/backup-info.txt" ]]; then
info "Informations du backup :"
while IFS= read -r line; do
echo " $line"
done < "$BACKUP_PATH/backup-info.txt"
echo ""
else
info "Backup : $BACKUP_NAME"
echo ""
fi
info "Configuration cible :"
echo " Compose dir : $TARGET_COMPOSE_DIR"
echo " Volume data : $TARGET_VOLUME_DATA"
echo " Volume MySQL: $TARGET_VOLUME_MYSQL"
echo " DB name : $DB_NAME"
echo ""
# ─── Vérifier les prérequis ─────────────────────────────────────────────────
check_prerequisites || {
echo ""
err "Prérequis manquants — impossible de continuer"
exit 1
}
# ─── Dry-run ────────────────────────────────────────────────────────────────
if [[ "$DRY_RUN" == "true" ]]; then
echo "🔍 DRY RUN — aucune action ne sera effectuée"
echo ""
echo "Actions qui seraient exécutées :"
echo ""
echo " 1. Arrêter les conteneurs Gitea existants"
echo " cd $TARGET_COMPOSE_DIR && $DOCKER_COMPOSE down"
echo ""
echo " 2. Créer les répertoires cibles"
echo " mkdir -p $TARGET_VOLUME_DATA $TARGET_VOLUME_MYSQL"
echo ""
echo " 3. Restaurer le volume MySQL"
echo " rm -rf ${TARGET_VOLUME_MYSQL}/*"
echo " tar -xzf $BACKUP_PATH/gitea-mysql.tar.gz -C $(dirname "$TARGET_VOLUME_MYSQL")"
echo ""
echo " 4. Restaurer le volume Gitea"
echo " rm -rf ${TARGET_VOLUME_DATA}/*"
echo " tar -xzf $BACKUP_PATH/gitea-data.tar.gz -C $(dirname "$TARGET_VOLUME_DATA")"
echo ""
echo " 5. Copier docker-compose.yml"
echo " cp $BACKUP_PATH/docker-compose.yml $TARGET_COMPOSE_DIR/"
echo ""
if [[ -f "$BACKUP_PATH/app.ini" ]]; then
echo " 6. Restaurer app.ini"
echo " mkdir -p ${TARGET_VOLUME_DATA}/gitea/conf"
echo " cp $BACKUP_PATH/app.ini ${TARGET_VOLUME_DATA}/gitea/conf/"
echo ""
fi
echo " 7. Démarrer les conteneurs"
echo " cd $TARGET_COMPOSE_DIR && $DOCKER_COMPOSE up -d"
echo ""
echo " 8. Restaurer la base de données"
echo " docker exec -i gitea_db mysql -u root -p$DB_ROOT_PASSWORD $DB_NAME < dump"
echo ""
echo " 9. Redémarrer"
echo " $DOCKER_COMPOSE restart"
echo ""
exit 0
fi
# ─── Confirmation ───────────────────────────────────────────────────────────
if [[ "$FORCE" != "true" ]]; then
echo "⚠️ ATTENTION : Cette opération va ÉCRASER l'installation Gitea existante !"
echo " Toute donnée non sauvegardée sera PERDUE."
echo ""
echo " Les conteneurs seront arrêtés et les volumes remplacés."
echo ""
read -p "Taper 'RESTORE' (en majuscules) pour confirmer : " CONFIRM
if [[ "$CONFIRM" != "RESTORE" ]]; then
echo "❌ Restauration annulée."
exit 0
fi
echo ""
fi
# ═════════════════════════════════════════════════════════════════════════════
# RESTAURATION
# ═════════════════════════════════════════════════════════════════════════════
TIMING_START=$(date +%s)
# ─── Étape 1 : Arrêter les conteneurs existants ─────────────────────────────
echo "📦 Étape 1/7 : Arrêt des conteneurs existants..."
if [[ -f "$TARGET_COMPOSE_DIR/docker-compose.yml" ]] || [[ -f "$TARGET_COMPOSE_DIR/$COMPOSE_FILE" ]]; then
cd "$TARGET_COMPOSE_DIR" 2>/dev/null && $DOCKER_COMPOSE down 2>&1 || true
fi
ok "Conteneurs arrêtés"
# ─── Étape 2 : Créer les répertoires ────────────────────────────────────────
echo "📦 Étape 2/7 : Création des répertoires cibles..."
mkdir -p "$TARGET_VOLUME_DATA" "$TARGET_VOLUME_MYSQL" "$TARGET_COMPOSE_DIR"
ok "Répertoires créés"
# ─── Étape 3 : Restaurer volume MySQL ───────────────────────────────────────
echo "📦 Étape 3/7 : Restauration volume MySQL..."
rm -rf "${TARGET_VOLUME_MYSQL:?}"/*
if ! tar -xzf "$BACKUP_PATH/gitea-mysql.tar.gz" -C "$(dirname "$TARGET_VOLUME_MYSQL")"; then
err "Échec de l'extraction du volume MySQL"
exit 1
fi
ok "Volume MySQL restauré"
# ─── Étape 4 : Restaurer volume Gitea ───────────────────────────────────────
echo "📦 Étape 4/7 : Restauration volume Gitea..."
rm -rf "${TARGET_VOLUME_DATA:?}"/*
if ! tar -xzf "$BACKUP_PATH/gitea-data.tar.gz" -C "$(dirname "$TARGET_VOLUME_DATA")"; then
err "Échec de l'extraction du volume Gitea"
exit 1
fi
ok "Volume Gitea restauré"
# ─── Étape 5 : Copier docker-compose ────────────────────────────────────────
echo "📦 Étape 5/7 : Copie docker-compose et configuration..."
cp "$BACKUP_PATH/docker-compose.yml" "$TARGET_COMPOSE_DIR/"
ok "docker-compose.yml copié"
if [[ -f "$BACKUP_PATH/app.ini" ]]; then
mkdir -p "${TARGET_VOLUME_DATA}/gitea/conf"
cp "$BACKUP_PATH/app.ini" "${TARGET_VOLUME_DATA}/gitea/conf/app.ini"
ok "app.ini restauré"
fi
# ─── Étape 6 : Démarrer les conteneurs ──────────────────────────────────────
echo "📦 Étape 6/7 : Démarrage des conteneurs..."
cd "$TARGET_COMPOSE_DIR"
if ! $DOCKER_COMPOSE up -d; then
err "Échec du démarrage des conteneurs"
warn "Vérifiez les logs : $DOCKER_COMPOSE logs"
exit 1
fi
ok "Conteneurs démarrés"
# Attendre que MySQL soit prêt
echo " ⏳ Attente démarrage MySQL..."
MYSQL_READY=false
sleep 10
for i in $(seq 1 30); 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
warn "MySQL n'a pas répondu — on tente la restauration DB quand même..."
fi
# ─── Étape 7 : Restaurer la base de données ─────────────────────────────────
echo "📦 Étape 7/7 : Restauration base de données..."
# Créer la base de données si elle n'existe pas
docker exec gitea_db 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
# Restaurer le dump
if [[ -f "$BACKUP_PATH/gitea-db.sql.gz" ]]; then
gunzip -c "$BACKUP_PATH/gitea-db.sql.gz" | docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" 2>&1 || {
err "Échec de la restauration du dump SQL (compressé)"
exit 1
}
elif [[ -f "$BACKUP_PATH/gitea-db.sql" ]]; then
docker exec -i gitea_db mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" < "$BACKUP_PATH/gitea-db.sql" 2>&1 || {
err "Échec de la restauration du dump SQL"
exit 1
}
fi
ok "Base de données restaurée"
# ─── Redémarrage final ──────────────────────────────────────────────────────
echo ""
echo "🔄 Redémarrage final des conteneurs..."
$DOCKER_COMPOSE restart 2>&1 || true
# ─── Vérification santé ─────────────────────────────────────────────────────
echo ""
echo "🏥 Vérification que Gitea 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")
if [[ "$STATUS" == "200" || "$STATUS" == "302" || "$STATUS" == "303" ]]; then
ok "Gitea répond (HTTP $STATUS) après ${i}s"
break
fi
if [[ $i -eq 30 ]]; then
warn "Gitea ne répond pas encore — vérifiez les logs"
fi
sleep 1
done
# ─── Résumé ─────────────────────────────────────────────────────────────────
TIMING_END=$(date +%s)
DURATION=$((TIMING_END - TIMING_START))
echo ""
echo "════════════════════════════════════════════════════════"
echo "✅ RESTAURATION TERMINÉE !"
echo ""
echo " Durée : ${DURATION}s"
echo " Backup : $BACKUP_NAME"
echo " Cible : $TARGET_COMPOSE_DIR"
echo " Gitea : http://localhost:3000"
echo ""
echo " Logs : $DOCKER_COMPOSE logs -f"
echo " Status : $DOCKER_COMPOSE ps"
echo "════════════════════════════════════════════════════════"
exit 0
+467
View File
@@ -0,0 +1,467 @@
#!/bin/bash
# =============================================================================
# setup.sh — Installation complète des scripts Gitea
# =============================================================================
# Usage :
# bash setup.sh → Installation interactive complète
# bash setup.sh --force → Installation sans confirmation
# bash setup.sh --update → Mise à jour des scripts (git pull)
# bash setup.sh --dry-run → Affiche ce qui serait fait
#
# Ce script :
# 1. Clone ou met à jour les scripts depuis Git
# 2. Vérifie que tous les prérequis sont installés
# 3. Rend les scripts exécutables
# 4. Crée les dossiers de logs
# 5. Installe les entrées cron
# 6. Configure la rotation des logs (logrotate)
# 7. Propose un premier backup
# =============================================================================
set -euo pipefail
# ─── Couleurs ───────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BLUE='\033[0;34m'; NC='\033[0m'
ok() { echo -e " ${GREEN}✅${NC} $1"; }
warn() { echo -e " ${YELLOW}⚠️${NC} $1"; }
err() { echo -e " ${RED}❌${NC} $1"; }
info() { echo -e " ${CYAN}📋${NC} $1"; }
step() { echo -e "\n${BLUE}════════════════════════════════════════════════════════${NC}"; echo -e "${BLUE}🔧 $1${NC}"; echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"; }
# ─── Configuration ──────────────────────────────────────────────────────────
GIT_REPO="${GIT_REPO:-https://git.dracodev.net/Outils/gitea-scripts.git}"
SCRIPTS_DIR="${SCRIPTS_DIR:-$HOME/scripts}"
BRANCH="${BRANCH:-main}"
# ─── Usage ──────────────────────────────────────────────────────────────────
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Options :
--force Installation sans confirmation
--update Mettre à jour les scripts existants (git pull)
--dry-run Affiche ce qui serait fait sans rien installer
--help Cette aide
Variables d'environnement :
GIT_REPO URL du dépôt Git (défaut: $GIT_REPO)
SCRIPTS_DIR Dossier d'installation (défaut: \$HOME/scripts)
BRANCH Branche Git à utiliser (défaut: main)
EOF
exit 0
}
# ─── Vérification des prérequis système ─────────────────────────────────────
check_system_prerequisites() {
step "Étape 1/6 : Vérification des prérequis système"
local missing=0
info "Vérification des commandes requises..."
for cmd in git docker curl tar gzip crontab; do
if command -v "$cmd" &>/dev/null; then
local version=""
case "$cmd" in
git) version=$(git --version 2>/dev/null | head -1) ;;
docker) version=$(docker --version 2>/dev/null | head -1) ;;
curl) version=$(curl --version 2>/dev/null | head -1) ;;
esac
ok "$cmd ${version:+($version)}"
else
err "$cmd : NON INSTALLÉ"
missing=1
fi
done
# Vérifier docker compose
if docker compose version &>/dev/null; then
ok "docker compose (plugin)"
elif command -v docker-compose &>/dev/null; then
ok "docker-compose (standalone)"
else
err "docker compose : NON INSTALLÉ"
missing=1
fi
# Vérifier le daemon Docker
if docker info &>/dev/null; then
ok "Docker daemon : actif"
else
warn "Docker daemon : injoignable (vérifier que Docker tourne)"
fi
if [[ $missing -gt 0 ]]; then
echo ""
err "$missing prérequis manquants. Installez-les avant de continuer."
exit 1
fi
return 0
}
# ─── Cloner ou mettre à jour les scripts ────────────────────────────────────
install_scripts() {
step "Étape 2/6 : Installation des scripts"
if [[ -d "$SCRIPTS_DIR/.git" ]]; then
info "Dépôt existant trouvé dans $SCRIPTS_DIR"
cd "$SCRIPTS_DIR"
local current_remote
current_remote=$(git remote get-url origin 2>/dev/null || echo "")
if [[ "$current_remote" == "$GIT_REPO" ]]; then
info "Mise à jour depuis $GIT_REPO (branche: $BRANCH)..."
git fetch origin "$BRANCH" 2>&1 || {
warn "Impossible de fetch — on continue avec la version actuelle"
}
if git diff --quiet "origin/$BRANCH" 2>/dev/null; then
ok "Scripts déjà à jour"
else
git pull origin "$BRANCH" 2>&1 || {
warn "Impossible de pull — on continue avec la version actuelle"
}
ok "Scripts mis à jour"
fi
else
warn "Remote différent ($current_remote != $GIT_REPO) — pas de mise à jour automatique"
fi
else
info "Clonage de $GIT_REPO vers $SCRIPTS_DIR..."
if [[ -d "$SCRIPTS_DIR" ]]; then
warn "$SCRIPTS_DIR existe déjà mais n'est pas un dépôt Git"
info "Sauvegarde dans ${SCRIPTS_DIR}.bak..."
mv "$SCRIPTS_DIR" "${SCRIPTS_DIR}.bak.$(date +%s)" 2>/dev/null || true
fi
git clone --branch "$BRANCH" "$GIT_REPO" "$SCRIPTS_DIR" 2>&1 || {
err "Échec du clone de $GIT_REPO"
exit 1
}
ok "Scripts clonés dans $SCRIPTS_DIR"
fi
# Vérifier que les scripts principaux existent
local scripts_ok=true
for script in "gitea-backup.sh" "gitea-auto-update.sh" "install_cron.sh" "restore.sh" "gitea-scripts.conf"; do
if [[ -f "$SCRIPTS_DIR/$script" ]]; then
ok "$script présent"
else
warn "$script : absent"
scripts_ok=false
fi
done
if [[ "$scripts_ok" != "true" ]]; then
warn "Certains scripts sont absents — le dépôt peut être incomplet"
fi
return 0
}
# ─── Rendre les scripts exécutables ─────────────────────────────────────────
make_executable() {
step "Étape 3/6 : Permissions des scripts"
for script in "gitea-backup.sh" "gitea-auto-update.sh" "install_cron.sh" "restore.sh" "setup.sh"; do
if [[ -f "$SCRIPTS_DIR/$script" ]]; then
chmod +x "$SCRIPTS_DIR/$script"
ok "$script → exécutable"
fi
done
return 0
}
# ─── Créer les dossiers de logs ─────────────────────────────────────────────
setup_logs() {
step "Étape 4/6 : Configuration des logs"
# Charger la config pour connaître LOG_DIR
if [[ -f "$SCRIPTS_DIR/gitea-scripts.conf" ]]; then
source "$SCRIPTS_DIR/gitea-scripts.conf"
fi
LOG_DIR="${LOG_DIR:-/var/log}"
if [[ -d "$LOG_DIR" ]] && [[ -w "$LOG_DIR" ]]; then
ok "Dossier de logs $LOG_DIR accessible"
elif [[ -d "$LOG_DIR" ]]; then
warn "Pas de droit d'écriture sur $LOG_DIR"
info "Les logs seront écrits dans $HOME/"
else
if mkdir -p "$LOG_DIR" 2>/dev/null; then
ok "Dossier de logs $LOG_DIR créé"
else
warn "Impossible de créer $LOG_DIR — logs dans $HOME/"
fi
fi
# Créer les fichiers de log s'ils n'existent pas
touch "$HOME/gitea-backup-cron.log" "$HOME/gitea-update-cron.log" 2>/dev/null || true
return 0
}
# ─── Configurer logrotate ───────────────────────────────────────────────────
setup_logrotate() {
step "Étape 5/6 : Rotation des logs"
local logrotate_conf="/etc/logrotate.d/gitea-scripts"
local need_sudo=false
if [[ -f "$logrotate_conf" ]]; then
ok "logrotate déjà configuré : $logrotate_conf"
return 0
fi
if [[ ! -w "/etc/logrotate.d" ]]; then
need_sudo=true
fi
local log_files="$HOME/gitea-backup-cron.log $HOME/gitea-update-cron.log /var/log/gitea-backup.log /var/log/gitea-update.log"
cat << LOGROTATE
# Rotation des logs Gitea scripts
$log_files {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
copytruncate
create 0644 $USER $USER
}
LOGROTATE
echo ""
if [[ "$need_sudo" == "true" ]]; then
warn "Droits sudo requis pour créer $logrotate_conf"
info "Voici la commande à exécuter :"
echo ""
echo " sudo tee $logrotate_conf << 'EOF'"
cat << LOGROTATE
# Rotation des logs Gitea scripts
$log_files {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
copytruncate
create 0644 $USER $USER
}
LOGROTATE
echo "EOF"
echo ""
read -p "Exécuter cette commande maintenant ? [O/n] " -r REPLY
if [[ "$REPLY" =~ ^[OoYy]?$ ]]; then
sudo tee "$logrotate_conf" > /dev/null << LOGROTATE_EOF
# Rotation des logs Gitea scripts
$log_files {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
copytruncate
create 0644 $USER $USER
}
LOGROTATE_EOF
ok "logrotate configuré"
else
info "Vous pourrez configurer logrotate plus tard avec la commande ci-dessus"
fi
else
cat > "$logrotate_conf" << LOGROTATE_EOF
# Rotation des logs Gitea scripts
$log_files {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
copytruncate
create 0644 $USER $USER
}
LOGROTATE_EOF
ok "logrotate configuré : $logrotate_conf"
fi
return 0
}
# ─── Installer les crons ────────────────────────────────────────────────────
setup_cron() {
step "Étape 6/6 : Installation des crons"
if [[ -f "$SCRIPTS_DIR/install_cron.sh" ]]; then
if [[ "$FORCE" == "true" ]]; then
bash "$SCRIPTS_DIR/install_cron.sh" --force
else
bash "$SCRIPTS_DIR/install_cron.sh"
fi
else
err "install_cron.sh introuvable"
return 1
fi
return 0
}
# ─── Mise à jour (git pull) ─────────────────────────────────────────────────
do_update() {
step "Mise à jour des scripts Gitea"
if [[ ! -d "$SCRIPTS_DIR/.git" ]]; then
err "$SCRIPTS_DIR n'est pas un dépôt Git — impossible de mettre à jour"
exit 1
fi
cd "$SCRIPTS_DIR"
info "Fetch des dernières modifications..."
git fetch origin "$BRANCH" 2>&1
if git diff --quiet "origin/$BRANCH" 2>/dev/null; then
ok "Scripts déjà à jour"
else
info "Mise à jour disponible — application..."
git pull origin "$BRANCH" 2>&1
ok "Scripts mis à jour"
# Rendre exécutables à nouveau (au cas où)
for script in *.sh; do
[[ -f "$script" ]] && chmod +x "$script"
done
echo ""
info "Scripts mis à jour : $(git log --oneline -3)"
fi
exit 0
}
# ═════════════════════════════════════════════════════════════════════════════
# MAIN
# ═════════════════════════════════════════════════════════════════════════════
FORCE=false
DRY_RUN=false
UPDATE=false
for arg in "$@"; do
case "$arg" in
--force) FORCE=true ;;
--dry-run) DRY_RUN=true ;;
--update) UPDATE=true ;;
--help|-h) usage ;;
*) echo "Option inconnue : $arg"; usage ;;
esac
done
echo ""
echo "╔══════════════════════════════════════════════════════╗"
echo "║ 🛠️ GITEA SCRIPTS — INSTALLATEUR ║"
echo "╚══════════════════════════════════════════════════════╝"
echo ""
info "Dossier scripts : $SCRIPTS_DIR"
info "Dépôt Git : $GIT_REPO"
info "Branche : $BRANCH"
echo ""
# ─── Mode mise à jour ──────────────────────────────────────────────────────
if [[ "$UPDATE" == "true" ]]; then
do_update
fi
# ─── Mode dry-run ───────────────────────────────────────────────────────────
if [[ "$DRY_RUN" == "true" ]]; then
echo "🔍 DRY RUN — aucune action ne sera effectuée"
echo ""
echo "Actions qui seraient exécutées :"
echo ""
echo " 1. Vérifier les prérequis (git, docker, curl, tar, gzip, crontab)"
echo " 2. Cloner/MàJ $GIT_REPO → $SCRIPTS_DIR"
echo " 3. Rendre les scripts exécutables"
echo " 4. Créer les dossiers de logs"
echo " 5. Configurer logrotate"
echo " 6. Installer les crons (backup + auto-update)"
echo ""
exit 0
fi
# ─── Confirmation interactive ───────────────────────────────────────────────
if [[ "$FORCE" != "true" ]]; then
echo "Ce script va installer complètement les scripts de maintenance Gitea :"
echo ""
echo " 1. Vérifier les prérequis système"
echo " 2. Cloner/MàJ les scripts depuis Git"
echo " 3. Rendre les scripts exécutables"
echo " 4. Créer les dossiers de logs"
echo " 5. Configurer la rotation des logs"
echo " 6. Installer les tâches cron (backup + auto-update)"
echo ""
read -p "Continuer l'installation ? [O/n] " -r REPLY
if [[ ! "$REPLY" =~ ^[OoYy]?$ ]]; then
echo "Installation annulée."
exit 0
fi
fi
# ─── Exécution des étapes ───────────────────────────────────────────────────
check_system_prerequisites
install_scripts
make_executable
setup_logs
setup_logrotate
setup_cron
# ═════════════════════════════════════════════════════════════════════════════
# RÉSUMÉ FINAL
# ═════════════════════════════════════════════════════════════════════════════
echo ""
echo "╔══════════════════════════════════════════════════════╗"
echo "║ ✅ INSTALLATION TERMINÉE ! ║"
echo "╚══════════════════════════════════════════════════════╝"
echo ""
echo "📂 Scripts installés dans : $SCRIPTS_DIR"
echo ""
echo "📋 Commandes utiles :"
echo ""
echo " 🔍 Vérifier l'état : bash $SCRIPTS_DIR/install_cron.sh --status"
echo " 💾 Backup manuel : bash $SCRIPTS_DIR/gitea-backup.sh"
echo " 🔄 Restaurer : bash $SCRIPTS_DIR/restore.sh <dossier_backup>"
echo " ⬆️ Mise à jour : bash $SCRIPTS_DIR/setup.sh --update"
echo " 🗑️ Désinstaller : bash $SCRIPTS_DIR/install_cron.sh --uninstall"
echo ""
echo "⚙️ Configuration : $SCRIPTS_DIR/gitea-scripts.conf"
echo ""
echo "📊 Logs :"
echo " Backup : ${LOG_DIR:-/var/log}/gitea-backup.log"
echo " Update : ${LOG_DIR:-/var/log}/gitea-update.log"
echo " Cron backup : $HOME/gitea-backup-cron.log"
echo " Cron update : $HOME/gitea-update-cron.log"
echo ""
echo "🕐 Tâches cron :"
echo " Backup : $(grep "$SCRIPTS_DIR/gitea-backup.sh" <(crontab -l 2>/dev/null) | awk '{print $1" "$2" "$3" "$4" "$5}')"
echo " Update : $(grep "$SCRIPTS_DIR/gitea-auto-update.sh" <(crontab -l 2>/dev/null) | awk '{print $1" "$2" "$3" "$4" "$5}')"
echo ""
# Proposer un premier backup
if [[ "$FORCE" != "true" ]]; then
read -p "💾 Lancer un premier backup maintenant ? [O/n] " -r REPLY
if [[ "$REPLY" =~ ^[OoYy]?$ ]]; then
echo ""
bash "$SCRIPTS_DIR/gitea-backup.sh"
fi
fi
echo ""
echo "🎉 Prêt ! Les scripts tourneront automatiquement selon le planning cron."
echo ""
exit 0