981 lines
44 KiB
Bash
981 lines
44 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# gitea-backup.sh — Sauvegarde & Restauration complète de Gitea
|
|
# Version : 1.0.0
|
|
# Usage : sudo ./gitea-backup.sh [backup|restore|list|cron|help]
|
|
# =============================================================================
|
|
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
# ── Couleurs ──────────────────────────────────────────────────────────────────
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
|
|
|
|
# ── Répertoire du script ──────────────────────────────────────────────────────
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ENV_FILE="${SCRIPT_DIR}/.env"
|
|
LOG_FILE="/var/log/gitea-backup.log"
|
|
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
|
|
|
# =============================================================================
|
|
# FONCTIONS UTILITAIRES
|
|
# =============================================================================
|
|
|
|
log() {
|
|
local level="$1"; shift
|
|
local msg="$*"
|
|
local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo -e "${ts} [${level}] ${msg}" >> "${LOG_FILE}" 2>/dev/null || true
|
|
case "${level}" in
|
|
INFO) echo -e "${GREEN}[INFO]${RESET} ${msg}" ;;
|
|
WARN) echo -e "${YELLOW}[WARN]${RESET} ${msg}" ;;
|
|
ERROR) echo -e "${RED}[ERROR]${RESET} ${msg}" ;;
|
|
STEP) echo -e "${CYAN}[STEP]${RESET} ${BOLD}${msg}${RESET}" ;;
|
|
SUCCESS) echo -e "${GREEN}[OK]${RESET} ${msg}" ;;
|
|
esac
|
|
}
|
|
|
|
die() {
|
|
log ERROR "$*"
|
|
exit 1
|
|
}
|
|
|
|
require_root() {
|
|
[[ "${EUID}" -eq 0 ]] || die "Ce script doit etre execute en tant que root (sudo)."
|
|
}
|
|
|
|
require_command() {
|
|
command -v "$1" &>/dev/null || die "Commande requise introuvable : $1"
|
|
}
|
|
|
|
confirm() {
|
|
local prompt="${1:-Continuer ?} [o/N] "
|
|
read -rp "$(echo -e "${YELLOW}${prompt}${RESET}")" answer
|
|
[[ "${answer,,}" =~ ^(o|oui|y|yes)$ ]]
|
|
}
|
|
|
|
spinner() {
|
|
local pid=$1; local msg="${2:-Traitement en cours...}"
|
|
local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
|
local i=0
|
|
while kill -0 "${pid}" 2>/dev/null; do
|
|
printf "\r${CYAN}%s${RESET} %s" "${spin:$((i % ${#spin})):1}" "${msg}"
|
|
i=$(( i + 1 )); sleep 0.1
|
|
done
|
|
printf "\r%-60s\r" " "
|
|
}
|
|
|
|
hr() { echo -e "${BLUE}$(printf '─%.0s' {1..70})${RESET}"; }
|
|
|
|
# =============================================================================
|
|
# CHARGEMENT DE LA CONFIGURATION
|
|
# =============================================================================
|
|
|
|
load_env() {
|
|
[[ -f "${ENV_FILE}" ]] || die "Fichier .env.gitea introuvable : ${ENV_FILE}"
|
|
|
|
# Valeurs par défaut
|
|
GITEA_USER="git"
|
|
GITEA_ROOT="/opt/gitea"
|
|
GITEA_WORK_DIR="/var/lib/gitea"
|
|
GITEA_CONF="/etc/gitea/app.ini"
|
|
GITEA_BINARY="/usr/local/bin/gitea"
|
|
BACKUP_DIR="/opt/Backups/gitea"
|
|
KEEP_BACKUPS=7
|
|
INCLUDE_LOG="false"
|
|
USE_NATIVE_DUMP="true"
|
|
DB_TYPE=""
|
|
DB_HOST="localhost"
|
|
DB_PORT=""
|
|
DB_NAME=""
|
|
DB_USER=""
|
|
DB_PASS=""
|
|
|
|
# Chargement sécurisé
|
|
while IFS='=' read -r key value; do
|
|
[[ "${key}" =~ ^[[:space:]]*# ]] && continue
|
|
[[ -z "${key// /}" ]] && continue
|
|
key="${key// /}"
|
|
value="${value%%#*}"
|
|
value="${value%"${value##*[![:space:]]}"}"
|
|
value="${value#\"}" ; value="${value%\"}"
|
|
value="${value#\'}" ; value="${value%\'}"
|
|
export "${key}=${value}" 2>/dev/null || true
|
|
done < "${ENV_FILE}"
|
|
|
|
# Vérifications
|
|
[[ -f "${GITEA_CONF}" ]] || die "app.ini introuvable : ${GITEA_CONF}"
|
|
|
|
mkdir -p "${BACKUP_DIR}" || die "Impossible de creer BACKUP_DIR : ${BACKUP_DIR}"
|
|
touch "${LOG_FILE}" 2>/dev/null || LOG_FILE="/tmp/gitea-backup.log"
|
|
}
|
|
|
|
# =============================================================================
|
|
# DÉTECTION AUTOMATIQUE DEPUIS app.ini
|
|
# =============================================================================
|
|
|
|
detect_gitea_config() {
|
|
log STEP "Lecture de la configuration Gitea (${GITEA_CONF})"
|
|
|
|
_ini_get() {
|
|
local section="$1" key="$2"
|
|
awk -F '=' \
|
|
-v sec="[${section}]" -v k="${key}" \
|
|
'in_section && /^\[/ { in_section=0 }
|
|
/^\[/ && $0==sec { in_section=1; next }
|
|
in_section && /^[[:space:]]*'"${key}"'[[:space:]]*=/ {
|
|
sub(/^[^=]+=/, ""); gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print; exit
|
|
}' "${GITEA_CONF}"
|
|
}
|
|
|
|
# Chemins de données
|
|
GITEA_REPO_ROOT="${GITEA_WORK_DIR}/repositories"
|
|
local repo_path; repo_path=$(_ini_get "repository" "ROOT")
|
|
[[ -n "${repo_path}" ]] && GITEA_REPO_ROOT="${repo_path}"
|
|
|
|
GITEA_DATA_DIR="${GITEA_WORK_DIR}/data"
|
|
local data_path; data_path=$(_ini_get "server" "APP_DATA_PATH")
|
|
[[ -n "${data_path}" ]] && GITEA_DATA_DIR="${data_path}"
|
|
|
|
GITEA_LOG_DIR="${GITEA_WORK_DIR}/log"
|
|
local log_path; log_path=$(_ini_get "log" "ROOT_PATH")
|
|
[[ -n "${log_path}" ]] && GITEA_LOG_DIR="${log_path}"
|
|
|
|
GITEA_AVATAR_DIR="${GITEA_DATA_DIR}/avatars"
|
|
GITEA_ATTACH_DIR="${GITEA_DATA_DIR}/attachments"
|
|
GITEA_LFS_DIR="${GITEA_DATA_DIR}/lfs"
|
|
|
|
# Base de données (si non forcée dans .env)
|
|
if [[ -z "${DB_TYPE}" ]]; then
|
|
DB_TYPE=$(_ini_get "database" "DB_TYPE")
|
|
fi
|
|
[[ -z "${DB_NAME}" ]] && DB_NAME=$(_ini_get "database" "NAME")
|
|
[[ -z "${DB_HOST}" ]] && DB_HOST=$(_ini_get "database" "HOST")
|
|
[[ -z "${DB_USER}" ]] && DB_USER=$(_ini_get "database" "USER")
|
|
[[ -z "${DB_PASS}" ]] && DB_PASS=$(_ini_get "database" "PASSWD")
|
|
|
|
# Séparer host:port si fourni ensemble
|
|
if [[ "${DB_HOST}" == *:* ]]; then
|
|
DB_PORT="${DB_HOST##*:}"
|
|
DB_HOST="${DB_HOST%%:*}"
|
|
fi
|
|
|
|
[[ -n "${DB_TYPE}" ]] || die "Impossible de detecter DB_TYPE depuis app.ini"
|
|
[[ -n "${DB_NAME}" ]] || die "Impossible de detecter le nom de la base de donnees"
|
|
|
|
# Normalisation du type DB
|
|
case "${DB_TYPE,,}" in
|
|
postgres|postgresql) DB_TYPE="postgresql" ;;
|
|
mysql|mysql2) DB_TYPE="mysql" ;;
|
|
sqlite3|sqlite) DB_TYPE="sqlite3" ;;
|
|
mssql) DB_TYPE="mssql" ;;
|
|
esac
|
|
|
|
log INFO "DB type : ${DB_TYPE}"
|
|
log INFO "DB name : ${DB_NAME}"
|
|
log INFO "Repos : ${GITEA_REPO_ROOT}"
|
|
log INFO "Data : ${GITEA_DATA_DIR}"
|
|
}
|
|
|
|
# =============================================================================
|
|
# SAUVEGARDE DB
|
|
# =============================================================================
|
|
|
|
backup_db_postgresql() {
|
|
log STEP "Dump PostgreSQL → ${DB_NAME}"
|
|
local dump_file="${TMP_DIR}/database.sql"
|
|
local pg_opts=(-U "${DB_USER}" -h "${DB_HOST}")
|
|
[[ -n "${DB_PORT}" ]] && pg_opts+=(-p "${DB_PORT}")
|
|
|
|
if [[ -n "${DB_PASS}" ]]; then
|
|
PGPASSWORD="${DB_PASS}" pg_dump "${pg_opts[@]}" \
|
|
--no-owner --no-acl --format=custom \
|
|
-f "${dump_file}" "${DB_NAME}" &
|
|
else
|
|
pg_dump "${pg_opts[@]}" --no-owner --no-acl --format=custom \
|
|
-f "${dump_file}" "${DB_NAME}" &
|
|
fi
|
|
spinner $! "Dump PostgreSQL en cours..."
|
|
wait $!
|
|
log SUCCESS "Dump PostgreSQL termine"
|
|
}
|
|
|
|
backup_db_mysql() {
|
|
log STEP "Dump MySQL/MariaDB → ${DB_NAME}"
|
|
local dump_file="${TMP_DIR}/database.sql"
|
|
local my_opts=(--single-transaction --routines --triggers --events)
|
|
[[ -n "${DB_HOST}" ]] && my_opts+=(-h "${DB_HOST}")
|
|
[[ -n "${DB_PORT}" ]] && my_opts+=(-P "${DB_PORT}")
|
|
[[ -n "${DB_USER}" ]] && my_opts+=(-u "${DB_USER}")
|
|
|
|
if [[ -n "${DB_PASS}" ]]; then
|
|
MYSQL_PWD="${DB_PASS}" mysqldump "${my_opts[@]}" "${DB_NAME}" > "${dump_file}" &
|
|
else
|
|
mysqldump "${my_opts[@]}" "${DB_NAME}" > "${dump_file}" &
|
|
fi
|
|
spinner $! "Dump MySQL en cours..."
|
|
wait $!
|
|
log SUCCESS "Dump MySQL termine"
|
|
}
|
|
|
|
backup_db_sqlite3() {
|
|
log STEP "Backup SQLite3 → ${DB_NAME}"
|
|
local dump_file="${TMP_DIR}/database.sql"
|
|
local sqlite_path="${DB_NAME}"
|
|
[[ "${sqlite_path}" != /* ]] && sqlite_path="${GITEA_WORK_DIR}/${sqlite_path}"
|
|
[[ -f "${sqlite_path}" ]] || die "Fichier SQLite introuvable : ${sqlite_path}"
|
|
# Copie à chaud + dump SQL pour double sécurité
|
|
cp "${sqlite_path}" "${TMP_DIR}/database.db"
|
|
sqlite3 "${sqlite_path}" .dump > "${dump_file}" &
|
|
spinner $! "Dump SQLite3 en cours..."
|
|
wait $!
|
|
log SUCCESS "Dump SQLite3 termine"
|
|
}
|
|
|
|
# =============================================================================
|
|
# RESTAURATION DB
|
|
# =============================================================================
|
|
|
|
restore_db_postgresql() {
|
|
log STEP "Restauration PostgreSQL → ${DB_NAME}"
|
|
local dump_file="${TMP_DIR}/database.sql"
|
|
local pg_opts=(-U "${DB_USER}" -h "${DB_HOST}")
|
|
[[ -n "${DB_PORT}" ]] && pg_opts+=(-p "${DB_PORT}")
|
|
|
|
if [[ -n "${DB_PASS}" ]]; then
|
|
PGPASSWORD="${DB_PASS}" psql "${pg_opts[@]}" -d postgres \
|
|
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='${DB_NAME}';" \
|
|
2>/dev/null || true
|
|
PGPASSWORD="${DB_PASS}" psql "${pg_opts[@]}" -d postgres \
|
|
-c "DROP DATABASE IF EXISTS \"${DB_NAME}\";" 2>/dev/null || true
|
|
PGPASSWORD="${DB_PASS}" psql "${pg_opts[@]}" -d postgres \
|
|
-c "CREATE DATABASE \"${DB_NAME}\" OWNER \"${DB_USER}\";"
|
|
PGPASSWORD="${DB_PASS}" pg_restore "${pg_opts[@]}" \
|
|
--no-owner --no-acl -d "${DB_NAME}" "${dump_file}" &
|
|
else
|
|
psql "${pg_opts[@]}" -d postgres \
|
|
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='${DB_NAME}';" \
|
|
2>/dev/null || true
|
|
psql "${pg_opts[@]}" -d postgres \
|
|
-c "DROP DATABASE IF EXISTS \"${DB_NAME}\";" 2>/dev/null || true
|
|
psql "${pg_opts[@]}" -d postgres \
|
|
-c "CREATE DATABASE \"${DB_NAME}\" OWNER \"${DB_USER}\";"
|
|
pg_restore "${pg_opts[@]}" --no-owner --no-acl \
|
|
-d "${DB_NAME}" "${dump_file}" &
|
|
fi
|
|
spinner $! "Restauration PostgreSQL..."
|
|
wait $! || log WARN "pg_restore : avertissements ignorés (objets existants)"
|
|
log SUCCESS "Restauration PostgreSQL terminee"
|
|
}
|
|
|
|
restore_db_mysql() {
|
|
log STEP "Restauration MySQL → ${DB_NAME}"
|
|
local dump_file="${TMP_DIR}/database.sql"
|
|
local my_opts=()
|
|
[[ -n "${DB_HOST}" ]] && my_opts+=(-h "${DB_HOST}")
|
|
[[ -n "${DB_PORT}" ]] && my_opts+=(-P "${DB_PORT}")
|
|
[[ -n "${DB_USER}" ]] && my_opts+=(-u "${DB_USER}")
|
|
|
|
if [[ -n "${DB_PASS}" ]]; then
|
|
MYSQL_PWD="${DB_PASS}" mysql "${my_opts[@]}" \
|
|
-e "DROP DATABASE IF EXISTS \`${DB_NAME}\`; CREATE DATABASE \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
|
MYSQL_PWD="${DB_PASS}" mysql "${my_opts[@]}" "${DB_NAME}" < "${dump_file}" &
|
|
else
|
|
mysql "${my_opts[@]}" \
|
|
-e "DROP DATABASE IF EXISTS \`${DB_NAME}\`; CREATE DATABASE \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
|
mysql "${my_opts[@]}" "${DB_NAME}" < "${dump_file}" &
|
|
fi
|
|
spinner $! "Restauration MySQL..."
|
|
wait $!
|
|
log SUCCESS "Restauration MySQL terminee"
|
|
}
|
|
|
|
restore_db_sqlite3() {
|
|
log STEP "Restauration SQLite3 → ${DB_NAME}"
|
|
local sqlite_path="${DB_NAME}"
|
|
[[ "${sqlite_path}" != /* ]] && sqlite_path="${GITEA_WORK_DIR}/${sqlite_path}"
|
|
|
|
if [[ -f "${TMP_DIR}/database.db" ]]; then
|
|
# Restauration depuis la copie binaire (plus fiable)
|
|
[[ -f "${sqlite_path}" ]] && cp "${sqlite_path}" "${sqlite_path}.pre_restore"
|
|
cp "${TMP_DIR}/database.db" "${sqlite_path}" &
|
|
spinner $! "Restauration SQLite3 (binaire)..."
|
|
wait $!
|
|
elif [[ -f "${TMP_DIR}/database.sql" ]]; then
|
|
[[ -f "${sqlite_path}" ]] && cp "${sqlite_path}" "${sqlite_path}.pre_restore"
|
|
sqlite3 "${sqlite_path}" < "${TMP_DIR}/database.sql" &
|
|
spinner $! "Restauration SQLite3 (SQL dump)..."
|
|
wait $!
|
|
else
|
|
die "Aucun fichier de base de donnees SQLite trouve dans l'archive"
|
|
fi
|
|
log SUCCESS "Restauration SQLite3 terminee"
|
|
}
|
|
|
|
# =============================================================================
|
|
# SAUVEGARDE PRINCIPALE
|
|
# =============================================================================
|
|
|
|
do_backup() {
|
|
hr
|
|
echo -e "${BOLD}${CYAN}"
|
|
echo " ██████╗ ██╗████████╗███████╗ █████╗ "
|
|
echo " ██╔════╝ ██║╚══██╔══╝██╔════╝██╔══██╗"
|
|
echo " ██║ ███╗██║ ██║ █████╗ ███████║"
|
|
echo " ██║ ██║██║ ██║ ██╔══╝ ██╔══██║"
|
|
echo " ╚██████╔╝██║ ██║ ███████╗██║ ██║"
|
|
echo " ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝"
|
|
echo -e "${RESET}${BOLD} Sauvegarde Gitea${RESET}"
|
|
hr
|
|
log INFO "Demarrage de la sauvegarde Gitea — ${TIMESTAMP}"
|
|
log INFO "Config : ${GITEA_CONF}"
|
|
log INFO "Destination : ${BACKUP_DIR}"
|
|
|
|
detect_gitea_config
|
|
|
|
TMP_DIR="$(mktemp -d /tmp/gitea_backup_XXXXXX)"
|
|
trap 'rm -rf "${TMP_DIR}"' EXIT
|
|
|
|
ARCHIVE_NAME="gitea_backup_${TIMESTAMP}.tar.gz"
|
|
ARCHIVE_PATH="${BACKUP_DIR}/${ARCHIVE_NAME}"
|
|
|
|
# ── Méthode 1 : gitea dump natif (si disponible et activé) ────────────────
|
|
if [[ "${USE_NATIVE_DUMP}" == "true" ]] && [[ -x "${GITEA_BINARY}" ]]; then
|
|
log STEP "Utilisation du dump natif Gitea (${GITEA_BINARY})"
|
|
local native_out="${TMP_DIR}/native_dump"
|
|
mkdir -p "${native_out}"
|
|
|
|
sudo -u "${GITEA_USER}" \
|
|
GITEA_WORK_DIR="${GITEA_WORK_DIR}" \
|
|
"${GITEA_BINARY}" dump \
|
|
--config "${GITEA_CONF}" \
|
|
--file "${native_out}/gitea_native_dump.zip" \
|
|
--type zip \
|
|
--skip-lfs-data=false \
|
|
2>&1 | while read -r line; do log INFO " [gitea dump] ${line}"; done || {
|
|
log WARN "gitea dump a echoue, basculement sur la methode manuelle"
|
|
USE_NATIVE_DUMP="failed"
|
|
}
|
|
|
|
if [[ "${USE_NATIVE_DUMP}" != "failed" ]] && \
|
|
[[ -f "${native_out}/gitea_native_dump.zip" ]]; then
|
|
log SUCCESS "Dump natif Gitea termine"
|
|
# On continue quand même avec la sauvegarde manuelle pour le manifest
|
|
fi
|
|
fi
|
|
|
|
# ── Dump de la base de données ─────────────────────────────────────────────
|
|
log STEP "Sauvegarde de la base de donnees (${DB_TYPE})"
|
|
mkdir -p "${TMP_DIR}/db"
|
|
local TMP_DIR_DB="${TMP_DIR}/db"
|
|
local TMP_DIR_SAVE="${TMP_DIR}"
|
|
TMP_DIR="${TMP_DIR_DB}"
|
|
case "${DB_TYPE}" in
|
|
postgresql) backup_db_postgresql ;;
|
|
mysql) backup_db_mysql ;;
|
|
sqlite3) backup_db_sqlite3 ;;
|
|
*) log WARN "Type DB '${DB_TYPE}' non supporte pour le dump manuel" ;;
|
|
esac
|
|
TMP_DIR="${TMP_DIR_SAVE}"
|
|
# Déplacer les dumps dans TMP_DIR
|
|
mv "${TMP_DIR_DB}"/* "${TMP_DIR}/" 2>/dev/null || true
|
|
|
|
# ── Manifest ──────────────────────────────────────────────────────────────
|
|
log STEP "Generation du manifest"
|
|
{
|
|
echo "=== GITEA BACKUP MANIFEST ==="
|
|
echo "Timestamp : ${TIMESTAMP}"
|
|
echo "Hostname : $(hostname -f)"
|
|
echo "GITEA_CONF : ${GITEA_CONF}"
|
|
echo "GITEA_WORK_DIR : ${GITEA_WORK_DIR}"
|
|
echo "GITEA_REPO_ROOT: ${GITEA_REPO_ROOT}"
|
|
echo "DB_TYPE : ${DB_TYPE}"
|
|
echo "DB_NAME : ${DB_NAME}"
|
|
echo "DB_HOST : ${DB_HOST}"
|
|
echo ""
|
|
echo "=== VERSION GITEA ==="
|
|
if [[ -x "${GITEA_BINARY}" ]]; then
|
|
"${GITEA_BINARY}" --version 2>/dev/null || echo "N/A"
|
|
else
|
|
echo "Binaire non trouve : ${GITEA_BINARY}"
|
|
fi
|
|
echo ""
|
|
echo "=== STATISTIQUES DEPOTS ==="
|
|
if [[ -d "${GITEA_REPO_ROOT}" ]]; then
|
|
local repo_count; repo_count=$(find "${GITEA_REPO_ROOT}" -maxdepth 2 \
|
|
-name "*.git" -o -name "HEAD" 2>/dev/null | \
|
|
grep -c "HEAD" || echo "0")
|
|
echo "Nombre de depots : ${repo_count}"
|
|
echo "Taille totale : $(du -sh "${GITEA_REPO_ROOT}" 2>/dev/null | cut -f1)"
|
|
fi
|
|
echo ""
|
|
echo "=== LFS ==="
|
|
if [[ -d "${GITEA_LFS_DIR}" ]]; then
|
|
echo "Taille LFS : $(du -sh "${GITEA_LFS_DIR}" 2>/dev/null | cut -f1)"
|
|
else
|
|
echo "LFS non configure ou vide"
|
|
fi
|
|
echo ""
|
|
echo "=== APP.INI (sections principales) ==="
|
|
grep -E '^\[|^APP_NAME|^RUN_USER|^DOMAIN|^ROOT_URL|^HTTP_PORT|^DB_TYPE|^HOST|^NAME' \
|
|
"${GITEA_CONF}" 2>/dev/null | head -40 || echo "N/A"
|
|
} > "${TMP_DIR}/manifest.txt"
|
|
log SUCCESS "Manifest genere"
|
|
|
|
# ── Copie de la configuration ──────────────────────────────────────────────
|
|
log STEP "Sauvegarde de la configuration (app.ini)"
|
|
mkdir -p "${TMP_DIR}/config"
|
|
cp "${GITEA_CONF}" "${TMP_DIR}/config/app.ini"
|
|
[[ -f "${ENV_FILE}" ]] && cp "${ENV_FILE}" "${TMP_DIR}/config/.env.gitea.backup"
|
|
# Clés SSH Gitea
|
|
local gitea_home; gitea_home=$(eval echo "~${GITEA_USER}")
|
|
if [[ -d "${gitea_home}/.ssh" ]]; then
|
|
cp -r "${gitea_home}/.ssh" "${TMP_DIR}/config/ssh_keys" 2>/dev/null || true
|
|
log INFO " Cles SSH incluses"
|
|
fi
|
|
log SUCCESS "Configuration sauvegardee"
|
|
|
|
# ── Archive des dépôts ────────────────────────────────────────────────────
|
|
if [[ -d "${GITEA_REPO_ROOT}" ]]; then
|
|
log STEP "Archivage des depots Git (${GITEA_REPO_ROOT})"
|
|
(
|
|
tar -czf "${TMP_DIR}/repositories.tar.gz" \
|
|
-C "$(dirname "${GITEA_REPO_ROOT}")" \
|
|
"$(basename "${GITEA_REPO_ROOT}")" 2>/dev/null
|
|
) &
|
|
spinner $! "Archivage des depots..."
|
|
wait $!
|
|
log SUCCESS "Depots archives"
|
|
else
|
|
log WARN "Dossier repositories introuvable : ${GITEA_REPO_ROOT}"
|
|
fi
|
|
|
|
# ── Archive des données (avatars, attachments, etc.) ─────────────────────
|
|
if [[ -d "${GITEA_DATA_DIR}" ]]; then
|
|
log STEP "Archivage des donnees (avatars, attachments...)"
|
|
local tar_data_excludes=()
|
|
[[ "${INCLUDE_LOG}" != "true" ]] && tar_data_excludes+=("--exclude=${GITEA_DATA_DIR}/log")
|
|
(
|
|
tar "${tar_data_excludes[@]+"${tar_data_excludes[@]}"}" \
|
|
-czf "${TMP_DIR}/data.tar.gz" \
|
|
-C "$(dirname "${GITEA_DATA_DIR}")" \
|
|
"$(basename "${GITEA_DATA_DIR}")" 2>/dev/null
|
|
) &
|
|
spinner $! "Archivage des donnees..."
|
|
wait $!
|
|
log SUCCESS "Donnees archivees"
|
|
fi
|
|
|
|
# ── Logs (optionnel) ──────────────────────────────────────────────────────
|
|
if [[ "${INCLUDE_LOG}" == "true" ]] && [[ -d "${GITEA_LOG_DIR}" ]]; then
|
|
log STEP "Archivage des logs"
|
|
(tar -czf "${TMP_DIR}/logs.tar.gz" \
|
|
-C "$(dirname "${GITEA_LOG_DIR}")" \
|
|
"$(basename "${GITEA_LOG_DIR}")" 2>/dev/null) &
|
|
spinner $! "Archivage des logs..."
|
|
wait $!
|
|
log SUCCESS "Logs archives"
|
|
fi
|
|
|
|
# ── Assemblage de l'archive finale ────────────────────────────────────────
|
|
log STEP "Assemblage de l'archive finale"
|
|
local files_to_pack=()
|
|
[[ -f "${TMP_DIR}/database.sql" ]] && files_to_pack+=("database.sql")
|
|
[[ -f "${TMP_DIR}/database.db" ]] && files_to_pack+=("database.db")
|
|
files_to_pack+=("manifest.txt" "config")
|
|
[[ -f "${TMP_DIR}/repositories.tar.gz" ]] && files_to_pack+=("repositories.tar.gz")
|
|
[[ -f "${TMP_DIR}/data.tar.gz" ]] && files_to_pack+=("data.tar.gz")
|
|
[[ -f "${TMP_DIR}/logs.tar.gz" ]] && files_to_pack+=("logs.tar.gz")
|
|
[[ -d "${TMP_DIR}/native_dump" ]] && files_to_pack+=("native_dump")
|
|
|
|
(tar -czf "${ARCHIVE_PATH}" -C "${TMP_DIR}" "${files_to_pack[@]}" 2>/dev/null) &
|
|
spinner $! "Assemblage final..."
|
|
wait $!
|
|
|
|
local size; size=$(du -sh "${ARCHIVE_PATH}" | cut -f1)
|
|
log SUCCESS "Archive creee : ${ARCHIVE_PATH} (${size})"
|
|
|
|
# ── Rotation ──────────────────────────────────────────────────────────────
|
|
log STEP "Rotation des archives (conservation : ${KEEP_BACKUPS})"
|
|
local count; count=$(find "${BACKUP_DIR}" -maxdepth 1 -name "gitea_backup_*.tar.gz" | wc -l)
|
|
if [[ "${count}" -gt "${KEEP_BACKUPS}" ]]; then
|
|
local to_delete=$(( count - KEEP_BACKUPS ))
|
|
find "${BACKUP_DIR}" -maxdepth 1 -name "gitea_backup_*.tar.gz" \
|
|
-printf '%T+ %p\n' | sort | head -n "${to_delete}" | \
|
|
awk '{print $2}' | while read -r old; do
|
|
rm -f "${old}"
|
|
log INFO " Supprime : $(basename "${old}")"
|
|
done
|
|
else
|
|
log INFO " Aucune rotation necessaire (${count}/${KEEP_BACKUPS})"
|
|
fi
|
|
|
|
hr
|
|
echo -e "${GREEN}${BOLD} ✔ Sauvegarde Gitea terminee avec succes !${RESET}"
|
|
echo -e "${GREEN} Archive : ${ARCHIVE_PATH}${RESET}"
|
|
echo -e "${GREEN} Taille : ${size}${RESET}"
|
|
hr
|
|
}
|
|
|
|
# =============================================================================
|
|
# RESTAURATION PRINCIPALE
|
|
# =============================================================================
|
|
|
|
do_restore() {
|
|
hr
|
|
echo -e "${BOLD}${YELLOW}"
|
|
echo " ██████╗ ███████╗███████╗████████╗ ██████╗ ██████╗ ███████╗"
|
|
echo " ██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝"
|
|
echo " ██████╔╝█████╗ ███████╗ ██║ ██║ ██║██████╔╝█████╗ "
|
|
echo " ██╔══██╗██╔══╝ ╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝ "
|
|
echo " ██║ ██║███████╗███████║ ██║ ╚██████╔╝██║ ██║███████╗"
|
|
echo " ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝"
|
|
echo -e "${RESET}${BOLD} Restauration Gitea${RESET}"
|
|
hr
|
|
|
|
# ── Sélection de l'archive ────────────────────────────────────────────────
|
|
local backup_list
|
|
mapfile -t backup_list < <(find "${BACKUP_DIR}" -maxdepth 1 \
|
|
-name "gitea_backup_*.tar.gz" | sort -r)
|
|
|
|
if [[ ${#backup_list[@]} -eq 0 ]]; then
|
|
die "Aucune archive de sauvegarde trouvee dans ${BACKUP_DIR}"
|
|
fi
|
|
|
|
echo -e "\n${BOLD} Sauvegardes disponibles :${RESET}\n"
|
|
local i=1
|
|
for archive in "${backup_list[@]}"; do
|
|
local fname; fname=$(basename "${archive}")
|
|
local fsize; fsize=$(du -sh "${archive}" | cut -f1)
|
|
local fdate; fdate=$(stat -c '%y' "${archive}" | cut -d'.' -f1)
|
|
printf " ${CYAN}[%2d]${RESET} %-45s ${YELLOW}%6s${RESET} %s\n" \
|
|
"${i}" "${fname}" "${fsize}" "${fdate}"
|
|
i=$(( i + 1 ))
|
|
done
|
|
echo -e " ${CYAN}[ 0]${RESET} Annuler\n"
|
|
hr
|
|
|
|
local choice
|
|
while true; do
|
|
read -rp "$(echo -e "${BOLD} Choisir une archive [0-$((i-1))] : ${RESET}")" choice
|
|
[[ "${choice}" =~ ^[0-9]+$ ]] || continue
|
|
[[ "${choice}" -eq 0 ]] && { log INFO "Restauration annulee."; return 0; }
|
|
[[ "${choice}" -ge 1 && "${choice}" -le ${#backup_list[@]} ]] && break
|
|
echo -e "${RED} Choix invalide.${RESET}"
|
|
done
|
|
|
|
local selected="${backup_list[$((choice-1))]}"
|
|
log INFO "Archive selectionnee : $(basename "${selected}")"
|
|
|
|
# ── Modes de restauration ─────────────────────────────────────────────────
|
|
hr
|
|
echo -e "\n${BOLD} Mode de restauration :${RESET}\n"
|
|
echo -e " ${CYAN}[1]${RESET} Restauration complete (DB + depots + donnees + config)"
|
|
echo -e " ${CYAN}[2]${RESET} Base de donnees uniquement"
|
|
echo -e " ${CYAN}[3]${RESET} Depots Git uniquement"
|
|
echo -e " ${CYAN}[4]${RESET} Donnees uniquement (avatars, attachments, LFS)"
|
|
echo -e " ${CYAN}[5]${RESET} Configuration uniquement (app.ini + cles SSH)"
|
|
echo -e " ${CYAN}[0]${RESET} Annuler\n"
|
|
hr
|
|
|
|
local mode
|
|
while true; do
|
|
read -rp "$(echo -e "${BOLD} Mode [0-5] : ${RESET}")" mode
|
|
[[ "${mode}" =~ ^[0-5]$ ]] && break
|
|
echo -e "${RED} Choix invalide.${RESET}"
|
|
done
|
|
[[ "${mode}" -eq 0 ]] && { log INFO "Restauration annulee."; return 0; }
|
|
|
|
# ── Confirmation ──────────────────────────────────────────────────────────
|
|
hr
|
|
echo -e "\n${RED}${BOLD} ATTENTION : Cette operation va ecraser les donnees existantes !${RESET}"
|
|
echo -e " Archive : $(basename "${selected}")"
|
|
local mode_label
|
|
case "${mode}" in
|
|
1) mode_label="Complete (DB + depots + donnees + config)" ;;
|
|
2) mode_label="Base de donnees uniquement" ;;
|
|
3) mode_label="Depots Git uniquement" ;;
|
|
4) mode_label="Donnees uniquement" ;;
|
|
5) mode_label="Configuration uniquement" ;;
|
|
esac
|
|
echo -e " Mode : ${BOLD}${mode_label}${RESET}"
|
|
echo ""
|
|
|
|
confirm " Confirmer la restauration ?" || { log INFO "Restauration annulee."; return 0; }
|
|
|
|
detect_gitea_config
|
|
|
|
# ── Extraction ────────────────────────────────────────────────────────────
|
|
TMP_DIR="$(mktemp -d /tmp/gitea_restore_XXXXXX)"
|
|
trap 'rm -rf "${TMP_DIR}"' EXIT
|
|
|
|
log STEP "Extraction de l'archive"
|
|
(tar -xzf "${selected}" -C "${TMP_DIR}") &
|
|
spinner $! "Extraction en cours..."
|
|
wait $!
|
|
log SUCCESS "Archive extraite"
|
|
|
|
if [[ -f "${TMP_DIR}/manifest.txt" ]]; then
|
|
hr
|
|
echo -e "${BOLD} Informations de la sauvegarde :${RESET}"
|
|
head -20 "${TMP_DIR}/manifest.txt" | sed 's/^/ /'
|
|
hr
|
|
fi
|
|
|
|
# ── Arrêt de Gitea avant restauration ─────────────────────────────────────
|
|
if [[ "${mode}" -ne 5 ]]; then
|
|
log STEP "Arret du service Gitea"
|
|
if systemctl is-active --quiet gitea 2>/dev/null; then
|
|
systemctl stop gitea
|
|
log SUCCESS "Service Gitea arrete"
|
|
# On redémarrera à la fin
|
|
GITEA_WAS_RUNNING=true
|
|
else
|
|
log WARN "Service Gitea non actif (ou non gere par systemd)"
|
|
GITEA_WAS_RUNNING=false
|
|
fi
|
|
fi
|
|
|
|
# ── Restauration DB ───────────────────────────────────────────────────────
|
|
if [[ "${mode}" -eq 1 || "${mode}" -eq 2 ]]; then
|
|
[[ -f "${TMP_DIR}/database.sql" ]] || [[ -f "${TMP_DIR}/database.db" ]] || \
|
|
die "Aucun dump de base de donnees dans l'archive"
|
|
case "${DB_TYPE}" in
|
|
postgresql) restore_db_postgresql ;;
|
|
mysql) restore_db_mysql ;;
|
|
sqlite3) restore_db_sqlite3 ;;
|
|
*) die "Type DB non supporte : ${DB_TYPE}" ;;
|
|
esac
|
|
fi
|
|
|
|
# ── Restauration dépôts ───────────────────────────────────────────────────
|
|
if [[ "${mode}" -eq 1 || "${mode}" -eq 3 ]]; then
|
|
if [[ -f "${TMP_DIR}/repositories.tar.gz" ]]; then
|
|
log STEP "Restauration des depots Git"
|
|
local repo_parent; repo_parent="$(dirname "${GITEA_REPO_ROOT}")"
|
|
if [[ -d "${GITEA_REPO_ROOT}" ]]; then
|
|
mv "${GITEA_REPO_ROOT}" "${GITEA_REPO_ROOT}.pre_restore_${TIMESTAMP}"
|
|
log INFO " Anciens depots sauvegardes : $(basename "${GITEA_REPO_ROOT}").pre_restore_${TIMESTAMP}"
|
|
fi
|
|
(tar -xzf "${TMP_DIR}/repositories.tar.gz" -C "${repo_parent}") &
|
|
spinner $! "Restauration des depots..."
|
|
wait $!
|
|
log SUCCESS "Depots restaures dans ${GITEA_REPO_ROOT}"
|
|
else
|
|
log WARN "Aucune archive de depots trouvee dans la sauvegarde"
|
|
fi
|
|
fi
|
|
|
|
# ── Restauration données ──────────────────────────────────────────────────
|
|
if [[ "${mode}" -eq 1 || "${mode}" -eq 4 ]]; then
|
|
if [[ -f "${TMP_DIR}/data.tar.gz" ]]; then
|
|
log STEP "Restauration des donnees (avatars, attachments, LFS...)"
|
|
local data_parent; data_parent="$(dirname "${GITEA_DATA_DIR}")"
|
|
if [[ -d "${GITEA_DATA_DIR}" ]]; then
|
|
mv "${GITEA_DATA_DIR}" "${GITEA_DATA_DIR}.pre_restore_${TIMESTAMP}"
|
|
log INFO " Anciennes donnees sauvegardees"
|
|
fi
|
|
(tar -xzf "${TMP_DIR}/data.tar.gz" -C "${data_parent}") &
|
|
spinner $! "Restauration des donnees..."
|
|
wait $!
|
|
log SUCCESS "Donnees restaurees dans ${GITEA_DATA_DIR}"
|
|
else
|
|
log WARN "Aucune archive de donnees trouvee dans la sauvegarde"
|
|
fi
|
|
fi
|
|
|
|
# ── Restauration configuration ────────────────────────────────────────────
|
|
if [[ "${mode}" -eq 1 || "${mode}" -eq 5 ]]; then
|
|
if [[ -f "${TMP_DIR}/config/app.ini" ]]; then
|
|
log STEP "Restauration de la configuration"
|
|
cp "${GITEA_CONF}" "${GITEA_CONF}.pre_restore_${TIMESTAMP}" 2>/dev/null || true
|
|
cp "${TMP_DIR}/config/app.ini" "${GITEA_CONF}"
|
|
log SUCCESS "app.ini restaure"
|
|
fi
|
|
if [[ -d "${TMP_DIR}/config/ssh_keys" ]]; then
|
|
local gitea_home; gitea_home=$(eval echo "~${GITEA_USER}")
|
|
cp -r "${TMP_DIR}/config/ssh_keys/." "${gitea_home}/.ssh/" 2>/dev/null || true
|
|
log SUCCESS "Cles SSH restaurees"
|
|
fi
|
|
fi
|
|
|
|
# ── Ajustement des permissions ────────────────────────────────────────────
|
|
log STEP "Ajustement des permissions pour '${GITEA_USER}'"
|
|
[[ -d "${GITEA_REPO_ROOT}" ]] && \
|
|
chown -R "${GITEA_USER}:${GITEA_USER}" "${GITEA_REPO_ROOT}" 2>/dev/null || true
|
|
[[ -d "${GITEA_DATA_DIR}" ]] && \
|
|
chown -R "${GITEA_USER}:${GITEA_USER}" "${GITEA_DATA_DIR}" 2>/dev/null || true
|
|
local gitea_home; gitea_home=$(eval echo "~${GITEA_USER}")
|
|
[[ -d "${gitea_home}/.ssh" ]] && \
|
|
chmod 700 "${gitea_home}/.ssh" && \
|
|
chmod 600 "${gitea_home}/.ssh/"* 2>/dev/null || true
|
|
log SUCCESS "Permissions ajustees"
|
|
|
|
# ── Redémarrage de Gitea ──────────────────────────────────────────────────
|
|
if [[ "${GITEA_WAS_RUNNING:-false}" == "true" ]]; then
|
|
log STEP "Redemarrage du service Gitea"
|
|
systemctl start gitea && log SUCCESS "Service Gitea redemarre" || \
|
|
log WARN "Echec du redemarrage — relancer manuellement : systemctl start gitea"
|
|
fi
|
|
|
|
hr
|
|
echo -e "${GREEN}${BOLD} ✔ Restauration Gitea terminee avec succes !${RESET}"
|
|
hr
|
|
}
|
|
|
|
# =============================================================================
|
|
# LISTING DES SAUVEGARDES
|
|
# =============================================================================
|
|
|
|
do_list() {
|
|
hr
|
|
echo -e "${BOLD} Sauvegardes disponibles dans : ${BACKUP_DIR}${RESET}\n"
|
|
local count=0
|
|
while IFS= read -r archive; do
|
|
local fname; fname=$(basename "${archive}")
|
|
local fsize; fsize=$(du -sh "${archive}" | cut -f1)
|
|
local fdate; fdate=$(stat -c '%y' "${archive}" | cut -d'.' -f1)
|
|
printf " ${CYAN}%-50s${RESET} ${YELLOW}%6s${RESET} %s\n" "${fname}" "${fsize}" "${fdate}"
|
|
count=$(( count + 1 ))
|
|
done < <(find "${BACKUP_DIR}" -maxdepth 1 -name "gitea_backup_*.tar.gz" | sort -r)
|
|
[[ "${count}" -eq 0 ]] && echo -e " ${YELLOW}Aucune sauvegarde trouvee.${RESET}"
|
|
echo ""
|
|
echo -e " Total : ${BOLD}${count} archive(s)${RESET}"
|
|
hr
|
|
}
|
|
|
|
# =============================================================================
|
|
# PLANIFICATION CRON
|
|
# =============================================================================
|
|
|
|
do_cron() {
|
|
local script_path; script_path="$(realpath "${BASH_SOURCE[0]}")"
|
|
local cron_tag="# gitea-backup-auto"
|
|
local cron_user="root"
|
|
|
|
hr
|
|
echo -e "${BOLD}${CYAN} Planification des sauvegardes automatiques (Cron)${RESET}"
|
|
hr
|
|
|
|
echo -e "\n${BOLD} Entrees cron actuelles pour gitea-backup :${RESET}\n"
|
|
local existing
|
|
existing=$(crontab -u "${cron_user}" -l 2>/dev/null | grep "${cron_tag}" || true)
|
|
if [[ -n "${existing}" ]]; then
|
|
echo "${existing}" | while read -r line; do
|
|
echo -e " ${YELLOW}${line}${RESET}"
|
|
done
|
|
else
|
|
echo -e " ${YELLOW}Aucune planification active.${RESET}"
|
|
fi
|
|
|
|
echo ""
|
|
hr
|
|
echo -e "\n${BOLD} Choisir une frequence de sauvegarde :${RESET}\n"
|
|
echo -e " ${CYAN}[1]${RESET} Quotidienne — tous les jours a 03h00"
|
|
echo -e " ${CYAN}[2]${RESET} Biquotidienne — 2x par jour a 03h00 et 15h00"
|
|
echo -e " ${CYAN}[3]${RESET} Hebdomadaire — tous les lundis a 03h00"
|
|
echo -e " ${CYAN}[4]${RESET} Mensuelle — le 1er du mois a 03h00"
|
|
echo -e " ${CYAN}[5]${RESET} Personnalisee — saisir une expression cron manuellement"
|
|
echo -e " ${CYAN}[6]${RESET} Supprimer — retirer toutes les planifications"
|
|
echo -e " ${CYAN}[0]${RESET} Retour au menu principal"
|
|
echo ""
|
|
hr
|
|
|
|
local cron_choice
|
|
while true; do
|
|
read -rp "$(echo -e "${BOLD} Votre choix [0-6] : ${RESET}")" cron_choice
|
|
[[ "${cron_choice}" =~ ^[0-6]$ ]] && break
|
|
echo -e "${RED} Choix invalide.${RESET}"
|
|
done
|
|
[[ "${cron_choice}" -eq 0 ]] && return 0
|
|
|
|
if [[ "${cron_choice}" -eq 6 ]]; then
|
|
local current_cron
|
|
current_cron=$(crontab -u "${cron_user}" -l 2>/dev/null \
|
|
| grep -v "${cron_tag}" || true)
|
|
if [[ -n "${current_cron}" ]]; then
|
|
echo "${current_cron}" | crontab -u "${cron_user}" -
|
|
else
|
|
crontab -u "${cron_user}" - <<< ""
|
|
fi
|
|
log SUCCESS "Planifications gitea-backup supprimees"
|
|
return 0
|
|
fi
|
|
|
|
local cron_expr=""
|
|
case "${cron_choice}" in
|
|
1) cron_expr="0 3 * * *" ;;
|
|
2) cron_expr="0 3,15 * * *" ;;
|
|
3) cron_expr="0 3 * * 1" ;;
|
|
4) cron_expr="0 3 1 * *" ;;
|
|
5)
|
|
echo -e "\n ${BOLD}Format :${RESET} minute heure jour_mois mois jour_semaine"
|
|
echo -e " ${YELLOW}Exemples :${RESET}"
|
|
echo -e " 0 3 * * * -> tous les jours a 03h00"
|
|
echo -e " 30 2 * * 0 -> tous les dimanches a 02h30"
|
|
echo -e " 0 4 */2 * * -> tous les 2 jours a 04h00"
|
|
echo ""
|
|
while true; do
|
|
read -rp "$(echo -e "${BOLD} Expression cron : ${RESET}")" cron_expr
|
|
local field_count; field_count=$(echo "${cron_expr}" | wc -w)
|
|
[[ "${field_count}" -eq 5 ]] && break
|
|
echo -e "${RED} Expression invalide (5 champs requis).${RESET}"
|
|
done
|
|
;;
|
|
esac
|
|
|
|
local mail_opt='MAILTO=""'
|
|
echo ""
|
|
read -rp "$(echo -e "${YELLOW} Envoyer les logs cron par email ? (laisser vide pour ignorer) : ${RESET}")" mail_addr
|
|
[[ -n "${mail_addr}" ]] && mail_opt="MAILTO=${mail_addr}"
|
|
|
|
local cron_line="${cron_expr} ${script_path} backup ${cron_tag}"
|
|
echo ""
|
|
hr
|
|
echo -e " ${BOLD}Entree cron qui sera ajoutee :${RESET}\n"
|
|
[[ "${mail_opt}" != 'MAILTO=""' ]] && echo -e " ${YELLOW}${mail_opt}${RESET}"
|
|
echo -e " ${YELLOW}${cron_line}${RESET}"
|
|
echo ""
|
|
|
|
confirm " Confirmer l'installation de cette tache cron ?" || {
|
|
log INFO "Planification annulee."
|
|
return 0
|
|
}
|
|
|
|
local clean_cron
|
|
clean_cron=$(crontab -u "${cron_user}" -l 2>/dev/null \
|
|
| grep -v "${cron_tag}" || true)
|
|
|
|
{
|
|
[[ -n "${clean_cron}" ]] && echo "${clean_cron}"
|
|
echo "${mail_opt}"
|
|
echo "${cron_line}"
|
|
} | crontab -u "${cron_user}" -
|
|
|
|
log SUCCESS "Tache cron installee pour root"
|
|
|
|
if ! systemctl is-active --quiet cron 2>/dev/null && \
|
|
! systemctl is-active --quiet crond 2>/dev/null; then
|
|
log WARN "Le service cron ne semble pas actif : systemctl start cron"
|
|
else
|
|
log INFO "Service cron actif"
|
|
fi
|
|
|
|
hr
|
|
echo -e "${GREEN}${BOLD} Planification cron configuree avec succes !${RESET}"
|
|
hr
|
|
}
|
|
|
|
# =============================================================================
|
|
# AIDE
|
|
# =============================================================================
|
|
|
|
do_help() {
|
|
hr
|
|
echo -e "${BOLD} gitea-backup.sh — Outil de sauvegarde/restauration Gitea${RESET}\n"
|
|
echo -e " ${BOLD}Usage :${RESET}"
|
|
echo -e " sudo $0 backup Creer une sauvegarde complete"
|
|
echo -e " sudo $0 restore Menu interactif de restauration"
|
|
echo -e " sudo $0 list Lister les sauvegardes disponibles"
|
|
echo -e " sudo $0 cron Gerer la planification automatique"
|
|
echo -e " sudo $0 help Afficher cette aide\n"
|
|
echo -e " ${BOLD}Configuration (.env.gitea) :${RESET}"
|
|
echo -e " GITEA_USER Utilisateur systeme qui fait tourner Gitea (defaut: git)"
|
|
echo -e " GITEA_ROOT Repertoire d'installation du binaire"
|
|
echo -e " GITEA_WORK_DIR Repertoire de travail Gitea (data, repos...)"
|
|
echo -e " GITEA_CONF Chemin vers app.ini"
|
|
echo -e " GITEA_BINARY Chemin vers le binaire gitea"
|
|
echo -e " BACKUP_DIR Dossier de stockage des archives"
|
|
echo -e " KEEP_BACKUPS Nombre d'archives a conserver (defaut: 7)"
|
|
echo -e " INCLUDE_LOG Inclure les logs dans la sauvegarde (true/false)"
|
|
echo -e " USE_NATIVE_DUMP Utiliser 'gitea dump' natif en complement (true/false)"
|
|
echo -e " DB_TYPE Forcer le type DB (postgresql|mysql|sqlite3)"
|
|
echo -e " DB_HOST/PORT Surcharger host/port de la DB"
|
|
echo -e " DB_NAME/USER/PASS Surcharger les credentials DB\n"
|
|
echo -e " ${BOLD}Contenu d'une archive :${RESET}"
|
|
echo -e " database.sql Dump complet de la base de donnees"
|
|
echo -e " manifest.txt Infos systeme, version, stats depots"
|
|
echo -e " config/ app.ini + cles SSH"
|
|
echo -e " repositories.tar.gz Tous les depots Git"
|
|
echo -e " data.tar.gz Avatars, attachments, LFS"
|
|
echo -e " native_dump/ Archive gitea dump natif (si active)\n"
|
|
echo -e " ${BOLD}Journal :${RESET} ${LOG_FILE}"
|
|
hr
|
|
}
|
|
|
|
# =============================================================================
|
|
# MENU INTERACTIF
|
|
# =============================================================================
|
|
|
|
interactive_menu() {
|
|
while true; do
|
|
clear
|
|
hr
|
|
echo -e "${BOLD}${CYAN}"
|
|
echo " ██████╗ ██╗████████╗███████╗ █████╗ ██████╗ █████╗ ██████╗██╗ ██╗██╗ ██╗██████╗"
|
|
echo " ██╔════╝ ██║╚══██╔══╝██╔════╝██╔══██╗ ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗"
|
|
echo " ██║ ███╗██║ ██║ █████╗ ███████║ ██████╔╝███████║██║ █████╔╝ ██║ ██║██████╔╝"
|
|
echo " ██║ ██║██║ ██║ ██╔══╝ ██╔══██║ ██╔══██╗██╔══██║██║ ██╔═██╗ ██║ ██║██╔═══╝"
|
|
echo " ╚██████╔╝██║ ██║ ███████╗██║ ██║ ██████╔╝██║ ██║╚██████╗██║ ██╗╚██████╔╝██║"
|
|
echo " ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝"
|
|
echo -e "${RESET}${BOLD} Gestionnaire de Sauvegarde Gitea${RESET}"
|
|
hr
|
|
# Infos live
|
|
local gitea_status="inconnu"
|
|
systemctl is-active --quiet gitea 2>/dev/null && gitea_status="${GREEN}actif${RESET}" \
|
|
|| gitea_status="${RED}inactif${RESET}"
|
|
local backup_count; backup_count=$(find "${BACKUP_DIR}" -maxdepth 1 \
|
|
-name "gitea_backup_*.tar.gz" 2>/dev/null | wc -l)
|
|
echo -e " ${BOLD}Config :${RESET} ${GITEA_CONF}"
|
|
echo -e " ${BOLD}Service Gitea :${RESET} $(echo -e "${gitea_status}") ${BOLD}Archives :${RESET} ${backup_count} disponible(s)"
|
|
hr
|
|
echo ""
|
|
echo -e " ${CYAN}[1]${RESET} ${BOLD}Creer une sauvegarde${RESET} - Sauvegarde complete (DB + depots + donnees + config)"
|
|
echo -e " ${CYAN}[2]${RESET} ${BOLD}Restaurer${RESET} - Menu interactif de restauration"
|
|
echo -e " ${CYAN}[3]${RESET} ${BOLD}Lister les sauvegardes${RESET} - Voir les archives disponibles"
|
|
echo -e " ${CYAN}[4]${RESET} ${BOLD}Planification Cron${RESET} - Gerer les sauvegardes automatiques"
|
|
echo -e " ${CYAN}[5]${RESET} ${BOLD}Afficher l'aide${RESET} - Documentation complete"
|
|
echo -e " ${CYAN}[0]${RESET} ${BOLD}Quitter${RESET}"
|
|
echo ""
|
|
hr
|
|
read -rp "$(echo -e "${BOLD} Votre choix : ${RESET}")" choice
|
|
echo ""
|
|
case "${choice}" in
|
|
1) do_backup ;;
|
|
2) do_restore ;;
|
|
3) do_list ;;
|
|
4) do_cron ;;
|
|
5) do_help ;;
|
|
0) echo -e "${GREEN} Au revoir !${RESET}\n"; exit 0 ;;
|
|
*) echo -e "${RED} Choix invalide.${RESET}" ; sleep 1; continue ;;
|
|
esac
|
|
echo ""
|
|
echo -e "${BLUE} Retour au menu dans 3 secondes...${RESET}"
|
|
sleep 3
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# POINT D'ENTRÉE
|
|
# =============================================================================
|
|
|
|
main() {
|
|
require_root
|
|
require_command tar
|
|
require_command gzip
|
|
load_env
|
|
|
|
local cmd="${1:-menu}"
|
|
case "${cmd}" in
|
|
backup) do_backup ;;
|
|
restore) do_restore ;;
|
|
list) do_list ;;
|
|
cron) do_cron ;;
|
|
help|-h|--help) do_help ;;
|
|
menu|"") interactive_menu ;;
|
|
*) echo -e "${RED}Commande inconnue : ${cmd}${RESET}"; do_help; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
main "$@" |