Files
KnowledgeBase/docker/import-and-verify.sh
lizhuoran 3c206e9e06 feat: 新增 Docker 部署支持、Swoole/Octane 集成及相关优化
- 添加 Dockerfile 与多套 docker-compose 配置(开发/生产环境)
- 集成 Laravel Octane (Swoole) 提升性能
- 新增健康检查、监控脚本及部署文档
- 新增 Docker 镜像离线导入包(MySQL/Redis/Meilisearch)
- 优化文档转换、预览服务及队列任务
- 添加 CreateAdminUser 命令与路由健康检查接口
- 新增 Swoole 队列兼容性测试套件

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:51:19 +08:00

496 lines
13 KiB
Bash
Executable File
Raw Permalink 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
# Docker镜像导入和验证脚本
# 用于导入Docker镜像并验证其完整性和兼容性
set -e
# 脚本配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_IMAGES_DIR="$(dirname "$SCRIPT_DIR")/docker-images"
LOG_FILE="${DEFAULT_IMAGES_DIR}/import-verify.log"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
}
log_success() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE"
}
log_warning() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE"
}
# 显示帮助信息
show_help() {
cat << EOF
Docker镜像导入和验证脚本
用法: $0 [选项] [镜像目录]
选项:
-h, --help 显示此帮助信息
-v, --verify-only 仅验证,不导入
-f, --force 强制导入,覆盖现有镜像
-c, --check-manifest 检查清单文件
--skip-compatibility 跳过兼容性检查
--parallel N 并行导入数量 (默认: 2)
--test-run 导入后运行测试容器
参数:
镜像目录 包含Docker镜像文件的目录 (默认: ./docker-images)
示例:
$0 # 导入默认目录中的所有镜像
$0 -v /path/to/images # 仅验证镜像文件
$0 -f --test-run # 强制导入并测试
$0 --check-manifest # 检查清单文件完整性
EOF
}
# 默认配置
VERIFY_ONLY=false
FORCE_IMPORT=false
CHECK_MANIFEST=false
SKIP_COMPATIBILITY=false
PARALLEL_JOBS=2
TEST_RUN=false
IMAGES_DIR="$DEFAULT_IMAGES_DIR"
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-v|--verify-only)
VERIFY_ONLY=true
shift
;;
-f|--force)
FORCE_IMPORT=true
shift
;;
-c|--check-manifest)
CHECK_MANIFEST=true
shift
;;
--skip-compatibility)
SKIP_COMPATIBILITY=true
shift
;;
--parallel)
PARALLEL_JOBS="$2"
if [[ ! "$PARALLEL_JOBS" =~ ^[1-9][0-9]*$ ]]; then
log_error "并行任务数必须是正整数"
exit 1
fi
shift 2
;;
--test-run)
TEST_RUN=true
shift
;;
-*)
log_error "未知参数: $1"
show_help
exit 1
;;
*)
IMAGES_DIR="$1"
shift
;;
esac
done
# 检查输入目录
if [[ ! -d "$IMAGES_DIR" ]]; then
log_error "镜像目录不存在: $IMAGES_DIR"
exit 1
fi
# 创建日志目录
mkdir -p "$(dirname "$LOG_FILE")"
log "开始Docker镜像导入和验证..."
log "镜像目录: $IMAGES_DIR"
log "仅验证: $VERIFY_ONLY"
log "强制导入: $FORCE_IMPORT"
log "并行任务: $PARALLEL_JOBS"
# 检查Docker是否运行
check_docker() {
log "检查Docker环境..."
if ! command -v docker >/dev/null 2>&1; then
log_error "Docker未安装"
exit 1
fi
if ! docker info >/dev/null 2>&1; then
log_error "Docker未运行或无法访问"
exit 1
fi
local docker_version=$(docker --version)
log_success "Docker环境正常: $docker_version"
# 检查系统架构
local system_arch=$(uname -m)
log "系统架构: $system_arch"
if [[ "$system_arch" != "x86_64" ]] && [[ "$SKIP_COMPATIBILITY" == false ]]; then
log_warning "系统架构不是x86_64可能存在兼容性问题"
fi
}
# 检查清单文件
check_manifest_file() {
if [[ "$CHECK_MANIFEST" == false ]]; then
return 0
fi
local manifest_file="${IMAGES_DIR}/images-manifest.txt"
log "检查清单文件..."
if [[ ! -f "$manifest_file" ]]; then
log_warning "清单文件不存在: $manifest_file"
return 1
fi
log "验证清单文件中的镜像..."
# 解析清单文件
local current_file=""
local expected_checksum=""
local verification_failed=0
while IFS= read -r line; do
if [[ "$line" =~ ^文件:\ (.+)$ ]]; then
current_file="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^SHA256:\ (.+)$ ]]; then
expected_checksum="${BASH_REMATCH[1]}"
if [[ -n "$current_file" ]] && [[ -n "$expected_checksum" ]]; then
local file_path="${IMAGES_DIR}/${current_file}"
if [[ -f "$file_path" ]]; then
log "验证文件: $current_file"
local actual_checksum=$(sha256sum "$file_path" | cut -d' ' -f1)
if [[ "$actual_checksum" == "$expected_checksum" ]]; then
log_success "校验和匹配: $current_file"
else
log_error "校验和不匹配: $current_file"
log_error "期望: $expected_checksum"
log_error "实际: $actual_checksum"
((verification_failed++))
fi
else
log_error "文件不存在: $current_file"
((verification_failed++))
fi
current_file=""
expected_checksum=""
fi
fi
done < "$manifest_file"
if [[ $verification_failed -eq 0 ]]; then
log_success "清单文件验证通过"
return 0
else
log_error "清单文件验证失败,$verification_failed 个文件有问题"
return 1
fi
}
# 验证镜像文件
verify_image_file() {
local file="$1"
local filename=$(basename "$file")
log "验证镜像文件: $filename"
# 检查文件是否存在
if [[ ! -f "$file" ]]; then
log_error "文件不存在: $filename"
return 1
fi
# 检查文件大小
local file_size=$(du -h "$file" | cut -f1)
log "文件大小: $file_size"
# 验证文件完整性
if [[ "$file" == *.tar.gz ]]; then
# 验证gzip文件
if gzip -t "$file" 2>/dev/null; then
log_success "压缩文件完整性验证通过: $filename"
else
log_error "压缩文件完整性验证失败: $filename"
return 1
fi
elif [[ "$file" == *.tar ]]; then
# 验证tar文件
if tar -tf "$file" >/dev/null 2>&1; then
log_success "tar文件完整性验证通过: $filename"
else
log_error "tar文件完整性验证失败: $filename"
return 1
fi
else
log_warning "未知文件类型,跳过验证: $filename"
return 1
fi
return 0
}
# 导入镜像文件
import_image_file() {
local file="$1"
local filename=$(basename "$file")
log "导入镜像文件: $filename"
# 首先验证文件
if ! verify_image_file "$file"; then
log_error "文件验证失败,跳过导入: $filename"
return 1
fi
# 检查是否需要强制导入
local import_args=""
if [[ "$FORCE_IMPORT" == true ]]; then
import_args="--quiet"
fi
# 导入镜像
local import_output
if [[ "$file" == *.tar.gz ]]; then
# 解压并导入
import_output=$(gunzip -c "$file" | docker load 2>&1)
else
# 直接导入
import_output=$(docker load -i "$file" 2>&1)
fi
if [[ $? -eq 0 ]]; then
log_success "镜像导入成功: $filename"
# 提取导入的镜像名称
local imported_image=$(echo "$import_output" | grep "Loaded image" | sed 's/Loaded image: //')
if [[ -n "$imported_image" ]]; then
log "导入的镜像: $imported_image"
# 验证镜像架构
if [[ "$SKIP_COMPATIBILITY" == false ]]; then
verify_image_architecture "$imported_image"
fi
fi
return 0
else
log_error "镜像导入失败: $filename"
log_error "错误信息: $import_output"
return 1
fi
}
# 验证镜像架构
verify_image_architecture() {
local image="$1"
log "验证镜像架构: $image"
# 获取镜像信息
local image_info=$(docker image inspect "$image" 2>/dev/null)
if [[ $? -ne 0 ]]; then
log_error "无法获取镜像信息: $image"
return 1
fi
# 提取架构信息
local architecture=$(echo "$image_info" | grep -o '"Architecture":"[^"]*"' | cut -d'"' -f4)
local os=$(echo "$image_info" | grep -o '"Os":"[^"]*"' | cut -d'"' -f4)
log "镜像架构: $os/$architecture"
# 检查架构兼容性
local system_arch=$(uname -m)
local expected_arch="amd64"
if [[ "$system_arch" == "x86_64" ]]; then
expected_arch="amd64"
elif [[ "$system_arch" == "aarch64" ]]; then
expected_arch="arm64"
fi
if [[ "$architecture" == "$expected_arch" ]]; then
log_success "镜像架构兼容: $architecture"
return 0
else
log_warning "镜像架构可能不兼容: $architecture (系统: $system_arch)"
return 1
fi
}
# 测试镜像运行
test_image_run() {
local image="$1"
log "测试镜像运行: $image"
# 根据镜像类型选择测试命令
local test_command=""
local container_name="test-$(echo "$image" | sed 's/[\/:]/_/g')-$$"
case "$image" in
*mysql*)
test_command="docker run --rm --name $container_name -e MYSQL_ROOT_PASSWORD=test -d $image"
;;
*redis*)
test_command="docker run --rm --name $container_name -d $image"
;;
*meilisearch*)
test_command="docker run --rm --name $container_name -e MEILI_MASTER_KEY=test -d $image"
;;
*knowledge-base-app*)
# 应用镜像需要更复杂的测试
log_warning "应用镜像测试需要完整环境,跳过单独测试"
return 0
;;
*)
log_warning "未知镜像类型,跳过运行测试: $image"
return 0
;;
esac
# 运行测试容器
if eval "$test_command"; then
log "测试容器启动成功: $container_name"
# 等待容器启动
sleep 5
# 检查容器状态
if docker ps | grep -q "$container_name"; then
log_success "镜像运行测试通过: $image"
# 停止测试容器
docker stop "$container_name" >/dev/null 2>&1 || true
return 0
else
log_error "测试容器启动失败: $image"
# 显示容器日志
docker logs "$container_name" 2>/dev/null || true
docker rm "$container_name" >/dev/null 2>&1 || true
return 1
fi
else
log_error "无法启动测试容器: $image"
return 1
fi
}
# 处理单个镜像文件
process_image_file() {
local file="$1"
local filename=$(basename "$file")
log "处理镜像文件: $filename"
if [[ "$VERIFY_ONLY" == true ]]; then
verify_image_file "$file"
return $?
else
if import_image_file "$file"; then
# 如果需要测试运行
if [[ "$TEST_RUN" == true ]]; then
# 提取镜像名称进行测试
local image_name=$(echo "$filename" | sed 's/\.tar.*$//' | sed 's/_/:/g')
test_image_run "$image_name" || true
fi
return 0
else
return 1
fi
fi
}
# 主处理流程
main() {
check_docker
check_manifest_file
# 查找镜像文件
local image_files=($(find "$IMAGES_DIR" -name "*.tar*" -type f))
if [[ ${#image_files[@]} -eq 0 ]]; then
log_error "$IMAGES_DIR 中没有找到镜像文件"
exit 1
fi
log "找到 ${#image_files[@]} 个镜像文件"
# 处理镜像文件
local processed=0
local failed=0
local total=${#image_files[@]}
# 使用并行处理
export -f process_image_file verify_image_file import_image_file verify_image_architecture test_image_run
export -f log log_success log_warning log_error
export VERIFY_ONLY FORCE_IMPORT SKIP_COMPATIBILITY TEST_RUN LOG_FILE
export RED GREEN YELLOW BLUE NC
for file in "${image_files[@]}"; do
if process_image_file "$file"; then
((processed++))
else
((failed++))
fi
done
# 显示导入的镜像
if [[ "$VERIFY_ONLY" == false ]]; then
log "当前Docker镜像列表:"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
fi
# 显示总结
log_success "处理完成!"
log "总文件数: $total"
log "成功处理: $processed"
log "失败数量: $failed"
if [[ $failed -gt 0 ]]; then
log_warning "$failed 个文件处理失败,请检查日志"
exit 1
else
log_success "所有文件处理成功"
fi
}
# 执行主流程
main