384 lines
15 KiB
Bash
Executable File
384 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
||
#
|
||
# Скрипт бэкапа Gitea из Docker
|
||
# Основан на рекомендациях: https://docs.gitea.com/administration/backup-and-restore
|
||
#
|
||
# Использует официальную команду `gitea dump` для создания консистентного бэкапа.
|
||
# Для PostgreSQL дополнительно делает нативный дамп БД (рекомендовано Gitea).
|
||
#
|
||
# База данных: PostgreSQL
|
||
# Запускать на исходном сервере (192.168.0.38)
|
||
#
|
||
|
||
set -e
|
||
|
||
# ============================================
|
||
# НАСТРОЙКИ - ОТРЕДАКТИРУЙТЕ ПОД ВАШУ КОНФИГУРАЦИЮ
|
||
# ============================================
|
||
|
||
# Имя Docker контейнера Gitea
|
||
# Для docker-compose обычно: gitea-server-1, gitea-gitea-1 или gitea_server_1
|
||
GITEA_CONTAINER="${GITEA_CONTAINER:-gitea-server-1}"
|
||
|
||
# Имя контейнера PostgreSQL
|
||
# Для docker-compose: gitea-db-1, gitea_db_1
|
||
DB_CONTAINER="${DB_CONTAINER:-gitea-db-1}"
|
||
|
||
# Credentials для PostgreSQL
|
||
DB_USER="${DB_USER:-gitea}"
|
||
DB_NAME="${DB_NAME:-gitea}"
|
||
DB_PASSWORD="${DB_PASSWORD:-}"
|
||
|
||
# Директория для бэкапа
|
||
BACKUP_DIR="${BACKUP_DIR:-/tmp/gitea-backup-$(date +%Y%m%d-%H%M%S)}"
|
||
|
||
# Формат архива: zip или tar.gz
|
||
ARCHIVE_TYPE="${ARCHIVE_TYPE:-zip}"
|
||
|
||
# ============================================
|
||
# ПУТИ GITEA (автоопределяются, но можно переопределить)
|
||
# ============================================
|
||
# Для rootless образа (docker.gitea.com/gitea:*-rootless):
|
||
# - Конфиг: /etc/gitea/app.ini
|
||
# - Бинарник: /usr/local/bin/gitea
|
||
# - Данные: /var/lib/gitea
|
||
# Для обычного образа (gitea/gitea:*):
|
||
# - Конфиг: /data/gitea/conf/app.ini
|
||
# - Бинарник: /app/gitea/gitea или /usr/local/bin/gitea
|
||
# - Данные: /data
|
||
GITEA_CONFIG="${GITEA_CONFIG:-}"
|
||
GITEA_BIN="${GITEA_BIN:-}"
|
||
|
||
# Rootless режим (автоопределяется)
|
||
ROOTLESS="${ROOTLESS:-auto}"
|
||
|
||
# ============================================
|
||
# ФУНКЦИИ
|
||
# ============================================
|
||
|
||
log() {
|
||
echo -e "\033[1;32m[$(date '+%Y-%m-%d %H:%M:%S')]\033[0m $1"
|
||
}
|
||
|
||
warn() {
|
||
echo -e "\033[1;33m[WARN]\033[0m $1"
|
||
}
|
||
|
||
error() {
|
||
echo -e "\033[1;31m[ERROR]\033[0m $1" >&2
|
||
exit 1
|
||
}
|
||
|
||
check_container() {
|
||
if ! docker ps --format '{{.Names}}' | grep -q "^${1}$"; then
|
||
if docker ps -a --format '{{.Names}}' | grep -q "^${1}$"; then
|
||
warn "Контейнер '$1' существует, но не запущен"
|
||
return 1
|
||
else
|
||
error "Контейнер '$1' не найден"
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
detect_gitea_paths() {
|
||
log "Автоопределение путей и режима Gitea..."
|
||
|
||
# Определяем rootless режим по образу
|
||
if [ "$ROOTLESS" = "auto" ]; then
|
||
IMAGE=$(docker inspect "$GITEA_CONTAINER" --format '{{.Config.Image}}' 2>/dev/null || echo "")
|
||
if echo "$IMAGE" | grep -q "rootless"; then
|
||
ROOTLESS="true"
|
||
log " Обнаружен rootless образ: $IMAGE"
|
||
else
|
||
ROOTLESS="false"
|
||
log " Обнаружен стандартный образ: $IMAGE"
|
||
fi
|
||
fi
|
||
|
||
# Пробуем найти app.ini (порядок зависит от режима)
|
||
if [ -z "$GITEA_CONFIG" ]; then
|
||
if [ "$ROOTLESS" = "true" ]; then
|
||
CONFIG_PATHS="/etc/gitea/app.ini /var/lib/gitea/custom/conf/app.ini /data/gitea/conf/app.ini"
|
||
else
|
||
CONFIG_PATHS="/data/gitea/conf/app.ini /etc/gitea/app.ini /app/gitea/custom/conf/app.ini"
|
||
fi
|
||
|
||
for config_path in $CONFIG_PATHS; do
|
||
if docker exec "$GITEA_CONTAINER" test -f "$config_path" 2>/dev/null; then
|
||
GITEA_CONFIG="$config_path"
|
||
log " Найден конфиг: $GITEA_CONFIG"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Пробуем найти бинарник gitea
|
||
if [ -z "$GITEA_BIN" ]; then
|
||
if [ "$ROOTLESS" = "true" ]; then
|
||
BIN_PATHS="/usr/local/bin/gitea /usr/bin/gitea /app/gitea/gitea"
|
||
else
|
||
BIN_PATHS="/app/gitea/gitea /usr/local/bin/gitea /usr/bin/gitea"
|
||
fi
|
||
|
||
for bin_path in $BIN_PATHS; do
|
||
if docker exec "$GITEA_CONTAINER" test -x "$bin_path" 2>/dev/null; then
|
||
GITEA_BIN="$bin_path"
|
||
log " Найден бинарник: $GITEA_BIN"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Устанавливаем дефолты если не найдено
|
||
GITEA_CONFIG="${GITEA_CONFIG:-/etc/gitea/app.ini}"
|
||
GITEA_BIN="${GITEA_BIN:-/usr/local/bin/gitea}"
|
||
}
|
||
|
||
# ============================================
|
||
# ОСНОВНОЙ СКРИПТ
|
||
# ============================================
|
||
|
||
log "=========================================="
|
||
log " Бэкап Gitea (официальный метод)"
|
||
log "=========================================="
|
||
log ""
|
||
log "Контейнер: $GITEA_CONTAINER"
|
||
log "БД контейнер: $DB_CONTAINER (PostgreSQL)"
|
||
log "Директория бэкапа: $BACKUP_DIR"
|
||
log ""
|
||
|
||
# Создаём директорию бэкапа
|
||
mkdir -p "$BACKUP_DIR"
|
||
|
||
# Проверяем наличие контейнера
|
||
if ! check_container "$GITEA_CONTAINER"; then
|
||
error "Контейнер Gitea должен быть запущен для определения путей"
|
||
fi
|
||
|
||
# Автоопределение путей
|
||
detect_gitea_paths
|
||
|
||
# Получаем версию Gitea
|
||
GITEA_VERSION=$(docker exec "$GITEA_CONTAINER" "$GITEA_BIN" --version 2>/dev/null | head -1 || echo "unknown")
|
||
log "Версия Gitea: $GITEA_VERSION"
|
||
echo "$GITEA_VERSION" > "$BACKUP_DIR/gitea-version.txt"
|
||
|
||
# Сохраняем информацию о Docker
|
||
log "Сохранение информации о Docker конфигурации..."
|
||
docker inspect "$GITEA_CONTAINER" > "$BACKUP_DIR/docker-inspect.json" 2>/dev/null || true
|
||
docker inspect "$GITEA_CONTAINER" --format '{{json .Mounts}}' 2>/dev/null | jq '.' > "$BACKUP_DIR/docker-mounts.json" 2>/dev/null || true
|
||
|
||
# ============================================
|
||
# ШАГ 1: Нативный дамп PostgreSQL
|
||
# Рекомендация Gitea: использовать нативные инструменты
|
||
# ============================================
|
||
|
||
log "Создание нативного дампа PostgreSQL..."
|
||
|
||
if [ -n "$DB_CONTAINER" ]; then
|
||
if [ -n "$DB_PASSWORD" ]; then
|
||
docker exec -e PGPASSWORD="$DB_PASSWORD" "$DB_CONTAINER" \
|
||
pg_dump -U "$DB_USER" -d "$DB_NAME" --no-owner --no-acl \
|
||
> "$BACKUP_DIR/gitea-db-native.sql" 2>/dev/null
|
||
else
|
||
docker exec "$DB_CONTAINER" \
|
||
pg_dump -U "$DB_USER" -d "$DB_NAME" --no-owner --no-acl \
|
||
> "$BACKUP_DIR/gitea-db-native.sql" 2>/dev/null
|
||
fi
|
||
log " ✓ PostgreSQL дамп создан: gitea-db-native.sql"
|
||
else
|
||
warn "DB_CONTAINER не указан, пропускаем нативный дамп"
|
||
fi
|
||
|
||
# ============================================
|
||
# ШАГ 2: Остановка Gitea (рекомендуется для консистентности)
|
||
# ============================================
|
||
|
||
log "Остановка Gitea для консистентного бэкапа..."
|
||
GITEA_WAS_RUNNING=false
|
||
if docker ps --format '{{.Names}}' | grep -q "^${GITEA_CONTAINER}$"; then
|
||
GITEA_WAS_RUNNING=true
|
||
docker stop "$GITEA_CONTAINER" >/dev/null
|
||
log " ✓ Gitea остановлена"
|
||
sleep 2
|
||
fi
|
||
|
||
# ============================================
|
||
# ШАГ 3: Создание бэкапа с помощью `gitea dump`
|
||
# Официальный метод согласно документации
|
||
# ============================================
|
||
|
||
log "Создание бэкапа через 'gitea dump'..."
|
||
|
||
# Временно запускаем контейнер для выполнения dump
|
||
docker start "$GITEA_CONTAINER" >/dev/null 2>&1 || true
|
||
sleep 3
|
||
|
||
# Определяем параметры для gitea dump
|
||
DUMP_ARGS="-c $GITEA_CONFIG"
|
||
DUMP_ARGS="$DUMP_ARGS --type $ARCHIVE_TYPE"
|
||
|
||
# Для rootless временная директория должна быть доступна пользователю git
|
||
if [ "$ROOTLESS" = "true" ]; then
|
||
DUMP_ARGS="$DUMP_ARGS --tempdir /var/lib/gitea"
|
||
DUMP_SEARCH_PATHS="/var/lib/gitea /tmp /home/git"
|
||
else
|
||
DUMP_ARGS="$DUMP_ARGS --tempdir /tmp"
|
||
DUMP_SEARCH_PATHS="/tmp /app/gitea /data"
|
||
fi
|
||
|
||
# Пропускаем встроенный дамп БД (уже сделали нативный)
|
||
if [ -f "$BACKUP_DIR/gitea-db-native.sql" ]; then
|
||
DUMP_ARGS="$DUMP_ARGS --skip-db"
|
||
log " (пропуск встроенного дампа БД - используем нативный)"
|
||
fi
|
||
|
||
# Выполняем gitea dump
|
||
log " Выполнение: gitea dump $DUMP_ARGS"
|
||
if [ "$ROOTLESS" = "true" ]; then
|
||
docker exec "$GITEA_CONTAINER" "$GITEA_BIN" dump $DUMP_ARGS 2>&1 | tee "$BACKUP_DIR/dump.log" || {
|
||
warn "gitea dump завершился с ошибкой, проверьте dump.log"
|
||
}
|
||
else
|
||
docker exec -u git "$GITEA_CONTAINER" "$GITEA_BIN" dump $DUMP_ARGS 2>&1 | tee "$BACKUP_DIR/dump.log" || {
|
||
warn "Повторная попытка без флага -u git..."
|
||
docker exec "$GITEA_CONTAINER" "$GITEA_BIN" dump $DUMP_ARGS 2>&1 | tee "$BACKUP_DIR/dump.log"
|
||
}
|
||
fi
|
||
|
||
# Находим созданный архив
|
||
log " Поиск архива..."
|
||
DUMP_FILE=""
|
||
for search_path in $DUMP_SEARCH_PATHS; do
|
||
DUMP_FILE=$(docker exec "$GITEA_CONTAINER" sh -c "ls -t ${search_path}/gitea-dump-*.${ARCHIVE_TYPE} 2>/dev/null | head -1" || true)
|
||
if [ -n "$DUMP_FILE" ] && [ "$" != "" ]; then
|
||
break
|
||
fi
|
||
done
|
||
|
||
# Если не нашли, ищем везде
|
||
if [ -z "$DUMP_FILE" ]; then
|
||
DUMP_FILE=$(docker exec "$GITEA_CONTAINER" find / -name "gitea-dump-*.${ARCHIVE_TYPE}" -type f 2>/dev/null | head -1 || true)
|
||
fi
|
||
|
||
if [ -n "$DUMP_FILE" ] && [ "$DUMP_FILE" != "" ]; then
|
||
log " Найден архив: $DUMP_FILE"
|
||
docker cp "$GITEA_CONTAINER:$DUMP_FILE" "$BACKUP_DIR/gitea-dump.${ARCHIVE_TYPE}"
|
||
log " ✓ Архив скопирован: gitea-dump.${ARCHIVE_TYPE}"
|
||
|
||
# Удаляем архив из контейнера
|
||
docker exec "$GITEA_CONTAINER" rm -f "$DUMP_FILE" 2>/dev/null || true
|
||
else
|
||
warn "Архив gitea dump не найден, делаем ручное копирование..."
|
||
|
||
# Fallback: ручное копирование данных
|
||
log " Копирование данных вручную..."
|
||
if [ "$ROOTLESS" = "true" ]; then
|
||
docker cp "$GITEA_CONTAINER:/var/lib/gitea" "$BACKUP_DIR/data" 2>/dev/null || \
|
||
docker cp "$GITEA_CONTAINER:/data" "$BACKUP_DIR/data" 2>/dev/null || \
|
||
warn " Не удалось скопировать данные"
|
||
else
|
||
docker cp "$GITEA_CONTAINER:/data" "$BACKUP_DIR/data" 2>/dev/null || \
|
||
docker cp "$GITEA_CONTAINER:/var/lib/gitea" "$BACKUP_DIR/data" 2>/dev/null || \
|
||
warn " Не удалось скопировать данные"
|
||
fi
|
||
fi
|
||
|
||
# ============================================
|
||
# ШАГ 4: Копирование конфигурации отдельно
|
||
# ============================================
|
||
|
||
log "Копирование конфигурации..."
|
||
docker cp "$GITEA_CONTAINER:$GITEA_CONFIG" "$BACKUP_DIR/app.ini" 2>/dev/null && \
|
||
log " ✓ app.ini скопирован" || \
|
||
warn " Не удалось скопировать app.ini (возDUMP_FILEможно включён в архив)"
|
||
|
||
# ============================================
|
||
# ШАГ 5: Возврат в исходное состояние
|
||
# ============================================
|
||
|
||
if [ "$GITEA_WAS_RUNNING" = true ]; then
|
||
log "Перезапуск Gitea..."
|
||
docker restart "$GITEA_CONTAINER" >/dev/null
|
||
log " ✓ Gitea запущена"
|
||
else
|
||
log "Остановка Gitea (была остановлена до бэкапа)..."
|
||
docker stop "$GITEA_CONTAINER" >/dev/null 2>&1 || true
|
||
fi
|
||
|
||
# ============================================
|
||
# ШАГ 6: Поиск docker-compose
|
||
# ============================================
|
||
|
||
log "Поиск docker-compose файлов..."
|
||
COMPOSE_FILES=$(find /home /opt /root /srv -maxdepth 4 \
|
||
\( -name "docker-compose*.yml" -o -name "docker-compose*.yaml" -o -name "compose*.yml" -o -name "compose*.yaml" \) \
|
||
2>/dev/null | xargs grep -l -i gitea 2>/dev/null || true)
|
||
|
||
if [ -n "$COMPOSE_FILES" ]; then
|
||
mkdir -p "$BACKUP_DIR/compose"
|
||
for f in $COMPOSE_FILES; do
|
||
cp "$f" "$BACKUP_DIR/compose/" 2>/dev/null || true
|
||
done
|
||
log " ✓ Docker Compose файлы скопированы"
|
||
fi
|
||
|
||
# ============================================
|
||
# ШАГ 7: Создание итогового архива
|
||
# ============================================
|
||
|
||
log "Создание итогового архива..."
|
||
ARCHIVE_NAME="gitea-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||
cd /tmp
|
||
tar -czf "$ARCHIVE_NAME" "$(basename $BACKUP_DIR)"
|
||
|
||
log ""
|
||
log "=========================================="
|
||
log " ✅ Бэкап успешно завершён!"
|
||
log "=========================================="
|
||
log ""
|
||
log "Содержимое бэкапа:"
|
||
ls -lh "$BACKUP_DIR"
|
||
log ""
|
||
log "Итоговый архив:"
|
||
ls -lh "/tmp/$ARCHIVE_NAME"
|
||
log ""
|
||
log "Файлы:"
|
||
log " Директория: $BACKUP_DIR"
|
||
log " Архив: /tmp/$ARCHIVE_NAME"
|
||
log ""
|
||
log "Следующий шаг - скопируйте архив на новый сервер:"
|
||
log " scp /tmp/$ARCHIVE_NAME root@NEW_SERVER_IP:/tmp/"
|
||
log ""
|
||
|
||
# Создаём файл с инструкцией
|
||
cat > "$BACKUP_DIR/RESTORE_INFO.txt" << EOF
|
||
Gitea Backup Information
|
||
========================
|
||
|
||
Created: $(date)
|
||
Gitea Version: $GITEA_VERSION
|
||
Database: PostgreSQL
|
||
|
||
Contents:
|
||
- gitea-dump.${ARCHIVE_TYPE} - Official Gitea dump (repos, data, config)
|
||
- gitea-db-native.sql - Native PostgreSQL dump (recommended for restore)
|
||
- app.ini - Configuration file
|
||
- gitea-version.txt - Version information
|
||
- docker-inspect.json - Docker container configuration
|
||
- docker-mounts.json - Docker volume mounts
|
||
$([ -d "$BACKUP_DIR/compose" ] && echo "- compose/ - Docker Compose files")
|
||
|
||
Restore Instructions:
|
||
1. Extract: tar -xzf $ARCHIVE_NAME
|
||
2. Restore PostgreSQL: psql -U gitea -d gitea < gitea-db-native.sql
|
||
3. Extract gitea-dump.${ARCHIVE_TYPE} and restore files
|
||
4. Update app.ini with new paths/domains
|
||
5. Start Gitea
|
||
|
||
For detailed instructions see: https://docs.gitea.com/administration/backup-and-restore
|
||
EOF
|
||
|
||
log "Информация о восстановлении сохранена в RESTORE_INFO.txt"
|