diff --git a/build_openwrt.sh b/build_openwrt.sh new file mode 100644 index 00000000000..233fe3b6741 --- /dev/null +++ b/build_openwrt.sh @@ -0,0 +1,587 @@ +#!/usr/bin/env bash + +#==================================================== +# OpenWrt 构建管理脚本 +# 功能:提供交互式菜单,可选择执行特定操作 +# 更新:2024-02-26 +#==================================================== + +#---------------基础配置---------------# +# 日志目录设置 +LOGS_ROOT="openwrt_logs" +SESSION_DIR="${LOGS_ROOT}/$(date +%Y%m%d_%H%M%S)" +LOG_FILE="${SESSION_DIR}/build.log" + +#---------------清理配置---------------# +# 需要移除的目录列表 +declare -a REMOVE_DIRS=( +) + +# 需要移除的包列表 +declare -a REMOVE_PACKAGES=( + "feeds/luci/applications/luci-app-mosdns" + "feeds/packages/net/alist" + "feeds/packages/net/adguardhome" + "feeds/packages/net/smartdns" +) + +#---------------自定义包配置---------------# +# 定义自定义包配置,格式:[包名]=[目标目录]=[仓库URL]=[分支]=[是否使用--depth 1] +declare -A CUSTOM_PACKAGES=( + ["partexp"]="package/luci-app-partexp=https://github.com/sirpdboy/luci-app-partexp=main=false" + ["openclash"]="package/openclash=https://github.com/vernesong/OpenClash=dev=true" + ["zerotier"]="package/luci-app-zerotier=https://nanako.site/gitea/Nanako/luci-app-zerotier.git=main=false" + ["5gsupport"]="package/openwrt-app-actions=https://github.com/Siriling/openwrt-app-actions.git=main=true" + ["design"]="package/luci-theme-design=https://github.com/SAENE/luci-theme-design.git=js=false" + ["mt76"]="package/firmware/mt76=https://github.com/openwrt/mt76.git=master=false" +) + +#---------------日志系统---------------# +# 保存原始的标准输出和错误输出 +exec {STDOUT_ORIG}>&1 +exec {STDERR_ORIG}>&2 + +# 初始化日志系统 +init_logger() { + # 创建日志目录结构 + mkdir -p "$SESSION_DIR" || { + echo "无法创建日志目录: $SESSION_DIR" + exit 1 + } + + # 设置全局变量标记是否记录日志 + LOGGING_ENABLED=true + + echo "日志保存在: $LOG_FILE" + + if [ "$LOGGING_ENABLED" = true ]; then + # 标准输出重定向 + exec > >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE" + done) + + # 标准错误重定向 + exec 2> >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE" + done >&2) + fi +} + +# 创建新的编译日志文件 +create_build_log() { + local build_type=$1 + local timestamp=$(date +%Y%m%d_%H%M%S) + BUILD_LOG_FILE="${SESSION_DIR}/${build_type}_${timestamp}.log" + echo "创建新的编译日志: $BUILD_LOG_FILE" + return 0 +} + +# 重定向输出到指定的编译日志文件 +redirect_to_build_log() { + local log_file=$1 + + # 保存当前的输出重定向状态 + LOGGING_TO_BUILD_LOG=true + + # 重定向输出到编译日志文件 + exec > >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" >> "$log_file" + done) + + # 重定向错误输出到编译日志文件 + exec 2> >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" >> "$log_file" + done >&2) +} + +# 恢复输出到主日志 +restore_main_logging() { + LOGGING_TO_BUILD_LOG=false + + # 恢复到主日志 + if [ "$LOGGING_ENABLED" = true ]; then + # 标准输出重定向 + exec > >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE" + done) + + # 标准错误重定向 + exec 2> >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE" + done >&2) + fi +} + +# 禁用日志输出 +disable_logging() { + LOGGING_ENABLED=false + exec >&$STDOUT_ORIG + exec 2>&$STDERR_ORIG +} + +# 启用日志输出 +enable_logging() { + LOGGING_ENABLED=true + # 标准输出重定向 + exec > >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE" + done) + + # 标准错误重定向 + exec 2> >(while read -r line; do + printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$line" | tee -a "$LOG_FILE" + done >&2) +} + +# 设置终端颜色 +setup_colors() { + if [[ -t 2 ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + NC='\033[0m' + else + RED='' GREEN='' YELLOW='' BLUE='' NC='' + fi +} + +# 记录日志函数 - 使用此函数来明确日志级别 +log() { + local level=$1 + local message=$2 + local timestamp=$(date "+%Y-%m-%d %H:%M:%S") + local color="" level_str="" + + case $level in + "INFO") color="$GREEN"; level_str="INFO" ;; + "WARN") color="$YELLOW"; level_str="WARN" ;; + "ERROR") color="$RED"; level_str="ERROR" ;; + "DEBUG") color="$BLUE"; level_str="DEBUG" ;; + esac + + printf "[%s] ${color}[%s]${NC}: %s\n" "$timestamp" "$level_str" "$message" +} + +# 同时记录到主日志和编译日志的函数 +log_to_both() { + local level=$1 + local message=$2 + local build_log=$3 + + # 记录到主日志 + log "$level" "$message" + + # 记录到编译日志 + local timestamp=$(date "+%Y-%m-%d %H:%M:%S") + echo "[$timestamp] [$level]: $message" >> "$build_log" +} + +#---------------自定义包管理函数---------------# +# 更新单个自定义包 +update_single_package() { + local pkg_name=$1 + local config=$2 + + # 解析配置 + local target_dir=$(echo "$config" | cut -d= -f1) + local repo_url=$(echo "$config" | cut -d= -f2) + local branch=$(echo "$config" | cut -d= -f3) + local use_depth=$(echo "$config" | cut -d= -f4) + + log "INFO" "更新 $pkg_name..." + + # 检查目标目录 + if [ -d "$target_dir" ]; then + log "INFO" "移除现有 $target_dir 目录" + rm -rf "$target_dir" || { + log "ERROR" "无法删除 $target_dir 目录" + return 1 + } + fi + + # 准备克隆命令 + local clone_cmd="git clone --progress" + + # 添加分支参数 (如果指定) + if [ -n "$branch" ] && [ "$branch" != "master" ]; then + clone_cmd="$clone_cmd -b $branch" + fi + + # 添加 depth 参数 (如果需要) + if [ "$use_depth" = "true" ]; then + clone_cmd="$clone_cmd --depth 1" + fi + + # 完成克隆命令 + clone_cmd="$clone_cmd $repo_url $target_dir" + + # 执行克隆 + log "INFO" "执行: $clone_cmd" + eval "$clone_cmd" || { + log "ERROR" "克隆 $pkg_name 失败" + return 1 + } + + log "INFO" "$pkg_name 更新完成" + return 0 +} + +# 更新自定义包的主函数 +update_custom_package() { + local failed_packages=() + local success_count=0 + local total_packages=${#CUSTOM_PACKAGES[@]} + + log "INFO" "开始更新 $total_packages 个自定义包..." + + for pkg_name in "${!CUSTOM_PACKAGES[@]}"; do + update_single_package "$pkg_name" "${CUSTOM_PACKAGES[$pkg_name]}" || { + failed_packages+=("$pkg_name") + continue + } + ((success_count++)) + done + + # 显示更新结果 + if [ ${#failed_packages[@]} -eq 0 ]; then + log "INFO" "所有自定义包更新成功 ($success_count/$total_packages)" + return 0 + else + log "WARN" "部分包更新失败 (成功: $success_count/$total_packages)" + log "ERROR" "更新失败的包: ${failed_packages[*]}" + return 1 + fi +} + +# 更新特定自定义包 +update_specific_package() { + local pkg_name=$1 + + if [ -z "$pkg_name" ]; then + log "ERROR" "未指定包名" + return 1 + fi + + if [ -z "${CUSTOM_PACKAGES[$pkg_name]}" ]; then + log "ERROR" "未找到包配置:$pkg_name" + log "INFO" "可用的包: ${!CUSTOM_PACKAGES[*]}" + return 1 + fi + + update_single_package "$pkg_name" "${CUSTOM_PACKAGES[$pkg_name]}" +} + +#---------------基础功能函数---------------# +# 清理工作区 +clean_workspace() { + log "INFO" "开始清理工作区..." + + log "INFO" "执行./scripts/feeds clean..." + ./scripts/feeds clean || log "WARN" "feeds clean 失败" + + log "INFO" "执行rm -rf ./tmp ./staging_dir ./build_dir ./bin ./dl..." + rm -rf ./tmp ./staging_dir ./build_dir ./bin ./dl || log "WARN" "删除临时目录失败" + + log "INFO" "工作区清理完成" +} + +# 清理编译临时文件 +clean_build_temp() { + log "INFO" "开始清理编译临时文件..." + + log "INFO" "删除临时编译目录和缓存..." + rm -rf ./tmp ./staging_dir ./build_dir || log "WARN" "删除临时编译目录失败" + + log "INFO" "编译临时文件清理完成" +} + +# 更新源码 +update_source() { + log "INFO" "开始更新源码..." + git pull || { log "ERROR" "源码更新失败"; return 1; } + log "INFO" "源码更新完成" +} + +# 更新feeds +update_feeds() { + log "INFO" "开始更新feeds..." + ./scripts/feeds update -a && ./scripts/feeds install -a || { + log "ERROR" "Feeds更新失败" + return 1 + } + log "INFO" "Feeds更新完成" + return 0 +} + +# 执行编译命令并仅记录到指定日志文件 +run_build_command() { + local cmd=$1 + local log_file=$2 + local msg=$3 + + log_to_both "INFO" "$msg" "$log_file" + + # 重定向输出到编译日志 + redirect_to_build_log "$log_file" + + # 执行编译命令 + local result=0 + eval "$cmd" || result=1 + + # 恢复输出到主日志 + restore_main_logging + + return $result +} + +# 编译固件 +build_firmware() { + # 创建新的编译日志 + create_build_log "firmware" + local current_log="$BUILD_LOG_FILE" + + log "INFO" "开始编译固件..." + log "INFO" "编译详细日志位置: $current_log" + + # 下载依赖 + log "INFO" "下载依赖包..." + run_build_command "make download -j$(nproc) V=s" "$current_log" "正在下载依赖包..." || { + log "ERROR" "依赖下载失败,详见日志: $current_log" + return 1 + } + + # 使用多线程编译 + local cpu_cores=$(nproc) + log "INFO" "使用 $cpu_cores 线程开始编译..." + + if run_build_command "make -j$cpu_cores V=s" "$current_log" "多线程编译进行中..."; then + log "INFO" "多线程编译成功完成" + return 0 + else + log "WARN" "多线程编译失败,清理临时文件后切换为单线程编译..." + + # 清理编译临时文件 + clean_build_temp + + # 创建一个新日志用于单线程编译 + create_build_log "firmware_singlethread" + current_log="$BUILD_LOG_FILE" + log "INFO" "单线程编译详细日志位置: $current_log" + + # 重新下载依赖 + log "INFO" "重新下载依赖包..." + run_build_command "make download -j1 V=s" "$current_log" "单线程模式重新下载依赖..." || { + log "ERROR" "单线程依赖下载也失败,详见日志: $current_log" + return 1 + } + + if run_build_command "make -j1 V=s" "$current_log" "单线程编译进行中..."; then + log "INFO" "单线程编译成功完成" + return 0 + else + log "ERROR" "编译失败,即使在单线程模式下。详见日志: $current_log" + return 1 + fi + fi +} + +# 清理旧包 +clean_packages() { + log "INFO" "开始清理旧包..." + local removed_count=0 + local failed_count=0 + + # 清理目录 + for dir in "${REMOVE_DIRS[@]}"; do + if [ -d "$dir" ]; then + rm -rf "$dir" && { + log "INFO" "已删除: $dir" + ((removed_count++)) + } || { + log "ERROR" "删除失败: $dir" + ((failed_count++)) + } + fi + done + + # 清理包 + for pkg in "${REMOVE_PACKAGES[@]}"; do + if [ -d "$pkg" ]; then + rm -rf "$pkg" && { + log "INFO" "已移除: $pkg" + ((removed_count++)) + } || { + log "ERROR" "删除失败: $pkg" + ((failed_count++)) + } + fi + done + + if [ $failed_count -eq 0 ]; then + log "INFO" "包清理完成,共移除 $removed_count 个包/目录" + return 0 + else + log "WARN" "包清理部分完成,成功: $removed_count, 失败: $failed_count" + return 1 + fi +} + +# 更新所有组件 +update_all_components() { + log "INFO" "开始更新所有组件..." + local failed=false + + # 更新源码 + log "INFO" "执行源码更新..." + update_source || failed=true + + # 清理旧包 + log "INFO" "执行清理旧包..." + clean_packages || log "WARN" "清理旧包部分失败,继续执行..." + + # 更新自定义包 + log "INFO" "执行自定义包更新..." + update_custom_package || failed=true + + # 更新feeds + log "INFO" "执行feeds更新..." + update_feeds || failed=true + + if [ "$failed" = true ]; then + log "WARN" "部分组件更新失败,请查看日志了解详情" + return 1 + else + log "INFO" "所有组件更新完成" + return 0 + fi +} + +# 菜单配置 +menuconfig() { + log "INFO" "开始菜单配置..." + disable_logging + make menuconfig + enable_logging + log "INFO" "菜单配置完成" +} + +#---------------组合功能函数---------------# +# 完整构建流程 +full_build() { + log "INFO" "启动完整构建流程..." + + clean_workspace || log "WARN" "工作区清理部分失败,继续执行..." + update_source || { log "ERROR" "源码更新失败,终止构建"; return 1; } + clean_packages || log "WARN" "清理旧包部分失败,继续执行..." + update_custom_package || { log "ERROR" "自定义包更新失败,终止构建"; return 1; } + update_feeds || { log "ERROR" "feeds更新失败,终止构建"; return 1; } + + log "INFO" "进入菜单配置..." + menuconfig + + log "INFO" "开始编译固件..." + build_firmware || { + log "ERROR" "固件编译失败" + return 1 + } + + log "INFO" "完整构建流程完成" + return 0 +} + +#---------------菜单函数---------------# +print_menu() { + echo -e "\n${BLUE}OpenWrt 构建管理菜单${NC}" + echo -e "${YELLOW}================================${NC}" + echo -e "${GREEN}1.${NC} 完整构建流程" + echo -e "${GREEN}2.${NC} 清理工作区" + echo -e "${GREEN}3.${NC} 更新所有组件" + echo -e "${GREEN}4.${NC} 清理旧包" + echo -e "${GREEN}5.${NC} 编译固件" + echo -e "${GREEN}6.${NC} 调整配置(menuconfig)" + echo -e "${GREEN}7.${NC} 更新自定义包" + echo -e "${GREEN}8.${NC} 更新特定自定义包" + echo -e "${GREEN}9.${NC} 更新源码" + echo -e "${GREEN}10.${NC} 更新feeds" + echo -e "${GREEN}0.${NC} 退出" + echo -e "${YELLOW}================================${NC}" + echo -ne "请选择操作 [0-10]: " +} + +# 显示可用的自定义包 +print_package_menu() { + echo -e "\n${BLUE}可用的自定义包${NC}" + echo -e "${YELLOW}================================${NC}" + + local i=1 + for pkg_name in "${!CUSTOM_PACKAGES[@]}"; do + echo -e "${GREEN}$i.${NC} $pkg_name" + ((i++)) + done + + echo -e "${YELLOW}================================${NC}" + echo -ne "请选择要更新的包 [1-$((i-1))]: " +} + +# 选择特定自定义包更新 +select_specific_package() { + local pkg_names=("${!CUSTOM_PACKAGES[@]}") + print_package_menu + + read -r choice + + # 验证选择 + if [[ ! "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt ${#pkg_names[@]} ]; then + log "ERROR" "无效选择: $choice" + return 1 + fi + + local selected_pkg=${pkg_names[$((choice-1))]} + log "INFO" "选择更新包: $selected_pkg" + + update_specific_package "$selected_pkg" +} + +#---------------主函数---------------# +main() { + # 初始化日志目录和日志系统 + init_logger + setup_colors + + log "INFO" "OpenWrt 构建管理脚本启动 - 会话日志目录: $SESSION_DIR" + + while true; do + print_menu + read -r choice + + case $choice in + 1) full_build ;; + 2) clean_workspace ;; + 3) update_all_components ;; + 4) clean_packages ;; + 5) build_firmware ;; + 6) menuconfig ;; + 7) update_custom_package ;; + 8) select_specific_package ;; + 9) update_source ;; + 10) update_feeds ;; + 0) + log "INFO" "退出脚本" + exit 0 + ;; + *) + log "WARN" "无效选择: $choice,请重试" + ;; + esac + + # 操作完成后暂停 + if [[ $choice != 0 ]]; then + echo -e "\n${YELLOW}操作完成,按回车键继续...${NC}" + read -r + fi + done +} + +# 执行主函数 +main "$@" \ No newline at end of file