From a00314964de3c99ad2e037391688c253c8fcd603 Mon Sep 17 00:00:00 2001 From: Wang Defa Date: Fri, 26 Dec 2025 13:06:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(backup):=20=E6=B7=BB=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=87=E4=BB=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 files 配置节,支持备份多个单独文件 - 实现 backup_files() 函数,处理单文件备份逻辑 - 更新 merge_backups() 函数,支持合并 files 备份 - 更新配置文件示例,添加 files 配置说明 - 更新 README 文档: - 添加单文件备份功能说明 - 添加 files 配置示例 - 添加单文件恢复详细步骤 - 更新备份文件结构说明 - 添加单文件完整性验证方法 - 添加 v1.3.0 更新日志 使用场景: - 备份配置文件(如 /etc/nginx/nginx.conf) - 备份环境变量文件(如 /opt/app/.env) - 备份其他重要的单个文件 备份结构: backup.tar.gz ├── folders/folders.tar.gz # 文件夹备份 ├── files/files.tar.gz # 单文件备份 └── mysql/mysql.sql.gz # 数据库备份 --- README.md | 56 +++++++++++++++++++++++++-- bin/backup.sh | 81 ++++++++++++++++++++++++++++++++++++++- config/backup.yml.example | 11 ++++++ 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index aa8dc7e..5fdd63d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ## 功能特性 - ✅ **文件夹备份**: 打包指定文件夹为 tar.gz 压缩文件 +- ✅ **单文件备份**: 支持备份多个单独的配置文件(如 .env、config.yml 等) - ✅ **排除目录**: 支持配置需要排除的文件夹和文件模式 - ✅ **MySQL 备份**: 备份 MySQL 容器数据库为 sql.gz 压缩文件 - ✅ **合并打包**: 将文件夹和数据库备份合并为单个压缩文件 @@ -89,6 +90,14 @@ folders: - "*/temp" - "*/cache" +# 单文件备份配置 +files: + enabled: true # 是否启用单文件备份 + sources: # 需要备份的文件列表 + - "/etc/docker-backup/config.yml" + - "/etc/nginx/nginx.conf" + - "/opt/app/.env" + # MySQL 数据库备份配置 mysql: enabled: true # 是否启用 MySQL 备份 @@ -252,6 +261,8 @@ backup-20250125-143022.tar.gz backup-20250125-143022.tar.gz ├── folders/ │ └── folders.tar.gz # 文件夹备份 +├── files/ +│ └── files.tar.gz # 单文件备份 └── mysql/ └── mysql.sql.gz # MySQL 数据库备份 ``` @@ -292,7 +303,33 @@ cp -r opt/gitea /tmp/restore/ # 检查无误后再复制到目标位置 ``` -### 3. 恢复 MySQL 数据库 +### 3. 恢复单个文件 + +```bash +# 解压单文件备份 +cd ../files +tar -xzf files.tar.gz + +# 查看解压的文件 +ls -lh +# 将显示备份时的完整路径,如:etc/docker-backup/config.yml + +# 方法一:直接恢复到原位置(会覆盖现有文件) +sudo cp etc/docker-backup/config.yml /etc/docker-backup/ +sudo cp etc/nginx/nginx.conf /etc/nginx/ +sudo cp opt/app/.env /opt/app/ + +# 方法二:选择性恢复 +# 只恢复需要的文件 +sudo cp etc/nginx/nginx.conf /etc/nginx/ + +# 方法三:恢复到临时目录检查 +mkdir -p /tmp/restore-files +cp -r etc /tmp/restore-files/ +# 检查无误后再复制到目标位置 +``` + +### 4. 恢复 MySQL 数据库 #### 方法一:恢复到 Docker 容器 @@ -354,7 +391,7 @@ docker exec mysql mysql -uroot -p'your_password' -e "SHOW DATABASES;" rm /tmp/restore_mysql.sql ``` -### 4. 完整恢复示例 +### 5. 完整恢复示例 假设需要完整恢复到新服务器: @@ -389,7 +426,7 @@ ls -lh /opt/gitea/ docker-compose up -d ``` -### 5. 恢复注意事项 +### 6. 恢复注意事项 ⚠️ **重要提示**: @@ -400,7 +437,7 @@ docker-compose up -d 5. **版本兼容**:确保 MySQL 版本兼容(备份和恢复的版本) 6. **磁盘空间**:确保有足够的磁盘空间进行解压和恢复 -### 6. 验证备份完整性 +### 7. 验证备份完整性 ```bash # 测试 tar.gz 文件完整性 @@ -416,6 +453,11 @@ echo "SQL 备份文件完整" cd ../folders tar -tzf folders.tar.gz > /dev/null echo "文件夹备份完整" + +# 测试单文件备份完整性 +cd ../files +tar -tzf files.tar.gz > /dev/null +echo "单文件备份完整" ``` ## 故障排查 @@ -577,6 +619,12 @@ rclone sync /var/backups/docker-backup/ remote:backup/ ## 更新日志 +### v1.3.0 (2025-12-26) + +- ✨ 新增:支持单个文件备份功能(files 配置) +- 📝 文档:添加单文件备份和恢复的详细说明 +- 🔧 优化:备份文件结构更清晰,支持文件夹、单文件、数据库分类备份 + ### v1.2.0 (2025-12-25) - ✨ 新增:支持 `keep_count` 按数量保留备份策略 diff --git a/bin/backup.sh b/bin/backup.sh index 6230b42..eb46389 100755 --- a/bin/backup.sh +++ b/bin/backup.sh @@ -151,6 +151,9 @@ load_config() { # 读取文件夹备份配置 FOLDERS_ENABLED=$(yq eval '.folders.enabled' "${CONFIG_FILE}") + # 读取单文件备份配置 + FILES_ENABLED=$(yq eval '.files.enabled' "${CONFIG_FILE}") + # 读取 MySQL 配置 MYSQL_ENABLED=$(yq eval '.mysql.enabled' "${CONFIG_FILE}") MYSQL_CONTAINER=$(yq eval '.mysql.container_name' "${CONFIG_FILE}") @@ -256,6 +259,68 @@ backup_folders() { fi } +############################################################################### +# 备份单个文件 +############################################################################### + +backup_files() { + if [[ "${FILES_ENABLED}" != "true" ]]; then + log_info "单文件备份未启用,跳过" + return 0 + fi + + log_info "开始备份单个文件..." + + # 创建临时目录 + local temp_backup_dir="${TEMP_DIR}/files" + mkdir -p "${temp_backup_dir}" + + # 获取要备份的文件数量 + local source_count=$(yq eval '.files.sources | length' "${CONFIG_FILE}") + + if [[ "${source_count}" == "0" ]] || [[ "${source_count}" == "null" ]]; then + log_warn "未配置要备份的文件" + return 0 + fi + + # 收集要备份的文件 + local files_tar="${temp_backup_dir}/files.tar.gz" + local tar_sources="" + local valid_files=0 + + for i in $(seq 0 $((source_count - 1))); do + local source_file=$(yq eval ".files.sources[$i]" "${CONFIG_FILE}") + + if [[ ! -f "${source_file}" ]]; then + log_warn "源文件不存在,跳过: ${source_file}" + continue + fi + + tar_sources+=" ${source_file}" + valid_files=$((valid_files + 1)) + log_info "添加备份文件: ${source_file}" + done + + if [[ ${valid_files} -eq 0 ]]; then + log_warn "没有有效的备份文件" + return 0 + fi + + # 执行打包 + log_info "开始打包文件..." + # 将 tar 的 stdout 和 stderr 都重定向到 stderr,避免干扰函数返回值 + eval "tar -czf '${files_tar}' ${tar_sources}" >&2 2>&1 + + if [[ -f "${files_tar}" ]]; then + local tar_size=$(du -h "${files_tar}" | cut -f1) + log_info "文件备份完成: ${files_tar} (大小: ${tar_size})" + echo "${files_tar}" + else + log_error "文件备份失败" + return 1 + fi +} + ############################################################################### # 备份 MySQL 数据库 ############################################################################### @@ -331,7 +396,8 @@ backup_mysql() { merge_backups() { local folders_tar=$1 - local mysql_dump=$2 + local files_tar=$2 + local mysql_dump=$3 log_info "开始合并备份文件..." @@ -346,6 +412,10 @@ merge_backups() { files_to_pack+=" ${folders_tar}" fi + if [[ -n "${files_tar}" ]] && [[ -f "${files_tar}" ]]; then + files_to_pack+=" ${files_tar}" + fi + if [[ -n "${mysql_dump}" ]] && [[ -f "${mysql_dump}" ]]; then files_to_pack+=" ${mysql_dump}" fi @@ -362,6 +432,10 @@ merge_backups() { files_list+=" ${folders_tar#${TEMP_DIR}/}" log_info "添加到合并: ${folders_tar#${TEMP_DIR}/} ($(du -h "${folders_tar}" | cut -f1))" fi + if [[ -n "${files_tar}" ]] && [[ -f "${files_tar}" ]]; then + files_list+=" ${files_tar#${TEMP_DIR}/}" + log_info "添加到合并: ${files_tar#${TEMP_DIR}/} ($(du -h "${files_tar}" | cut -f1))" + fi if [[ -n "${mysql_dump}" ]] && [[ -f "${mysql_dump}" ]]; then files_list+=" ${mysql_dump#${TEMP_DIR}/}" log_info "添加到合并: ${mysql_dump#${TEMP_DIR}/} ($(du -h "${mysql_dump}" | cut -f1))" @@ -586,11 +660,14 @@ run_backup() { # 备份文件夹 local folders_tar=$(backup_folders) + # 备份单个文件 + local files_tar=$(backup_files) + # 备份 MySQL local mysql_dump=$(backup_mysql) # 合并备份 - local final_backup=$(merge_backups "${folders_tar}" "${mysql_dump}") + local final_backup=$(merge_backups "${folders_tar}" "${files_tar}" "${mysql_dump}") # 清理旧备份 cleanup_old_backups diff --git a/config/backup.yml.example b/config/backup.yml.example index 96d6672..ac1ace8 100644 --- a/config/backup.yml.example +++ b/config/backup.yml.example @@ -36,6 +36,17 @@ folders: - "*/.git" - "*/node_modules" +# 单文件备份配置 +files: + # 是否启用单文件备份 + enabled: true + + # 需要备份的文件列表(支持多个) + sources: + - "/etc/docker-backup/config.yml" + - "/etc/nginx/nginx.conf" + - "/opt/app/.env" + # MySQL 数据库备份配置 mysql: # 是否启用 MySQL 备份