Files
gitea-backup/migration-scripts/backup-gitea-for-migration.sh
2026-01-06 22:12:07 +05:00

384 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"