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:
+174
-34
@@ -5,43 +5,127 @@
|
||||
# À placer dans /usr/local/bin/ ou ~/scripts/
|
||||
# Cron recommandé : 0 4 * * * /usr/local/bin/gitea-auto-update.sh
|
||||
#
|
||||
# Paramètres configurables (modifier ci-dessous ou passer en variables d'env)
|
||||
# Paramètres configurables : voir gitea-scripts.conf
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ─── Configuration (modifiable) ─────────────────────────────────────────────
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/gitea}"
|
||||
# ─── Charger la configuration commune ─────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/gitea-scripts.conf}"
|
||||
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# ─── Configuration locale (après chargement conf, permet surcharge env) ──────
|
||||
# Chemins et conteneurs
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-$HOME/dev/git/outils/docker/gitea}"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
|
||||
CONTAINER_NAME="${CONTAINER_NAME:-gitea_server}"
|
||||
APP_SERVICE="${APP_SERVICE:-gitea_server}"
|
||||
APP_CONTAINER="${APP_CONTAINER:-gitea_server}"
|
||||
DB_CONTAINER="${DB_CONTAINER:-gitea_db}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-gitea/gitea}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-latest}" # "latest" ou version spécifique
|
||||
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
||||
|
||||
# Base de données
|
||||
DB_NAME="${DB_NAME:-gitea}"
|
||||
DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-gitea}"
|
||||
|
||||
# Logs
|
||||
LOG_FILE="${LOG_FILE:-/var/log/gitea-update.log}"
|
||||
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}" # true pour envoyer un résumé
|
||||
NOTIFY_METHOD="${NOTIFY_METHOD:-log}" # log, telegram, ntfy
|
||||
LOG_DIR="$(dirname "$LOG_FILE")"
|
||||
|
||||
# Notifications
|
||||
ENABLE_NOTIFICATIONS="${ENABLE_NOTIFICATIONS:-false}"
|
||||
NOTIFY_METHOD="${NOTIFY_METHOD:-log}"
|
||||
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
||||
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
|
||||
NTPY_TOPIC="${NTPY_TOPIC:-}"
|
||||
|
||||
# Santé
|
||||
HEALTH_CHECK_URL="${HEALTH_CHECK_URL:-http://localhost:3000}"
|
||||
HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-30}"
|
||||
|
||||
# Lock file
|
||||
LOCK_DIR="${LOCK_DIR:-/tmp}"
|
||||
LOCK_FILE="${LOCK_DIR}/gitea-auto-update.lock"
|
||||
|
||||
# ─── Prérequis : s'assurer que le dossier de log existe ──────────────────────
|
||||
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
||||
|
||||
# ─── Logging ─────────────────────────────────────────────────────────────────
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
echo "$msg"
|
||||
echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ─── Notification ────────────────────────────────────────────────────────────
|
||||
notify() {
|
||||
local msg="$1"
|
||||
case "$NOTIFY_METHOD" in
|
||||
telegram)
|
||||
[[ -n "$TELEGRAM_CHAT_ID" ]] && curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=$TELEGRAM_CHAT_ID" -d "text=$msg" > /dev/null 2>&1 || true
|
||||
if [[ -n "$TELEGRAM_BOT_TOKEN" ]] && [[ -n "$TELEGRAM_CHAT_ID" ]]; then
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=$TELEGRAM_CHAT_ID" -d "text=$msg" > /dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
ntfy)
|
||||
[[ -n "$NTPY_TOPIC" ]] && curl -s -H "Title: Gitea Update" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
|
||||
if [[ -n "$NTPY_TOPIC" ]]; then
|
||||
curl -s -H "Title: Gitea Update" -d "$msg" "https://ntfy.sh/$NTPY_TOPIC" > /dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Lock file ───────────────────────────────────────────────────────────────
|
||||
acquire_lock() {
|
||||
if [[ -f "$LOCK_FILE" ]]; then
|
||||
local pid
|
||||
pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
|
||||
# Vérifier si le processus existe encore
|
||||
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
log "⚠️ Une autre instance (PID $pid) est déjà en cours d'exécution — arrêt"
|
||||
exit 0
|
||||
fi
|
||||
# Stale lock, on le nettoie
|
||||
log "⚠️ Lock stale détecté (PID $pid n'existe plus), on continue"
|
||||
rm -f "$LOCK_FILE"
|
||||
fi
|
||||
echo $$ > "$LOCK_FILE"
|
||||
}
|
||||
|
||||
release_lock() {
|
||||
rm -f "$LOCK_FILE"
|
||||
}
|
||||
|
||||
# ─── Nettoyage en cas d'erreur ───────────────────────────────────────────────
|
||||
cleanup() {
|
||||
release_lock
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ─── Récupération du digest (compatible POSIX grep) ──────────────────────────
|
||||
get_image_digest() {
|
||||
local image="$1"
|
||||
local digest
|
||||
# Essayer d'abord avec -E (ERE, disponible presque partout)
|
||||
digest=$(docker image inspect "$image" --format '{{.RepoDigests}}' 2>/dev/null | grep -oE 'sha256:[a-f0-9]{64}' | head -1 || echo "")
|
||||
if [[ -z "$digest" ]]; then
|
||||
# Fallback : utiliser awk
|
||||
digest=$(docker image inspect "$image" --format '{{.RepoDigests}}' 2>/dev/null | grep -o 'sha256:[a-f0-9]\{64\}' | head -1 || echo "")
|
||||
fi
|
||||
echo "$digest"
|
||||
}
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
acquire_lock
|
||||
|
||||
log "════════════════════════════════════════════════════════"
|
||||
log "🔄 Début vérification mise à jour Gitea"
|
||||
|
||||
@@ -53,23 +137,32 @@ if ! command -v docker &>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ─── Récupérer l'image digest actuelle ──────────────────────────────────────
|
||||
OLD_DIGEST=$(docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format '{{.RepoDigests}}' 2>/dev/null | grep -oP 'sha256:[a-f0-9]+' || echo "")
|
||||
# Vérifier si docker compose (plugin) ou docker-compose (standalone) est dispo
|
||||
if docker compose version &>/dev/null; then
|
||||
DOCKER_COMPOSE="docker compose"
|
||||
elif command -v docker-compose &>/dev/null; then
|
||||
DOCKER_COMPOSE="docker-compose"
|
||||
else
|
||||
log "❌ Ni 'docker compose' ni 'docker-compose' n'est disponible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "📦 Image actuelle : ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
# ─── Récupérer l'image digest actuelle ──────────────────────────────────────
|
||||
FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
OLD_DIGEST=$(get_image_digest "$FULL_IMAGE")
|
||||
|
||||
log "📦 Image actuelle : $FULL_IMAGE"
|
||||
log "🔍 Digest actuel : ${OLD_DIGEST:-non disponible}"
|
||||
|
||||
# ─── Pull de la nouvelle image ──────────────────────────────────────────────
|
||||
log "📥 Pull de la nouvelle image..."
|
||||
PULL_OUTPUT=$(docker compose -f "$COMPOSE_FILE" pull "$CONTAINER_NAME" 2>&1)
|
||||
PULL_EXIT=$?
|
||||
|
||||
if [[ $PULL_EXIT -ne 0 ]]; then
|
||||
log "❌ Échec du pull : $PULL_OUTPUT"
|
||||
PULL_OUTPUT=$($DOCKER_COMPOSE -f "$COMPOSE_FILE" pull "$APP_SERVICE" 2>&1) || {
|
||||
PULL_EXIT=$?
|
||||
log "❌ Échec du pull (code $PULL_EXIT) : $PULL_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
NEW_DIGEST=$(docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format '{{.RepoDigests}}' 2>/dev/null | grep -oP 'sha256:[a-f0-9]+' || echo "")
|
||||
NEW_DIGEST=$(get_image_digest "$FULL_IMAGE")
|
||||
|
||||
log "🔍 Nouveau digest : ${NEW_DIGEST:-non disponible}"
|
||||
|
||||
@@ -83,34 +176,68 @@ fi
|
||||
# ─── MISE À JOUR NÉCESSAIRE ─────────────────────────────────────────────────
|
||||
log "🔔 Nouvelle version détectée ! Mise à jour en cours..."
|
||||
|
||||
# Backup rapide avant mise à jour (optionnel, le script de backup complet gère le quotidien)
|
||||
# Sauvegarder l'image actuelle pour rollback éventuel
|
||||
log "💾 Sauvegarde de l'image actuelle pour rollback..."
|
||||
OLD_IMAGE_ID=$(docker image inspect "$FULL_IMAGE" --format '{{.ID}}' 2>/dev/null || echo "")
|
||||
ROLLBACK_TAG="${IMAGE_NAME}:rollback-$(date +%Y%m%d-%H%M%S)"
|
||||
if [[ -n "$OLD_IMAGE_ID" ]]; then
|
||||
docker tag "$OLD_IMAGE_ID" "$ROLLBACK_TAG" 2>/dev/null || {
|
||||
log "⚠️ Impossible de tagger l'image pour rollback (on continue)"
|
||||
ROLLBACK_TAG=""
|
||||
}
|
||||
else
|
||||
ROLLBACK_TAG=""
|
||||
fi
|
||||
|
||||
# Backup rapide DB avant mise à jour
|
||||
BACKUP_PRE_DIR="${BACKUP_PRE_DIR:-/tmp/gitea-pre-update-$(date +%Y%m%d-%H%M%S)}"
|
||||
mkdir -p "$BACKUP_PRE_DIR"
|
||||
|
||||
log "💾 Backup pré-update vers $BACKUP_PRE_DIR"
|
||||
docker exec gitea_db mysqldump -u root -pgitea gitea > "$BACKUP_PRE_DIR/gitea_db.sql" 2>/dev/null || {
|
||||
log "⚠️ Backup pré-update DB échoué, on continue quand même"
|
||||
}
|
||||
log "💾 Backup DB pré-update vers $BACKUP_PRE_DIR"
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
|
||||
docker exec "$DB_CONTAINER" mysqldump \
|
||||
-u root -p"$DB_ROOT_PASSWORD" \
|
||||
--single-transaction \
|
||||
"$DB_NAME" > "$BACKUP_PRE_DIR/gitea_db.sql" 2>/dev/null || {
|
||||
log "⚠️ Backup pré-update DB échoué, on continue quand même"
|
||||
}
|
||||
else
|
||||
log "⚠️ Conteneur $DB_CONTAINER non trouvé — pas de backup pré-update"
|
||||
fi
|
||||
|
||||
# Recréer le conteneur avec la nouvelle image
|
||||
log "🚀 Redémarrage du conteneur avec la nouvelle image..."
|
||||
UP_OUTPUT=$(docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER_NAME" 2>&1)
|
||||
UP_EXIT=$?
|
||||
UP_OUTPUT=$($DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1) || {
|
||||
UP_EXIT=$?
|
||||
log "❌ Échec du redémarrage (code $UP_EXIT) : $UP_OUTPUT"
|
||||
|
||||
if [[ $UP_EXIT -ne 0 ]]; then
|
||||
log "❌ Échec du redémarrage : $UP_OUTPUT"
|
||||
log "⚠️ Tentative de rollback..."
|
||||
docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER_NAME" 2>&1 || true
|
||||
# Rollback réel : restaurer l'ancienne image
|
||||
if [[ -n "$ROLLBACK_TAG" ]]; then
|
||||
log "🔄 ROLLBACK : restauration de l'image $ROLLBACK_TAG..."
|
||||
docker tag "$ROLLBACK_TAG" "$FULL_IMAGE" 2>/dev/null || true
|
||||
$DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1 || true
|
||||
docker rmi "$ROLLBACK_TAG" 2>/dev/null || true
|
||||
else
|
||||
log "⚠️ Rollback impossible (pas d'image sauvegardée)"
|
||||
$DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d "$APP_SERVICE" 2>&1 || true
|
||||
fi
|
||||
notify "❌ GITEA UPDATE FAILED sur $(hostname) — rollback effectué"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Nettoyer le tag de rollback si tout va bien
|
||||
if [[ -n "$ROLLBACK_TAG" ]]; then
|
||||
docker rmi "$ROLLBACK_TAG" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ─── Vérification santé ─────────────────────────────────────────────────────
|
||||
log "🏥 Vérification santé (timeout=${HEALTH_CHECK_TIMEOUT}s)..."
|
||||
HEALTH_OK=false
|
||||
for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_CHECK_URL" 2>/dev/null || echo "000")
|
||||
if [[ "$STATUS" == "200" || "$STATUS" == "302" || "$STATUS" == "303" ]]; then
|
||||
log "✅ Gitea répond (HTTP $STATUS) après ${i}s"
|
||||
HEALTH_OK=true
|
||||
break
|
||||
fi
|
||||
if [[ $i -eq "$HEALTH_CHECK_TIMEOUT" ]]; then
|
||||
@@ -122,7 +249,7 @@ for i in $(seq 1 "$HEALTH_CHECK_TIMEOUT"); do
|
||||
done
|
||||
|
||||
# ─── Rapport final ──────────────────────────────────────────────────────────
|
||||
GITEA_VERSION=$(curl -s "$HEALTH_CHECK_URL/api/v1/version" 2>/dev/null | grep -oP '"version":"\K[^"]+' || echo "inconnue")
|
||||
GITEA_VERSION=$(curl -s "$HEALTH_CHECK_URL/api/v1/version" 2>/dev/null | grep -oE '"version":"[^"]+"' | cut -d'"' -f4 || echo "inconnue")
|
||||
|
||||
log "✅ Mise à jour réussie !"
|
||||
log "📋 Nouvelle version : $GITEA_VERSION"
|
||||
@@ -132,7 +259,20 @@ log "═══ Fin ────────────────────
|
||||
|
||||
notify "✅ Gitea mis à jour vers $GITEA_VERSION sur $(hostname)"
|
||||
|
||||
# Nettoyage vieilles images
|
||||
# ─── Nettoyage ciblé : seulement les vieilles images Gitea ──────────────────
|
||||
log "🧹 Nettoyage des anciennes images Gitea..."
|
||||
# Trouver toutes les images gitea/gitea sauf la dernière utilisée
|
||||
CURRENT_IMAGE_ID=$(docker image inspect "$FULL_IMAGE" --format '{{.ID}}' 2>/dev/null || echo "")
|
||||
if [[ -n "$CURRENT_IMAGE_ID" ]]; then
|
||||
# Lister les images Gitea non taggées ou anciennes
|
||||
docker images "$IMAGE_NAME" --format '{{.ID}} {{.Tag}}' 2>/dev/null | while read -r img_id img_tag; do
|
||||
if [[ "$img_id" != "$CURRENT_IMAGE_ID" ]] && [[ "$img_tag" != "$IMAGE_TAG" ]]; then
|
||||
log " 🗑️ Suppression ancienne image : $img_id ($img_tag)"
|
||||
docker rmi "$img_id" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
# Nettoyer aussi les images dangling (sans tag)
|
||||
docker image prune -f 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user