#!/bin/bash # ============================================================================ # Orange Pi 5 Ultra - update.img 完整编译脚本 # 官方orangepi-build系统 - 支持多种源码获取方式 # ============================================================================ set -e LEGACY_WORK_DIR="/data/OrangePi CM5" WORK_DIR="/data/OrangePi_CM5" CACHE_ROOT="/data/OrangePi_cache" BUILD_DATE=$(date +%Y%m%d_%H%M%S) LOG_FILE="${WORK_DIR}/build_${BUILD_DATE}.log" MODE="auto" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # ============================================================================ # 日志记录函数 # ============================================================================ log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" } log_error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" } log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" } # ============================================================================ # 处理工作目录(避免路径中空格导致官方脚本异常) # ============================================================================ prepare_workspace() { mkdir -p "$WORK_DIR" if [ -d "$LEGACY_WORK_DIR" ]; then log "检测到旧目录: $LEGACY_WORK_DIR" # 同步关键目录,尽量复用已有下载/缓存,避免重复拉取。 if [ -d "$LEGACY_WORK_DIR/SDKs" ] && [ ! -e "$WORK_DIR/SDKs" ]; then cp -a "$LEGACY_WORK_DIR/SDKs" "$WORK_DIR/" log "✓ 已迁移 SDKs 到新目录" fi if [ -d "$LEGACY_WORK_DIR/sources" ] && [ ! -e "$WORK_DIR/sources" ]; then cp -a "$LEGACY_WORK_DIR/sources" "$WORK_DIR/" log "✓ 已迁移 sources 到新目录" fi if [ -d "$LEGACY_WORK_DIR/orangepi-build" ] && [ ! -e "$WORK_DIR/orangepi-build" ]; then cp -a "$LEGACY_WORK_DIR/orangepi-build" "$WORK_DIR/" log "✓ 已迁移 orangepi-build 到新目录" fi fi } # ============================================================================ # 检查环境和依赖 # ============================================================================ check_environment() { log "检查编译环境..." # 检查必需工具 local required_tools=("git" "gcc" "make" "device-tree-compiler") for tool in "${required_tools[@]}"; do if ! command -v "$tool" &> /dev/null; then log_warn "$tool 未安装,尝试安装..." apt-get update apt-get install -y "$tool" else log "✓ $tool 已安装" fi done # 检查交叉编译工具链 if ! command -v aarch64-linux-gnu-gcc &> /dev/null; then log_warn "aarch64-linux-gnu-gcc 未安装,尝试安装..." apt-get install -y gcc-aarch64-linux-gnu build-essential else log "✓ aarch64-linux-gnu-gcc 已安装" fi log "环境检查完成" } # ============================================================================ # 配置 git 以应对网络问题 # ============================================================================ configure_git() { log "配置 git 网络参数..." git config --global http.connectTimeout 120 git config --global http.postBuffer 524288000 git config --global core.compression 0 git config --global url."https://".insteadOf git:// log "✓ git 配置完成" } # ============================================================================ # 下载源码 - 方式1: Gitee镜像(国内优先) # ============================================================================ download_from_gitee() { local repo_name=$1 local repo_url=$2 local branch=$3 local target_dir=$4 log "尝试从 Gitee 下载 $repo_name (分支: $branch)..." if [ -d "$target_dir" ]; then log "目录已存在,更新代码..." cd "$target_dir" git pull origin "$branch" 2>&1 && return 0 cd - > /dev/null fi if git clone --depth=1 -b "$branch" "$repo_url" "$target_dir" 2>&1; then log "✓ 从 Gitee 下载成功" return 0 else log_warn "从 Gitee 下载失败" return 1 fi } # ============================================================================ # 下载源码 - 方式2: GitHub (带重试) # ============================================================================ download_from_github() { local repo_name=$1 local repo_url=$2 local branch=$3 local target_dir=$4 local retry_count=3 log "尝试从 GitHub 下载 $repo_name (分支: $branch)..." if [ -d "$target_dir" ]; then log "目录已存在,更新代码..." cd "$target_dir" git pull origin "$branch" 2>&1 && return 0 cd - > /dev/null fi for ((i=1; i<=retry_count; i++)); do log "GitHub 克隆尝试 $i/$retry_count..." if git clone --depth=1 -b "$branch" "$repo_url" "$target_dir" 2>&1; then log "✓ 从 GitHub 下载成功" return 0 fi sleep 5 done log_warn "GitHub 下载最终失败" return 1 } # ============================================================================ # 获取 orangepi-build # ============================================================================ get_orangepi_build() { log "获取 orangepi-build 编译系统..." local BUILD_DIR="${WORK_DIR}/orangepi-build" # 第1优先级: 从本地 SDKs 目录提取 if extract_from_sdks "orangepi-build" "$BUILD_DIR"; then ORANGEPI_BUILD_DIR="$BUILD_DIR" return 0 fi # 第2优先级: Gitee 镜像 if download_from_gitee \ "orangepi-build" \ "https://gitee.com/orangepi-xunlong/orangepi-build.git" \ "next" \ "$BUILD_DIR"; then ORANGEPI_BUILD_DIR="$BUILD_DIR" return 0 fi # 第3优先级: GitHub if download_from_github \ "orangepi-build" \ "https://github.com/orangepi-xunlong/orangepi-build.git" \ "next" \ "$BUILD_DIR"; then ORANGEPI_BUILD_DIR="$BUILD_DIR" return 0 fi log_error "无法获取 orangepi-build" return 1 } # ============================================================================ # 建立持久化缓存(避免每次重复下载工具链) # ============================================================================ setup_build_cache_links() { local build_dir=$1 local shared_dl="${CACHE_ROOT}/dl" local shared_toolchains="${CACHE_ROOT}/toolchains" local shared_ext_cache="${CACHE_ROOT}/external-cache" mkdir -p "$shared_dl" "$shared_toolchains" "$shared_ext_cache" # 如果旧目录里已有下载内容,首次迁移到共享缓存。 if [ -d "$build_dir/dl" ] && [ ! -L "$build_dir/dl" ] && [ -z "$(ls -A "$shared_dl" 2>/dev/null)" ]; then cp -a "$build_dir/dl/." "$shared_dl/" 2>/dev/null || true fi if [ -d "$build_dir/toolchains" ] && [ ! -L "$build_dir/toolchains" ] && [ -z "$(ls -A "$shared_toolchains" 2>/dev/null)" ]; then cp -a "$build_dir/toolchains/." "$shared_toolchains/" 2>/dev/null || true fi if [ -d "$build_dir/external/cache" ] && [ ! -L "$build_dir/external/cache" ] && [ -z "$(ls -A "$shared_ext_cache" 2>/dev/null)" ]; then cp -a "$build_dir/external/cache/." "$shared_ext_cache/" 2>/dev/null || true fi mkdir -p "$build_dir/external" rm -rf "$build_dir/dl" "$build_dir/toolchains" "$build_dir/external/cache" ln -s "$shared_dl" "$build_dir/dl" ln -s "$shared_toolchains" "$build_dir/toolchains" ln -s "$shared_ext_cache" "$build_dir/external/cache" log "✓ 已启用共享缓存" log " dl: $shared_dl" log " toolchains: $shared_toolchains" log " external cache: $shared_ext_cache" } # ============================================================================ # 方式3: 从本地 SDKs 目录提取源码包 # ============================================================================ extract_from_sdks() { local repo_name=$1 local target_dir=$2 log "尝试从 SDKs 目录提取 $repo_name..." # 检查目标目录是否已存在 if [ -d "$target_dir" ]; then log "✓ 目录已存在: $target_dir" return 0 fi # 方式1: 查找 orangepi-build-next.zip if [ "$repo_name" = "orangepi-build" ]; then local archive="${WORK_DIR}/SDKs/orangepi-build-next.zip" if [ -f "$archive" ]; then log "找到源码包: $archive" log "解压中..." mkdir -p "$target_dir" unzip -q "$archive" -d "$target_dir" 2>/dev/null || return 1 # 处理可能的子目录结构 if [ -d "${target_dir}/orangepi-build"* ]; then mv "${target_dir}"/orangepi-build* "${target_dir}_tmp" rm -rf "$target_dir" mv "${target_dir}_tmp" "$target_dir" elif [ -d "${target_dir}/orangepi-build" ]; then local temp_build="${target_dir}/orangepi-build" mv "$temp_build"/* "$target_dir"/ 2>/dev/null || true rm -rf "$temp_build" fi if [ -f "${target_dir}/build.sh" ]; then log "✓ 从 SDKs 解压成功" return 0 fi fi # 方式2: 查找 OrangePi_Build-master.zip archive="${WORK_DIR}/SDKs/OrangePi_Build-master.zip" if [ -f "$archive" ]; then log "找到备选源码包: $archive" log "解压中..." mkdir -p "$target_dir" unzip -q "$archive" -d "$target_dir" 2>/dev/null || return 1 # 处理可能的子目录结构 if [ -d "${target_dir}/OrangePi_Build-master" ]; then mv "${target_dir}/OrangePi_Build-master"/* "$target_dir"/ 2>/dev/null || true rmdir "${target_dir}/OrangePi_Build-master" 2>/dev/null || true fi if [ -f "${target_dir}/build.sh" ]; then log "✓ 从 SDKs 解压成功" return 0 fi fi return 1 fi return 1 } # ============================================================================ # 检查本地预准备的源码包 # ============================================================================ check_local_sources() { log "检查本地是否存在预准备的源码包..." local sources_dir="${WORK_DIR}/sources" if [ ! -d "$sources_dir" ]; then return 1 fi # 检查是否存在 orangepi-build 源码 for archive in "$sources_dir"/{orangepi-build,u-boot,linux,kernel}*.tar* "$sources_dir"/*.zip; do if [ -f "$archive" ]; then log "找到本地源码包: $archive" return 0 fi done return 1 } # ============================================================================ # 编译 update.img (使用 orangepi-build) # ============================================================================ build_update_img() { local BUILD_DIR=$1 log "开始编译 update.img..." cd "$BUILD_DIR" # 显示已有源码信息 log "编译系统目录结构:" ls -la | head -20 >> "$LOG_FILE" # 确保脚本有执行权限 chmod +x build.sh # 使用官方编译命令编译完整镜像 # 参数说明: # BOARD=orangepi5ultra - 开发板型号 # BRANCH=legacy - 使用 linux5.10 (支持 IMX586 相机) # BUILD_OPT=image - 编译完整镜像 # RELEASE=bullseye - Debian Bullseye 发行版 # BUILD_DESKTOP=no - 编译服务器版 (更小体积,可选改为 yes) if [ "$MODE" = "menu" ]; then log "执行官方编译脚本(交互模式)..." log "将进入 ncurses 菜单,请手动选择编译类型" # ncurses 必须直连终端,不能通过 tee 管道输出 sudo ./build.sh local build_rc=$? else log "执行官方编译脚本(自动模式)..." log "编译参数: BOARD=orangepi5ultra BRANCH=legacy BUILD_OPT=image KERNEL_CONFIGURE=no" sudo ./build.sh \ BOARD=orangepi5ultra \ BRANCH=legacy \ BUILD_OPT=image \ RELEASE=bullseye \ BUILD_DESKTOP=no \ KERNEL_CONFIGURE=no 2>&1 | tee -a "$LOG_FILE" local build_rc=${PIPESTATUS[0]} fi if [ ${build_rc:-1} -eq 0 ]; then log "✓ 编译成功" return 0 else # 某些上游脚本会在收尾阶段返回非0,但镜像已成功产出。 local produced_img produced_img=$(find "$BUILD_DIR/output/images" -name "*.img" -type f 2>/dev/null | head -1) if [ -n "$produced_img" ]; then log_warn "build.sh 返回非0,但已检测到镜像产物: $produced_img" log_warn "按成功继续执行验证步骤" return 0 fi log_error "编译失败" return 1 fi } # ============================================================================ # 验证编译结果 # ============================================================================ verify_build() { local BUILD_DIR=$1 log "验证编译结果..." local output_dir="${BUILD_DIR}/output/images" if [ ! -d "$output_dir" ]; then log_error "输出目录不存在: $output_dir" return 1 fi # 优先查找 update.img,找不到则接受任意 .img 作为总包产物。 local update_img update_img=$(find "$output_dir" -name "*update*.img" -type f 2>/dev/null | head -1) if [ -z "$update_img" ]; then update_img=$(find "$output_dir" -name "*.img" -type f 2>/dev/null | head -1) fi if [ -z "$update_img" ]; then log_error "未找到镜像文件 (*.img)" return 1 fi log "✓ 找到编译结果: $update_img" local img_size=$(du -sh "$update_img" | awk '{print $1}') log "镜像大小: $img_size" # 生成校验和 log "生成 SHA256 校验和..." sha256sum "$update_img" > "${update_img}.sha256" log "编译结果信息:" cat >> "$LOG_FILE" << EOF === 编译完成 === 时间: $(date) 镜像文件: $update_img 大小: $img_size 校验和: $(cat "${update_img}.sha256") EOF return 0 } # ============================================================================ # 主流程 # ============================================================================ main() { case "${1:-}" in menu|interactive) MODE="menu" ;; auto|"") MODE="auto" ;; *) echo "用法: $0 [auto|menu]" echo " auto: 默认自动编译(无 ncurses 菜单)" echo " menu: 进入官方 ncurses 菜单手动选择" exit 1 ;; esac log "==========================================" log "Orange Pi 5 Ultra - update.img 编译" log "==========================================" log "工作目录: $WORK_DIR" log "旧目录(兼容): $LEGACY_WORK_DIR" log "日志文件: $LOG_FILE" log "运行模式: $MODE" prepare_workspace cd "$WORK_DIR" || { log_error "无法进入工作目录"; exit 1; } # 第1步: 检查环境 check_environment # 第2步: 配置git configure_git # 第3步: 获取 orangepi-build log "" log "========== 步骤1: 获取源码 ==========" if ! get_orangepi_build; then log_error "获取 orangepi-build 失败" log "" log "解决方案:" log "1. ✓ 自动优先使用 \${WORK_DIR}/SDKs/ 目录下的源码包:" log " - orangepi-build-next.zip" log " - OrangePi_Build-master.zip" log "" log "2. 如果上述 SDKs 目录缺少文件,请:" log " a) 检查 ${WORK_DIR}/SDKs/ 目录是否存在" log " b) 确保源码包完整且未损坏" log " c) 或将官方源码包放入该目录并重新运行" log "" log "3. 作为备选,可将源码放在 ${WORK_DIR}/sources/ 目录下" log "" log "4. 若选择网络下载:" log " - 确保有稳定的网络连接" log " - 如需认证,请配置 git 凭证: git config --global credential.helper store" exit 1 fi BUILD_DIR="$ORANGEPI_BUILD_DIR" # 第3.5步: 配置持久缓存 setup_build_cache_links "$BUILD_DIR" # 第4步: 编译 update.img log "" log "========== 步骤2: 编译镜像 ==========" if ! build_update_img "$BUILD_DIR"; then log_error "编译失败,详情查看日志: $LOG_FILE" exit 1 fi # 第5步: 验证结果 log "" log "========== 步骤3: 验证结果 ==========" if ! verify_build "$BUILD_DIR"; then log_error "验证失败" exit 1 fi log "" log "==========================================" log "✓ 编译完成!" log "==========================================" log "后续步骤:" log "1. 下载镜像: scp root@www.yuquanjun.cn:/data/OrangePi_CM5/orangepi-build/output/images/*/update.img ./" log "2. 使用 RKDevTool 或 balenaEtcher 烧录镜像" log "==========================================" } # 运行主程序 main "$@"