From 7df21b29282f14d686ae81afcb27fa276ec1de74 Mon Sep 17 00:00:00 2001 From: 0xAAcodeislaw <0xAAcodeislaw@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:45:15 +0800 Subject: [PATCH 1/3] fix workflows and sync upstream firmware versions --- .github/workflows/build-custom.yml | 16 +- .github/workflows/build-esiropenwrt.yml | 11 +- .github/workflows/build-ezopwrt.yml | 11 +- .github/workflows/build-haos.yml | 18 ++- .github/workflows/build-imm.yml | 20 ++- .github/workflows/build-istoreos-24.10.1.yml | 20 ++- .github/workflows/build.yml | 26 ++- autobuild/autobuild.sh | 7 +- build.sh | 158 ++++++++++++++----- custom.sh | 41 ++--- esir.sh | 46 +++--- ezopwrt.sh | 44 +++--- haos.sh | 41 ++--- imm.sh | 100 ++++++++---- istoreos.sh | 107 ++++++++++--- supportFiles/build.sh | 2 +- supportFiles/custom/build.sh | 2 +- supportFiles/esirplayground/build.sh | 2 +- supportFiles/ezopwrt/build.sh | 2 +- supportFiles/haos/build.sh | 2 +- supportFiles/immortalwrt/build.sh | 2 +- supportFiles/istoreos/build.sh | 2 +- 22 files changed, 456 insertions(+), 224 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 7cbfee87..1e63e50f 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -8,7 +8,7 @@ on: required: true default: 'https://default.example.com/file.img.gz' -# 显式添加权限,确保 Release 发布正常 +# Release 上传需要写权限;仓库若采用只读默认权限,显式声明可避免 403。 permissions: contents: write @@ -17,10 +17,12 @@ jobs: build-release: name: "Build and Release" runs-on: "ubuntu-22.04" + env: + DOWNLOAD_URL: ${{ inputs.download_url }} steps: - name: "Checking out git repository" - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set executable permissions run: | @@ -30,23 +32,21 @@ jobs: - name: Validate Download URL run: | DEFAULT_URL="https://default.example.com/file.img.gz" - USER_INPUT_URL="${{ github.event.inputs.download_url }}" - if [[ "$USER_INPUT_URL" == "$DEFAULT_URL" ]]; then + if [[ "$DOWNLOAD_URL" == "$DEFAULT_URL" ]]; then echo "❌ 错误:请修改默认下载地址!当前地址为无效占位符。" exit 1 fi - if [[ ! "$USER_INPUT_URL" =~ ^https?://.+\.[gG][zZ]$|^https?://.+\.[xX][zZ]$|^https?://.+\.[zZ][iI][pP]$ ]]; then + if [[ ! "$DOWNLOAD_URL" =~ ^https?://[^[:space:]]+\.(gz|xz|zip)(\?[^[:space:]]*)?$ ]]; then echo "❌ 错误:地址需以 http(s) 开头且扩展名为 .gz/.xz/.zip" - echo "当前输入:$USER_INPUT_URL" + echo "当前输入:$DOWNLOAD_URL" exit 1 fi - name: "Build Image" run: | - download_url="${{ github.event.inputs.download_url }}" - ./custom.sh "$download_url" + bash ./custom.sh "$DOWNLOAD_URL" - name: "Publish" uses: softprops/action-gh-release@v2.2.1 diff --git a/.github/workflows/build-esiropenwrt.yml b/.github/workflows/build-esiropenwrt.yml index 058e46fa..862af872 100644 --- a/.github/workflows/build-esiropenwrt.yml +++ b/.github/workflows/build-esiropenwrt.yml @@ -3,6 +3,9 @@ name: "Build eSirOpenWrt Installer ISO" on: workflow_dispatch: +permissions: + contents: write + jobs: build-release: name: "Build and Release" @@ -11,15 +14,15 @@ jobs: steps: - name: "Get Date" run: | - echo "DATESTAMP=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + echo "DATESTAMP=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" - name: "Checking out git repository" - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set executable permissions run: | - chmod +x ${{ github.workspace }}/esir.sh - chmod +x ${{ github.workspace }}/supportFiles/esirplayground/build.sh + chmod +x "${{ github.workspace }}/esir.sh" + chmod +x "${{ github.workspace }}/supportFiles/esirplayground/build.sh" - name: "Build Image" run: | diff --git a/.github/workflows/build-ezopwrt.yml b/.github/workflows/build-ezopwrt.yml index cf1cb743..93053f07 100644 --- a/.github/workflows/build-ezopwrt.yml +++ b/.github/workflows/build-ezopwrt.yml @@ -3,6 +3,9 @@ name: "Build EzOpWrt Installer ISO" on: workflow_dispatch: +permissions: + contents: write + jobs: build-release: name: "Build and Release" @@ -11,15 +14,15 @@ jobs: steps: - name: "Get Date" run: | - echo "DATESTAMP=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + echo "DATESTAMP=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" - name: "Checking out git repository" - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set executable permissions run: | - chmod +x ${{ github.workspace }}/ezopwrt.sh - chmod +x ${{ github.workspace }}/supportFiles/ezopwrt/build.sh + chmod +x "${{ github.workspace }}/ezopwrt.sh" + chmod +x "${{ github.workspace }}/supportFiles/ezopwrt/build.sh" - name: "Build Image" run: | diff --git a/.github/workflows/build-haos.yml b/.github/workflows/build-haos.yml index 586f207e..c41d2fcc 100644 --- a/.github/workflows/build-haos.yml +++ b/.github/workflows/build-haos.yml @@ -8,14 +8,19 @@ on: required: true default: 'https://github.com/home-assistant/operating-system/releases/download/15.0/haos_generic-x86-64-15.0.img.xz' +permissions: + contents: write + jobs: build-release: name: "Build and Release" runs-on: "ubuntu-22.04" + env: + DOWNLOAD_URL: ${{ inputs.download_url }} steps: - name: "Checking out git repository" - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set executable permissions run: | @@ -24,18 +29,15 @@ jobs: - name: Validate Download URL run: | - USER_INPUT_URL="${{ github.event.inputs.download_url }}" - - if [[ ! "$USER_INPUT_URL" =~ ^https?://.+\.[gG][zZ]$|^https?://.+\.[xX][zZ]$|^https?://.+\.[zZ][iI][pP]$ ]]; then + if [[ ! "$DOWNLOAD_URL" =~ ^https?://[^[:space:]]+\.(gz|xz|zip)(\?[^[:space:]]*)?$ ]]; then echo "❌ 错误:地址需以 http(s) 开头且扩展名为 .gz/.xz/.zip" - echo "当前输入:$USER_INPUT_URL" + echo "当前输入:$DOWNLOAD_URL" exit 1 fi - name: "Build Image" run: | - download_url="${{ github.event.inputs.download_url }}" - ./haos.sh "$download_url" + bash ./haos.sh "$DOWNLOAD_URL" - name: "Publish" uses: softprops/action-gh-release@v2.2.1 @@ -44,4 +46,4 @@ jobs: body_path: "${{ github.workspace }}/supportFiles/haos/info.md" files: | output/haos-installer-x86_64.iso - token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file + token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/build-imm.yml b/.github/workflows/build-imm.yml index 1941aa86..69e57f4b 100644 --- a/.github/workflows/build-imm.yml +++ b/.github/workflows/build-imm.yml @@ -2,28 +2,38 @@ name: "Build ImmortalWrt Installer ISO" on: workflow_dispatch: + inputs: + upstream_version: + description: "ImmortalWrt 版本;latest 自动选择上游 x86-64 EFI 固件" + required: true + default: "latest" + +permissions: + contents: write jobs: build-release: name: "Build and Release" runs-on: "ubuntu-22.04" + env: + IMMORTALWRT_VERSION: ${{ inputs.upstream_version }} steps: - name: "Get Date" run: | - echo "DATESTAMP=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + echo "DATESTAMP=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" - name: "Checking out git repository" - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set executable permissions run: | - chmod +x ${{ github.workspace }}/imm.sh - chmod +x ${{ github.workspace }}/supportFiles/immortalwrt/build.sh + chmod +x "${{ github.workspace }}/imm.sh" + chmod +x "${{ github.workspace }}/supportFiles/immortalwrt/build.sh" - name: "Build Image" run: | - ./imm.sh + bash ./imm.sh - name: "Publish" uses: softprops/action-gh-release@v2.2.1 diff --git a/.github/workflows/build-istoreos-24.10.1.yml b/.github/workflows/build-istoreos-24.10.1.yml index 165a353c..93fc1c55 100644 --- a/.github/workflows/build-istoreos-24.10.1.yml +++ b/.github/workflows/build-istoreos-24.10.1.yml @@ -1,26 +1,36 @@ -name: "Build iStoreOS 24.10.1 Installer ISO" +name: "Build iStoreOS Installer ISO" on: workflow_dispatch: + inputs: + upstream_release: + description: "上游 Release 标签;latest 自动选择最新 iStoreOS x86-64 固件" + required: true + default: "latest" + +permissions: + contents: write jobs: build-release: name: "Build and Release" runs-on: "ubuntu-22.04" + env: + ISTOREOS_RELEASE: ${{ inputs.upstream_release }} steps: - name: "Checking out git repository" - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set executable permissions run: | - chmod +x ${{ github.workspace }}/istoreos.sh - chmod +x ${{ github.workspace }}/supportFiles/istoreos/build.sh + chmod +x "${{ github.workspace }}/istoreos.sh" + chmod +x "${{ github.workspace }}/supportFiles/istoreos/build.sh" - name: "Build iStoreOS Installer ISO" run: | - ./istoreos.sh + bash ./istoreos.sh - name: "Publish" uses: softprops/action-gh-release@v2.2.1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb98c335..3331e972 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,18 +13,34 @@ on: - "debian12_minimal" - "ubuntu24_minimal" - "homeassistant_debian12_minimal" + upstream_release: + description: "上游固件 Release 标签;latest 会自动选择匹配类型的最新版本" + required: true + default: "latest" + +permissions: + contents: write jobs: build-release: runs-on: ubuntu-22.04 env: - VERSION_TYPE: ${{ github.event.inputs.version_type }} + VERSION_TYPE: ${{ inputs.version_type }} + ARMBIAN_RELEASE: ${{ inputs.upstream_release }} steps: - uses: actions/checkout@v4 - name: Build Image - run: ./build.sh + run: bash ./build.sh + + - name: Report selected upstream firmware + run: | + { + echo "### 上游固件" + echo "- Release: \`${ARMBIAN_SELECTED_RELEASE}\`" + echo "- Asset: \`${ARMBIAN_SELECTED_ASSET}\`" + } >> "$GITHUB_STEP_SUMMARY" - name: "Prepare Release Assets" run: | @@ -40,6 +56,8 @@ jobs: if [ "$VERSION_TYPE" = "homeassistant_debian12_minimal" ]; then mv output/armbian-installer-x86_64-standard.iso output/armbian-installer-x86_64-homeassistant_debian12_minimal.iso fi + + test -s "output/armbian-installer-x86_64-${VERSION_TYPE}.iso" - name: "Publish" @@ -48,5 +66,5 @@ jobs: tag_name: "Armbian-Installer-x86_64-ISO" body_path: ${{ github.workspace }}/info.md files: | - output/armbian-installer-x86_64-${{ github.event.inputs.version_type }}.iso - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + output/armbian-installer-x86_64-${{ inputs.version_type }}.iso + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/autobuild/autobuild.sh b/autobuild/autobuild.sh index 6f561737..2a7f8170 100644 --- a/autobuild/autobuild.sh +++ b/autobuild/autobuild.sh @@ -48,9 +48,10 @@ else fi mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/imm/custom.img:/mnt/custom.img \ + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/custom.img:/mnt/custom.img:ro" \ debian:buster \ /supportFiles/custom/build.sh diff --git a/build.sh b/build.sh index bd532213..a117f372 100755 --- a/build.sh +++ b/build.sh @@ -1,53 +1,133 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail + mkdir -p armbian -# 读取环境变量 (带默认值) +# The upstream repository publishes the x86 images as release assets. Keep the +# profile names stable for the workflow while resolving the actual asset from +# the newest release that contains a matching image. VERSION_TYPE="${VERSION_TYPE:-standard}" -if [ "$VERSION_TYPE" = "debian12_minimal" ]; then - echo "构建debian12_minimal-armbian..." - FILE_NAME="Armbian_25.2.1_Uefi-x86_bookworm_current_6.12.13_minimal.img.xz" -elif [ "$VERSION_TYPE" = "ubuntu24_minimal" ]; then - echo "构建ubuntu24_minimal-armbian..." - FILE_NAME="Armbian_25.2.1_Uefi-x86_noble_current_6.12.13_minimal.img.xz" -elif [ "$VERSION_TYPE" = "homeassistant_debian12_minimal" ]; then - echo "构建homeassistant全家桶版armbian..." - FILE_NAME="Armbian_25.2.3_Uefi-x86_bookworm_current_6.12.17-homeassistant_minimal.img.xz" -else - echo "构建standard-armbian..." - FILE_NAME="Armbian_25.2.1_Uefi-x86_noble_current_6.12.13.img.xz" -fi - -REPO="wukongdaily/img-installer" -TAG="2025-03-12" +ARMBIAN_RELEASE="${ARMBIAN_RELEASE:-latest}" +ARMBIAN_REPO="${ARMBIAN_REPO:-wukongdaily/img-installer}" OUTPUT_PATH="armbian/armbian.img.xz" -DOWNLOAD_URL=$(curl -s https://api.github.com/repos/$REPO/releases/tags/$TAG | jq -r '.assets[] | select(.name == "'"$FILE_NAME"'") | .browser_download_url') +case "$VERSION_TYPE" in + standard) + PROFILE_DESCRIPTION="standard x86 UEFI" + PROFILE_REGEX='_Uefi-x86_[^_]+_current_[^_]+\.img\.xz$' + ;; + debian12_minimal) + PROFILE_DESCRIPTION="Debian 12 minimal x86 UEFI" + PROFILE_REGEX='_Uefi-x86_bookworm_current_[0-9][0-9.]*_minimal\.img\.xz$' + ;; + ubuntu24_minimal) + PROFILE_DESCRIPTION="Ubuntu 24 minimal x86 UEFI" + PROFILE_REGEX='_Uefi-x86_noble_current_.*_minimal\.img\.xz$' + ;; + homeassistant_debian12_minimal) + PROFILE_DESCRIPTION="Home Assistant Debian 12 minimal x86 UEFI" + PROFILE_REGEX='_Uefi-x86_bookworm_current_.*homeassistant_minimal\.img\.xz$' + ;; + *) + echo "错误:不支持的 VERSION_TYPE: $VERSION_TYPE" >&2 + exit 1 + ;; +esac + +API_BASE="https://api.github.com/repos/${ARMBIAN_REPO}" + +api_get() { + local url="$1" + local -a headers=( + -H "Accept: application/vnd.github+json" + -H "X-GitHub-Api-Version: 2022-11-28" + ) + + if [[ -n "${GITHUB_TOKEN:-}" ]]; then + headers+=( -H "Authorization: Bearer ${GITHUB_TOKEN}" ) + fi + + curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "${headers[@]}" "$url" +} + +asset_from_release() { + jq -r --arg pattern "$PROFILE_REGEX" \ + '[.assets[]? | select(.name | test($pattern; "i")) | [.name, .browser_download_url]] | .[0] // empty | @tsv' +} + +asset_from_release_list() { + jq -r --arg pattern "$PROFILE_REGEX" \ + '[.[] | .assets[]? | select(.name | test($pattern; "i")) | [.name, .browser_download_url]] | .[0] // empty | @tsv' +} + +selected_asset="" +selected_release="$ARMBIAN_RELEASE" + +if [[ "$ARMBIAN_RELEASE" == "latest" ]]; then + # GitHub returns releases newest-first. Paginate so this keeps working even + # after the upstream project has more than 100 releases. + for page in $(seq 1 10); do + release_list="$(api_get "${API_BASE}/releases?per_page=100&page=${page}")" + selected_asset="$(printf '%s' "$release_list" | asset_from_release_list)" + if [[ -n "$selected_asset" ]]; then + selected_release="$(printf '%s' "$release_list" | jq -r --arg pattern "$PROFILE_REGEX" \ + '[.[] | select([.assets[]?.name | test($pattern; "i")] | any) | .tag_name] | .[0] // empty')" + break + fi + [[ "$(printf '%s' "$release_list" | jq 'length')" -lt 100 ]] && break + done +else + encoded_release="$(jq -rn --arg value "$ARMBIAN_RELEASE" '$value | @uri')" + release_json="$(api_get "${API_BASE}/releases/tags/${encoded_release}")" + selected_asset="$(printf '%s' "$release_json" | asset_from_release)" +fi -if [[ -z "$DOWNLOAD_URL" ]]; then - echo "错误:未找到文件 $FILE_NAME" +if [[ -z "$selected_asset" ]]; then + echo "错误:上游 ${ARMBIAN_REPO} 没有找到匹配 ${PROFILE_DESCRIPTION} 的 .img.xz 固件" >&2 + echo "VERSION_TYPE=$VERSION_TYPE, ARMBIAN_RELEASE=$ARMBIAN_RELEASE" >&2 exit 1 fi -echo "下载地址: $DOWNLOAD_URL" -echo "下载文件: $FILE_NAME -> $OUTPUT_PATH" -curl -L -o "$OUTPUT_PATH" "$DOWNLOAD_URL" - -if [[ $? -eq 0 ]]; then - echo "下载armbian成功!" - file armbian/armbian.img.xz - echo "正在解压为:armbian.img" - xz -d armbian/armbian.img.xz - ls -lh armbian/ - echo "准备合成 armbian 安装器" -else - echo "下载失败!" +IFS=$'\t' read -r file_name download_url <<< "$selected_asset" +if [[ -z "$file_name" || -z "$download_url" ]]; then + echo "错误:上游返回的固件资产信息不完整" >&2 exit 1 fi +echo "上游仓库: ${ARMBIAN_REPO}" +echo "上游 Release: ${selected_release}" +echo "固件类型: ${PROFILE_DESCRIPTION}" +echo "固件文件: ${file_name}" +echo "下载地址: ${download_url}" + +if [[ -n "${GITHUB_ENV:-}" ]]; then + printf 'ARMBIAN_SELECTED_RELEASE=%s\n' "$selected_release" >> "$GITHUB_ENV" + printf 'ARMBIAN_SELECTED_ASSET=%s\n' "$file_name" >> "$GITHUB_ENV" +fi + +if [[ "${ARMBIAN_RESOLVE_ONLY:-0}" == "1" ]]; then + echo "ARMBIAN_RESOLVE_ONLY=1,跳过下载和 ISO 构建。" + exit 0 +fi + +rm -f armbian/armbian.img armbian/armbian.img.xz +curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$download_url" +xz -t "$OUTPUT_PATH" +xz -d -f "$OUTPUT_PATH" +test -s armbian/armbian.img +file armbian/armbian.img + +if [[ "${ARMBIAN_SKIP_DOCKER:-0}" == "1" ]]; then + echo "ARMBIAN_SKIP_DOCKER=1,已完成固件解析、下载和校验。" + exit 0 +fi + +echo "准备合成 Armbian 安装器" mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/armbian/armbian.img:/mnt/armbian.img \ - debian:buster \ - /supportFiles/build.sh \ No newline at end of file + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/armbian/armbian.img:/mnt/armbian.img:ro" \ + debian:buster \ + /supportFiles/build.sh diff --git a/custom.sh b/custom.sh index a3cddaad..1c834717 100644 --- a/custom.sh +++ b/custom.sh @@ -1,5 +1,5 @@ -#!/bin/bash -set -euo pipefail +#!/usr/bin/env bash +set -Eeuo pipefail # 校验参数是否存在 if [ -z "$1" ]; then @@ -7,16 +7,18 @@ if [ -z "$1" ]; then exit 1 fi +rm -rf imm mkdir -p imm DOWNLOAD_URL="$1" -filename=$(basename "$DOWNLOAD_URL") # 从 URL 提取文件名 +url_path="${DOWNLOAD_URL%%\?*}" +filename=$(basename "$url_path") OUTPUT_PATH="imm/$filename" echo "下载地址: $DOWNLOAD_URL" echo "保存路径: $OUTPUT_PATH" # 下载文件 -if ! curl -k -L -o "$OUTPUT_PATH" "$DOWNLOAD_URL"; then +if ! curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$DOWNLOAD_URL"; then echo "❌ 下载失败!" exit 1 fi @@ -25,25 +27,20 @@ echo "✅ 下载成功!" file "$OUTPUT_PATH" # 根据扩展名解压 -extension="${filename##*.}" # 获取文件扩展名 -case $extension in +extension="${filename##*.}" +extension="${extension,,}" +case "$extension" in gz) echo "gz正在解压$OUTPUT_PATH" - gunzip -f "$OUTPUT_PATH" || true - final_name=$(find imm -name '*.img' -print -quit) - mv "$final_name" "imm/custom.img" + gunzip -f "$OUTPUT_PATH" ;; zip) echo "zip正在解压$OUTPUT_PATH" - unzip -j -o "$OUTPUT_PATH" -d imm/ # -j 忽略目录结构 - final_name=$(find imm -name '*.img' -print -quit) - mv "$final_name" "imm/custom.img" + unzip -j -o "$OUTPUT_PATH" -d imm/ ;; xz) echo "xz正在解压$OUTPUT_PATH" - xz -d --keep "$OUTPUT_PATH" # 保留原文件 - final_name="${OUTPUT_PATH%.*}" - mv "$final_name" "imm/custom.img" + xz -d -f "$OUTPUT_PATH" ;; *) echo "❌ 不支持的压缩格式: $extension" @@ -51,6 +48,13 @@ case $extension in ;; esac +final_name=$(find imm -maxdepth 1 -type f -name '*.img' -print -quit) +if [[ -z "$final_name" ]]; then + echo "❌ 错误:压缩包中没有找到 .img 文件" + exit 1 +fi +mv -f -- "$final_name" imm/custom.img + # 检查最终文件 if [ -f "imm/custom.img" ]; then @@ -63,9 +67,10 @@ else fi mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/imm/custom.img:/mnt/custom.img \ + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/custom.img:/mnt/custom.img:ro" \ debian:buster \ /supportFiles/custom/build.sh diff --git a/esir.sh b/esir.sh index 79488274..9eca8263 100644 --- a/esir.sh +++ b/esir.sh @@ -1,11 +1,16 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail REPO="wkccd/esirOpenWrt" -TAG=$(curl -sL "https://api.github.com/repos/$REPO/releases/latest" | jq -r '.tag_name // empty') -[ -z "$TAG" ] && TAG=$(curl -sL "https://api.github.com/repos/$REPO/tags" | jq -r '.[0].name') +api_base="https://api.github.com/repos/$REPO" +TAG=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/releases/latest" | jq -r '.tag_name // empty') +if [[ -z "$TAG" ]]; then + TAG=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/tags" | jq -r '.[0].name // empty') +fi +[ -n "$TAG" ] || { echo "Error: no release or tag found for $REPO" >&2; exit 1; } echo "最新TAG: $TAG" # 获取该 Tag 下所有以 .img.gz 结尾的文件 -DOWNLOAD_URLS=$(curl -sL "https://api.github.com/repos/$REPO/releases/tags/$TAG" \ +DOWNLOAD_URLS=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/releases/tags/$TAG" \ | jq -r '.assets[] | select(.name | endswith("img.gz")) | .browser_download_url') # 保存位置 mkdir -p imm @@ -16,26 +21,23 @@ if [ -z "$DOWNLOAD_URLS" ]; then exit 1 fi -FIRST_DOWNLOAD_URL=$(echo "$DOWNLOAD_URLS" | head -n1) +FIRST_DOWNLOAD_URL=$(printf '%s\n' "$DOWNLOAD_URLS" | head -n1) echo "下载地址: $FIRST_DOWNLOAD_URL" -curl -L -o "$OUTPUT_PATH" "$FIRST_DOWNLOAD_URL" - -if [[ $? -eq 0 ]]; then - echo "下载esiropenwrt成功!" - file imm/esiropenwrt.img.gz - echo "正在解压为:esiropenwrt.img" - gzip -d imm/esiropenwrt.img.gz - ls -lh imm/ - echo "准备合成 eSirOpenWrt 安装器" -else - echo "下载失败!" - exit 1 -fi +curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$FIRST_DOWNLOAD_URL" +echo "下载esiropenwrt成功!" +file imm/esiropenwrt.img.gz +echo "正在解压为:esiropenwrt.img" +gzip -t imm/esiropenwrt.img.gz +gzip -d -f imm/esiropenwrt.img.gz +test -s imm/esiropenwrt.img +ls -lh imm/ +echo "准备合成 eSirOpenWrt 安装器" mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/imm/esiropenwrt.img:/mnt/esiropenwrt.img \ + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/esiropenwrt.img:/mnt/esiropenwrt.img:ro" \ debian:buster \ - /supportFiles/esirplayground/build.sh \ No newline at end of file + /supportFiles/esirplayground/build.sh diff --git a/ezopwrt.sh b/ezopwrt.sh index 025cc777..dac90d5b 100644 --- a/ezopwrt.sh +++ b/ezopwrt.sh @@ -1,11 +1,16 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail REPO="sirpdboy/openwrt" -TAG=$(curl -sL "https://api.github.com/repos/$REPO/releases/latest" | jq -r '.tag_name // empty') -[ -z "$TAG" ] && TAG=$(curl -sL "https://api.github.com/repos/$REPO/tags" | jq -r '.[0].name') +api_base="https://api.github.com/repos/$REPO" +TAG=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/releases/latest" | jq -r '.tag_name // empty') +if [[ -z "$TAG" ]]; then + TAG=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/tags" | jq -r '.[0].name // empty') +fi +[ -n "$TAG" ] || { echo "Error: no release or tag found for $REPO" >&2; exit 1; } echo "最新TAG: $TAG" # 获取该 Tag 下所有以 .img.gz 结尾的文件 -DOWNLOAD_URLS=$(curl -sL "https://api.github.com/repos/$REPO/releases/tags/$TAG" \ +DOWNLOAD_URLS=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/releases/tags/$TAG" \ | jq -r '.assets[] | select(.name | endswith("img.gz")) | .browser_download_url') # 保存位置 mkdir -p imm @@ -16,26 +21,23 @@ if [ -z "$DOWNLOAD_URLS" ]; then exit 1 fi -FIRST_DOWNLOAD_URL=$(echo "$DOWNLOAD_URLS" | head -n1) +FIRST_DOWNLOAD_URL=$(printf '%s\n' "$DOWNLOAD_URLS" | head -n1) echo "下载地址: $FIRST_DOWNLOAD_URL" -curl -L -o "$OUTPUT_PATH" "$FIRST_DOWNLOAD_URL" - -if [[ $? -eq 0 ]]; then - echo "下载ezopwrt成功!" - file imm/ezopwrt.img.gz - echo "正在解压为:ezopwrt.img" - gzip -d imm/ezopwrt.img.gz - ls -lh imm/ - echo "准备合成 EzOpWrt 安装器" -else - echo "下载失败!" - exit 1 -fi +curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$FIRST_DOWNLOAD_URL" +echo "下载ezopwrt成功!" +file imm/ezopwrt.img.gz +echo "正在解压为:ezopwrt.img" +gzip -t imm/ezopwrt.img.gz +gzip -d -f imm/ezopwrt.img.gz +test -s imm/ezopwrt.img +ls -lh imm/ +echo "准备合成 EzOpWrt 安装器" mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/imm/ezopwrt.img:/mnt/ezopwrt.img \ + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/ezopwrt.img:/mnt/ezopwrt.img:ro" \ debian:buster \ /supportFiles/ezopwrt/build.sh diff --git a/haos.sh b/haos.sh index 579ce870..cf1fe743 100644 --- a/haos.sh +++ b/haos.sh @@ -1,5 +1,5 @@ -#!/bin/bash -set -euo pipefail +#!/usr/bin/env bash +set -Eeuo pipefail # 校验参数是否存在 if [ -z "$1" ]; then @@ -7,16 +7,18 @@ if [ -z "$1" ]; then exit 1 fi +rm -rf imm mkdir -p imm DOWNLOAD_URL="$1" -filename=$(basename "$DOWNLOAD_URL") # 从 URL 提取文件名 +url_path="${DOWNLOAD_URL%%\?*}" +filename=$(basename "$url_path") OUTPUT_PATH="imm/$filename" echo "下载地址: $DOWNLOAD_URL" echo "保存路径: $OUTPUT_PATH" # 下载文件 -if ! curl -k -L -o "$OUTPUT_PATH" "$DOWNLOAD_URL"; then +if ! curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$DOWNLOAD_URL"; then echo "❌ 下载失败!" exit 1 fi @@ -25,25 +27,20 @@ echo "✅ 下载成功!" file "$OUTPUT_PATH" # 根据扩展名解压 -extension="${filename##*.}" # 获取文件扩展名 -case $extension in +extension="${filename##*.}" +extension="${extension,,}" +case "$extension" in gz) echo "gz正在解压$OUTPUT_PATH" - gunzip -f "$OUTPUT_PATH" || true - final_name=$(find imm -name '*.img' -print -quit) - mv "$final_name" "imm/haos.img" + gunzip -f "$OUTPUT_PATH" ;; zip) echo "zip正在解压$OUTPUT_PATH" - unzip -j -o "$OUTPUT_PATH" -d imm/ # -j 忽略目录结构 - final_name=$(find imm -name '*.img' -print -quit) - mv "$final_name" "imm/haos.img" + unzip -j -o "$OUTPUT_PATH" -d imm/ ;; xz) echo "xz正在解压$OUTPUT_PATH" - xz -d --keep "$OUTPUT_PATH" # 保留原文件 - final_name="${OUTPUT_PATH%.*}" - mv "$final_name" "imm/haos.img" + xz -d -f "$OUTPUT_PATH" ;; *) echo "❌ 不支持的压缩格式: $extension" @@ -51,6 +48,13 @@ case $extension in ;; esac +final_name=$(find imm -maxdepth 1 -type f -name '*.img' -print -quit) +if [[ -z "$final_name" ]]; then + echo "❌ 错误:压缩包中没有找到 .img 文件" + exit 1 +fi +mv -f -- "$final_name" imm/haos.img + # 检查最终文件 if [ -f "imm/haos.img" ]; then @@ -63,9 +67,10 @@ else fi mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/imm/haos.img:/mnt/haos.img \ + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/haos.img:/mnt/haos.img:ro" \ debian:buster \ /supportFiles/haos/build.sh diff --git a/imm.sh b/imm.sh index 75458433..d10c6523 100644 --- a/imm.sh +++ b/imm.sh @@ -1,47 +1,79 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail + mkdir -p imm -#https://github.com/wukongdaily/AutoBuildImmortalWrt/releases/download/Autobuild-x86-64/immortalwrt-24.10.0-x86-64-generic-squashfs-combined-efi.img.gz -REPO="wukongdaily/AutoBuildImmortalWrt" -TAG="img-installer" -FILE_NAME="immortalwrt-24.10.2-x86-64-generic-squashfs-combined-efi.img.gz" +REPO="${IMMORTALWRT_REPO:-wukongdaily/AutoBuildImmortalWrt}" +RELEASE_TAG="${IMMORTALWRT_RELEASE:-Autobuild-x86-64}" +VERSION="${IMMORTALWRT_VERSION:-latest}" +API_BASE="https://api.github.com/repos/${REPO}" OUTPUT_PATH="imm/immortalwrt.img.gz" -DOWNLOAD_URL=$(curl -s https://api.github.com/repos/$REPO/releases/tags/$TAG | jq -r '.assets[] | select(.name == "'"$FILE_NAME"'") | .browser_download_url') +api_get() { + local url="$1" + local -a headers=( + -H "Accept: application/vnd.github+json" + -H "X-GitHub-Api-Version: 2022-11-28" + ) + [[ -n "${GITHUB_TOKEN:-}" ]] && headers+=( -H "Authorization: Bearer ${GITHUB_TOKEN}" ) + curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "${headers[@]}" "$url" +} -# 此处可以替换op固件下载地址,但必须是 直链才可以,网盘那种地址是不行滴。举3个例子 -# 原版OpenWrt -# DOWNLOAD_URL="https://downloads.openwrt.org/releases/24.10.0/targets/x86/64/openwrt-24.10.0-x86-64-generic-squashfs-combined-efi.img.gz" -# 原版immortalwrt -# DOWNLOAD_URL="https://downloads.immortalwrt.org/releases/24.10.0/targets/x86/64/immortalwrt-24.10.0-x86-64-generic-squashfs-combined-efi.img.gz" -# 原版KWRT -# DOWNLOAD_URL="https://dl.openwrt.ai/releases/24.10/targets/x86/64/kwrt-03.08.2025-x86-64-generic-squashfs-combined-efi.img.gz" +encoded_release="$(jq -rn --arg value "$RELEASE_TAG" '$value | @uri')" +release_json="$(api_get "${API_BASE}/releases/tags/${encoded_release}")" -if [[ -z "$DOWNLOAD_URL" ]]; then - echo "错误:未找到文件 $FILE_NAME" - exit 1 +if [[ "$VERSION" == "latest" ]]; then + file_name="$(printf '%s' "$release_json" | jq -r ' + .assets[]?.name | select(test("^immortalwrt-[0-9][0-9A-Za-z.+~-]*-x86-64-generic-squashfs-combined-efi\\.img\\.gz$"))' | sort -V | tail -n 1)" +else + file_name="immortalwrt-${VERSION}-x86-64-generic-squashfs-combined-efi.img.gz" + if ! printf '%s' "$release_json" | jq -e --arg name "$file_name" '.assets[]? | select(.name == $name)' >/dev/null; then + file_name="" + fi fi -echo "下载地址: $DOWNLOAD_URL" -echo "下载文件: $FILE_NAME -> $OUTPUT_PATH" -curl -L -o "$OUTPUT_PATH" "$DOWNLOAD_URL" - -if [[ $? -eq 0 ]]; then - echo "下载immortalwrt-24.10.1成功!" - file imm/immortalwrt.img.gz - echo "正在解压为:immortalwrt.img" - gzip -d imm/immortalwrt.img.gz - ls -lh imm/ - echo "准备合成 immortalwrt 安装器" -else - echo "下载失败!" +if [[ -z "$file_name" ]]; then + echo "错误:Release ${RELEASE_TAG} 中没有找到 ImmortalWrt x86-64 EFI 固件(版本:${VERSION})" >&2 exit 1 fi +download_url="$(printf '%s' "$release_json" | jq -r --arg name "$file_name" '.assets[] | select(.name == $name) | .browser_download_url')" +echo "上游仓库: ${REPO}" +echo "上游 Release: ${RELEASE_TAG}" +echo "固件文件: ${file_name}" +echo "下载地址: ${download_url}" + +rm -f imm/immortalwrt.img imm/immortalwrt.img.gz +curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$download_url" +gzip_log="$(mktemp)" +trap 'rm -f "$gzip_log"' EXIT +if ! gzip -t "$OUTPUT_PATH" 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + echo "警告:上游 gzip 包含可忽略的 trailing garbage,继续提取有效镜像。" >&2 +fi +if ! gzip -dc "$OUTPUT_PATH" > imm/immortalwrt.img 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi +fi +test -s imm/immortalwrt.img +file imm/immortalwrt.img + +if [[ "${IMMORTALWRT_SKIP_DOCKER:-0}" == "1" ]]; then + echo "IMMORTALWRT_SKIP_DOCKER=1,已完成固件解析、下载和校验。" + exit 0 +fi + +echo "准备合成 ImmortalWrt 安装器" mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/imm/immortalwrt.img:/mnt/immortalwrt.img \ - debian:buster \ - /supportFiles/immortalwrt/build.sh + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/immortalwrt.img:/mnt/immortalwrt.img:ro" \ + debian:buster \ + /supportFiles/immortalwrt/build.sh diff --git a/istoreos.sh b/istoreos.sh index 50c09cf4..87b758fb 100644 --- a/istoreos.sh +++ b/istoreos.sh @@ -1,36 +1,95 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail + mkdir -p openwrt -REPO="wukongdaily/img-installer" -TAG="2025-03-12" -FILE_NAME="istoreos-24.10.1-2025060614-x86-64-squashfs-combined-efi.img.gz" +REPO="${ISTOREOS_REPO:-wukongdaily/img-installer}" +UPSTREAM_RELEASE="${ISTOREOS_RELEASE:-latest}" +API_BASE="https://api.github.com/repos/${REPO}" OUTPUT_PATH="openwrt/istoreos.img.gz" -DOWNLOAD_URL=$(curl -s https://api.github.com/repos/$REPO/releases/tags/$TAG | jq -r '.assets[] | select(.name == "'"$FILE_NAME"'") | .browser_download_url') +PROFILE_REGEX='^istoreos-.*-x86-64-squashfs-combined-efi\.img\.gz$' + +api_get() { + local url="$1" + local -a headers=( + -H "Accept: application/vnd.github+json" + -H "X-GitHub-Api-Version: 2022-11-28" + ) + [[ -n "${GITHUB_TOKEN:-}" ]] && headers+=( -H "Authorization: Bearer ${GITHUB_TOKEN}" ) + curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "${headers[@]}" "$url" +} + +selected_asset="" +selected_release="$UPSTREAM_RELEASE" +if [[ "$UPSTREAM_RELEASE" == "latest" ]]; then + for page in $(seq 1 10); do + release_list="$(api_get "${API_BASE}/releases?per_page=100&page=${page}")" + selected_name="$(printf '%s' "$release_list" | jq -r --arg pattern "$PROFILE_REGEX" \ + '.[] | .assets[]? | select(.name | test($pattern; "i")) | .name' | LC_ALL=C sort -V | tail -n 1)" + if [[ -n "$selected_name" ]]; then + selected_release="$(printf '%s' "$release_list" | jq -r --arg pattern "$PROFILE_REGEX" ' + [.[] | select([.assets[]?.name | test($pattern; "i")] | any) | .tag_name] + | .[0] // empty')" + selected_asset="$(printf '%s' "$release_list" | jq -r --arg name "$selected_name" \ + '.[] | .assets[]? | select(.name == $name) | [.name, .browser_download_url] | @tsv' | head -n 1)" + break + fi + [[ "$(printf '%s' "$release_list" | jq 'length')" -lt 100 ]] && break + done +else + encoded_release="$(jq -rn --arg value "$UPSTREAM_RELEASE" '$value | @uri')" + release_json="$(api_get "${API_BASE}/releases/tags/${encoded_release}")" + selected_name="$(printf '%s' "$release_json" | jq -r --arg pattern "$PROFILE_REGEX" \ + '.assets[]? | select(.name | test($pattern; "i")) | .name' | LC_ALL=C sort -V | tail -n 1)" + if [[ -n "$selected_name" ]]; then + selected_asset="$(printf '%s' "$release_json" | jq -r --arg name "$selected_name" \ + '.assets[]? | select(.name == $name) | [.name, .browser_download_url] | @tsv')" + fi +fi -if [[ -z "$DOWNLOAD_URL" ]]; then - echo "错误:未找到文件 $FILE_NAME" +if [[ -z "$selected_asset" ]]; then + echo "错误:上游 ${REPO} 没有找到 iStoreOS x86-64 EFI 固件" >&2 + echo "ISTOREOS_RELEASE=${UPSTREAM_RELEASE}" >&2 exit 1 fi -echo "下载地址: $DOWNLOAD_URL" -echo "下载文件: $FILE_NAME -> $OUTPUT_PATH" -curl -L -o "$OUTPUT_PATH" "$DOWNLOAD_URL" +IFS=$'\t' read -r file_name download_url <<< "$selected_asset" +echo "上游仓库: ${REPO}" +echo "上游 Release: ${selected_release}" +echo "固件文件: ${file_name}" +echo "下载地址: ${download_url}" -if [[ $? -eq 0 ]]; then - echo "下载istoreos成功!" - echo "正在解压为:istoreos.img" - gzip -d openwrt/istoreos.img.gz - ls -lh openwrt/ - echo "准备合成 istoreos 安装器" -else - echo "下载失败!" - exit 1 +rm -f openwrt/istoreos.img openwrt/istoreos.img.gz +curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$download_url" +gzip_log="$(mktemp)" +trap 'rm -f "$gzip_log"' EXIT +if ! gzip -t "$OUTPUT_PATH" 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + echo "警告:上游 gzip 包含可忽略的 trailing garbage,继续提取有效镜像。" >&2 +fi +if ! gzip -dc "$OUTPUT_PATH" > openwrt/istoreos.img 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi +fi +test -s openwrt/istoreos.img +file openwrt/istoreos.img + +if [[ "${ISTOREOS_SKIP_DOCKER:-0}" == "1" ]]; then + echo "ISTOREOS_SKIP_DOCKER=1,已完成固件解析、下载和校验。" + exit 0 fi +echo "准备合成 iStoreOS 安装器" mkdir -p output +repo_root="$(pwd -P)" docker run --privileged --rm \ - -v $(pwd)/output:/output \ - -v $(pwd)/supportFiles:/supportFiles:ro \ - -v $(pwd)/openwrt/istoreos.img:/mnt/istoreos.img \ - debian:buster \ - /supportFiles/istoreos/build.sh + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/openwrt/istoreos.img:/mnt/istoreos.img:ro" \ + debian:buster \ + /supportFiles/istoreos/build.sh diff --git a/supportFiles/build.sh b/supportFiles/build.sh index 35b6a688..eef1279f 100755 --- a/supportFiles/build.sh +++ b/supportFiles/build.sh @@ -82,7 +82,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot diff --git a/supportFiles/custom/build.sh b/supportFiles/custom/build.sh index 70f27018..99c01877 100644 --- a/supportFiles/custom/build.sh +++ b/supportFiles/custom/build.sh @@ -85,7 +85,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot diff --git a/supportFiles/esirplayground/build.sh b/supportFiles/esirplayground/build.sh index 7b4a4c20..8ab4b9c4 100644 --- a/supportFiles/esirplayground/build.sh +++ b/supportFiles/esirplayground/build.sh @@ -83,7 +83,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot diff --git a/supportFiles/ezopwrt/build.sh b/supportFiles/ezopwrt/build.sh index 5df69100..67269eb0 100644 --- a/supportFiles/ezopwrt/build.sh +++ b/supportFiles/ezopwrt/build.sh @@ -82,7 +82,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot diff --git a/supportFiles/haos/build.sh b/supportFiles/haos/build.sh index def527c0..fcd892fc 100644 --- a/supportFiles/haos/build.sh +++ b/supportFiles/haos/build.sh @@ -83,7 +83,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot diff --git a/supportFiles/immortalwrt/build.sh b/supportFiles/immortalwrt/build.sh index da2f7e7d..6318687f 100644 --- a/supportFiles/immortalwrt/build.sh +++ b/supportFiles/immortalwrt/build.sh @@ -85,7 +85,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot diff --git a/supportFiles/istoreos/build.sh b/supportFiles/istoreos/build.sh index 8fd79599..6cc3e84a 100644 --- a/supportFiles/istoreos/build.sh +++ b/supportFiles/istoreos/build.sh @@ -83,7 +83,7 @@ echo Make UEFI grub files grub-mkstandalone --format=x86_64-efi --output=$HOME/LIVE_BOOT/tmp/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg" cd $HOME/LIVE_BOOT/staging/EFI/boot -SIZE=`expr $(stat --format=%s $HOME/LIVE_BOOT/tmp/bootx64.efi) + 65536` +SIZE=$(( $(stat --format=%s "$HOME/LIVE_BOOT/tmp/bootx64.efi") + 65536 )) dd if=/dev/zero of=efiboot.img bs=$SIZE count=1 /sbin/mkfs.vfat efiboot.img mmd -i efiboot.img efi efi/boot From 73d38434ce27f7a9fb99a879e0dbeab01bfb0202 Mon Sep 17 00:00:00 2001 From: 0xAAcodeislaw <0xAAcodeislaw@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:00:11 +0800 Subject: [PATCH 2/3] make upstream assets explicit and add build-only mode --- .github/workflows/build-custom.yml | 6 ++ .github/workflows/build-esiropenwrt.yml | 7 ++ .github/workflows/build-ezopwrt.yml | 22 ++++- .github/workflows/build-haos.yml | 6 ++ .github/workflows/build-imm.yml | 6 ++ .github/workflows/build-istoreos-24.10.1.yml | 6 ++ .github/workflows/build.yml | 6 ++ esir.sh | 17 +++- ezopwrt.sh | 90 +++++++++++++------- 9 files changed, 134 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 1e63e50f..824bbef4 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -7,6 +7,11 @@ on: description: '请输入下载地址(扩展名 .img.gz/.img.xz/.img.zip)' required: true default: 'https://default.example.com/file.img.gz' + publish: + description: '是否创建或更新 GitHub Release' + required: false + default: true + type: boolean # Release 上传需要写权限;仓库若采用只读默认权限,显式声明可避免 403。 permissions: @@ -49,6 +54,7 @@ jobs: bash ./custom.sh "$DOWNLOAD_URL" - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "Custom-Installer-x86_64-ISO" diff --git a/.github/workflows/build-esiropenwrt.yml b/.github/workflows/build-esiropenwrt.yml index 862af872..0be51115 100644 --- a/.github/workflows/build-esiropenwrt.yml +++ b/.github/workflows/build-esiropenwrt.yml @@ -2,6 +2,12 @@ name: "Build eSirOpenWrt Installer ISO" on: workflow_dispatch: + inputs: + publish: + description: "是否创建或更新 GitHub Release" + required: false + default: true + type: boolean permissions: contents: write @@ -29,6 +35,7 @@ jobs: ./esir.sh - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "eSirOpenWrt-Installer-x86_64-ISO" diff --git a/.github/workflows/build-ezopwrt.yml b/.github/workflows/build-ezopwrt.yml index 93053f07..4b8ce5b0 100644 --- a/.github/workflows/build-ezopwrt.yml +++ b/.github/workflows/build-ezopwrt.yml @@ -2,6 +2,15 @@ name: "Build EzOpWrt Installer ISO" on: workflow_dispatch: + inputs: + download_url: + description: "EzOpWrt 上游固件直链(.img.gz/.img.xz/.img.zip)" + required: true + publish: + description: "是否创建或更新 GitHub Release" + required: false + default: true + type: boolean permissions: contents: write @@ -10,6 +19,8 @@ jobs: build-release: name: "Build and Release" runs-on: "ubuntu-22.04" + env: + DOWNLOAD_URL: ${{ inputs.download_url }} steps: - name: "Get Date" @@ -24,11 +35,20 @@ jobs: chmod +x "${{ github.workspace }}/ezopwrt.sh" chmod +x "${{ github.workspace }}/supportFiles/ezopwrt/build.sh" + - name: Validate Download URL + run: | + if [[ ! "$DOWNLOAD_URL" =~ ^https?://[^[:space:]]+\.(gz|xz|zip)(\?[^[:space:]]*)?$ ]]; then + echo "❌ 错误:地址需以 http(s) 开头且扩展名为 .gz/.xz/.zip" + echo "当前输入:$DOWNLOAD_URL" + exit 1 + fi + - name: "Build Image" run: | - ./ezopwrt.sh + bash ./ezopwrt.sh "$DOWNLOAD_URL" - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "EzOpWrt-Installer-x86_64-ISO" diff --git a/.github/workflows/build-haos.yml b/.github/workflows/build-haos.yml index c41d2fcc..c59d0ed9 100644 --- a/.github/workflows/build-haos.yml +++ b/.github/workflows/build-haos.yml @@ -7,6 +7,11 @@ on: description: '请输入haos下载地址(扩展名 .img.gz/.img.xz/.img.zip)' required: true default: 'https://github.com/home-assistant/operating-system/releases/download/15.0/haos_generic-x86-64-15.0.img.xz' + publish: + description: '是否创建或更新 GitHub Release' + required: false + default: true + type: boolean permissions: contents: write @@ -40,6 +45,7 @@ jobs: bash ./haos.sh "$DOWNLOAD_URL" - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "HAOS-Installer-x86_64-ISO" diff --git a/.github/workflows/build-imm.yml b/.github/workflows/build-imm.yml index 69e57f4b..9f5f60a2 100644 --- a/.github/workflows/build-imm.yml +++ b/.github/workflows/build-imm.yml @@ -7,6 +7,11 @@ on: description: "ImmortalWrt 版本;latest 自动选择上游 x86-64 EFI 固件" required: true default: "latest" + publish: + description: "是否创建或更新 GitHub Release" + required: false + default: true + type: boolean permissions: contents: write @@ -36,6 +41,7 @@ jobs: bash ./imm.sh - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "ImmortalWrt-Installer-x86_64-ISO" diff --git a/.github/workflows/build-istoreos-24.10.1.yml b/.github/workflows/build-istoreos-24.10.1.yml index 93fc1c55..6afb9de6 100644 --- a/.github/workflows/build-istoreos-24.10.1.yml +++ b/.github/workflows/build-istoreos-24.10.1.yml @@ -7,6 +7,11 @@ on: description: "上游 Release 标签;latest 自动选择最新 iStoreOS x86-64 固件" required: true default: "latest" + publish: + description: "是否创建或更新 GitHub Release" + required: false + default: true + type: boolean permissions: contents: write @@ -33,6 +38,7 @@ jobs: bash ./istoreos.sh - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "iStoreOS-Installer-x86_64-ISO" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3331e972..6ebcb572 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,11 @@ on: description: "上游固件 Release 标签;latest 会自动选择匹配类型的最新版本" required: true default: "latest" + publish: + description: "是否创建或更新 GitHub Release" + required: false + default: true + type: boolean permissions: contents: write @@ -61,6 +66,7 @@ jobs: - name: "Publish" + if: ${{ inputs.publish }} uses: softprops/action-gh-release@v2.2.1 with: tag_name: "Armbian-Installer-x86_64-ISO" diff --git a/esir.sh b/esir.sh index 9eca8263..919723c8 100644 --- a/esir.sh +++ b/esir.sh @@ -27,8 +27,21 @@ curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$FIRS echo "下载esiropenwrt成功!" file imm/esiropenwrt.img.gz echo "正在解压为:esiropenwrt.img" -gzip -t imm/esiropenwrt.img.gz -gzip -d -f imm/esiropenwrt.img.gz +gzip_log="$(mktemp)" +trap 'rm -f "$gzip_log"' EXIT +if ! gzip -t imm/esiropenwrt.img.gz 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + echo "警告:上游 gzip 包含可忽略的 trailing garbage,继续提取有效镜像。" >&2 +fi +if ! gzip -dc imm/esiropenwrt.img.gz > imm/esiropenwrt.img 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi +fi test -s imm/esiropenwrt.img ls -lh imm/ echo "准备合成 eSirOpenWrt 安装器" diff --git a/ezopwrt.sh b/ezopwrt.sh index dac90d5b..c9fa4196 100644 --- a/ezopwrt.sh +++ b/ezopwrt.sh @@ -1,43 +1,75 @@ #!/usr/bin/env bash set -Eeuo pipefail -REPO="sirpdboy/openwrt" -api_base="https://api.github.com/repos/$REPO" -TAG=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/releases/latest" | jq -r '.tag_name // empty') -if [[ -z "$TAG" ]]; then - TAG=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/tags" | jq -r '.[0].name // empty') -fi -[ -n "$TAG" ] || { echo "Error: no release or tag found for $REPO" >&2; exit 1; } -echo "最新TAG: $TAG" -# 获取该 Tag 下所有以 .img.gz 结尾的文件 -DOWNLOAD_URLS=$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 "$api_base/releases/tags/$TAG" \ - | jq -r '.assets[] | select(.name | endswith("img.gz")) | .browser_download_url') -# 保存位置 -mkdir -p imm -OUTPUT_PATH="imm/ezopwrt.img.gz" - -if [ -z "$DOWNLOAD_URLS" ]; then - echo "Error: No .img.gz files found under tag $TAG" +DOWNLOAD_URL="${1:-${EZOPWRT_URL:-}}" +if [[ -z "$DOWNLOAD_URL" ]]; then + echo "Error: 请提供 EzOpWrt .img.gz/.img.xz/.img.zip 直链。" >&2 + echo "sirpdboy/openwrt 当前没有可下载的 GitHub Release,无法安全地自动猜测固件地址。" >&2 exit 1 fi -FIRST_DOWNLOAD_URL=$(printf '%s\n' "$DOWNLOAD_URLS" | head -n1) -echo "下载地址: $FIRST_DOWNLOAD_URL" -curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$FIRST_DOWNLOAD_URL" +rm -rf imm +mkdir -p imm +url_path="${DOWNLOAD_URL%%\?*}" +filename="$(basename "$url_path")" +OUTPUT_PATH="imm/$filename" + +echo "下载地址: $DOWNLOAD_URL" +curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$OUTPUT_PATH" "$DOWNLOAD_URL" echo "下载ezopwrt成功!" -file imm/ezopwrt.img.gz -echo "正在解压为:ezopwrt.img" -gzip -t imm/ezopwrt.img.gz -gzip -d -f imm/ezopwrt.img.gz +file "$OUTPUT_PATH" + +extension="${filename##*.}" +extension="${extension,,}" +case "$extension" in + gz) + gzip_log="$(mktemp)" + trap 'rm -f "$gzip_log"' EXIT + if ! gzip -t "$OUTPUT_PATH" 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + echo "警告:上游 gzip 包含可忽略的 trailing garbage,继续提取有效镜像。" >&2 + fi + if ! gzip -dc "$OUTPUT_PATH" > imm/ezopwrt.img 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + fi + ;; + xz) + xz -t "$OUTPUT_PATH" + xz -dc "$OUTPUT_PATH" > imm/ezopwrt.img + ;; + zip) + unzip -j -o "$OUTPUT_PATH" -d imm/ + final_name="$(find imm -maxdepth 1 -type f -name '*.img' -print -quit)" + [[ -n "$final_name" ]] || { echo "Error: zip 中没有 .img 文件" >&2; exit 1; } + mv -f -- "$final_name" imm/ezopwrt.img + ;; + *) + echo "Error: 不支持的压缩格式: $extension" >&2 + exit 1 + ;; +esac + test -s imm/ezopwrt.img +file imm/ezopwrt.img ls -lh imm/ echo "准备合成 EzOpWrt 安装器" +if [[ "${EZOPWRT_SKIP_DOCKER:-0}" == "1" ]]; then + echo "EZOPWRT_SKIP_DOCKER=1,已完成固件下载、解压和校验。" + exit 0 +fi + mkdir -p output repo_root="$(pwd -P)" docker run --privileged --rm \ - -v "${repo_root}/output:/output" \ - -v "${repo_root}/supportFiles:/supportFiles:ro" \ - -v "${repo_root}/imm/ezopwrt.img:/mnt/ezopwrt.img:ro" \ - debian:buster \ - /supportFiles/ezopwrt/build.sh + -v "${repo_root}/output:/output" \ + -v "${repo_root}/supportFiles:/supportFiles:ro" \ + -v "${repo_root}/imm/ezopwrt.img:/mnt/ezopwrt.img:ro" \ + debian:buster \ + /supportFiles/ezopwrt/build.sh From c0a2134a4b56c8e62189c751bfe788d9d66b3fa8 Mon Sep 17 00:00:00 2001 From: 0xAAcodeislaw <0xAAcodeislaw@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:31:41 +0800 Subject: [PATCH 3/3] handle trailing gzip data in custom image flow --- custom.sh | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/custom.sh b/custom.sh index 1c834717..dbd10e99 100644 --- a/custom.sh +++ b/custom.sh @@ -32,7 +32,21 @@ extension="${extension,,}" case "$extension" in gz) echo "gz正在解压$OUTPUT_PATH" - gunzip -f "$OUTPUT_PATH" + gzip_log="$(mktemp)" + trap 'rm -f "$gzip_log"' EXIT + if ! gzip -t "$OUTPUT_PATH" 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + echo "⚠️ 上游 gzip 包含可忽略的 trailing garbage,继续提取有效镜像。" >&2 + fi + if ! gzip -dc "$OUTPUT_PATH" > imm/custom.img 2>"$gzip_log"; then + if ! grep -qi 'trailing garbage ignored' "$gzip_log"; then + cat "$gzip_log" >&2 + exit 1 + fi + fi ;; zip) echo "zip正在解压$OUTPUT_PATH" @@ -53,7 +67,9 @@ if [[ -z "$final_name" ]]; then echo "❌ 错误:压缩包中没有找到 .img 文件" exit 1 fi -mv -f -- "$final_name" imm/custom.img +if [[ "$final_name" != "imm/custom.img" ]]; then + mv -f -- "$final_name" imm/custom.img +fi # 检查最终文件