@@ -318,86 +318,250 @@ create_restore_script() {
318318
319319 log_info " 创建恢复脚本..."
320320
321- cat > " ${backup_dir} /restore.sh" << ' EOF '
321+ cat > " ${backup_dir} /restore.sh" << EOF
322322#!/bin/bash
323323
324324# Docker容器恢复脚本
325325# 此脚本由docker-backup.sh自动生成
326326
327327set -euo pipefail
328328
329- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
330- CONTAINER_NAME=$(basename "$(dirname "${SCRIPT_DIR}")" | sed 's/_[0-9]*$//')
329+ # 颜色定义
330+ RED='\033[0;31m'
331+ GREEN='\033[0;32m'
332+ YELLOW='\033[1;33m'
333+ BLUE='\033[0;34m'
334+ NC='\033[0m'
335+
336+ log_info() { echo -e "\$ {BLUE}[INFO]\$ {NC} \$ 1"; }
337+ log_success() { echo -e "\$ {GREEN}[SUCCESS]\$ {NC} \$ 1"; }
338+ log_warning() { echo -e "\$ {YELLOW}[WARNING]\$ {NC} \$ 1"; }
339+ log_error() { echo -e "\$ {RED}[ERROR]\$ {NC} \$ 1" >&2; }
340+
341+ # 脚本目录和容器名称
342+ SCRIPT_DIR="\$ (cd "\$ (dirname "\$ {BASH_SOURCE[0]}")" && pwd)"
343+ CONTAINER_NAME="${container_name} "
344+ CONFIG_FILE="\$ {SCRIPT_DIR}/config/container_inspect.json"
331345
332- echo "开始恢复容器: ${CONTAINER_NAME}"
346+ log_info "开始恢复容器: \$ {CONTAINER_NAME}"
347+
348+ # 检查必需文件
349+ if [[ ! -f "\$ {CONFIG_FILE}" ]]; then
350+ log_error "配置文件不存在: \$ {CONFIG_FILE}"
351+ exit 1
352+ fi
353+
354+ # 检查jq工具
355+ if ! command -v jq >/dev/null 2>&1; then
356+ log_error "需要安装jq工具来解析配置文件"
357+ exit 1
358+ fi
333359
334360# 检查Docker是否运行
335361if ! docker info >/dev/null 2>&1; then
336- echo "错误: Docker未运行或无法访问"
362+ log_error " Docker未运行或无法访问"
337363 exit 1
338364fi
339365
340366# 停止并删除现有容器(如果存在)
341- if docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
342- echo "停止并删除现有容器..."
343- docker stop "${CONTAINER_NAME}" 2>/dev/null || true
344- docker rm "${CONTAINER_NAME}" 2>/dev/null || true
367+ if docker ps -a --format "{{.Names}}" | grep -q "^\$ {CONTAINER_NAME}\$ "; then
368+ log_warning "发现现有容器,正在停止并删除..."
369+ docker stop "\$ {CONTAINER_NAME}" 2>/dev/null || true
370+ docker rm "\$ {CONTAINER_NAME}" 2>/dev/null || true
371+ log_info "现有容器已删除"
345372fi
346373
347374# 恢复镜像(如果存在)
348- if [[ -f "${SCRIPT_DIR}/${CONTAINER_NAME}_image.tar.gz" ]]; then
349- echo "恢复Docker镜像..."
350- gunzip -c "${SCRIPT_DIR}/${CONTAINER_NAME}_image.tar.gz" | docker load
375+ if [[ -f "\$ {SCRIPT_DIR}/\$ {CONTAINER_NAME}_image.tar.gz" ]]; then
376+ log_info "恢复Docker镜像..."
377+ if gunzip -c "\$ {SCRIPT_DIR}/\$ {CONTAINER_NAME}_image.tar.gz" | docker load; then
378+ log_success "镜像恢复成功"
379+ else
380+ log_warning "镜像恢复失败,将尝试从远程拉取"
381+ fi
351382fi
352383
353384# 恢复数据卷
354- if [[ -d "${SCRIPT_DIR}/volumes" ]]; then
355- echo "恢复数据卷..."
356- for volume_file in "${SCRIPT_DIR}/volumes"/*.tar.gz; do
357- if [[ -f "${volume_file}" ]]; then
358- volume_name=$(basename "${volume_file}" .tar.gz)
359- echo " 恢复数据卷: ${volume_name}"
385+ if [[ -d "\ $ {SCRIPT_DIR}/volumes" ]]; then
386+ log_info "恢复数据卷..."
387+ for volume_file in "\ $ {SCRIPT_DIR}/volumes"/*.tar.gz; do
388+ if [[ -f "\ $ {volume_file}" ]]; then
389+ volume_name=\ $ (basename "\ $ {volume_file}" .tar.gz)
390+ log_info " 恢复数据卷: \ $ {volume_name}"
360391
361392 # 创建数据卷
362- docker volume create "${volume_name}" >/dev/null 2>&1 || true
393+ docker volume create "\ $ {volume_name}" >/dev/null 2>&1 || true
363394
364395 # 恢复数据
365- docker run --rm -v "${volume_name}:/data" -v "${SCRIPT_DIR}/volumes:/backup" \
366- alpine:latest tar -xzf "/backup/${volume_name}.tar.gz" -C /data
396+ if docker run --rm -v "\$ {volume_name}:/data" -v "\$ {SCRIPT_DIR}/volumes:/backup" \
397+ alpine:latest tar -xzf "/backup/\$ {volume_name}.tar.gz" -C /data 2>/dev/null; then
398+ log_success " 数据卷 '\$ {volume_name}' 恢复成功"
399+ else
400+ log_warning " 数据卷 '\$ {volume_name}' 恢复失败"
401+ fi
367402 fi
368403 done
369404fi
370405
371406# 恢复挂载点
372- if [[ -d "${SCRIPT_DIR}/mounts" ]]; then
373- echo "恢复挂载点..."
374- for mount_dir in "${SCRIPT_DIR}/mounts"/mount_*; do
375- if [[ -d "${mount_dir}" ]]; then
376- mount_info="${mount_dir}/mount_info.json"
377- if [[ -f "${mount_info}" ]]; then
378- source_path=$(jq -r '.Source' "${mount_info}")
379-
380- echo " 恢复挂载点: ${source_path}"
407+ if [[ -d "\$ {SCRIPT_DIR}/mounts" ]]; then
408+ log_info "恢复挂载点..."
409+ for mount_dir in "\$ {SCRIPT_DIR}/mounts"/mount_*; do
410+ if [[ -d "\$ {mount_dir}" ]]; then
411+ mount_info="\$ {mount_dir}/mount_info.json"
412+ if [[ -f "\$ {mount_info}" ]]; then
413+ source_path=\$ (jq -r '.Source' "\$ {mount_info}" 2>/dev/null || echo "")
414+ destination=\$ (jq -r '.Destination' "\$ {mount_info}" 2>/dev/null || echo "")
381415
382- # 创建目录结构
383- mkdir -p "$(dirname "${source_path}")"
384-
385- # 恢复数据
386- if [[ -f "${mount_dir}/data.tar.gz" ]]; then
387- tar -xzf "${mount_dir}/data.tar.gz" -C "$(dirname "${source_path}")"
388- elif [[ -f "${mount_dir}/data.file" ]]; then
389- cp "${mount_dir}/data.file" "${source_path}"
416+ if [[ -n "\$ {source_path}" ]]; then
417+ log_info " 恢复挂载点: \$ {source_path} -> \$ {destination}"
418+
419+ # 创建目录结构
420+ mkdir -p "\$ (dirname "\$ {source_path}")"
421+
422+ # 恢复数据
423+ if [[ -f "\$ {mount_dir}/data.tar.gz" ]]; then
424+ if tar -xzf "\$ {mount_dir}/data.tar.gz" -C "\$ (dirname "\$ {source_path}")" 2>/dev/null; then
425+ log_success " 挂载点数据恢复成功: \$ {source_path}"
426+ else
427+ log_warning " 挂载点数据恢复失败: \$ {source_path}"
428+ fi
429+ elif [[ -f "\$ {mount_dir}/data.file" ]]; then
430+ if cp "\$ {mount_dir}/data.file" "\$ {source_path}" 2>/dev/null; then
431+ log_success " 挂载点文件恢复成功: \$ {source_path}"
432+ else
433+ log_warning " 挂载点文件恢复失败: \$ {source_path}"
434+ fi
435+ fi
390436 fi
391437 fi
392438 fi
393439 done
394440fi
395441
396- echo "数据恢复完成"
397- echo "请使用以下命令检查配置并手动启动容器:"
398- echo " docker inspect \$(cat ${SCRIPT_DIR}/config/container_inspect.json | jq -r '.[0].Config')"
399- echo "或参考 ${SCRIPT_DIR}/config/ 目录中的配置文件"
442+ # 解析容器配置并重建容器
443+ log_info "解析容器配置并重建容器..."
444+
445+ # 获取镜像名称
446+ IMAGE=\$ (jq -r '.[0].Config.Image' "\$ {CONFIG_FILE}")
447+ if [[ "\$ {IMAGE}" == "null" || -z "\$ {IMAGE}" ]]; then
448+ log_error "无法获取镜像名称"
449+ exit 1
450+ fi
451+
452+ log_info "容器镜像: \$ {IMAGE}"
453+
454+ # 尝试拉取镜像(如果本地没有)
455+ if ! docker image inspect "\$ {IMAGE}" >/dev/null 2>&1; then
456+ log_info "本地镜像不存在,尝试拉取: \$ {IMAGE}"
457+ if docker pull "\$ {IMAGE}"; then
458+ log_success "镜像拉取成功"
459+ else
460+ log_error "无法拉取镜像: \$ {IMAGE}"
461+ exit 1
462+ fi
463+ fi
464+
465+ # 构建docker run命令
466+ log_info "构建容器运行命令..."
467+ DOCKER_CMD="docker run -d --name \$ {CONTAINER_NAME}"
468+
469+ # 添加端口映射
470+ PORTS=\$ (jq -r '.[0].NetworkSettings.Ports // {} | to_entries[] | select(.value != null) | "\(.key):\(.value[0].HostPort // "")"' "\$ {CONFIG_FILE}" 2>/dev/null || true)
471+ if [[ -n "\$ {PORTS}" ]]; then
472+ while IFS= read -r port_mapping; do
473+ if [[ -n "\$ {port_mapping}" ]]; then
474+ container_port=\$ (echo "\$ {port_mapping}" | cut -d: -f1)
475+ host_port=\$ (echo "\$ {port_mapping}" | cut -d: -f2)
476+ if [[ -n "\$ {host_port}" && "\$ {host_port}" != "null" ]]; then
477+ DOCKER_CMD="\$ {DOCKER_CMD} -p \$ {host_port}:\$ {container_port}"
478+ log_info " 添加端口映射: \$ {host_port}:\$ {container_port}"
479+ fi
480+ fi
481+ done <<< "\$ {PORTS}"
482+ fi
483+
484+ # 添加环境变量
485+ ENV_VARS=\$ (jq -r '.[0].Config.Env[]?' "\$ {CONFIG_FILE}" 2>/dev/null || true)
486+ if [[ -n "\$ {ENV_VARS}" ]]; then
487+ while IFS= read -r env_var; do
488+ if [[ -n "\$ {env_var}" ]]; then
489+ DOCKER_CMD="\$ {DOCKER_CMD} -e '\$ {env_var}'"
490+ log_info " 添加环境变量: \$ {env_var}"
491+ fi
492+ done <<< "\$ {ENV_VARS}"
493+ fi
494+
495+ # 添加挂载点
496+ MOUNTS=\$ (jq -c '.[0].Mounts[]?' "\$ {CONFIG_FILE}" 2>/dev/null || true)
497+ if [[ -n "\$ {MOUNTS}" ]]; then
498+ while IFS= read -r mount_info; do
499+ if [[ -n "\$ {mount_info}" ]]; then
500+ mount_type=\$ (echo "\$ {mount_info}" | jq -r '.Type' 2>/dev/null || echo "")
501+ source=\$ (echo "\$ {mount_info}" | jq -r '.Source' 2>/dev/null || echo "")
502+ destination=\$ (echo "\$ {mount_info}" | jq -r '.Destination' 2>/dev/null || echo "")
503+
504+ if [[ "\$ {mount_type}" == "bind" && -n "\$ {source}" && -n "\$ {destination}" ]]; then
505+ DOCKER_CMD="\$ {DOCKER_CMD} -v \$ {source}:\$ {destination}"
506+ log_info " 添加绑定挂载: \$ {source}:\$ {destination}"
507+ elif [[ "\$ {mount_type}" == "volume" ]]; then
508+ volume_name=\$ (echo "\$ {mount_info}" | jq -r '.Name' 2>/dev/null || echo "")
509+ if [[ -n "\$ {volume_name}" && -n "\$ {destination}" ]]; then
510+ DOCKER_CMD="\$ {DOCKER_CMD} -v \$ {volume_name}:\$ {destination}"
511+ log_info " 添加数据卷: \$ {volume_name}:\$ {destination}"
512+ fi
513+ fi
514+ fi
515+ done <<< "\$ {MOUNTS}"
516+ fi
517+
518+ # 添加工作目录
519+ WORKDIR=\$ (jq -r '.[0].Config.WorkingDir' "\$ {CONFIG_FILE}" 2>/dev/null || echo "")
520+ if [[ -n "\$ {WORKDIR}" && "\$ {WORKDIR}" != "null" ]]; then
521+ DOCKER_CMD="\$ {DOCKER_CMD} -w \$ {WORKDIR}"
522+ log_info " 设置工作目录: \$ {WORKDIR}"
523+ fi
524+
525+ # 添加镜像
526+ DOCKER_CMD="\$ {DOCKER_CMD} \$ {IMAGE}"
527+
528+ # 添加启动命令
529+ CMD=\$ (jq -r '.[0].Config.Cmd[]?' "\$ {CONFIG_FILE}" 2>/dev/null | tr '\n' ' ' || echo "")
530+ if [[ -n "\$ {CMD}" ]]; then
531+ DOCKER_CMD="\$ {DOCKER_CMD} \$ {CMD}"
532+ log_info " 添加启动命令: \$ {CMD}"
533+ fi
534+
535+ # 保存命令到文件
536+ echo "\$ {DOCKER_CMD}" > "\$ {SCRIPT_DIR}/docker_run_command.sh"
537+ chmod +x "\$ {SCRIPT_DIR}/docker_run_command.sh"
538+
539+ log_info "Docker运行命令已保存到: \$ {SCRIPT_DIR}/docker_run_command.sh"
540+ log_info "执行命令: \$ {DOCKER_CMD}"
541+
542+ # 执行容器创建
543+ log_info "正在启动容器..."
544+ if eval "\$ {DOCKER_CMD}"; then
545+ log_success "容器启动成功: \$ {CONTAINER_NAME}"
546+
547+ # 等待容器启动
548+ sleep 3
549+
550+ # 检查容器状态
551+ if docker ps --format "{{.Names}}" | grep -q "^\$ {CONTAINER_NAME}\$ "; then
552+ log_success "容器正在运行"
553+ docker ps --filter "name=\$ {CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
554+ else
555+ log_warning "容器可能未正常启动,请检查日志"
556+ log_info "查看日志: docker logs \$ {CONTAINER_NAME}"
557+ fi
558+ else
559+ log_error "容器启动失败"
560+ log_info "请检查生成的命令: \$ {SCRIPT_DIR}/docker_run_command.sh"
561+ exit 1
562+ fi
400563
564+ log_success "容器恢复完成!"
401565EOF
402566
403567 chmod +x " ${backup_dir} /restore.sh"
0 commit comments