#!/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"