name: Build WRT on: workflow_dispatch: inputs: project: description: "选择 WRT 项目" required: true default: "immortalwrt" type: choice options: - openwrt - immortalwrt - immortalwrt-mt798x - immortalwrt-mt798x-6.6 tag: description: "版本号" required: false default: "v24.10.3" type: string version_type: description: "版本类型" required: true default: "stable" type: choice options: - "snapshot" - "stable" config_path: description: "配置文件路径(留空则跳过)" required: false default: "config/x86_64_config" type: string proxy_passwall2: description: "启用Passwall2" required: false default: false type: boolean proxy_luci_app_zzz: description: "启用luci-app-zzz" required: false default: false type: boolean proxy_luci_app_momo: description: "启用luci-app-momo" required: false default: false type: boolean threads: description: "编译线程数(填写 0 则使用 nproc)" required: false default: "0" type: string cache_enabled: description: "启用缓存加速编译" required: false default: true type: boolean enable_bbr: description: "启用 BBR 拥塞控制" required: false default: true type: boolean custom_shell: description: "默认 shell" required: false default: "bash" type: choice options: - ash - bash luci_theme: description: "LuCI 主题" required: false default: "argon" type: choice options: - argon - bootstrap - material custom_hostname: description: "自定义 hostname(留空则跳过)" required: false default: "Dwrt" type: string custom_ip: description: "自定义 IP 地址(留空则跳过)" required: false default: "192.168.1.1" type: string root_password: description: "自定义 root 密码(留空则跳过)" required: false default: "" type: string custom_banner: description: "自定义 SSH 横幅(留空则默认)" required: false default: "" type: string env: CCACHE_DIR: ${{ github.workspace }}/wrt/.ccache DL_DIR: ${{ github.workspace }}/wrt/dl jobs: validate: name: 验证输入参数 runs-on: ubuntu-latest outputs: is_valid: ${{ steps.validate.outputs.is_valid }} config_exists: ${{ steps.validate.outputs.config_exists }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Validate inputs id: validate run: | echo "🔍 验证输入参数..." # 验证 threads 参数 if [ -n "${{ inputs.threads }}" ]; then if ! [[ "${{ inputs.threads }}" =~ ^[0-9]+$ ]]; then echo "❌ threads 参数必须是数字" echo "is_valid=false" >> "$GITHUB_OUTPUT" exit 1 fi fi # 验证配置文件是否存在(如果提供了路径) if [ -n "${{ inputs.config_path }}" ]; then if [ -f "${{ inputs.config_path }}" ]; then echo "config_exists=true" >> "$GITHUB_OUTPUT" echo "✅ 配置文件存在:${{ inputs.config_path }}" else echo "config_exists=false" >> "$GITHUB_OUTPUT" echo "⚠️ 配置文件不存在:${{ inputs.config_path }},将跳过配置文件" fi else echo "config_exists=false" >> "$GITHUB_OUTPUT" echo "ℹ️ 未提供配置文件路径,将使用默认配置" fi echo "is_valid=true" >> "$GITHUB_OUTPUT" echo "✅ 所有输入参数验证通过" build: name: 编译固件 runs-on: ubuntu-latest needs: validate timeout-minutes: 360 steps: - name: Checkout current repository uses: actions/checkout@v4 - name: Install dependencies run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/{ghc,az,microsoft} sudo apt-get update && sudo apt-get install -y \ build-essential clang flex bison g++ gawk gettext git \ libncurses-dev libssl-dev python3{,-dev,-setuptools} \ rsync unzip zlib1g-dev file wget curl \ {gzip,tar,zip,xz-utils,bzip2,zstd} \ make cmake {autoconf,automake,libtool,patch,diffutils} \ findutils grep sed help2man texinfo \ libelf-dev libfuse-dev liblzma-dev libxml2-dev libyaml-dev \ uuid-dev device-tree-compiler antlr3 gperf \ time bc jq xxd swig upx-ucl ccache ecj fastjar imagemagick \ llvm linux-tools-common libbpf-dev linux-tools-$(uname -r) \ u-boot-tools device-tree-compiler - name: Determine Git URL id: projectinfo run: | case "${{ inputs.project }}" in immortalwrt) echo "url=https://github.com/immortalwrt/immortalwrt.git" >> $GITHUB_OUTPUT ;; immortalwrt-mt798x) echo "url=https://github.com/hanwckf/immortalwrt-mt798x.git" >> $GITHUB_OUTPUT ;; immortalwrt-mt798x-6.6) echo "url=https://github.com/padavanonly/immortalwrt-mt798x-6.6.git" >> $GITHUB_OUTPUT ;; openwrt) echo "url=https://git.openwrt.org/openwrt/openwrt.git" >> $GITHUB_OUTPUT ;; *) echo "❌ 未知项目" >&2; exit 1 ;; esac - name: Clone Wrt source run: | echo "📥 克隆 ${{ inputs.project }} (${{ inputs.version_type }})" [ "${{ inputs.version_type }}" = "snapshot" ] && BRANCH_MODE=1 || BRANCH_MODE=0 if [ $BRANCH_MODE -eq 1 ]; then git clone --depth 1 "${{ steps.projectinfo.outputs.url }}" wrt cd wrt DEFAULT_BRANCH=$(git remote show origin | grep 'HEAD branch' | awk '{print $NF}') [ -z "$DEFAULT_BRANCH" ] && for b in main master openwrt-24.10; do git ls-remote --heads origin "$b" | grep -q "$b" && DEFAULT_BRANCH=$b && break done [ -z "$DEFAULT_BRANCH" ] && { echo "❌ 无法确定默认分支"; exit 1; } git fetch origin "${DEFAULT_BRANCH}" 2>/dev/null git switch "${DEFAULT_BRANCH}" 2>/dev/null || git checkout "${DEFAULT_BRANCH}" else git clone "${{ steps.projectinfo.outputs.url }}" wrt cd wrt git checkout "${{ inputs.tag }}" 2>/dev/null || \ (git fetch --tags && git checkout "${{ inputs.tag }}") || \ { echo "❌ 标签不存在"; exit 1; } fi echo "🔖 $(git describe --tags --always 2>/dev/null || git rev-parse --short HEAD)" echo "🌿 $(git branch --show-current 2>/dev/null || echo 'detached')" - name: Setup ccache uses: hendrikmuhs/ccache-action@v1.2 if: inputs.cache_enabled == true with: key: ${{ runner.os }}-wrt-ccache-${{ inputs.project }}-${{ inputs.version_type }} max-size: 2G update-package-index: true - name: Cache downloads uses: actions/cache@v4 if: inputs.cache_enabled == true with: path: wrt/dl key: ${{ runner.os }}-wrt-dl-${{ inputs.project }}-${{ inputs.version_type }} - name: Cache build directory uses: actions/cache@v4 if: inputs.cache_enabled == true with: path: wrt/build_dir key: ${{ runner.os }}-wrt-build-${{ inputs.project }}-${{ inputs.version_type }} - name: Apply custom configurations working-directory: wrt run: | TARGET_TYPE=$(grep -qi immortalwrt package/base-files/files/etc/openwrt_release 2>/dev/null && echo ImmortalWrt || \ [ -d feeds/packages ] && echo ImmortalWrt || echo OpenWrt) CONFIG_GEN=package/base-files/files/bin/config_generate [ -n "${{ inputs.custom_hostname }}" ] && [ -f "$CONFIG_GEN" ] && \ sed -i "s/${TARGET_TYPE}/${{ inputs.custom_hostname }}/g" "$CONFIG_GEN" && echo "✅ Hostname: ${{ inputs.custom_hostname }}" [ -n "${{ inputs.custom_ip }}" ] && [ -f "$CONFIG_GEN" ] && \ sed -i 's/192\.168\.[0-9]*\.[0-9]*/${{ inputs.custom_ip }}/g' "$CONFIG_GEN" && echo "✅ IP: ${{ inputs.custom_ip }}" [ -n "${{ inputs.root_password }}" ] && [ -f package/base-files/files/etc/shadow ] && { HASH=$(openssl passwd -1 '${{ inputs.root_password }}') sed -i "s|^root:[^:]*:|root:${HASH}:|" package/base-files/files/etc/shadow echo "✅ Root 密码已设置" } mkdir -p package/base-files/files/etc/uci-defaults cat >package/base-files/files/etc/uci-defaults/99_set_theme <<'EOF' uci set luci.main.mediaurlbase=/luci-static/${{ inputs.luci_theme }}; uci commit luci EOF chmod +x package/base-files/files/etc/uci-defaults/99_set_theme if [ "${{ inputs.enable_bbr }}" = "true" ]; then mkdir -p package/base-files/files/etc/sysctl.d echo -e 'net.core.default_qdisc=fq_codel\nnet.ipv4.tcp_congestion_control=bbr' \ > package/base-files/files/etc/sysctl.d/99-bbr.conf && echo "✅ BBR 已启用" else rm -f package/base-files/files/etc/sysctl.d/99-bbr.conf && echo "ℹ️ BBR 已禁用" fi PASSWD=package/base-files/files/etc/passwd if [ -f "$PASSWD" ]; then [ "${{ inputs.custom_shell }}" = "bash" ] && sed -i 's|/bin/ash|/bin/bash|g' "$PASSWD" || \ sed -i 's|/bin/bash|/bin/ash|g' "$PASSWD" echo "✅ Shell: ${{ inputs.custom_shell }}" fi mkdir -p package/base-files/files/etc if [ -n "${{ inputs.custom_banner }}" ]; then echo "${{ inputs.custom_banner }}" > package/base-files/files/etc/banner else cat >package/base-files/files/etc/banner <<'EOF' | | _____ _____ ____________/ |______ | | | |/ \ / \ / _ \_ __ \ __\__ \ | | | | Y Y \ Y Y ( <_> ) | \/| | / __ \| |__ |___|__|_| /__|_| /\____/|__| |__| (____ /____/ \/ \/ By Dich \/ ----------------------------------------------------- EOF fi && echo "✅ Banner 已设置" - name: Update and install feeds working-directory: wrt run: | if [ "${{ inputs.proxy_luci_app_zzz }}" = "true" ]; then echo "src-git luci-app-zzz https://github.com/Dichgrem/luci-app-zzz.git" >> feeds.conf.default fi if [ "${{ inputs.proxy_passwall2 }}" = "true" ]; then echo "src-git passwall https://github.com/xiaorouji/openwrt-passwall2.git" >> feeds.conf.default fi if [ "${{ inputs.proxy_luci_app_momo }}" = "true" ]; then echo "src-git momo https://github.com/nikkinikki-org/OpenWrt-momo.git" >> feeds.conf.default fi ./scripts/feeds update -a && ./scripts/feeds install -a - name: Setup configuration working-directory: wrt run: | # 复制配置文件(如果提供) if [ -n "${{ inputs.config_path }}" ] && [ -f "${{ inputs.config_path }}" ]; then echo "📋 复制配置:${{ inputs.config_path }} → .config" cp "${{ inputs.config_path }}" .config else echo "ℹ️ 未提供配置文件,使用默认配置" # 生成默认配置 make defconfig || true fi # 备份配置文件以供对比 cp .config .config.before_oldconfig echo "🔄 运行 defconfig" make defconfig echo "🔄 运行 oldconfig" make oldconfig echo "🔍 对比 make oldconfig 前后的 .config 差异" if cmp -s .config.before_oldconfig .config; then echo "✅ .config 在 make oldconfig 后未发生变化" else echo "⚠️ .config 在 make oldconfig 后发生变化,差异如下:" diff -u .config.before_oldconfig .config || true fi - name: Download packages working-directory: wrt run: | echo "⬇️ 下载所有源码包(使用单线程 -j1 避免卡住)" set -o pipefail START=$(date +%s) time make download -j1 END=$(date +%s) DURATION=$((END - START)) echo "✅ 下载完成,耗时:${DURATION}秒" - name: Build Firmware id: build working-directory: wrt run: | if [ "${{ inputs.threads }}" = "0" ] || [ -z "${{ inputs.threads }}" ]; then JOBS=$(nproc) else JOBS=${{ inputs.threads }} fi echo "🚀 全量编译(并行 ${JOBS})开始" START=$(date "+%Y-%m-%d %H:%M:%S") START_TS=$(date +%s) echo "⏱️ 编译开始:${START}" echo "jobs_count=${JOBS}" >> "$GITHUB_OUTPUT" set -o pipefail # 第一次尝试多线程编译,并保存日志 if ! (time make world -j${JOBS} 2>&1 | tee world_debug.log); then echo "⚠️ 多线程编译失败,尝试使用单线程并输出详细信息..." # 单线程模式输出详细信息,并追加到同一日志 time make world -j1 V=s 2>&1 | tee -a world_debug.log echo "❌ 单线程编译后依然失败,以下是匹配关键字的错误行:" grep -E -i "(error:|failed|fatal|cannot install package)" -n world_debug.log || true exit 1 fi END=$(date "+%Y-%m-%d %H:%M:%S") END_TS=$(date +%s) DURATION=$((END_TS - START_TS)) DURATION_H=$((DURATION / 3600)) DURATION_M=$(((DURATION % 3600) / 60)) DURATION_S=$((DURATION % 60)) echo "✅ 编译成功" echo "⏱️ 编译结束:${END}" echo "⏱️ 总耗时:${DURATION_H}小时 ${DURATION_M}分钟 ${DURATION_S}秒" echo "build_success=true" >> "$GITHUB_OUTPUT" - name: Build statistics if: steps.build.outputs.build_success == 'true' working-directory: wrt run: | echo "📊 编译统计信息:" echo "项目:${{ inputs.project }}" echo "版本类型:${{ inputs.version_type }}" if [ "${{ inputs.version_type }}" = "snapshot" ]; then VERSION_INFO=$(git describe --tags --always 2>/dev/null || echo "latest") BRANCH_INFO=$(git branch --show-current 2>/dev/null || echo "unknown") echo "版本:${VERSION_INFO} (${BRANCH_INFO}分支)" else echo "版本:${{ inputs.tag }} (稳定版)" fi echo "编译线程:${{ steps.build.outputs.jobs_count }}" echo "缓存状态:${{ inputs.cache_enabled }}" echo "" echo "固件文件列表:" if [ -d bin/targets ]; then find bin/targets -type f -exec ls -lh {} \; | awk '{print $9, $5}' echo "" echo "总固件大小:" du -sh bin/targets fi - name: Upload build log on failure uses: actions/upload-artifact@v4 if: failure() with: name: ${{ inputs.project }}-build-log-error path: wrt/world_debug.log retention-days: 7 if-no-files-found: ignore - name: Upload build artifacts uses: actions/upload-artifact@v4 if: steps.build.outputs.build_success == 'true' with: name: ${{ inputs.project }}-output-${{ inputs.version_type }}-${{ github.run_number }} path: | wrt/bin/targets/** !wrt/bin/targets/x86/64/packages/** !wrt/bin/targets/**/packages/** if-no-files-found: warn compression-level: 6 overwrite: false retention-days: 30 - name: Upload config diff uses: actions/upload-artifact@v4 if: steps.build.outputs.build_success == 'true' with: name: ${{ inputs.project }}-config-diff path: | wrt/.config wrt/.config.before_oldconfig if-no-files-found: ignore retention-days: 7 - name: Cleanup if: always() run: | echo "🧹 清理工作空间..." if [ -d "wrt" ]; then cd wrt make clean || true echo "✅ 清理完成" else echo "ℹ️ wrt 目录不存在,跳过清理" fi