From 7a7cca92d0c22320655a633ec67709b00d8cc323 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Sat, 27 Jun 2026 16:07:15 +0200 Subject: [PATCH] ci: replace generated artifact-matrix workflows with one reusable + thin callers Drop the 6 generated ~9400-line complete-artifact-matrix-*.yml files, the 600-line recreate-matrix.yml bash/awk generator, the Jinja chunk templates and the per-track gha_config_*.yaml. Replace with one hand-written reusable pipeline (complete-artifact-matrix.yml) plus two reusable chunk bodies (build-artifacts-chunk.yml, build-images-chunk.yml) fanned out over a chunk matrix, and six thin per-track callers (nightly, standard-support, community, apps, all, all-stable). Targets are already fetched at runtime from github.armbian.com/release-targets/, so no board classification / targets generation is needed here. Per-track behaviour (schedules, repos, upload paths, menu defaults) is preserved. Also drop the external watchdog.yml cron and integrate repeat-on-failure directly into the build steps (retry the compile.sh invocation BUILD_ATTEMPTS times). Update nothing else. Signed-off-by: Igor Pecovnik --- .github/actionlint.yaml | 10 + .github/workflows/build-all-stable.yml | 63 + .github/workflows/build-all.yml | 64 + .github/workflows/build-apps.yml | 61 + .../workflows/build-artifacts-chunk.yml | 64 +- .github/workflows/build-community.yml | 63 + .../workflows/build-images-chunk.yml | 98 +- .github/workflows/build-nightly.yml | 68 + .github/workflows/build-standard-support.yml | 61 + .../complete-artifact-matrix-all-stable.yml | 9062 ---------------- .../complete-artifact-matrix-all.yml | 9063 ---------------- .../complete-artifact-matrix-apps.yml | 9410 ---------------- ...e-artifact-matrix-community-maintained.yml | 9412 ---------------- .../complete-artifact-matrix-nightly.yml | 9416 ----------------- ...plete-artifact-matrix-standard-support.yml | 9410 ---------------- .../workflows/complete-artifact-matrix.yml | 652 ++ .github/workflows/recreate-matrix.yml | 523 - .github/workflows/watchdog.yml | 46 - userpatches/gha/chunks/050.single_header.yaml | 452 - .../150.per-chunk-artifacts_prep-outputs.yaml | 7 - .../151.per-chunk-images_prep-outputs.yaml | 7 - .../gha/chunks/250.single_aggr-jobs.yaml | 32 - userpatches/gha/chunks/750.single_repo.yaml | 126 - userpatches/gha/chunks/950.single_footer.yaml | 70 - userpatches/gha/gha_config.yaml | 29 - userpatches/gha/gha_config_all.yaml | 32 - userpatches/gha/gha_config_all_stable.yaml | 32 - userpatches/gha/gha_config_nightly.yaml | 33 - userpatches/gha/gha_config_old_stable.yaml | 32 - userpatches/gha/gha_config_release_apps.yaml | 33 - ...a_config_release_community_maintained.yaml | 32 - .../gha_config_release_standard_support.yaml | 32 - 32 files changed, 1165 insertions(+), 57330 deletions(-) create mode 100644 .github/actionlint.yaml create mode 100644 .github/workflows/build-all-stable.yml create mode 100644 .github/workflows/build-all.yml create mode 100644 .github/workflows/build-apps.yml rename userpatches/gha/chunks/550.per-chunk-artifacts_job.yaml => .github/workflows/build-artifacts-chunk.yml (56%) create mode 100644 .github/workflows/build-community.yml rename userpatches/gha/chunks/650.per-chunk-images_job.yaml => .github/workflows/build-images-chunk.yml (78%) create mode 100644 .github/workflows/build-nightly.yml create mode 100644 .github/workflows/build-standard-support.yml delete mode 100644 .github/workflows/complete-artifact-matrix-all-stable.yml delete mode 100644 .github/workflows/complete-artifact-matrix-all.yml delete mode 100644 .github/workflows/complete-artifact-matrix-apps.yml delete mode 100644 .github/workflows/complete-artifact-matrix-community-maintained.yml delete mode 100644 .github/workflows/complete-artifact-matrix-nightly.yml delete mode 100644 .github/workflows/complete-artifact-matrix-standard-support.yml create mode 100644 .github/workflows/complete-artifact-matrix.yml delete mode 100644 .github/workflows/recreate-matrix.yml delete mode 100644 .github/workflows/watchdog.yml delete mode 100644 userpatches/gha/chunks/050.single_header.yaml delete mode 100644 userpatches/gha/chunks/150.per-chunk-artifacts_prep-outputs.yaml delete mode 100644 userpatches/gha/chunks/151.per-chunk-images_prep-outputs.yaml delete mode 100644 userpatches/gha/chunks/250.single_aggr-jobs.yaml delete mode 100644 userpatches/gha/chunks/750.single_repo.yaml delete mode 100644 userpatches/gha/chunks/950.single_footer.yaml delete mode 100644 userpatches/gha/gha_config.yaml delete mode 100644 userpatches/gha/gha_config_all.yaml delete mode 100644 userpatches/gha/gha_config_all_stable.yaml delete mode 100644 userpatches/gha/gha_config_nightly.yaml delete mode 100644 userpatches/gha/gha_config_old_stable.yaml delete mode 100644 userpatches/gha/gha_config_release_apps.yaml delete mode 100644 userpatches/gha/gha_config_release_community_maintained.yaml delete mode 100644 userpatches/gha/gha_config_release_standard_support.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000000..b60773ac1e2 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,10 @@ +self-hosted-runner: + # Armbian self-hosted runner labels (not known to actionlint) + labels: + - super + - repository + - repo + - fast + - alfa + - images + - aarch64 diff --git a/.github/workflows/build-all-stable.yml b/.github/workflows/build-all-stable.yml new file mode 100644 index 00000000000..b8ee1e1a82e --- /dev/null +++ b/.github/workflows/build-all-stable.yml @@ -0,0 +1,63 @@ +# Track caller: Build All Stable Artifacts (cronjob). Thin wrapper over complete-artifact-matrix.yml. +name: "Build All Stable Artifacts (cronjob)" + +on: + schedule: + - cron: "0 2 * * *" + workflow_dispatch: + inputs: + skipImages: + description: 'Skip building images? no = build images, yes = skip images' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + checkOci: + description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + extraParamsAllBuilds: + description: 'Extra params for all builds/jobs (eg: DEBUG=yes)' + type: string + default: '' + branch: + description: 'Framework build branch' + type: string + default: 'main' + targetsFilterInclude: + description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' + type: string + default: '' + nightlybuild: + description: 'yes = nightly, no = stable' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + bumpversion: + description: 'Bump version' + type: boolean + default: false + versionOverride: + description: 'Version override' + type: string + default: '' + +concurrency: + group: pipeline + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/complete-artifact-matrix.yml + with: + release_repository: "os" + target_path: "stable/" + team: "Board Maintainers" + repository_ref: "all" + prepare_config: "armbian-images" + targets_filename: "targets-all-not-eos.yaml" + skipimages_default: "yes" + checkoci_default: "yes" + nightlybuild_default: "no" + bumpversion_default: false + secrets: inherit diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml new file mode 100644 index 00000000000..a594b5aaea0 --- /dev/null +++ b/.github/workflows/build-all.yml @@ -0,0 +1,64 @@ +# Track caller: Build All Artifacts (cronjob). Thin wrapper over complete-artifact-matrix.yml. +name: "Build All Artifacts (cronjob)" + +on: + schedule: + - cron: "0 20-23/2,0-4/2 * * *" + - cron: "0 8,14 * * *" + workflow_dispatch: + inputs: + skipImages: + description: 'Skip building images? no = build images, yes = skip images' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + checkOci: + description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + extraParamsAllBuilds: + description: 'Extra params for all builds/jobs (eg: DEBUG=yes)' + type: string + default: '' + branch: + description: 'Framework build branch' + type: string + default: 'main' + targetsFilterInclude: + description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' + type: string + default: '' + nightlybuild: + description: 'yes = nightly, no = stable' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + bumpversion: + description: 'Bump version' + type: boolean + default: true + versionOverride: + description: 'Version override' + type: string + default: '' + +concurrency: + group: pipeline + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/complete-artifact-matrix.yml + with: + release_repository: "os" + target_path: "cron/" + team: "Board Maintainers" + repository_ref: "all" + prepare_config: "armbian-images" + targets_filename: "targets-all-not-eos.yaml" + skipimages_default: "yes" + checkoci_default: "yes" + nightlybuild_default: "yes" + bumpversion_default: true + secrets: inherit diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml new file mode 100644 index 00000000000..05396b6aaba --- /dev/null +++ b/.github/workflows/build-apps.yml @@ -0,0 +1,61 @@ +# Track caller: Build Apps Images (cronjob). Thin wrapper over complete-artifact-matrix.yml. +name: "Build Apps Images (cronjob)" + +on: + workflow_dispatch: + inputs: + skipImages: + description: 'Skip building images? no = build images, yes = skip images' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + checkOci: + description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + extraParamsAllBuilds: + description: 'Extra params for all builds/jobs (eg: DEBUG=yes)' + type: string + default: '' + branch: + description: 'Framework build branch' + type: string + default: 'main' + targetsFilterInclude: + description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' + type: string + default: '' + nightlybuild: + description: 'yes = nightly, no = stable' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + bumpversion: + description: 'Bump version' + type: boolean + default: false + versionOverride: + description: 'Version override' + type: string + default: '' + +concurrency: + group: ${{ github.run_id }}-appsapps + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/complete-artifact-matrix.yml + with: + release_repository: "distribution" + target_path: "apps/" + team: "Release manager" + repository_ref: "stable" + prepare_config: "armbian-images" + targets_filename: "targets-release-apps.yaml" + skipimages_default: "no" + checkoci_default: "yes" + nightlybuild_default: "no" + bumpversion_default: false + secrets: inherit diff --git a/userpatches/gha/chunks/550.per-chunk-artifacts_job.yaml b/.github/workflows/build-artifacts-chunk.yml similarity index 56% rename from userpatches/gha/chunks/550.per-chunk-artifacts_job.yaml rename to .github/workflows/build-artifacts-chunk.yml index 36cdea3096e..29f6d31b416 100644 --- a/userpatches/gha/chunks/550.per-chunk-artifacts_job.yaml +++ b/.github/workflows/build-artifacts-chunk.yml @@ -1,17 +1,43 @@ -on: # - workflow_dispatch: # -jobs: # - "TEMPLATE-JOB-NAME": # - if: ${{ github.repository_owner == '[[org]]' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-[[chunk]] == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] +# Reusable: build ONE chunk of artifacts. +# +# Called (once per chunk) by complete-artifact-matrix.yml via a matrix. The chunk's +# build matrix arrives as JSON in `chunk_json`; everything board/branch-specific lives +# inside that JSON (produced at runtime by `compile.sh gha-matrix`). This file holds the +# artifact-build job body exactly once -- there are no per-chunk copies anywhere. +name: "Build artifacts chunk (reusable)" + +on: + workflow_call: + inputs: + chunk_json: { required: true, type: string } # fromJSON -> strategy.matrix + not_empty: { required: true, type: string } # 'yes' if this chunk has builds + version: { required: true, type: string } + build_sha1: { required: true, type: string } + build_repository: { required: false, type: string, default: "armbian/build" } + userpatches_repository: { required: false, type: string, default: "armbian/os" } + userpatches_ref: { required: false, type: string, default: "main" } + userpatches_dir: { required: false, type: string, default: "userpatches" } + extra_params_all_builds: { required: false, type: string, default: "UPLOAD_TO_OCI_ONLY=yes" } + +# Re-exposed as env so the step bodies read the same names as the rest of the pipeline. +env: + BUILD_REPOSITORY: ${{ inputs.build_repository }} + USERPATCHES_REPOSITORY: ${{ inputs.userpatches_repository }} + USERPATCHES_REF: ${{ inputs.userpatches_ref }} + USERPATCHES_DIR: ${{ inputs.userpatches_dir }} + EXTRA_PARAMS_ALL_BUILDS: ${{ inputs.extra_params_all_builds }} + +jobs: + build: + if: ${{ github.repository_owner == 'armbian' && inputs.not_empty == 'yes' }} strategy: fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-[[chunk]]) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A[[chunk]]' }} # <-- HERE: Chunk number. + matrix: ${{ fromJSON(inputs.chunk_json) }} + name: ${{ matrix.desc || 'Empty artifact chunk' }} runs-on: ${{ matrix.runs_on }} steps: - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" + - name: "Runner clean ${{ inputs.version }}" uses: armbian/actions/runner-clean@main # Login to ghcr.io, for later uploading rootfs to ghcr.io @@ -19,7 +45,7 @@ jobs: # uses: docker/login-action@v4 with: registry: ghcr.io - username: "[[ghcr_user]]" # GitHub username or org + username: "${{ github.repository_owner }}" # GitHub username or org password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later @@ -31,7 +57,7 @@ jobs: # uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. with: repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} + ref: ${{ inputs.build_sha1 }} fetch-depth: ${{ matrix.fdepth }} clean: false # true is default. it *will* delete the hosts /dev if mounted inside. @@ -86,7 +112,21 @@ jobs: # if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} + # Repeat on failure: retry the build up to BUILD_ATTEMPTS times to ride out transient + # runner/network failures. Replaces the old external watchdog cron job. + BUILD_ATTEMPTS="${BUILD_ATTEMPTS:-3}" + for attempt in $(seq 1 "$BUILD_ATTEMPTS"); do + echo "::group::Build attempt ${attempt}/${BUILD_ATTEMPTS}" + if bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ inputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}}; then + echo "::endgroup::" + exit 0 + fi + echo "::endgroup::" + echo "::warning::build attempt ${attempt}/${BUILD_ATTEMPTS} failed" + [ "$attempt" -lt "$BUILD_ATTEMPTS" ] && sleep 30 + done + echo "::error::build failed after ${BUILD_ATTEMPTS} attempts" + exit 1 - name: "Logs: ${{ steps.build.outputs.logs_url }}" if: always() diff --git a/.github/workflows/build-community.yml b/.github/workflows/build-community.yml new file mode 100644 index 00000000000..26832e54064 --- /dev/null +++ b/.github/workflows/build-community.yml @@ -0,0 +1,63 @@ +# Track caller: Build Community Images (cronjob). Thin wrapper over complete-artifact-matrix.yml. +name: "Build Community Images (cronjob)" + +on: + schedule: + - cron: "0 23 * * THU" + workflow_dispatch: + inputs: + skipImages: + description: 'Skip building images? no = build images, yes = skip images' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + checkOci: + description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + extraParamsAllBuilds: + description: 'Extra params for all builds/jobs (eg: DEBUG=yes)' + type: string + default: '' + branch: + description: 'Framework build branch' + type: string + default: 'main' + targetsFilterInclude: + description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' + type: string + default: '' + nightlybuild: + description: 'yes = nightly, no = stable' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + bumpversion: + description: 'Bump version' + type: boolean + default: true + versionOverride: + description: 'Version override' + type: string + default: '' + +concurrency: + group: ${{ github.run_id }}-unsupportedstable + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/complete-artifact-matrix.yml + with: + release_repository: "community" + target_path: "community/" + team: "Release manager" + repository_ref: "stable" + prepare_config: "armbian-community" + targets_filename: "targets-release-community-maintained.yaml" + skipimages_default: "no" + checkoci_default: "yes" + nightlybuild_default: "yes" + bumpversion_default: true + secrets: inherit diff --git a/userpatches/gha/chunks/650.per-chunk-images_job.yaml b/.github/workflows/build-images-chunk.yml similarity index 78% rename from userpatches/gha/chunks/650.per-chunk-images_job.yaml rename to .github/workflows/build-images-chunk.yml index 3cafddfb5bb..8c044ef3c32 100644 --- a/userpatches/gha/chunks/650.per-chunk-images_job.yaml +++ b/.github/workflows/build-images-chunk.yml @@ -1,14 +1,50 @@ -on: # - workflow_dispatch: # -jobs: # - "TEMPLATE-JOB-NAME": # - needs: [ "matrix_prep", "all-artifacts-ready" ] +# Reusable: build ONE chunk of images (and sign/torrent/upload them). +# +# Called once per chunk by complete-artifact-matrix.yml via a matrix. Holds the +# image-build job body exactly once. Named secrets must be declared here and passed in +# by the orchestrator (reusable workflows only auto-inherit GITHUB_TOKEN). +name: "Build images chunk (reusable)" + +on: + workflow_call: + inputs: + chunk_json: { required: true, type: string } + not_empty: { required: true, type: string } + version: { required: true, type: string } + build_sha1: { required: true, type: string } + release_repository: { required: true, type: string } # os | community | distribution + nightlybuild: { required: true, type: string } # effective 'yes' | 'no' + build_repository: { required: false, type: string, default: "armbian/build" } + userpatches_repository: { required: false, type: string, default: "armbian/os" } + userpatches_ref: { required: false, type: string, default: "main" } + userpatches_dir: { required: false, type: string, default: "userpatches" } + extra_params_all_builds: { required: false, type: string, default: "UPLOAD_TO_OCI_ONLY=yes" } + extra_params_image: { required: false, type: string, default: "SHARE_LOG=yes " } + secrets: + ACCESS_TOKEN: { required: true } + KEY_UPLOAD: { required: true } + KNOWN_HOSTS_ARMBIAN_UPLOAD: { required: true } + GPG_KEY1: { required: false } + GPG_PASSPHRASE1: { required: false } + +env: + BUILD_REPOSITORY: ${{ inputs.build_repository }} + USERPATCHES_REPOSITORY: ${{ inputs.userpatches_repository }} + USERPATCHES_REF: ${{ inputs.userpatches_ref }} + USERPATCHES_DIR: ${{ inputs.userpatches_dir }} + RELEASE_REPOSITORY: ${{ inputs.release_repository }} + EXTRA_PARAMS_ALL_BUILDS: ${{ inputs.extra_params_all_builds }} + EXTRA_PARAMS_IMAGE: ${{ inputs.extra_params_image }} + GH_TOKEN: ${{ secrets.ACCESS_TOKEN }} + +jobs: + build: + if: ${{ github.repository_owner == 'armbian' && inputs.not_empty == 'yes' }} timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == '[[org]]' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-[[chunk]] == 'yes' ) }} # <-- HERE: Chunk number. strategy: fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-[[chunk]]) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I[[chunk]]' }} # <-- HERE: Chunk number. + matrix: ${{ fromJSON(inputs.chunk_json) }} + name: ${{ matrix.desc || 'Empty image chunk' }} runs-on: ${{ matrix.runs_on }} steps: @@ -20,7 +56,7 @@ jobs: # fi # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" + - name: "Runner clean ${{ inputs.version }}" uses: armbian/actions/runner-clean@main # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later @@ -32,7 +68,7 @@ jobs: # uses: actions/checkout@v6 with: repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} + ref: ${{ inputs.build_sha1 }} fetch-depth: ${{ matrix.fdepth }} clean: false # true is default. it *will* delete the hosts /dev if mounted inside. @@ -94,7 +130,21 @@ jobs: # if [ -z "${ImageOS}" ]; then USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} + # Repeat on failure: retry the image build up to BUILD_ATTEMPTS times to ride out transient + # runner/network failures. Replaces the old external watchdog cron job. + BUILD_ATTEMPTS="${BUILD_ATTEMPTS:-3}" + for attempt in $(seq 1 "$BUILD_ATTEMPTS"); do + echo "::group::Build attempt ${attempt}/${BUILD_ATTEMPTS}" + if bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ inputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ inputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}}; then + echo "::endgroup::" + exit 0 + fi + echo "::endgroup::" + echo "::warning::image build attempt ${attempt}/${BUILD_ATTEMPTS} failed" + [ "$attempt" -lt "$BUILD_ATTEMPTS" ] && sleep 30 + done + echo "::error::image build failed after ${BUILD_ATTEMPTS} attempts" + exit 1 - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" if: always() @@ -194,11 +244,9 @@ jobs: # exit 1 fi - # Nightly / stable logic (templated) + # Nightly / stable logic RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="[[nightlybuildDefaults]]" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" + effective_nightlybuild="${{ inputs.nightlybuild }}" WEBSEEDS="" @@ -219,12 +267,12 @@ jobs: # ################################################################## # NIGHTLY BUILDS # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/[[release_repo]]// + # Path is FIXED: https://SERVER/cache/// ################################################################## WEBSEEDS=$( jq -r \ - --arg repo "[[release_repo]]" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ + --arg repo "${{ env.RELEASE_REPOSITORY }}" \ + --arg ver "${{ inputs.version }}" \ --arg file "$FILE" ' .[] | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" @@ -235,7 +283,7 @@ jobs: # if [ -n "$WEBSEEDS" ]; then WEBSEEDS+="," fi - WEBSEEDS+="https://github.com/armbian/[[release_repo]]/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" + WEBSEEDS+="https://github.com/armbian/${{ env.RELEASE_REPOSITORY }}/releases/download/${{ inputs.version }}/${FILE}" fi echo "WEBSEEDS: $WEBSEEDS" @@ -274,12 +322,11 @@ jobs: # - name: "Upload artefacts except .asc, .sha and .torrent" timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || [[nightlybuildDefaults]] ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} + if: ${{ inputs.nightlybuild == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} uses: ncipollo/release-action@v1 with: repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" + tag: "${{ inputs.version }}" artifacts: "output/release/output/images/*/*/Armbian_*.*" omitBody: true replacesArtifacts: true @@ -394,12 +441,9 @@ jobs: # done < <(jq -c '.[]' "$json_file") } - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="[[nightlybuildDefaults]]" + effective_nightlybuild="${{ inputs.nightlybuild }}" RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - # Upload to cache servers: only .sha/.asc/.torrent sync_from_json "$tmpdir/cache.jq" cache @@ -409,6 +453,6 @@ jobs: # fi # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" + - name: "Runner clean ${{ inputs.version }}" if: always() uses: armbian/actions/runner-clean@main diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml new file mode 100644 index 00000000000..e20bc6a0713 --- /dev/null +++ b/.github/workflows/build-nightly.yml @@ -0,0 +1,68 @@ +# Track caller: nightly images. Thin wrapper over complete-artifact-matrix.yml. +name: "Build Nightly Images (cronjob)" + +on: + schedule: + - cron: "30 22 * * *" + push: + branches: + - 'main' + paths: + - 'userpatches/targets-release-nightly.yaml' + workflow_dispatch: + inputs: + skipImages: + description: 'Skip building images? no = build images, yes = skip images' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + checkOci: + description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + extraParamsAllBuilds: + description: 'Extra params for all builds/jobs (eg: DEBUG=yes)' + type: string + default: '' + branch: + description: 'Framework build branch' + type: string + default: 'main' + targetsFilterInclude: + description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' + type: string + default: '' + nightlybuild: + description: 'yes = nightly, no = stable' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + bumpversion: + description: 'Bump version' + type: boolean + default: true + versionOverride: + description: 'Version override' + type: string + default: '' + +concurrency: + group: pipeline + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/complete-artifact-matrix.yml + with: + release_repository: "os" + target_path: "nightly/" + team: "Release manager" + repository_ref: "nightly" + prepare_config: "armbian-images" + targets_filename: "targets-release-nightly.yaml" + skipimages_default: "no" + checkoci_default: "yes" + nightlybuild_default: "yes" + bumpversion_default: true + secrets: inherit diff --git a/.github/workflows/build-standard-support.yml b/.github/workflows/build-standard-support.yml new file mode 100644 index 00000000000..2b045ebc8d5 --- /dev/null +++ b/.github/workflows/build-standard-support.yml @@ -0,0 +1,61 @@ +# Track caller: Build Standard Support Images (admin). Thin wrapper over complete-artifact-matrix.yml. +name: "Build Standard Support Images (admin)" + +on: + workflow_dispatch: + inputs: + skipImages: + description: 'Skip building images? no = build images, yes = skip images' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + checkOci: + description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' + type: choice + options: [ 'yes', 'no' ] + default: 'yes' + extraParamsAllBuilds: + description: 'Extra params for all builds/jobs (eg: DEBUG=yes)' + type: string + default: '' + branch: + description: 'Framework build branch' + type: string + default: 'main' + targetsFilterInclude: + description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' + type: string + default: '' + nightlybuild: + description: 'yes = nightly, no = stable' + type: choice + options: [ 'yes', 'no' ] + default: 'no' + bumpversion: + description: 'Bump version' + type: boolean + default: false + versionOverride: + description: 'Version override' + type: string + default: '' + +concurrency: + group: ${{ github.run_id }}-standard-supportstable + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/complete-artifact-matrix.yml + with: + release_repository: "os" + target_path: "stable/" + team: "Release manager" + repository_ref: "stable" + prepare_config: "armbian-images" + targets_filename: "targets-release-standard-support.yaml" + skipimages_default: "no" + checkoci_default: "yes" + nightlybuild_default: "no" + bumpversion_default: false + secrets: inherit diff --git a/.github/workflows/complete-artifact-matrix-all-stable.yml b/.github/workflows/complete-artifact-matrix-all-stable.yml deleted file mode 100644 index fef68389da9..00000000000 --- a/.github/workflows/complete-artifact-matrix-all-stable.yml +++ /dev/null @@ -1,9062 +0,0 @@ - -# template file: 050.single_header.yaml - -name: "Build All Stable Artifacts (cronjob)" - -on: - schedule: - - cron: '0 2 * * *' - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - - main - - kernel-config-nfs-rootfs-vendor - - rootfs-cache-invalidate-on-configng-change - - update-kernel-configs - - fix/bleedingedge-7.2-track-master - - fix/apt-cacher-ng-fallback - - fix/loop-device-mkfs-busy - - bigtreetech-cb1-uboot-2026.01 - - fix/offline-work-respect-cache - - fix/docker-riscv64-cross-compilers - - feat/sysrq-serial-trigger - - xxxxxxx - - v26.05 - - fix/runner-clean-pages-home - - seeed - - artifact-rootfs-fetch-configng - - breakingtest - - nvidia-auto-detect-driver-version - - uefi-riscv64-add-cloud-kernel - - artifact-uboot-debug-h-divergence - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - "150balbes" - - "1ubuntuuser" - - "AGM1968" - - "AaronNGray" - - "CodeChenL" - - "ColorfulRhino" - - "DylanHP" - - "Grippy98" - - "HackingGate" - - "Heisath" - - "HeyMeco" - - "IsMrX" - - "JackHuang021" - - "Janmcha" - - "JohnTheCoolingFan" - - "Kreyren" - - "Lemon1151" - - "NicoD-SBC" - - "PanderMusubi" - - "Pillar1989" - - "Qvy-png" - - "SeeleVolleri" - - "Shadowrom2020" - - "StephenGraf" - - "SuperKali" - - "The-going" - - "TheSnowfield" - - "Tonymac32" - - "ZazaBR" - - "adeepn" - - "ahoneybun" - - "alexl83" - - "amazingfate" - - "andyshrk" - - "baldpope" - - "brentr" - - "catalinii" - - "chainsx" - - "chraac" - - "crackerjacques" - - "devdotnetorg" - - "efectn" - - "eliasbakken" - - "engineer-80" - - "fridtjof" - - "ginkage" - - "glneo" - - "hoochiwetech" - - "hqnicolas" - - "hzyitc" - - "iav" - - "ifroncy01" - - "igorpecovnik" - - "janprunk" - - "jeanrhum" - - "joekhoobyar" - - "jomadeto" - - "jonaswood01" - - "juanesf" - - "juanlufont" - - "kamilsaigol" - - "krachlatte" - - "lbmendes" - - "leggewie" - - "libiunc" - - "linhz0hz" - - "mahdichi" - - "mattx433" - - "mhawkins-consultant" - - "mlegenovic" - - "paolosabatino" - - "prahal" - - "pyavitz" - - "rbqvq" - - "redrathnure" - - "retro98boy" - - "rpardini" - - "schmiedelm" - - "schwar3kat" - - "sgjava" - - "shkolnik" - - "sicXnull" - - "spendist" - - "sputnik2019" - - "sven-ola" - - "teknoid" - - "tomazzaman" - - "torte71" - - "utlark" - - "vamzii" - - "vidplace7" - - "wei633" - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: 'no' - bumpversion: - type: boolean - description: "Bump version" - default: 'false' - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "armbian/build" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "armbian/os" - USERPATCHES_REF: "main" # branch or tag or sha1 - USERPATCHES_DIR: "userpatches" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "os" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "stable/" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || 'false' }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: pipeline - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "Board Maintainers" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || 'no' }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-all - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || 'yes') != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || 'no') == 'yes' && (github.event.inputs.skipImages || 'yes') != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: 17/16 :: 17 artifact chunks, 16 image chunks" - if: ${{ github.repository_owner == 'armbian' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix armbian-images \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || 'no' }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=17 \ - MATRIX_IMAGE_CHUNKS=16 \ - CHECK_OCI=${{ github.event.inputs.checkOci || 'no' }} \ - TARGETS_FILENAME="targets-all-not-eos.yaml" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || 'yes'}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} - -# template file: 150.per-chunk-artifacts_prep-outputs.yaml - - # artifacts-1 of 17 - artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} - artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} - artifacts-chunk-size-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-1 }} - # artifacts-2 of 17 - artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} - artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} - artifacts-chunk-size-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-2 }} - # artifacts-3 of 17 - artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} - artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} - artifacts-chunk-size-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-3 }} - # artifacts-4 of 17 - artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} - artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} - artifacts-chunk-size-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-4 }} - # artifacts-5 of 17 - artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} - artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} - artifacts-chunk-size-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-5 }} - # artifacts-6 of 17 - artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} - artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} - artifacts-chunk-size-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-6 }} - # artifacts-7 of 17 - artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} - artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} - artifacts-chunk-size-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-7 }} - # artifacts-8 of 17 - artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} - artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} - artifacts-chunk-size-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-8 }} - # artifacts-9 of 17 - artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} - artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} - artifacts-chunk-size-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-9 }} - # artifacts-10 of 17 - artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} - artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} - artifacts-chunk-size-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-10 }} - # artifacts-11 of 17 - artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} - artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} - artifacts-chunk-size-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-11 }} - # artifacts-12 of 17 - artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} - artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} - artifacts-chunk-size-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-12 }} - # artifacts-13 of 17 - artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} - artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} - artifacts-chunk-size-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-13 }} - # artifacts-14 of 17 - artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} - artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} - artifacts-chunk-size-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-14 }} - # artifacts-15 of 17 - artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} - artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} - artifacts-chunk-size-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-15 }} - # artifacts-16 of 17 - artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} - artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} - artifacts-chunk-size-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-16 }} - # artifacts-17 of 17 - artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} - artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} - artifacts-chunk-size-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-17 }} - -# template file: 151.per-chunk-images_prep-outputs.yaml - - # artifacts-1 of 16 - images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} - images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} - images-chunk-size-1: ${{ steps.prepare-matrix.outputs.images-chunk-size-1 }} - # artifacts-2 of 16 - images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} - images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} - images-chunk-size-2: ${{ steps.prepare-matrix.outputs.images-chunk-size-2 }} - # artifacts-3 of 16 - images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} - images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} - images-chunk-size-3: ${{ steps.prepare-matrix.outputs.images-chunk-size-3 }} - # artifacts-4 of 16 - images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} - images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} - images-chunk-size-4: ${{ steps.prepare-matrix.outputs.images-chunk-size-4 }} - # artifacts-5 of 16 - images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} - images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} - images-chunk-size-5: ${{ steps.prepare-matrix.outputs.images-chunk-size-5 }} - # artifacts-6 of 16 - images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} - images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} - images-chunk-size-6: ${{ steps.prepare-matrix.outputs.images-chunk-size-6 }} - # artifacts-7 of 16 - images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} - images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} - images-chunk-size-7: ${{ steps.prepare-matrix.outputs.images-chunk-size-7 }} - # artifacts-8 of 16 - images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} - images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} - images-chunk-size-8: ${{ steps.prepare-matrix.outputs.images-chunk-size-8 }} - # artifacts-9 of 16 - images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} - images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} - images-chunk-size-9: ${{ steps.prepare-matrix.outputs.images-chunk-size-9 }} - # artifacts-10 of 16 - images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} - images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} - images-chunk-size-10: ${{ steps.prepare-matrix.outputs.images-chunk-size-10 }} - # artifacts-11 of 16 - images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} - images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} - images-chunk-size-11: ${{ steps.prepare-matrix.outputs.images-chunk-size-11 }} - # artifacts-12 of 16 - images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} - images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} - images-chunk-size-12: ${{ steps.prepare-matrix.outputs.images-chunk-size-12 }} - # artifacts-13 of 16 - images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} - images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} - images-chunk-size-13: ${{ steps.prepare-matrix.outputs.images-chunk-size-13 }} - # artifacts-14 of 16 - images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} - images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} - images-chunk-size-14: ${{ steps.prepare-matrix.outputs.images-chunk-size-14 }} - # artifacts-15 of 16 - images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} - images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} - images-chunk-size-15: ${{ steps.prepare-matrix.outputs.images-chunk-size-15 }} - # artifacts-16 of 16 - images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} - images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} - images-chunk-size-16: ${{ steps.prepare-matrix.outputs.images-chunk-size-16 }} - -# template file: 250.single_aggr-jobs.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "17 artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-artifacts-chunk-1","build-artifacts-chunk-2","build-artifacts-chunk-3","build-artifacts-chunk-4","build-artifacts-chunk-5","build-artifacts-chunk-6","build-artifacts-chunk-7","build-artifacts-chunk-8","build-artifacts-chunk-9","build-artifacts-chunk-10","build-artifacts-chunk-11","build-artifacts-chunk-12","build-artifacts-chunk-13","build-artifacts-chunk-14","build-artifacts-chunk-15","build-artifacts-chunk-16","build-artifacts-chunk-17" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-images-chunk-1","build-images-chunk-2","build-images-chunk-3","build-images-chunk-4","build-images-chunk-5","build-images-chunk-6","build-images-chunk-7","build-images-chunk-8","build-images-chunk-9","build-images-chunk-10","build-images-chunk-11","build-images-chunk-12","build-images-chunk-13","build-images-chunk-14","build-images-chunk-15","build-images-chunk-16" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "17 artifacts and 16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - -# template file: 550.per-chunk-artifacts_job.yaml - - "build-artifacts-chunk-1": # templated "build-artifacts-chunk-1" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-1 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-2": # templated "build-artifacts-chunk-2" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-2 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-3": # templated "build-artifacts-chunk-3" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-3 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-4": # templated "build-artifacts-chunk-4" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-4 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-5": # templated "build-artifacts-chunk-5" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-5 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-6": # templated "build-artifacts-chunk-6" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-6 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-7": # templated "build-artifacts-chunk-7" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-7 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-8": # templated "build-artifacts-chunk-8" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-8 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-9": # templated "build-artifacts-chunk-9" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-9 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-10": # templated "build-artifacts-chunk-10" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-10 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-11": # templated "build-artifacts-chunk-11" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-11 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-12": # templated "build-artifacts-chunk-12" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-12 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-13": # templated "build-artifacts-chunk-13" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-13 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-14": # templated "build-artifacts-chunk-14" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-14 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-15": # templated "build-artifacts-chunk-15" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-15 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-16": # templated "build-artifacts-chunk-16" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-16 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-17": # templated "build-artifacts-chunk-17" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-17 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-17) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A17' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - -# template file: 650.per-chunk-images_job.yaml - - "build-images-chunk-1": # templated "build-images-chunk-1" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-1 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-2": # templated "build-images-chunk-2" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-2 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-3": # templated "build-images-chunk-3" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-3 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-4": # templated "build-images-chunk-4" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-4 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-5": # templated "build-images-chunk-5" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-5 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-6": # templated "build-images-chunk-6" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-6 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-7": # templated "build-images-chunk-7" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-7 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-8": # templated "build-images-chunk-8" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-8 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-9": # templated "build-images-chunk-9" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-9 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-10": # templated "build-images-chunk-10" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-10 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-11": # templated "build-images-chunk-11" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-11 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-12": # templated "build-images-chunk-12" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-12 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-13": # templated "build-images-chunk-13" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-13 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-14": # templated "build-images-chunk-14" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-14 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-15": # templated "build-images-chunk-15" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-15 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-16": # templated "build-images-chunk-16" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-16 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - -# template file: 750.single_repo.yaml - - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || 'yes') == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} - -# template file: 950.single_footer.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || 'no') == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || 'yes') != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-all - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-all - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || 'yes') != 'yes' && (github.event.inputs.nightlybuild || 'no') == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || 'yes') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || 'yes') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/.github/workflows/complete-artifact-matrix-all.yml b/.github/workflows/complete-artifact-matrix-all.yml deleted file mode 100644 index b76db82991d..00000000000 --- a/.github/workflows/complete-artifact-matrix-all.yml +++ /dev/null @@ -1,9063 +0,0 @@ - -# template file: 050.single_header.yaml - -name: "Build All Artifacts (cronjob)" - -on: - schedule: - - cron: '0 20-23/2,0-4/2 * * *' - - cron: '0 8,14 * * *' - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - - main - - kernel-config-nfs-rootfs-vendor - - rootfs-cache-invalidate-on-configng-change - - update-kernel-configs - - fix/bleedingedge-7.2-track-master - - fix/apt-cacher-ng-fallback - - fix/loop-device-mkfs-busy - - bigtreetech-cb1-uboot-2026.01 - - fix/offline-work-respect-cache - - fix/docker-riscv64-cross-compilers - - feat/sysrq-serial-trigger - - xxxxxxx - - v26.05 - - fix/runner-clean-pages-home - - seeed - - artifact-rootfs-fetch-configng - - breakingtest - - nvidia-auto-detect-driver-version - - uefi-riscv64-add-cloud-kernel - - artifact-uboot-debug-h-divergence - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - "150balbes" - - "1ubuntuuser" - - "AGM1968" - - "AaronNGray" - - "CodeChenL" - - "ColorfulRhino" - - "DylanHP" - - "Grippy98" - - "HackingGate" - - "Heisath" - - "HeyMeco" - - "IsMrX" - - "JackHuang021" - - "Janmcha" - - "JohnTheCoolingFan" - - "Kreyren" - - "Lemon1151" - - "NicoD-SBC" - - "PanderMusubi" - - "Pillar1989" - - "Qvy-png" - - "SeeleVolleri" - - "Shadowrom2020" - - "StephenGraf" - - "SuperKali" - - "The-going" - - "TheSnowfield" - - "Tonymac32" - - "ZazaBR" - - "adeepn" - - "ahoneybun" - - "alexl83" - - "amazingfate" - - "andyshrk" - - "baldpope" - - "brentr" - - "catalinii" - - "chainsx" - - "chraac" - - "crackerjacques" - - "devdotnetorg" - - "efectn" - - "eliasbakken" - - "engineer-80" - - "fridtjof" - - "ginkage" - - "glneo" - - "hoochiwetech" - - "hqnicolas" - - "hzyitc" - - "iav" - - "ifroncy01" - - "igorpecovnik" - - "janprunk" - - "jeanrhum" - - "joekhoobyar" - - "jomadeto" - - "jonaswood01" - - "juanesf" - - "juanlufont" - - "kamilsaigol" - - "krachlatte" - - "lbmendes" - - "leggewie" - - "libiunc" - - "linhz0hz" - - "mahdichi" - - "mattx433" - - "mhawkins-consultant" - - "mlegenovic" - - "paolosabatino" - - "prahal" - - "pyavitz" - - "rbqvq" - - "redrathnure" - - "retro98boy" - - "rpardini" - - "schmiedelm" - - "schwar3kat" - - "sgjava" - - "shkolnik" - - "sicXnull" - - "spendist" - - "sputnik2019" - - "sven-ola" - - "teknoid" - - "tomazzaman" - - "torte71" - - "utlark" - - "vamzii" - - "vidplace7" - - "wei633" - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - bumpversion: - type: boolean - description: "Bump version" - default: 'true' - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "armbian/build" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "armbian/os" - USERPATCHES_REF: "main" # branch or tag or sha1 - USERPATCHES_DIR: "userpatches" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "os" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "cron/" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || 'true' }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: pipeline - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "Board Maintainers" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || 'yes' }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-all - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || 'yes') != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || 'yes') == 'yes' && (github.event.inputs.skipImages || 'yes') != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: 17/16 :: 17 artifact chunks, 16 image chunks" - if: ${{ github.repository_owner == 'armbian' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix armbian-images \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || 'yes' }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=17 \ - MATRIX_IMAGE_CHUNKS=16 \ - CHECK_OCI=${{ github.event.inputs.checkOci || 'yes' }} \ - TARGETS_FILENAME="targets-all-not-eos.yaml" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || 'yes'}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} - -# template file: 150.per-chunk-artifacts_prep-outputs.yaml - - # artifacts-1 of 17 - artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} - artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} - artifacts-chunk-size-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-1 }} - # artifacts-2 of 17 - artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} - artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} - artifacts-chunk-size-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-2 }} - # artifacts-3 of 17 - artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} - artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} - artifacts-chunk-size-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-3 }} - # artifacts-4 of 17 - artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} - artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} - artifacts-chunk-size-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-4 }} - # artifacts-5 of 17 - artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} - artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} - artifacts-chunk-size-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-5 }} - # artifacts-6 of 17 - artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} - artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} - artifacts-chunk-size-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-6 }} - # artifacts-7 of 17 - artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} - artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} - artifacts-chunk-size-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-7 }} - # artifacts-8 of 17 - artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} - artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} - artifacts-chunk-size-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-8 }} - # artifacts-9 of 17 - artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} - artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} - artifacts-chunk-size-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-9 }} - # artifacts-10 of 17 - artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} - artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} - artifacts-chunk-size-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-10 }} - # artifacts-11 of 17 - artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} - artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} - artifacts-chunk-size-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-11 }} - # artifacts-12 of 17 - artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} - artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} - artifacts-chunk-size-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-12 }} - # artifacts-13 of 17 - artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} - artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} - artifacts-chunk-size-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-13 }} - # artifacts-14 of 17 - artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} - artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} - artifacts-chunk-size-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-14 }} - # artifacts-15 of 17 - artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} - artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} - artifacts-chunk-size-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-15 }} - # artifacts-16 of 17 - artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} - artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} - artifacts-chunk-size-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-16 }} - # artifacts-17 of 17 - artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} - artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} - artifacts-chunk-size-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-17 }} - -# template file: 151.per-chunk-images_prep-outputs.yaml - - # artifacts-1 of 16 - images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} - images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} - images-chunk-size-1: ${{ steps.prepare-matrix.outputs.images-chunk-size-1 }} - # artifacts-2 of 16 - images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} - images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} - images-chunk-size-2: ${{ steps.prepare-matrix.outputs.images-chunk-size-2 }} - # artifacts-3 of 16 - images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} - images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} - images-chunk-size-3: ${{ steps.prepare-matrix.outputs.images-chunk-size-3 }} - # artifacts-4 of 16 - images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} - images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} - images-chunk-size-4: ${{ steps.prepare-matrix.outputs.images-chunk-size-4 }} - # artifacts-5 of 16 - images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} - images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} - images-chunk-size-5: ${{ steps.prepare-matrix.outputs.images-chunk-size-5 }} - # artifacts-6 of 16 - images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} - images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} - images-chunk-size-6: ${{ steps.prepare-matrix.outputs.images-chunk-size-6 }} - # artifacts-7 of 16 - images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} - images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} - images-chunk-size-7: ${{ steps.prepare-matrix.outputs.images-chunk-size-7 }} - # artifacts-8 of 16 - images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} - images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} - images-chunk-size-8: ${{ steps.prepare-matrix.outputs.images-chunk-size-8 }} - # artifacts-9 of 16 - images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} - images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} - images-chunk-size-9: ${{ steps.prepare-matrix.outputs.images-chunk-size-9 }} - # artifacts-10 of 16 - images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} - images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} - images-chunk-size-10: ${{ steps.prepare-matrix.outputs.images-chunk-size-10 }} - # artifacts-11 of 16 - images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} - images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} - images-chunk-size-11: ${{ steps.prepare-matrix.outputs.images-chunk-size-11 }} - # artifacts-12 of 16 - images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} - images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} - images-chunk-size-12: ${{ steps.prepare-matrix.outputs.images-chunk-size-12 }} - # artifacts-13 of 16 - images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} - images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} - images-chunk-size-13: ${{ steps.prepare-matrix.outputs.images-chunk-size-13 }} - # artifacts-14 of 16 - images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} - images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} - images-chunk-size-14: ${{ steps.prepare-matrix.outputs.images-chunk-size-14 }} - # artifacts-15 of 16 - images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} - images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} - images-chunk-size-15: ${{ steps.prepare-matrix.outputs.images-chunk-size-15 }} - # artifacts-16 of 16 - images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} - images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} - images-chunk-size-16: ${{ steps.prepare-matrix.outputs.images-chunk-size-16 }} - -# template file: 250.single_aggr-jobs.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "17 artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-artifacts-chunk-1","build-artifacts-chunk-2","build-artifacts-chunk-3","build-artifacts-chunk-4","build-artifacts-chunk-5","build-artifacts-chunk-6","build-artifacts-chunk-7","build-artifacts-chunk-8","build-artifacts-chunk-9","build-artifacts-chunk-10","build-artifacts-chunk-11","build-artifacts-chunk-12","build-artifacts-chunk-13","build-artifacts-chunk-14","build-artifacts-chunk-15","build-artifacts-chunk-16","build-artifacts-chunk-17" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-images-chunk-1","build-images-chunk-2","build-images-chunk-3","build-images-chunk-4","build-images-chunk-5","build-images-chunk-6","build-images-chunk-7","build-images-chunk-8","build-images-chunk-9","build-images-chunk-10","build-images-chunk-11","build-images-chunk-12","build-images-chunk-13","build-images-chunk-14","build-images-chunk-15","build-images-chunk-16" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "17 artifacts and 16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - -# template file: 550.per-chunk-artifacts_job.yaml - - "build-artifacts-chunk-1": # templated "build-artifacts-chunk-1" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-1 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-2": # templated "build-artifacts-chunk-2" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-2 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-3": # templated "build-artifacts-chunk-3" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-3 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-4": # templated "build-artifacts-chunk-4" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-4 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-5": # templated "build-artifacts-chunk-5" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-5 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-6": # templated "build-artifacts-chunk-6" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-6 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-7": # templated "build-artifacts-chunk-7" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-7 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-8": # templated "build-artifacts-chunk-8" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-8 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-9": # templated "build-artifacts-chunk-9" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-9 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-10": # templated "build-artifacts-chunk-10" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-10 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-11": # templated "build-artifacts-chunk-11" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-11 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-12": # templated "build-artifacts-chunk-12" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-12 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-13": # templated "build-artifacts-chunk-13" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-13 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-14": # templated "build-artifacts-chunk-14" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-14 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-15": # templated "build-artifacts-chunk-15" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-15 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-16": # templated "build-artifacts-chunk-16" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-16 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-17": # templated "build-artifacts-chunk-17" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-17 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-17) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A17' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - -# template file: 650.per-chunk-images_job.yaml - - "build-images-chunk-1": # templated "build-images-chunk-1" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-1 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-2": # templated "build-images-chunk-2" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-2 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-3": # templated "build-images-chunk-3" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-3 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-4": # templated "build-images-chunk-4" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-4 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-5": # templated "build-images-chunk-5" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-5 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-6": # templated "build-images-chunk-6" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-6 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-7": # templated "build-images-chunk-7" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-7 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-8": # templated "build-images-chunk-8" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-8 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-9": # templated "build-images-chunk-9" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-9 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-10": # templated "build-images-chunk-10" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-10 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-11": # templated "build-images-chunk-11" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-11 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-12": # templated "build-images-chunk-12" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-12 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-13": # templated "build-images-chunk-13" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-13 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-14": # templated "build-images-chunk-14" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-14 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-15": # templated "build-images-chunk-15" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-15 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-16": # templated "build-images-chunk-16" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-16 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - -# template file: 750.single_repo.yaml - - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || 'yes') == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} - -# template file: 950.single_footer.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || 'yes') == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || 'yes') != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-all - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-all - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || 'yes') != 'yes' && (github.event.inputs.nightlybuild || 'yes') == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || 'yes') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || 'yes') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/.github/workflows/complete-artifact-matrix-apps.yml b/.github/workflows/complete-artifact-matrix-apps.yml deleted file mode 100644 index 096284641bc..00000000000 --- a/.github/workflows/complete-artifact-matrix-apps.yml +++ /dev/null @@ -1,9410 +0,0 @@ - -# template file: 050.single_header.yaml - -name: "Build Apps Images (cronjob)" - -on: - - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'no' - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - - main - - kernel-config-nfs-rootfs-vendor - - rootfs-cache-invalidate-on-configng-change - - update-kernel-configs - - fix/bleedingedge-7.2-track-master - - fix/apt-cacher-ng-fallback - - fix/loop-device-mkfs-busy - - bigtreetech-cb1-uboot-2026.01 - - fix/offline-work-respect-cache - - fix/docker-riscv64-cross-compilers - - feat/sysrq-serial-trigger - - xxxxxxx - - v26.05 - - fix/runner-clean-pages-home - - seeed - - artifact-rootfs-fetch-configng - - breakingtest - - nvidia-auto-detect-driver-version - - uefi-riscv64-add-cloud-kernel - - artifact-uboot-debug-h-divergence - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - 9tripod-x3568-v4 - - aml-a311d-cc - - aml-c400-plus - - aml-s805-mxq - - aml-s905d3-cc - - aml-s9xx-box - - aml-t95z-plus - - anbernic-rg-ds - - arduino-uno-q - - armsom-aim7-io - - armsom-cm5-io - - armsom-cm5-rpi-cm4-io - - armsom-forge1 - - armsom-sige1 - - armsom-sige3 - - armsom-sige5 - - armsom-sige7 - - armsom-w3 - - avaota-a1 - - ayn-odin2 - - ayn-odin2mini - - ayn-odin2portal - - ayn-thor - - bananapi - - bananapicm4io - - bananapif3 - - bananapim1plus - - bananapim2plus - - bananapim2pro - - bananapim2s - - bananapim2ultra - - bananapim2zero - - bananapim3 - - bananapim4berry - - bananapim4zero - - bananapim5 - - bananapim5pro - - bananapim64 - - bananapim7 - - bananapipro - - bananapir2 - - bananapir2pro - - bananapir4 - - beaglebadge - - beaglebone-ai64 - - beagleplay - - beagley-ai - - beelinkx2 - - bestv-r3300-l - - bigtreetech-cb1 - - bigtreetech-cb2 - - cainiao-cniot-core - - cherryba-m1 - - cix-acpi - - clearfogbase - - clearfogpro - - clockworkpi-a06 - - cm3588-nas - - coolpi-cm5 - - coolpi-genbook - - cubieboard - - cubieboard2 - - cubietruck - - cyber-aib-rk3588 - - dg-svr-865-tiny - - dshanpi-a1 - - dshanpi-r1 - - dusun-dsom-010r - - easepi-a2 - - easepi-r2 - - ebyte-ecb41-pge - - espressobin - - fine3399 - - firefly-itx-3588j - - firefly-rk3399 - - forlinx-ok3506-s12 - - fxblox-rk1 - - gateway-dk - - gateway-gz80x - - h96-tvbox-3566 - - helios4 - - helios64 - - hikey960 - - hinlink-h28k - - hinlink-h66k - - hinlink-h68k - - hinlink-h88k - - hinlink-hnas - - hinlink-ht2 - - imb3588 - - indiedroid-nova - - inovato-quadra - - jethubj100 - - jethubj200 - - jethubj80 - - jp-tvbox-3566 - - k3picoitx - - khadas-edge - - khadas-edge2 - - khadas-vim1 - - khadas-vim1s - - khadas-vim2 - - khadas-vim3 - - khadas-vim3l - - khadas-vim4 - - kickpik2b - - lafrite - - lckfb-taishanpi - - leez-p710 - - lepotato - - lime - - lime-a33 - - lime-a64 - - lime2 - - longanpi-3h - - longanpi-4b - - lubancat2 - - luckfox-core3566 - - luckfox-lyra-plus - - luckfox-lyra-ultra-w - - luckfox-lyra-zero-w - - luckfox-pico-max - - luckfox-pico-mini - - luckfox-rk3308b-nova - - mangopi-m28k - - mba8mpxl - - mba8mpxl-ras314 - - mba93xxla-mini - - mekotronics-r58-4x4 - - mekotronics-r58-minipc - - mekotronics-r58hd - - mekotronics-r58s2 - - mekotronics-r58x - - mekotronics-r58x-4g - - mekotronics-r58x-pro - - melea1000 - - mixtile-blade3 - - mixtile-core3588e - - mixtile-edge2 - - mk808c - - mksklipad50 - - mkspi - - musebook - - musepipro - - nanopct4 - - nanopct6 - - nanopct6-lts - - nanopi-m5 - - nanopi-m6 - - nanopi-r1 - - nanopi-r1s-h5 - - nanopi-r2c - - nanopi-r2s - - nanopi-r2s-plus - - nanopi-r3s - - nanopi-r3s-lts - - nanopi-r4s - - nanopi-r4se - - nanopi-r5c - - nanopi-r5s - - nanopi-r6c - - nanopi-r6s - - nanopi-r76s - - nanopi-zero2 - - nanopia64 - - nanopiair - - nanopiduo - - nanopiduo2 - - nanopik1plus - - nanopik2-s905 - - nanopim4 - - nanopim4v2 - - nanopineo - - nanopineo2 - - nanopineo2black - - nanopineo3 - - nanopineo4 - - nanopineocore2 - - nanopineoplus2 - - norco-emb-3531 - - numaker-iot-ma35d16f90 - - odroidc1 - - odroidc2 - - odroidc4 - - odroidhc4 - - odroidm1 - - odroidm1s - - odroidm2 - - odroidn2 - - odroidn2l - - odroidxu4 - - olimex-a20-olinuxino-micro - - olimex-teres-a64 - - olinux-som-a13 - - onecloud - - oneplus-kebab - - orangepi-4a - - orangepi-r1 - - orangepi-r1plus - - orangepi-r1plus-lts - - orangepi-rk3399 - - orangepi2 - - orangepi3 - - orangepi3-lts - - orangepi3b - - orangepi4 - - orangepi4-lts - - orangepi4pro - - orangepi5 - - orangepi5-max - - orangepi5-plus - - orangepi5-ultra - - orangepi5b - - orangepi5pro - - orangepilite - - orangepilite2 - - orangepione - - orangepioneplus - - orangepipc - - orangepipc2 - - orangepipcplus - - orangepiplus - - orangepiplus2e - - orangepiprime - - orangepir2s - - orangepirv2 - - orangepiwin - - orangepizero - - orangepizero2 - - orangepizero2w - - orangepizero3 - - orangepizeroplus - - orangepizeroplus2-h3 - - orangepizeroplus2-h5 - - pcduino3 - - photonicat2 - - phytiumpi - - pine64 - - pine64so - - pinebook-a64 - - pinebook-pro - - pinecube - - pineh64 - - pineh64-b - - pocketbeagle2 - - pocketchip-sd - - qemu-uboot-arm64 - - qemu-uboot-x86 - - qemu-uefi-x86 - - qidi-x6 - - quartz64a - - quartz64b - - radxa-cm4-io - - radxa-cm5-io - - radxa-cubie-a5e - - radxa-dragon-q6a - - radxa-e20c - - radxa-e24c - - radxa-e25 - - radxa-e52c - - radxa-e54c - - radxa-nio-12l - - radxa-rock-4d - - radxa-zero - - radxa-zero2 - - radxa-zero3 - - recomputer-rk3576-devkit - - recomputer-rk3588-devkit - - recore - - renegade - - retro-lite-cm5 - - retroidpocket-rp5 - - retroidpocket-rpmini - - rk322x-box - - rk3318-box - - rk3328-heltec - - rk3566-box-demo - - roc-rk3399-pc - - rock-2a - - rock-2f - - rock-3a - - rock-3c - - rock-4se - - rock-5-cm-rpi-cm4-io - - rock-5-cmio - - rock-5-itx - - rock-5a - - rock-5b - - rock-5b-plus - - rock-5c - - rock-5t - - rock-s0 - - rock64 - - rockpi-4a - - rockpi-4b - - rockpi-4bplus - - rockpi-4c - - rockpi-4cplus - - rockpi-e - - rockpi-n10 - - rockpi-s - - rockpro64 - - rpi4b - - sakurapi-rk3308b - - sk-am62-lp - - sk-am62-sip - - sk-am62b - - sk-am62p - - sk-am64b - - sk-am68 - - sk-am69 - - sk-tda4vm - - smart-am40 - - station-m1 - - station-m2 - - station-m3 - - station-p1 - - station-p2 - - sunvell-r69 - - sweet-potato - - tanix-tx6 - - thinkpad-x13s - - tinker-edge-r - - tinkerboard - - tinkerboard-2 - - tmds62levm - - tmds64evm - - tq-smarc2-tqma8mpxs - - tritium-h3 - - tritium-h5 - - turing-rk1 - - udoo - - uefi-arm64 - - uefi-arm64-dt - - uefi-loong64 - - uefi-riscv64 - - uefi-x86 - - visionfive2 - - x96-mate - - x96q - - xiaobao-nas - - xiaomi-elish - - xpressreal-t3 - - xt-q8l-v10 - - youyeetoo-r1-v3 - - youyeetoo-yy3588 - - yy3568 - - z28pro - - zeropi - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - "150balbes" - - "1ubuntuuser" - - "AGM1968" - - "AaronNGray" - - "CodeChenL" - - "ColorfulRhino" - - "DylanHP" - - "Grippy98" - - "HackingGate" - - "Heisath" - - "HeyMeco" - - "IsMrX" - - "JackHuang021" - - "Janmcha" - - "JohnTheCoolingFan" - - "Kreyren" - - "Lemon1151" - - "NicoD-SBC" - - "PanderMusubi" - - "Pillar1989" - - "Qvy-png" - - "SeeleVolleri" - - "Shadowrom2020" - - "StephenGraf" - - "SuperKali" - - "The-going" - - "TheSnowfield" - - "Tonymac32" - - "ZazaBR" - - "adeepn" - - "ahoneybun" - - "alexl83" - - "amazingfate" - - "andyshrk" - - "baldpope" - - "brentr" - - "catalinii" - - "chainsx" - - "chraac" - - "crackerjacques" - - "devdotnetorg" - - "efectn" - - "eliasbakken" - - "engineer-80" - - "fridtjof" - - "ginkage" - - "glneo" - - "hoochiwetech" - - "hqnicolas" - - "hzyitc" - - "iav" - - "ifroncy01" - - "igorpecovnik" - - "janprunk" - - "jeanrhum" - - "joekhoobyar" - - "jomadeto" - - "jonaswood01" - - "juanesf" - - "juanlufont" - - "kamilsaigol" - - "krachlatte" - - "lbmendes" - - "leggewie" - - "libiunc" - - "linhz0hz" - - "mahdichi" - - "mattx433" - - "mhawkins-consultant" - - "mlegenovic" - - "paolosabatino" - - "prahal" - - "pyavitz" - - "rbqvq" - - "redrathnure" - - "retro98boy" - - "rpardini" - - "schmiedelm" - - "schwar3kat" - - "sgjava" - - "shkolnik" - - "sicXnull" - - "spendist" - - "sputnik2019" - - "sven-ola" - - "teknoid" - - "tomazzaman" - - "torte71" - - "utlark" - - "vamzii" - - "vidplace7" - - "wei633" - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: 'no' - bumpversion: - type: boolean - description: "Bump version" - default: 'false' - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "armbian/build" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "armbian/os" - USERPATCHES_REF: "main" # branch or tag or sha1 - USERPATCHES_DIR: "userpatches" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "distribution" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "apps/" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || 'false' }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: ${{ github.run_id }}-appsapps - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "Release manager" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || 'no' }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-stable - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || 'no') == 'yes' && (github.event.inputs.skipImages || 'no') != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: 17/16 :: 17 artifact chunks, 16 image chunks" - if: ${{ github.repository_owner == 'armbian' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix armbian-images \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || 'no' }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=17 \ - MATRIX_IMAGE_CHUNKS=16 \ - CHECK_OCI=${{ github.event.inputs.checkOci || 'no' }} \ - TARGETS_FILENAME="targets-release-apps.yaml" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || 'no'}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} - -# template file: 150.per-chunk-artifacts_prep-outputs.yaml - - # artifacts-1 of 17 - artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} - artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} - artifacts-chunk-size-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-1 }} - # artifacts-2 of 17 - artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} - artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} - artifacts-chunk-size-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-2 }} - # artifacts-3 of 17 - artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} - artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} - artifacts-chunk-size-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-3 }} - # artifacts-4 of 17 - artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} - artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} - artifacts-chunk-size-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-4 }} - # artifacts-5 of 17 - artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} - artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} - artifacts-chunk-size-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-5 }} - # artifacts-6 of 17 - artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} - artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} - artifacts-chunk-size-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-6 }} - # artifacts-7 of 17 - artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} - artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} - artifacts-chunk-size-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-7 }} - # artifacts-8 of 17 - artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} - artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} - artifacts-chunk-size-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-8 }} - # artifacts-9 of 17 - artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} - artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} - artifacts-chunk-size-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-9 }} - # artifacts-10 of 17 - artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} - artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} - artifacts-chunk-size-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-10 }} - # artifacts-11 of 17 - artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} - artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} - artifacts-chunk-size-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-11 }} - # artifacts-12 of 17 - artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} - artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} - artifacts-chunk-size-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-12 }} - # artifacts-13 of 17 - artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} - artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} - artifacts-chunk-size-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-13 }} - # artifacts-14 of 17 - artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} - artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} - artifacts-chunk-size-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-14 }} - # artifacts-15 of 17 - artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} - artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} - artifacts-chunk-size-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-15 }} - # artifacts-16 of 17 - artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} - artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} - artifacts-chunk-size-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-16 }} - # artifacts-17 of 17 - artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} - artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} - artifacts-chunk-size-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-17 }} - -# template file: 151.per-chunk-images_prep-outputs.yaml - - # artifacts-1 of 16 - images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} - images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} - images-chunk-size-1: ${{ steps.prepare-matrix.outputs.images-chunk-size-1 }} - # artifacts-2 of 16 - images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} - images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} - images-chunk-size-2: ${{ steps.prepare-matrix.outputs.images-chunk-size-2 }} - # artifacts-3 of 16 - images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} - images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} - images-chunk-size-3: ${{ steps.prepare-matrix.outputs.images-chunk-size-3 }} - # artifacts-4 of 16 - images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} - images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} - images-chunk-size-4: ${{ steps.prepare-matrix.outputs.images-chunk-size-4 }} - # artifacts-5 of 16 - images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} - images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} - images-chunk-size-5: ${{ steps.prepare-matrix.outputs.images-chunk-size-5 }} - # artifacts-6 of 16 - images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} - images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} - images-chunk-size-6: ${{ steps.prepare-matrix.outputs.images-chunk-size-6 }} - # artifacts-7 of 16 - images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} - images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} - images-chunk-size-7: ${{ steps.prepare-matrix.outputs.images-chunk-size-7 }} - # artifacts-8 of 16 - images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} - images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} - images-chunk-size-8: ${{ steps.prepare-matrix.outputs.images-chunk-size-8 }} - # artifacts-9 of 16 - images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} - images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} - images-chunk-size-9: ${{ steps.prepare-matrix.outputs.images-chunk-size-9 }} - # artifacts-10 of 16 - images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} - images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} - images-chunk-size-10: ${{ steps.prepare-matrix.outputs.images-chunk-size-10 }} - # artifacts-11 of 16 - images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} - images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} - images-chunk-size-11: ${{ steps.prepare-matrix.outputs.images-chunk-size-11 }} - # artifacts-12 of 16 - images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} - images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} - images-chunk-size-12: ${{ steps.prepare-matrix.outputs.images-chunk-size-12 }} - # artifacts-13 of 16 - images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} - images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} - images-chunk-size-13: ${{ steps.prepare-matrix.outputs.images-chunk-size-13 }} - # artifacts-14 of 16 - images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} - images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} - images-chunk-size-14: ${{ steps.prepare-matrix.outputs.images-chunk-size-14 }} - # artifacts-15 of 16 - images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} - images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} - images-chunk-size-15: ${{ steps.prepare-matrix.outputs.images-chunk-size-15 }} - # artifacts-16 of 16 - images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} - images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} - images-chunk-size-16: ${{ steps.prepare-matrix.outputs.images-chunk-size-16 }} - -# template file: 250.single_aggr-jobs.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "17 artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-artifacts-chunk-1","build-artifacts-chunk-2","build-artifacts-chunk-3","build-artifacts-chunk-4","build-artifacts-chunk-5","build-artifacts-chunk-6","build-artifacts-chunk-7","build-artifacts-chunk-8","build-artifacts-chunk-9","build-artifacts-chunk-10","build-artifacts-chunk-11","build-artifacts-chunk-12","build-artifacts-chunk-13","build-artifacts-chunk-14","build-artifacts-chunk-15","build-artifacts-chunk-16","build-artifacts-chunk-17" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-images-chunk-1","build-images-chunk-2","build-images-chunk-3","build-images-chunk-4","build-images-chunk-5","build-images-chunk-6","build-images-chunk-7","build-images-chunk-8","build-images-chunk-9","build-images-chunk-10","build-images-chunk-11","build-images-chunk-12","build-images-chunk-13","build-images-chunk-14","build-images-chunk-15","build-images-chunk-16" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "17 artifacts and 16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - -# template file: 550.per-chunk-artifacts_job.yaml - - "build-artifacts-chunk-1": # templated "build-artifacts-chunk-1" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-1 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-2": # templated "build-artifacts-chunk-2" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-2 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-3": # templated "build-artifacts-chunk-3" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-3 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-4": # templated "build-artifacts-chunk-4" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-4 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-5": # templated "build-artifacts-chunk-5" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-5 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-6": # templated "build-artifacts-chunk-6" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-6 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-7": # templated "build-artifacts-chunk-7" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-7 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-8": # templated "build-artifacts-chunk-8" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-8 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-9": # templated "build-artifacts-chunk-9" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-9 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-10": # templated "build-artifacts-chunk-10" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-10 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-11": # templated "build-artifacts-chunk-11" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-11 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-12": # templated "build-artifacts-chunk-12" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-12 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-13": # templated "build-artifacts-chunk-13" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-13 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-14": # templated "build-artifacts-chunk-14" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-14 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-15": # templated "build-artifacts-chunk-15" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-15 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-16": # templated "build-artifacts-chunk-16" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-16 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-17": # templated "build-artifacts-chunk-17" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-17 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-17) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A17' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - -# template file: 650.per-chunk-images_job.yaml - - "build-images-chunk-1": # templated "build-images-chunk-1" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-1 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-2": # templated "build-images-chunk-2" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-2 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-3": # templated "build-images-chunk-3" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-3 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-4": # templated "build-images-chunk-4" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-4 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-5": # templated "build-images-chunk-5" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-5 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-6": # templated "build-images-chunk-6" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-6 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-7": # templated "build-images-chunk-7" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-7 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-8": # templated "build-images-chunk-8" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-8 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-9": # templated "build-images-chunk-9" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-9 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-10": # templated "build-images-chunk-10" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-10 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-11": # templated "build-images-chunk-11" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-11 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-12": # templated "build-images-chunk-12" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-12 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-13": # templated "build-images-chunk-13" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-13 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-14": # templated "build-images-chunk-14" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-14 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-15": # templated "build-images-chunk-15" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-15 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-16": # templated "build-images-chunk-16" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-16 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/distribution// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "distribution" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/distribution/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - -# template file: 750.single_repo.yaml - - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} - -# template file: 950.single_footer.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || 'no') == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-stable - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-stable - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' && (github.event.inputs.nightlybuild || 'no') == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/.github/workflows/complete-artifact-matrix-community-maintained.yml b/.github/workflows/complete-artifact-matrix-community-maintained.yml deleted file mode 100644 index 4a99af3e68c..00000000000 --- a/.github/workflows/complete-artifact-matrix-community-maintained.yml +++ /dev/null @@ -1,9412 +0,0 @@ - -# template file: 050.single_header.yaml - -name: "Build Community Images (cronjob)" - -on: - schedule: - - cron: '0 23 * * THU' - - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'no' - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - - main - - kernel-config-nfs-rootfs-vendor - - rootfs-cache-invalidate-on-configng-change - - update-kernel-configs - - fix/bleedingedge-7.2-track-master - - fix/apt-cacher-ng-fallback - - fix/loop-device-mkfs-busy - - bigtreetech-cb1-uboot-2026.01 - - fix/offline-work-respect-cache - - fix/docker-riscv64-cross-compilers - - feat/sysrq-serial-trigger - - xxxxxxx - - v26.05 - - fix/runner-clean-pages-home - - seeed - - artifact-rootfs-fetch-configng - - breakingtest - - nvidia-auto-detect-driver-version - - uefi-riscv64-add-cloud-kernel - - artifact-uboot-debug-h-divergence - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - 9tripod-x3568-v4 - - aml-a311d-cc - - aml-c400-plus - - aml-s805-mxq - - aml-s905d3-cc - - aml-s9xx-box - - aml-t95z-plus - - anbernic-rg-ds - - arduino-uno-q - - armsom-aim7-io - - armsom-cm5-io - - armsom-cm5-rpi-cm4-io - - armsom-forge1 - - armsom-sige1 - - armsom-sige3 - - armsom-sige5 - - armsom-sige7 - - armsom-w3 - - avaota-a1 - - ayn-odin2 - - ayn-odin2mini - - ayn-odin2portal - - ayn-thor - - bananapi - - bananapicm4io - - bananapif3 - - bananapim1plus - - bananapim2plus - - bananapim2pro - - bananapim2s - - bananapim2ultra - - bananapim2zero - - bananapim3 - - bananapim4berry - - bananapim4zero - - bananapim5 - - bananapim5pro - - bananapim64 - - bananapim7 - - bananapipro - - bananapir2 - - bananapir2pro - - bananapir4 - - beaglebadge - - beaglebone-ai64 - - beagleplay - - beagley-ai - - beelinkx2 - - bestv-r3300-l - - bigtreetech-cb1 - - bigtreetech-cb2 - - cainiao-cniot-core - - cherryba-m1 - - cix-acpi - - clearfogbase - - clearfogpro - - clockworkpi-a06 - - cm3588-nas - - coolpi-cm5 - - coolpi-genbook - - cubieboard - - cubieboard2 - - cubietruck - - cyber-aib-rk3588 - - dg-svr-865-tiny - - dshanpi-a1 - - dshanpi-r1 - - dusun-dsom-010r - - easepi-a2 - - easepi-r2 - - ebyte-ecb41-pge - - espressobin - - fine3399 - - firefly-itx-3588j - - firefly-rk3399 - - forlinx-ok3506-s12 - - fxblox-rk1 - - gateway-dk - - gateway-gz80x - - h96-tvbox-3566 - - helios4 - - helios64 - - hikey960 - - hinlink-h28k - - hinlink-h66k - - hinlink-h68k - - hinlink-h88k - - hinlink-hnas - - hinlink-ht2 - - imb3588 - - indiedroid-nova - - inovato-quadra - - jethubj100 - - jethubj200 - - jethubj80 - - jp-tvbox-3566 - - k3picoitx - - khadas-edge - - khadas-edge2 - - khadas-vim1 - - khadas-vim1s - - khadas-vim2 - - khadas-vim3 - - khadas-vim3l - - khadas-vim4 - - kickpik2b - - lafrite - - lckfb-taishanpi - - leez-p710 - - lepotato - - lime - - lime-a33 - - lime-a64 - - lime2 - - longanpi-3h - - longanpi-4b - - lubancat2 - - luckfox-core3566 - - luckfox-lyra-plus - - luckfox-lyra-ultra-w - - luckfox-lyra-zero-w - - luckfox-pico-max - - luckfox-pico-mini - - luckfox-rk3308b-nova - - mangopi-m28k - - mba8mpxl - - mba8mpxl-ras314 - - mba93xxla-mini - - mekotronics-r58-4x4 - - mekotronics-r58-minipc - - mekotronics-r58hd - - mekotronics-r58s2 - - mekotronics-r58x - - mekotronics-r58x-4g - - mekotronics-r58x-pro - - melea1000 - - mixtile-blade3 - - mixtile-core3588e - - mixtile-edge2 - - mk808c - - mksklipad50 - - mkspi - - musebook - - musepipro - - nanopct4 - - nanopct6 - - nanopct6-lts - - nanopi-m5 - - nanopi-m6 - - nanopi-r1 - - nanopi-r1s-h5 - - nanopi-r2c - - nanopi-r2s - - nanopi-r2s-plus - - nanopi-r3s - - nanopi-r3s-lts - - nanopi-r4s - - nanopi-r4se - - nanopi-r5c - - nanopi-r5s - - nanopi-r6c - - nanopi-r6s - - nanopi-r76s - - nanopi-zero2 - - nanopia64 - - nanopiair - - nanopiduo - - nanopiduo2 - - nanopik1plus - - nanopik2-s905 - - nanopim4 - - nanopim4v2 - - nanopineo - - nanopineo2 - - nanopineo2black - - nanopineo3 - - nanopineo4 - - nanopineocore2 - - nanopineoplus2 - - norco-emb-3531 - - numaker-iot-ma35d16f90 - - odroidc1 - - odroidc2 - - odroidc4 - - odroidhc4 - - odroidm1 - - odroidm1s - - odroidm2 - - odroidn2 - - odroidn2l - - odroidxu4 - - olimex-a20-olinuxino-micro - - olimex-teres-a64 - - olinux-som-a13 - - onecloud - - oneplus-kebab - - orangepi-4a - - orangepi-r1 - - orangepi-r1plus - - orangepi-r1plus-lts - - orangepi-rk3399 - - orangepi2 - - orangepi3 - - orangepi3-lts - - orangepi3b - - orangepi4 - - orangepi4-lts - - orangepi4pro - - orangepi5 - - orangepi5-max - - orangepi5-plus - - orangepi5-ultra - - orangepi5b - - orangepi5pro - - orangepilite - - orangepilite2 - - orangepione - - orangepioneplus - - orangepipc - - orangepipc2 - - orangepipcplus - - orangepiplus - - orangepiplus2e - - orangepiprime - - orangepir2s - - orangepirv2 - - orangepiwin - - orangepizero - - orangepizero2 - - orangepizero2w - - orangepizero3 - - orangepizeroplus - - orangepizeroplus2-h3 - - orangepizeroplus2-h5 - - pcduino3 - - photonicat2 - - phytiumpi - - pine64 - - pine64so - - pinebook-a64 - - pinebook-pro - - pinecube - - pineh64 - - pineh64-b - - pocketbeagle2 - - pocketchip-sd - - qemu-uboot-arm64 - - qemu-uboot-x86 - - qemu-uefi-x86 - - qidi-x6 - - quartz64a - - quartz64b - - radxa-cm4-io - - radxa-cm5-io - - radxa-cubie-a5e - - radxa-dragon-q6a - - radxa-e20c - - radxa-e24c - - radxa-e25 - - radxa-e52c - - radxa-e54c - - radxa-nio-12l - - radxa-rock-4d - - radxa-zero - - radxa-zero2 - - radxa-zero3 - - recomputer-rk3576-devkit - - recomputer-rk3588-devkit - - recore - - renegade - - retro-lite-cm5 - - retroidpocket-rp5 - - retroidpocket-rpmini - - rk322x-box - - rk3318-box - - rk3328-heltec - - rk3566-box-demo - - roc-rk3399-pc - - rock-2a - - rock-2f - - rock-3a - - rock-3c - - rock-4se - - rock-5-cm-rpi-cm4-io - - rock-5-cmio - - rock-5-itx - - rock-5a - - rock-5b - - rock-5b-plus - - rock-5c - - rock-5t - - rock-s0 - - rock64 - - rockpi-4a - - rockpi-4b - - rockpi-4bplus - - rockpi-4c - - rockpi-4cplus - - rockpi-e - - rockpi-n10 - - rockpi-s - - rockpro64 - - rpi4b - - sakurapi-rk3308b - - sk-am62-lp - - sk-am62-sip - - sk-am62b - - sk-am62p - - sk-am64b - - sk-am68 - - sk-am69 - - sk-tda4vm - - smart-am40 - - station-m1 - - station-m2 - - station-m3 - - station-p1 - - station-p2 - - sunvell-r69 - - sweet-potato - - tanix-tx6 - - thinkpad-x13s - - tinker-edge-r - - tinkerboard - - tinkerboard-2 - - tmds62levm - - tmds64evm - - tq-smarc2-tqma8mpxs - - tritium-h3 - - tritium-h5 - - turing-rk1 - - udoo - - uefi-arm64 - - uefi-arm64-dt - - uefi-loong64 - - uefi-riscv64 - - uefi-x86 - - visionfive2 - - x96-mate - - x96q - - xiaobao-nas - - xiaomi-elish - - xpressreal-t3 - - xt-q8l-v10 - - youyeetoo-r1-v3 - - youyeetoo-yy3588 - - yy3568 - - z28pro - - zeropi - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - "150balbes" - - "1ubuntuuser" - - "AGM1968" - - "AaronNGray" - - "CodeChenL" - - "ColorfulRhino" - - "DylanHP" - - "Grippy98" - - "HackingGate" - - "Heisath" - - "HeyMeco" - - "IsMrX" - - "JackHuang021" - - "Janmcha" - - "JohnTheCoolingFan" - - "Kreyren" - - "Lemon1151" - - "NicoD-SBC" - - "PanderMusubi" - - "Pillar1989" - - "Qvy-png" - - "SeeleVolleri" - - "Shadowrom2020" - - "StephenGraf" - - "SuperKali" - - "The-going" - - "TheSnowfield" - - "Tonymac32" - - "ZazaBR" - - "adeepn" - - "ahoneybun" - - "alexl83" - - "amazingfate" - - "andyshrk" - - "baldpope" - - "brentr" - - "catalinii" - - "chainsx" - - "chraac" - - "crackerjacques" - - "devdotnetorg" - - "efectn" - - "eliasbakken" - - "engineer-80" - - "fridtjof" - - "ginkage" - - "glneo" - - "hoochiwetech" - - "hqnicolas" - - "hzyitc" - - "iav" - - "ifroncy01" - - "igorpecovnik" - - "janprunk" - - "jeanrhum" - - "joekhoobyar" - - "jomadeto" - - "jonaswood01" - - "juanesf" - - "juanlufont" - - "kamilsaigol" - - "krachlatte" - - "lbmendes" - - "leggewie" - - "libiunc" - - "linhz0hz" - - "mahdichi" - - "mattx433" - - "mhawkins-consultant" - - "mlegenovic" - - "paolosabatino" - - "prahal" - - "pyavitz" - - "rbqvq" - - "redrathnure" - - "retro98boy" - - "rpardini" - - "schmiedelm" - - "schwar3kat" - - "sgjava" - - "shkolnik" - - "sicXnull" - - "spendist" - - "sputnik2019" - - "sven-ola" - - "teknoid" - - "tomazzaman" - - "torte71" - - "utlark" - - "vamzii" - - "vidplace7" - - "wei633" - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - bumpversion: - type: boolean - description: "Bump version" - default: 'true' - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "armbian/build" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "armbian/os" - USERPATCHES_REF: "main" # branch or tag or sha1 - USERPATCHES_DIR: "userpatches" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "community" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "community/" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || 'true' }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: ${{ github.run_id }}-unsupportedstable - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "Release manager" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || 'yes' }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-stable - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || 'yes') == 'yes' && (github.event.inputs.skipImages || 'no') != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: 17/16 :: 17 artifact chunks, 16 image chunks" - if: ${{ github.repository_owner == 'armbian' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix armbian-community \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || 'yes' }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=17 \ - MATRIX_IMAGE_CHUNKS=16 \ - CHECK_OCI=${{ github.event.inputs.checkOci || 'yes' }} \ - TARGETS_FILENAME="targets-release-community-maintained.yaml" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || 'no'}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} - -# template file: 150.per-chunk-artifacts_prep-outputs.yaml - - # artifacts-1 of 17 - artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} - artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} - artifacts-chunk-size-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-1 }} - # artifacts-2 of 17 - artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} - artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} - artifacts-chunk-size-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-2 }} - # artifacts-3 of 17 - artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} - artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} - artifacts-chunk-size-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-3 }} - # artifacts-4 of 17 - artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} - artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} - artifacts-chunk-size-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-4 }} - # artifacts-5 of 17 - artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} - artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} - artifacts-chunk-size-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-5 }} - # artifacts-6 of 17 - artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} - artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} - artifacts-chunk-size-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-6 }} - # artifacts-7 of 17 - artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} - artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} - artifacts-chunk-size-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-7 }} - # artifacts-8 of 17 - artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} - artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} - artifacts-chunk-size-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-8 }} - # artifacts-9 of 17 - artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} - artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} - artifacts-chunk-size-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-9 }} - # artifacts-10 of 17 - artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} - artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} - artifacts-chunk-size-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-10 }} - # artifacts-11 of 17 - artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} - artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} - artifacts-chunk-size-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-11 }} - # artifacts-12 of 17 - artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} - artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} - artifacts-chunk-size-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-12 }} - # artifacts-13 of 17 - artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} - artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} - artifacts-chunk-size-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-13 }} - # artifacts-14 of 17 - artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} - artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} - artifacts-chunk-size-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-14 }} - # artifacts-15 of 17 - artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} - artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} - artifacts-chunk-size-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-15 }} - # artifacts-16 of 17 - artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} - artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} - artifacts-chunk-size-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-16 }} - # artifacts-17 of 17 - artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} - artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} - artifacts-chunk-size-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-17 }} - -# template file: 151.per-chunk-images_prep-outputs.yaml - - # artifacts-1 of 16 - images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} - images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} - images-chunk-size-1: ${{ steps.prepare-matrix.outputs.images-chunk-size-1 }} - # artifacts-2 of 16 - images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} - images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} - images-chunk-size-2: ${{ steps.prepare-matrix.outputs.images-chunk-size-2 }} - # artifacts-3 of 16 - images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} - images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} - images-chunk-size-3: ${{ steps.prepare-matrix.outputs.images-chunk-size-3 }} - # artifacts-4 of 16 - images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} - images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} - images-chunk-size-4: ${{ steps.prepare-matrix.outputs.images-chunk-size-4 }} - # artifacts-5 of 16 - images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} - images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} - images-chunk-size-5: ${{ steps.prepare-matrix.outputs.images-chunk-size-5 }} - # artifacts-6 of 16 - images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} - images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} - images-chunk-size-6: ${{ steps.prepare-matrix.outputs.images-chunk-size-6 }} - # artifacts-7 of 16 - images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} - images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} - images-chunk-size-7: ${{ steps.prepare-matrix.outputs.images-chunk-size-7 }} - # artifacts-8 of 16 - images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} - images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} - images-chunk-size-8: ${{ steps.prepare-matrix.outputs.images-chunk-size-8 }} - # artifacts-9 of 16 - images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} - images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} - images-chunk-size-9: ${{ steps.prepare-matrix.outputs.images-chunk-size-9 }} - # artifacts-10 of 16 - images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} - images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} - images-chunk-size-10: ${{ steps.prepare-matrix.outputs.images-chunk-size-10 }} - # artifacts-11 of 16 - images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} - images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} - images-chunk-size-11: ${{ steps.prepare-matrix.outputs.images-chunk-size-11 }} - # artifacts-12 of 16 - images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} - images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} - images-chunk-size-12: ${{ steps.prepare-matrix.outputs.images-chunk-size-12 }} - # artifacts-13 of 16 - images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} - images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} - images-chunk-size-13: ${{ steps.prepare-matrix.outputs.images-chunk-size-13 }} - # artifacts-14 of 16 - images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} - images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} - images-chunk-size-14: ${{ steps.prepare-matrix.outputs.images-chunk-size-14 }} - # artifacts-15 of 16 - images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} - images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} - images-chunk-size-15: ${{ steps.prepare-matrix.outputs.images-chunk-size-15 }} - # artifacts-16 of 16 - images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} - images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} - images-chunk-size-16: ${{ steps.prepare-matrix.outputs.images-chunk-size-16 }} - -# template file: 250.single_aggr-jobs.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "17 artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-artifacts-chunk-1","build-artifacts-chunk-2","build-artifacts-chunk-3","build-artifacts-chunk-4","build-artifacts-chunk-5","build-artifacts-chunk-6","build-artifacts-chunk-7","build-artifacts-chunk-8","build-artifacts-chunk-9","build-artifacts-chunk-10","build-artifacts-chunk-11","build-artifacts-chunk-12","build-artifacts-chunk-13","build-artifacts-chunk-14","build-artifacts-chunk-15","build-artifacts-chunk-16","build-artifacts-chunk-17" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-images-chunk-1","build-images-chunk-2","build-images-chunk-3","build-images-chunk-4","build-images-chunk-5","build-images-chunk-6","build-images-chunk-7","build-images-chunk-8","build-images-chunk-9","build-images-chunk-10","build-images-chunk-11","build-images-chunk-12","build-images-chunk-13","build-images-chunk-14","build-images-chunk-15","build-images-chunk-16" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "17 artifacts and 16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - -# template file: 550.per-chunk-artifacts_job.yaml - - "build-artifacts-chunk-1": # templated "build-artifacts-chunk-1" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-1 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-2": # templated "build-artifacts-chunk-2" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-2 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-3": # templated "build-artifacts-chunk-3" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-3 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-4": # templated "build-artifacts-chunk-4" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-4 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-5": # templated "build-artifacts-chunk-5" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-5 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-6": # templated "build-artifacts-chunk-6" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-6 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-7": # templated "build-artifacts-chunk-7" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-7 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-8": # templated "build-artifacts-chunk-8" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-8 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-9": # templated "build-artifacts-chunk-9" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-9 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-10": # templated "build-artifacts-chunk-10" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-10 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-11": # templated "build-artifacts-chunk-11" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-11 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-12": # templated "build-artifacts-chunk-12" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-12 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-13": # templated "build-artifacts-chunk-13" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-13 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-14": # templated "build-artifacts-chunk-14" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-14 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-15": # templated "build-artifacts-chunk-15" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-15 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-16": # templated "build-artifacts-chunk-16" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-16 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-17": # templated "build-artifacts-chunk-17" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-17 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-17) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A17' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - -# template file: 650.per-chunk-images_job.yaml - - "build-images-chunk-1": # templated "build-images-chunk-1" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-1 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-2": # templated "build-images-chunk-2" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-2 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-3": # templated "build-images-chunk-3" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-3 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-4": # templated "build-images-chunk-4" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-4 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-5": # templated "build-images-chunk-5" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-5 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-6": # templated "build-images-chunk-6" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-6 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-7": # templated "build-images-chunk-7" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-7 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-8": # templated "build-images-chunk-8" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-8 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-9": # templated "build-images-chunk-9" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-9 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-10": # templated "build-images-chunk-10" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-10 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-11": # templated "build-images-chunk-11" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-11 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-12": # templated "build-images-chunk-12" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-12 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-13": # templated "build-images-chunk-13" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-13 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-14": # templated "build-images-chunk-14" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-14 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-15": # templated "build-images-chunk-15" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-15 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-16": # templated "build-images-chunk-16" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-16 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/community// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "community" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/community/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - -# template file: 750.single_repo.yaml - - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} - -# template file: 950.single_footer.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || 'yes') == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-stable - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-stable - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' && (github.event.inputs.nightlybuild || 'yes') == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/.github/workflows/complete-artifact-matrix-nightly.yml b/.github/workflows/complete-artifact-matrix-nightly.yml deleted file mode 100644 index c2f356bc8d4..00000000000 --- a/.github/workflows/complete-artifact-matrix-nightly.yml +++ /dev/null @@ -1,9416 +0,0 @@ - -# template file: 050.single_header.yaml - -name: "Build Nightly Images (cronjob)" - -on: - schedule: - - cron: '30 22 * * *' - push: - branches: - - 'main' - paths: - - 'userpatches/targets-release-nightly.yaml' - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'no' - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - - main - - kernel-config-nfs-rootfs-vendor - - rootfs-cache-invalidate-on-configng-change - - update-kernel-configs - - fix/bleedingedge-7.2-track-master - - fix/apt-cacher-ng-fallback - - fix/loop-device-mkfs-busy - - bigtreetech-cb1-uboot-2026.01 - - fix/offline-work-respect-cache - - fix/docker-riscv64-cross-compilers - - feat/sysrq-serial-trigger - - xxxxxxx - - v26.05 - - fix/runner-clean-pages-home - - seeed - - artifact-rootfs-fetch-configng - - breakingtest - - nvidia-auto-detect-driver-version - - uefi-riscv64-add-cloud-kernel - - artifact-uboot-debug-h-divergence - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - 9tripod-x3568-v4 - - aml-a311d-cc - - aml-c400-plus - - aml-s805-mxq - - aml-s905d3-cc - - aml-s9xx-box - - aml-t95z-plus - - anbernic-rg-ds - - arduino-uno-q - - armsom-aim7-io - - armsom-cm5-io - - armsom-cm5-rpi-cm4-io - - armsom-forge1 - - armsom-sige1 - - armsom-sige3 - - armsom-sige5 - - armsom-sige7 - - armsom-w3 - - avaota-a1 - - ayn-odin2 - - ayn-odin2mini - - ayn-odin2portal - - ayn-thor - - bananapi - - bananapicm4io - - bananapif3 - - bananapim1plus - - bananapim2plus - - bananapim2pro - - bananapim2s - - bananapim2ultra - - bananapim2zero - - bananapim3 - - bananapim4berry - - bananapim4zero - - bananapim5 - - bananapim5pro - - bananapim64 - - bananapim7 - - bananapipro - - bananapir2 - - bananapir2pro - - bananapir4 - - beaglebadge - - beaglebone-ai64 - - beagleplay - - beagley-ai - - beelinkx2 - - bestv-r3300-l - - bigtreetech-cb1 - - bigtreetech-cb2 - - cainiao-cniot-core - - cherryba-m1 - - cix-acpi - - clearfogbase - - clearfogpro - - clockworkpi-a06 - - cm3588-nas - - coolpi-cm5 - - coolpi-genbook - - cubieboard - - cubieboard2 - - cubietruck - - cyber-aib-rk3588 - - dg-svr-865-tiny - - dshanpi-a1 - - dshanpi-r1 - - dusun-dsom-010r - - easepi-a2 - - easepi-r2 - - ebyte-ecb41-pge - - espressobin - - fine3399 - - firefly-itx-3588j - - firefly-rk3399 - - forlinx-ok3506-s12 - - fxblox-rk1 - - gateway-dk - - gateway-gz80x - - h96-tvbox-3566 - - helios4 - - helios64 - - hikey960 - - hinlink-h28k - - hinlink-h66k - - hinlink-h68k - - hinlink-h88k - - hinlink-hnas - - hinlink-ht2 - - imb3588 - - indiedroid-nova - - inovato-quadra - - jethubj100 - - jethubj200 - - jethubj80 - - jp-tvbox-3566 - - k3picoitx - - khadas-edge - - khadas-edge2 - - khadas-vim1 - - khadas-vim1s - - khadas-vim2 - - khadas-vim3 - - khadas-vim3l - - khadas-vim4 - - kickpik2b - - lafrite - - lckfb-taishanpi - - leez-p710 - - lepotato - - lime - - lime-a33 - - lime-a64 - - lime2 - - longanpi-3h - - longanpi-4b - - lubancat2 - - luckfox-core3566 - - luckfox-lyra-plus - - luckfox-lyra-ultra-w - - luckfox-lyra-zero-w - - luckfox-pico-max - - luckfox-pico-mini - - luckfox-rk3308b-nova - - mangopi-m28k - - mba8mpxl - - mba8mpxl-ras314 - - mba93xxla-mini - - mekotronics-r58-4x4 - - mekotronics-r58-minipc - - mekotronics-r58hd - - mekotronics-r58s2 - - mekotronics-r58x - - mekotronics-r58x-4g - - mekotronics-r58x-pro - - melea1000 - - mixtile-blade3 - - mixtile-core3588e - - mixtile-edge2 - - mk808c - - mksklipad50 - - mkspi - - musebook - - musepipro - - nanopct4 - - nanopct6 - - nanopct6-lts - - nanopi-m5 - - nanopi-m6 - - nanopi-r1 - - nanopi-r1s-h5 - - nanopi-r2c - - nanopi-r2s - - nanopi-r2s-plus - - nanopi-r3s - - nanopi-r3s-lts - - nanopi-r4s - - nanopi-r4se - - nanopi-r5c - - nanopi-r5s - - nanopi-r6c - - nanopi-r6s - - nanopi-r76s - - nanopi-zero2 - - nanopia64 - - nanopiair - - nanopiduo - - nanopiduo2 - - nanopik1plus - - nanopik2-s905 - - nanopim4 - - nanopim4v2 - - nanopineo - - nanopineo2 - - nanopineo2black - - nanopineo3 - - nanopineo4 - - nanopineocore2 - - nanopineoplus2 - - norco-emb-3531 - - numaker-iot-ma35d16f90 - - odroidc1 - - odroidc2 - - odroidc4 - - odroidhc4 - - odroidm1 - - odroidm1s - - odroidm2 - - odroidn2 - - odroidn2l - - odroidxu4 - - olimex-a20-olinuxino-micro - - olimex-teres-a64 - - olinux-som-a13 - - onecloud - - oneplus-kebab - - orangepi-4a - - orangepi-r1 - - orangepi-r1plus - - orangepi-r1plus-lts - - orangepi-rk3399 - - orangepi2 - - orangepi3 - - orangepi3-lts - - orangepi3b - - orangepi4 - - orangepi4-lts - - orangepi4pro - - orangepi5 - - orangepi5-max - - orangepi5-plus - - orangepi5-ultra - - orangepi5b - - orangepi5pro - - orangepilite - - orangepilite2 - - orangepione - - orangepioneplus - - orangepipc - - orangepipc2 - - orangepipcplus - - orangepiplus - - orangepiplus2e - - orangepiprime - - orangepir2s - - orangepirv2 - - orangepiwin - - orangepizero - - orangepizero2 - - orangepizero2w - - orangepizero3 - - orangepizeroplus - - orangepizeroplus2-h3 - - orangepizeroplus2-h5 - - pcduino3 - - photonicat2 - - phytiumpi - - pine64 - - pine64so - - pinebook-a64 - - pinebook-pro - - pinecube - - pineh64 - - pineh64-b - - pocketbeagle2 - - pocketchip-sd - - qemu-uboot-arm64 - - qemu-uboot-x86 - - qemu-uefi-x86 - - qidi-x6 - - quartz64a - - quartz64b - - radxa-cm4-io - - radxa-cm5-io - - radxa-cubie-a5e - - radxa-dragon-q6a - - radxa-e20c - - radxa-e24c - - radxa-e25 - - radxa-e52c - - radxa-e54c - - radxa-nio-12l - - radxa-rock-4d - - radxa-zero - - radxa-zero2 - - radxa-zero3 - - recomputer-rk3576-devkit - - recomputer-rk3588-devkit - - recore - - renegade - - retro-lite-cm5 - - retroidpocket-rp5 - - retroidpocket-rpmini - - rk322x-box - - rk3318-box - - rk3328-heltec - - rk3566-box-demo - - roc-rk3399-pc - - rock-2a - - rock-2f - - rock-3a - - rock-3c - - rock-4se - - rock-5-cm-rpi-cm4-io - - rock-5-cmio - - rock-5-itx - - rock-5a - - rock-5b - - rock-5b-plus - - rock-5c - - rock-5t - - rock-s0 - - rock64 - - rockpi-4a - - rockpi-4b - - rockpi-4bplus - - rockpi-4c - - rockpi-4cplus - - rockpi-e - - rockpi-n10 - - rockpi-s - - rockpro64 - - rpi4b - - sakurapi-rk3308b - - sk-am62-lp - - sk-am62-sip - - sk-am62b - - sk-am62p - - sk-am64b - - sk-am68 - - sk-am69 - - sk-tda4vm - - smart-am40 - - station-m1 - - station-m2 - - station-m3 - - station-p1 - - station-p2 - - sunvell-r69 - - sweet-potato - - tanix-tx6 - - thinkpad-x13s - - tinker-edge-r - - tinkerboard - - tinkerboard-2 - - tmds62levm - - tmds64evm - - tq-smarc2-tqma8mpxs - - tritium-h3 - - tritium-h5 - - turing-rk1 - - udoo - - uefi-arm64 - - uefi-arm64-dt - - uefi-loong64 - - uefi-riscv64 - - uefi-x86 - - visionfive2 - - x96-mate - - x96q - - xiaobao-nas - - xiaomi-elish - - xpressreal-t3 - - xt-q8l-v10 - - youyeetoo-r1-v3 - - youyeetoo-yy3588 - - yy3568 - - z28pro - - zeropi - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - "150balbes" - - "1ubuntuuser" - - "AGM1968" - - "AaronNGray" - - "CodeChenL" - - "ColorfulRhino" - - "DylanHP" - - "Grippy98" - - "HackingGate" - - "Heisath" - - "HeyMeco" - - "IsMrX" - - "JackHuang021" - - "Janmcha" - - "JohnTheCoolingFan" - - "Kreyren" - - "Lemon1151" - - "NicoD-SBC" - - "PanderMusubi" - - "Pillar1989" - - "Qvy-png" - - "SeeleVolleri" - - "Shadowrom2020" - - "StephenGraf" - - "SuperKali" - - "The-going" - - "TheSnowfield" - - "Tonymac32" - - "ZazaBR" - - "adeepn" - - "ahoneybun" - - "alexl83" - - "amazingfate" - - "andyshrk" - - "baldpope" - - "brentr" - - "catalinii" - - "chainsx" - - "chraac" - - "crackerjacques" - - "devdotnetorg" - - "efectn" - - "eliasbakken" - - "engineer-80" - - "fridtjof" - - "ginkage" - - "glneo" - - "hoochiwetech" - - "hqnicolas" - - "hzyitc" - - "iav" - - "ifroncy01" - - "igorpecovnik" - - "janprunk" - - "jeanrhum" - - "joekhoobyar" - - "jomadeto" - - "jonaswood01" - - "juanesf" - - "juanlufont" - - "kamilsaigol" - - "krachlatte" - - "lbmendes" - - "leggewie" - - "libiunc" - - "linhz0hz" - - "mahdichi" - - "mattx433" - - "mhawkins-consultant" - - "mlegenovic" - - "paolosabatino" - - "prahal" - - "pyavitz" - - "rbqvq" - - "redrathnure" - - "retro98boy" - - "rpardini" - - "schmiedelm" - - "schwar3kat" - - "sgjava" - - "shkolnik" - - "sicXnull" - - "spendist" - - "sputnik2019" - - "sven-ola" - - "teknoid" - - "tomazzaman" - - "torte71" - - "utlark" - - "vamzii" - - "vidplace7" - - "wei633" - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - bumpversion: - type: boolean - description: "Bump version" - default: 'true' - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "armbian/build" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "armbian/os" - USERPATCHES_REF: "main" # branch or tag or sha1 - USERPATCHES_DIR: "userpatches" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "os" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "nightly/" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || 'true' }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: pipeline - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "Release manager" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || 'yes' }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-nightly - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || 'yes') == 'yes' && (github.event.inputs.skipImages || 'no') != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: 17/16 :: 17 artifact chunks, 16 image chunks" - if: ${{ github.repository_owner == 'armbian' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix armbian-images \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || 'yes' }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=17 \ - MATRIX_IMAGE_CHUNKS=16 \ - CHECK_OCI=${{ github.event.inputs.checkOci || 'yes' }} \ - TARGETS_FILENAME="targets-release-nightly.yaml" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || 'no'}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} - -# template file: 150.per-chunk-artifacts_prep-outputs.yaml - - # artifacts-1 of 17 - artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} - artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} - artifacts-chunk-size-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-1 }} - # artifacts-2 of 17 - artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} - artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} - artifacts-chunk-size-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-2 }} - # artifacts-3 of 17 - artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} - artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} - artifacts-chunk-size-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-3 }} - # artifacts-4 of 17 - artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} - artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} - artifacts-chunk-size-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-4 }} - # artifacts-5 of 17 - artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} - artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} - artifacts-chunk-size-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-5 }} - # artifacts-6 of 17 - artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} - artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} - artifacts-chunk-size-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-6 }} - # artifacts-7 of 17 - artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} - artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} - artifacts-chunk-size-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-7 }} - # artifacts-8 of 17 - artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} - artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} - artifacts-chunk-size-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-8 }} - # artifacts-9 of 17 - artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} - artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} - artifacts-chunk-size-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-9 }} - # artifacts-10 of 17 - artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} - artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} - artifacts-chunk-size-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-10 }} - # artifacts-11 of 17 - artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} - artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} - artifacts-chunk-size-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-11 }} - # artifacts-12 of 17 - artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} - artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} - artifacts-chunk-size-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-12 }} - # artifacts-13 of 17 - artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} - artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} - artifacts-chunk-size-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-13 }} - # artifacts-14 of 17 - artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} - artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} - artifacts-chunk-size-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-14 }} - # artifacts-15 of 17 - artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} - artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} - artifacts-chunk-size-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-15 }} - # artifacts-16 of 17 - artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} - artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} - artifacts-chunk-size-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-16 }} - # artifacts-17 of 17 - artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} - artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} - artifacts-chunk-size-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-17 }} - -# template file: 151.per-chunk-images_prep-outputs.yaml - - # artifacts-1 of 16 - images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} - images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} - images-chunk-size-1: ${{ steps.prepare-matrix.outputs.images-chunk-size-1 }} - # artifacts-2 of 16 - images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} - images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} - images-chunk-size-2: ${{ steps.prepare-matrix.outputs.images-chunk-size-2 }} - # artifacts-3 of 16 - images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} - images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} - images-chunk-size-3: ${{ steps.prepare-matrix.outputs.images-chunk-size-3 }} - # artifacts-4 of 16 - images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} - images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} - images-chunk-size-4: ${{ steps.prepare-matrix.outputs.images-chunk-size-4 }} - # artifacts-5 of 16 - images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} - images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} - images-chunk-size-5: ${{ steps.prepare-matrix.outputs.images-chunk-size-5 }} - # artifacts-6 of 16 - images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} - images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} - images-chunk-size-6: ${{ steps.prepare-matrix.outputs.images-chunk-size-6 }} - # artifacts-7 of 16 - images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} - images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} - images-chunk-size-7: ${{ steps.prepare-matrix.outputs.images-chunk-size-7 }} - # artifacts-8 of 16 - images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} - images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} - images-chunk-size-8: ${{ steps.prepare-matrix.outputs.images-chunk-size-8 }} - # artifacts-9 of 16 - images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} - images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} - images-chunk-size-9: ${{ steps.prepare-matrix.outputs.images-chunk-size-9 }} - # artifacts-10 of 16 - images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} - images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} - images-chunk-size-10: ${{ steps.prepare-matrix.outputs.images-chunk-size-10 }} - # artifacts-11 of 16 - images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} - images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} - images-chunk-size-11: ${{ steps.prepare-matrix.outputs.images-chunk-size-11 }} - # artifacts-12 of 16 - images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} - images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} - images-chunk-size-12: ${{ steps.prepare-matrix.outputs.images-chunk-size-12 }} - # artifacts-13 of 16 - images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} - images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} - images-chunk-size-13: ${{ steps.prepare-matrix.outputs.images-chunk-size-13 }} - # artifacts-14 of 16 - images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} - images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} - images-chunk-size-14: ${{ steps.prepare-matrix.outputs.images-chunk-size-14 }} - # artifacts-15 of 16 - images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} - images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} - images-chunk-size-15: ${{ steps.prepare-matrix.outputs.images-chunk-size-15 }} - # artifacts-16 of 16 - images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} - images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} - images-chunk-size-16: ${{ steps.prepare-matrix.outputs.images-chunk-size-16 }} - -# template file: 250.single_aggr-jobs.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "17 artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-artifacts-chunk-1","build-artifacts-chunk-2","build-artifacts-chunk-3","build-artifacts-chunk-4","build-artifacts-chunk-5","build-artifacts-chunk-6","build-artifacts-chunk-7","build-artifacts-chunk-8","build-artifacts-chunk-9","build-artifacts-chunk-10","build-artifacts-chunk-11","build-artifacts-chunk-12","build-artifacts-chunk-13","build-artifacts-chunk-14","build-artifacts-chunk-15","build-artifacts-chunk-16","build-artifacts-chunk-17" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-images-chunk-1","build-images-chunk-2","build-images-chunk-3","build-images-chunk-4","build-images-chunk-5","build-images-chunk-6","build-images-chunk-7","build-images-chunk-8","build-images-chunk-9","build-images-chunk-10","build-images-chunk-11","build-images-chunk-12","build-images-chunk-13","build-images-chunk-14","build-images-chunk-15","build-images-chunk-16" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "17 artifacts and 16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - -# template file: 550.per-chunk-artifacts_job.yaml - - "build-artifacts-chunk-1": # templated "build-artifacts-chunk-1" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-1 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-2": # templated "build-artifacts-chunk-2" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-2 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-3": # templated "build-artifacts-chunk-3" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-3 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-4": # templated "build-artifacts-chunk-4" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-4 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-5": # templated "build-artifacts-chunk-5" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-5 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-6": # templated "build-artifacts-chunk-6" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-6 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-7": # templated "build-artifacts-chunk-7" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-7 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-8": # templated "build-artifacts-chunk-8" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-8 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-9": # templated "build-artifacts-chunk-9" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-9 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-10": # templated "build-artifacts-chunk-10" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-10 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-11": # templated "build-artifacts-chunk-11" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-11 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-12": # templated "build-artifacts-chunk-12" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-12 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-13": # templated "build-artifacts-chunk-13" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-13 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-14": # templated "build-artifacts-chunk-14" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-14 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-15": # templated "build-artifacts-chunk-15" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-15 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-16": # templated "build-artifacts-chunk-16" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-16 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-17": # templated "build-artifacts-chunk-17" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-17 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-17) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A17' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - -# template file: 650.per-chunk-images_job.yaml - - "build-images-chunk-1": # templated "build-images-chunk-1" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-1 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-2": # templated "build-images-chunk-2" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-2 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-3": # templated "build-images-chunk-3" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-3 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-4": # templated "build-images-chunk-4" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-4 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-5": # templated "build-images-chunk-5" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-5 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-6": # templated "build-images-chunk-6" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-6 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-7": # templated "build-images-chunk-7" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-7 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-8": # templated "build-images-chunk-8" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-8 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-9": # templated "build-images-chunk-9" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-9 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-10": # templated "build-images-chunk-10" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-10 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-11": # templated "build-images-chunk-11" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-11 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-12": # templated "build-images-chunk-12" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-12 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-13": # templated "build-images-chunk-13" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-13 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-14": # templated "build-images-chunk-14" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-14 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-15": # templated "build-images-chunk-15" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-15 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-16": # templated "build-images-chunk-16" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-16 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'yes' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'yes'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - -# template file: 750.single_repo.yaml - - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} - -# template file: 950.single_footer.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || 'yes') == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-nightly - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-nightly - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' && (github.event.inputs.nightlybuild || 'yes') == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/.github/workflows/complete-artifact-matrix-standard-support.yml b/.github/workflows/complete-artifact-matrix-standard-support.yml deleted file mode 100644 index d2f7ba78440..00000000000 --- a/.github/workflows/complete-artifact-matrix-standard-support.yml +++ /dev/null @@ -1,9410 +0,0 @@ - -# template file: 050.single_header.yaml - -name: "Build Standard Support Images (admin)" - -on: - - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'no' - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: 'yes' - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - - main - - kernel-config-nfs-rootfs-vendor - - rootfs-cache-invalidate-on-configng-change - - update-kernel-configs - - fix/bleedingedge-7.2-track-master - - fix/apt-cacher-ng-fallback - - fix/loop-device-mkfs-busy - - bigtreetech-cb1-uboot-2026.01 - - fix/offline-work-respect-cache - - fix/docker-riscv64-cross-compilers - - feat/sysrq-serial-trigger - - xxxxxxx - - v26.05 - - fix/runner-clean-pages-home - - seeed - - artifact-rootfs-fetch-configng - - breakingtest - - nvidia-auto-detect-driver-version - - uefi-riscv64-add-cloud-kernel - - artifact-uboot-debug-h-divergence - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - 9tripod-x3568-v4 - - aml-a311d-cc - - aml-c400-plus - - aml-s805-mxq - - aml-s905d3-cc - - aml-s9xx-box - - aml-t95z-plus - - anbernic-rg-ds - - arduino-uno-q - - armsom-aim7-io - - armsom-cm5-io - - armsom-cm5-rpi-cm4-io - - armsom-forge1 - - armsom-sige1 - - armsom-sige3 - - armsom-sige5 - - armsom-sige7 - - armsom-w3 - - avaota-a1 - - ayn-odin2 - - ayn-odin2mini - - ayn-odin2portal - - ayn-thor - - bananapi - - bananapicm4io - - bananapif3 - - bananapim1plus - - bananapim2plus - - bananapim2pro - - bananapim2s - - bananapim2ultra - - bananapim2zero - - bananapim3 - - bananapim4berry - - bananapim4zero - - bananapim5 - - bananapim5pro - - bananapim64 - - bananapim7 - - bananapipro - - bananapir2 - - bananapir2pro - - bananapir4 - - beaglebadge - - beaglebone-ai64 - - beagleplay - - beagley-ai - - beelinkx2 - - bestv-r3300-l - - bigtreetech-cb1 - - bigtreetech-cb2 - - cainiao-cniot-core - - cherryba-m1 - - cix-acpi - - clearfogbase - - clearfogpro - - clockworkpi-a06 - - cm3588-nas - - coolpi-cm5 - - coolpi-genbook - - cubieboard - - cubieboard2 - - cubietruck - - cyber-aib-rk3588 - - dg-svr-865-tiny - - dshanpi-a1 - - dshanpi-r1 - - dusun-dsom-010r - - easepi-a2 - - easepi-r2 - - ebyte-ecb41-pge - - espressobin - - fine3399 - - firefly-itx-3588j - - firefly-rk3399 - - forlinx-ok3506-s12 - - fxblox-rk1 - - gateway-dk - - gateway-gz80x - - h96-tvbox-3566 - - helios4 - - helios64 - - hikey960 - - hinlink-h28k - - hinlink-h66k - - hinlink-h68k - - hinlink-h88k - - hinlink-hnas - - hinlink-ht2 - - imb3588 - - indiedroid-nova - - inovato-quadra - - jethubj100 - - jethubj200 - - jethubj80 - - jp-tvbox-3566 - - k3picoitx - - khadas-edge - - khadas-edge2 - - khadas-vim1 - - khadas-vim1s - - khadas-vim2 - - khadas-vim3 - - khadas-vim3l - - khadas-vim4 - - kickpik2b - - lafrite - - lckfb-taishanpi - - leez-p710 - - lepotato - - lime - - lime-a33 - - lime-a64 - - lime2 - - longanpi-3h - - longanpi-4b - - lubancat2 - - luckfox-core3566 - - luckfox-lyra-plus - - luckfox-lyra-ultra-w - - luckfox-lyra-zero-w - - luckfox-pico-max - - luckfox-pico-mini - - luckfox-rk3308b-nova - - mangopi-m28k - - mba8mpxl - - mba8mpxl-ras314 - - mba93xxla-mini - - mekotronics-r58-4x4 - - mekotronics-r58-minipc - - mekotronics-r58hd - - mekotronics-r58s2 - - mekotronics-r58x - - mekotronics-r58x-4g - - mekotronics-r58x-pro - - melea1000 - - mixtile-blade3 - - mixtile-core3588e - - mixtile-edge2 - - mk808c - - mksklipad50 - - mkspi - - musebook - - musepipro - - nanopct4 - - nanopct6 - - nanopct6-lts - - nanopi-m5 - - nanopi-m6 - - nanopi-r1 - - nanopi-r1s-h5 - - nanopi-r2c - - nanopi-r2s - - nanopi-r2s-plus - - nanopi-r3s - - nanopi-r3s-lts - - nanopi-r4s - - nanopi-r4se - - nanopi-r5c - - nanopi-r5s - - nanopi-r6c - - nanopi-r6s - - nanopi-r76s - - nanopi-zero2 - - nanopia64 - - nanopiair - - nanopiduo - - nanopiduo2 - - nanopik1plus - - nanopik2-s905 - - nanopim4 - - nanopim4v2 - - nanopineo - - nanopineo2 - - nanopineo2black - - nanopineo3 - - nanopineo4 - - nanopineocore2 - - nanopineoplus2 - - norco-emb-3531 - - numaker-iot-ma35d16f90 - - odroidc1 - - odroidc2 - - odroidc4 - - odroidhc4 - - odroidm1 - - odroidm1s - - odroidm2 - - odroidn2 - - odroidn2l - - odroidxu4 - - olimex-a20-olinuxino-micro - - olimex-teres-a64 - - olinux-som-a13 - - onecloud - - oneplus-kebab - - orangepi-4a - - orangepi-r1 - - orangepi-r1plus - - orangepi-r1plus-lts - - orangepi-rk3399 - - orangepi2 - - orangepi3 - - orangepi3-lts - - orangepi3b - - orangepi4 - - orangepi4-lts - - orangepi4pro - - orangepi5 - - orangepi5-max - - orangepi5-plus - - orangepi5-ultra - - orangepi5b - - orangepi5pro - - orangepilite - - orangepilite2 - - orangepione - - orangepioneplus - - orangepipc - - orangepipc2 - - orangepipcplus - - orangepiplus - - orangepiplus2e - - orangepiprime - - orangepir2s - - orangepirv2 - - orangepiwin - - orangepizero - - orangepizero2 - - orangepizero2w - - orangepizero3 - - orangepizeroplus - - orangepizeroplus2-h3 - - orangepizeroplus2-h5 - - pcduino3 - - photonicat2 - - phytiumpi - - pine64 - - pine64so - - pinebook-a64 - - pinebook-pro - - pinecube - - pineh64 - - pineh64-b - - pocketbeagle2 - - pocketchip-sd - - qemu-uboot-arm64 - - qemu-uboot-x86 - - qemu-uefi-x86 - - qidi-x6 - - quartz64a - - quartz64b - - radxa-cm4-io - - radxa-cm5-io - - radxa-cubie-a5e - - radxa-dragon-q6a - - radxa-e20c - - radxa-e24c - - radxa-e25 - - radxa-e52c - - radxa-e54c - - radxa-nio-12l - - radxa-rock-4d - - radxa-zero - - radxa-zero2 - - radxa-zero3 - - recomputer-rk3576-devkit - - recomputer-rk3588-devkit - - recore - - renegade - - retro-lite-cm5 - - retroidpocket-rp5 - - retroidpocket-rpmini - - rk322x-box - - rk3318-box - - rk3328-heltec - - rk3566-box-demo - - roc-rk3399-pc - - rock-2a - - rock-2f - - rock-3a - - rock-3c - - rock-4se - - rock-5-cm-rpi-cm4-io - - rock-5-cmio - - rock-5-itx - - rock-5a - - rock-5b - - rock-5b-plus - - rock-5c - - rock-5t - - rock-s0 - - rock64 - - rockpi-4a - - rockpi-4b - - rockpi-4bplus - - rockpi-4c - - rockpi-4cplus - - rockpi-e - - rockpi-n10 - - rockpi-s - - rockpro64 - - rpi4b - - sakurapi-rk3308b - - sk-am62-lp - - sk-am62-sip - - sk-am62b - - sk-am62p - - sk-am64b - - sk-am68 - - sk-am69 - - sk-tda4vm - - smart-am40 - - station-m1 - - station-m2 - - station-m3 - - station-p1 - - station-p2 - - sunvell-r69 - - sweet-potato - - tanix-tx6 - - thinkpad-x13s - - tinker-edge-r - - tinkerboard - - tinkerboard-2 - - tmds62levm - - tmds64evm - - tq-smarc2-tqma8mpxs - - tritium-h3 - - tritium-h5 - - turing-rk1 - - udoo - - uefi-arm64 - - uefi-arm64-dt - - uefi-loong64 - - uefi-riscv64 - - uefi-x86 - - visionfive2 - - x96-mate - - x96q - - xiaobao-nas - - xiaomi-elish - - xpressreal-t3 - - xt-q8l-v10 - - youyeetoo-r1-v3 - - youyeetoo-yy3588 - - yy3568 - - z28pro - - zeropi - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - "150balbes" - - "1ubuntuuser" - - "AGM1968" - - "AaronNGray" - - "CodeChenL" - - "ColorfulRhino" - - "DylanHP" - - "Grippy98" - - "HackingGate" - - "Heisath" - - "HeyMeco" - - "IsMrX" - - "JackHuang021" - - "Janmcha" - - "JohnTheCoolingFan" - - "Kreyren" - - "Lemon1151" - - "NicoD-SBC" - - "PanderMusubi" - - "Pillar1989" - - "Qvy-png" - - "SeeleVolleri" - - "Shadowrom2020" - - "StephenGraf" - - "SuperKali" - - "The-going" - - "TheSnowfield" - - "Tonymac32" - - "ZazaBR" - - "adeepn" - - "ahoneybun" - - "alexl83" - - "amazingfate" - - "andyshrk" - - "baldpope" - - "brentr" - - "catalinii" - - "chainsx" - - "chraac" - - "crackerjacques" - - "devdotnetorg" - - "efectn" - - "eliasbakken" - - "engineer-80" - - "fridtjof" - - "ginkage" - - "glneo" - - "hoochiwetech" - - "hqnicolas" - - "hzyitc" - - "iav" - - "ifroncy01" - - "igorpecovnik" - - "janprunk" - - "jeanrhum" - - "joekhoobyar" - - "jomadeto" - - "jonaswood01" - - "juanesf" - - "juanlufont" - - "kamilsaigol" - - "krachlatte" - - "lbmendes" - - "leggewie" - - "libiunc" - - "linhz0hz" - - "mahdichi" - - "mattx433" - - "mhawkins-consultant" - - "mlegenovic" - - "paolosabatino" - - "prahal" - - "pyavitz" - - "rbqvq" - - "redrathnure" - - "retro98boy" - - "rpardini" - - "schmiedelm" - - "schwar3kat" - - "sgjava" - - "shkolnik" - - "sicXnull" - - "spendist" - - "sputnik2019" - - "sven-ola" - - "teknoid" - - "tomazzaman" - - "torte71" - - "utlark" - - "vamzii" - - "vidplace7" - - "wei633" - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: 'no' - bumpversion: - type: boolean - description: "Bump version" - default: 'false' - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "armbian/build" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "armbian/os" - USERPATCHES_REF: "main" # branch or tag or sha1 - USERPATCHES_DIR: "userpatches" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "os" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "stable/" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || 'false' }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: ${{ github.run_id }}-standard-supportstable - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "Release manager" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || 'no' }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-stable - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || 'no') == 'yes' && (github.event.inputs.skipImages || 'no') != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: 17/16 :: 17 artifact chunks, 16 image chunks" - if: ${{ github.repository_owner == 'armbian' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix armbian-images \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || 'no' }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=17 \ - MATRIX_IMAGE_CHUNKS=16 \ - CHECK_OCI=${{ github.event.inputs.checkOci || 'no' }} \ - TARGETS_FILENAME="targets-release-standard-support.yaml" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || 'no'}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} - -# template file: 150.per-chunk-artifacts_prep-outputs.yaml - - # artifacts-1 of 17 - artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} - artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} - artifacts-chunk-size-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-1 }} - # artifacts-2 of 17 - artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} - artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} - artifacts-chunk-size-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-2 }} - # artifacts-3 of 17 - artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} - artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} - artifacts-chunk-size-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-3 }} - # artifacts-4 of 17 - artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} - artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} - artifacts-chunk-size-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-4 }} - # artifacts-5 of 17 - artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} - artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} - artifacts-chunk-size-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-5 }} - # artifacts-6 of 17 - artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} - artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} - artifacts-chunk-size-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-6 }} - # artifacts-7 of 17 - artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} - artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} - artifacts-chunk-size-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-7 }} - # artifacts-8 of 17 - artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} - artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} - artifacts-chunk-size-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-8 }} - # artifacts-9 of 17 - artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} - artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} - artifacts-chunk-size-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-9 }} - # artifacts-10 of 17 - artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} - artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} - artifacts-chunk-size-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-10 }} - # artifacts-11 of 17 - artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} - artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} - artifacts-chunk-size-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-11 }} - # artifacts-12 of 17 - artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} - artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} - artifacts-chunk-size-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-12 }} - # artifacts-13 of 17 - artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} - artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} - artifacts-chunk-size-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-13 }} - # artifacts-14 of 17 - artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} - artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} - artifacts-chunk-size-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-14 }} - # artifacts-15 of 17 - artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} - artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} - artifacts-chunk-size-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-15 }} - # artifacts-16 of 17 - artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} - artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} - artifacts-chunk-size-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-16 }} - # artifacts-17 of 17 - artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} - artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} - artifacts-chunk-size-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-17 }} - -# template file: 151.per-chunk-images_prep-outputs.yaml - - # artifacts-1 of 16 - images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} - images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} - images-chunk-size-1: ${{ steps.prepare-matrix.outputs.images-chunk-size-1 }} - # artifacts-2 of 16 - images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} - images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} - images-chunk-size-2: ${{ steps.prepare-matrix.outputs.images-chunk-size-2 }} - # artifacts-3 of 16 - images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} - images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} - images-chunk-size-3: ${{ steps.prepare-matrix.outputs.images-chunk-size-3 }} - # artifacts-4 of 16 - images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} - images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} - images-chunk-size-4: ${{ steps.prepare-matrix.outputs.images-chunk-size-4 }} - # artifacts-5 of 16 - images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} - images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} - images-chunk-size-5: ${{ steps.prepare-matrix.outputs.images-chunk-size-5 }} - # artifacts-6 of 16 - images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} - images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} - images-chunk-size-6: ${{ steps.prepare-matrix.outputs.images-chunk-size-6 }} - # artifacts-7 of 16 - images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} - images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} - images-chunk-size-7: ${{ steps.prepare-matrix.outputs.images-chunk-size-7 }} - # artifacts-8 of 16 - images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} - images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} - images-chunk-size-8: ${{ steps.prepare-matrix.outputs.images-chunk-size-8 }} - # artifacts-9 of 16 - images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} - images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} - images-chunk-size-9: ${{ steps.prepare-matrix.outputs.images-chunk-size-9 }} - # artifacts-10 of 16 - images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} - images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} - images-chunk-size-10: ${{ steps.prepare-matrix.outputs.images-chunk-size-10 }} - # artifacts-11 of 16 - images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} - images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} - images-chunk-size-11: ${{ steps.prepare-matrix.outputs.images-chunk-size-11 }} - # artifacts-12 of 16 - images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} - images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} - images-chunk-size-12: ${{ steps.prepare-matrix.outputs.images-chunk-size-12 }} - # artifacts-13 of 16 - images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} - images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} - images-chunk-size-13: ${{ steps.prepare-matrix.outputs.images-chunk-size-13 }} - # artifacts-14 of 16 - images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} - images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} - images-chunk-size-14: ${{ steps.prepare-matrix.outputs.images-chunk-size-14 }} - # artifacts-15 of 16 - images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} - images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} - images-chunk-size-15: ${{ steps.prepare-matrix.outputs.images-chunk-size-15 }} - # artifacts-16 of 16 - images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} - images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} - images-chunk-size-16: ${{ steps.prepare-matrix.outputs.images-chunk-size-16 }} - -# template file: 250.single_aggr-jobs.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "17 artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-artifacts-chunk-1","build-artifacts-chunk-2","build-artifacts-chunk-3","build-artifacts-chunk-4","build-artifacts-chunk-5","build-artifacts-chunk-6","build-artifacts-chunk-7","build-artifacts-chunk-8","build-artifacts-chunk-9","build-artifacts-chunk-10","build-artifacts-chunk-11","build-artifacts-chunk-12","build-artifacts-chunk-13","build-artifacts-chunk-14","build-artifacts-chunk-15","build-artifacts-chunk-16","build-artifacts-chunk-17" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "build-images-chunk-1","build-images-chunk-2","build-images-chunk-3","build-images-chunk-4","build-images-chunk-5","build-images-chunk-6","build-images-chunk-7","build-images-chunk-8","build-images-chunk-9","build-images-chunk-10","build-images-chunk-11","build-images-chunk-12","build-images-chunk-13","build-images-chunk-14","build-images-chunk-15","build-images-chunk-16" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "17 artifacts and 16 image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - -# template file: 550.per-chunk-artifacts_job.yaml - - "build-artifacts-chunk-1": # templated "build-artifacts-chunk-1" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-1 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-2": # templated "build-artifacts-chunk-2" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-2 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-3": # templated "build-artifacts-chunk-3" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-3 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-4": # templated "build-artifacts-chunk-4" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-4 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-5": # templated "build-artifacts-chunk-5" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-5 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-6": # templated "build-artifacts-chunk-6" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-6 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-7": # templated "build-artifacts-chunk-7" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-7 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-8": # templated "build-artifacts-chunk-8" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-8 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-9": # templated "build-artifacts-chunk-9" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-9 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-10": # templated "build-artifacts-chunk-10" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-10 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-11": # templated "build-artifacts-chunk-11" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-11 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-12": # templated "build-artifacts-chunk-12" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-12 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-13": # templated "build-artifacts-chunk-13" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-13 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-14": # templated "build-artifacts-chunk-14" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-14 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-15": # templated "build-artifacts-chunk-15" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-15 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-16": # templated "build-artifacts-chunk-16" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-16 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - "build-artifacts-chunk-17": # templated "build-artifacts-chunk-17" - if: ${{ github.repository_owner == 'armbian' && needs.matrix_prep.outputs.artifacts-chunk-not-empty-17 == 'yes' }} # <-- HERE: Chunk number. - needs: [ "matrix_prep" ] - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.artifacts-chunk-json-17) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty A17' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 # We don't need to clone git, really. A wget would suffice for GH-hosted runners. But using clone is better for Igor-hosted runners. - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - - name: "Run only on GitHub-hosted" - if: ${{ runner.environment == 'github-hosted' }} - run: | - echo "GitHub-hosted runner" - sudo ./compile.sh requirements - sudo chown -R "$(id -u):$(id -g)" . - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE git_proxy:GITPROXY_ADDRESS; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: Build ${{matrix.desc}} - timeout-minutes: 90 - id: build - run: | - # If the runner exported a remote ccache storage, pass it on and enable the ccache-remote extension. - declare -a CCACHE_PARAMS=() - if [ -n "${CCACHE_REMOTE_STORAGE}" ]; then - CCACHE_PARAMS+=( "CCACHE_REMOTE_STORAGE=${CCACHE_REMOTE_STORAGE}" "ENABLE_EXTENSIONS=ccache-remote" ) - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" SHOW_DEBUG=yes SHARE_LOG=yes "${CCACHE_PARAMS[@]}" ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build.outputs.logs_url }}" - -# template file: 650.per-chunk-images_job.yaml - - "build-images-chunk-1": # templated "build-images-chunk-1" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-1 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-1) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I1' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-2": # templated "build-images-chunk-2" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-2 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-2) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I2' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-3": # templated "build-images-chunk-3" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-3 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-3) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I3' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-4": # templated "build-images-chunk-4" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-4 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-4) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I4' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-5": # templated "build-images-chunk-5" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-5 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-5) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I5' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-6": # templated "build-images-chunk-6" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-6 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-6) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I6' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-7": # templated "build-images-chunk-7" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-7 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-7) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I7' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-8": # templated "build-images-chunk-8" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-8 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-8) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I8' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-9": # templated "build-images-chunk-9" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-9 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-9) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I9' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-10": # templated "build-images-chunk-10" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-10 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-10) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I10' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-11": # templated "build-images-chunk-11" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-11 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-11) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I11' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-12": # templated "build-images-chunk-12" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-12 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-12) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I12' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-13": # templated "build-images-chunk-13" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-13 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-13) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I13' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-14": # templated "build-images-chunk-14" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-14 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-14) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I14' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-15": # templated "build-images-chunk-15" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-15 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-15) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I15' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - "build-images-chunk-16": # templated "build-images-chunk-16" - needs: [ "matrix_prep", "all-artifacts-ready" ] - timeout-minutes: 240 - if: ${{ !failure() && !cancelled() && ( github.repository_owner == 'armbian' ) && ( needs.matrix_prep.outputs.images-chunk-not-empty-16 == 'yes' ) }} # <-- HERE: Chunk number. - strategy: - fail-fast: false # let other jobs try to complete if one fails - matrix: ${{ fromJSON(needs.matrix_prep.outputs.images-chunk-json-16) }} # <-- HERE: Chunk number. - name: ${{ matrix.desc || 'Empty I16' }} # <-- HERE: Chunk number. - runs-on: ${{ matrix.runs_on }} - steps: - - - name: Install dependencies - run: | - if [ ! -e /usr/bin/mktorrent ]; then - sudo apt-get update - sudo apt-get install -y mktorrent - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - uses: armbian/actions/runner-clean@main - - # cleanup the place where we will clone the userpatches repo, to avoid git going insane and cleaning everything later - - name: Cleanup userpatches repo - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: rm -rf userpatches.repo - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: ${{ matrix.fdepth }} - clean: false # true is default. - path: userpatches.repo - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - mkdir -pv userpatches - rsync -av userpatches.repo/${{env.USERPATCHES_DIR}}/. userpatches/ - rm -rf userpatches.repo - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. userpatches/ - - - name: "Cleanup leftover output" - run: | - rm -f userpatches/VERSION - - - name: "Runner info for ${{ runner.name }}" - run: | - info="$(curl -fsSL "https://github.armbian.com/servers/github-runners.jq" \ - | jq --arg name "${{ runner.name }}" ' - ($name | sub("-[0-9]+$"; "")) as $base - | first(.[] | select((.host | split(".")[0]) == $base)) // {} - ')" - echo "Runner info for ${{ runner.name }}:" - echo "$info" - echo "--- Exported environment ---" - for map in apt_proxy:APT_PROXY_ADDR oci_cache:GHCR_MIRROR_ADDRESS redis_cache:CCACHE_REMOTE_STORAGE; do - key="${map%%:*}"; var="${map##*:}" - value="$(jq -r --arg k "$key" '.[$k] // ""' <<< "$info")" - echo "${var}=${value}" - echo "${var}=${value}" >> "$GITHUB_ENV" - done - - - name: ${{matrix.desc}} - id: build-one-image - timeout-minutes: 90 - run: | - # calculate loop from runner name - if [ -z "${ImageOS}" ]; then - USE_FIXED_LOOP_DEVICE=$(echo ${RUNNER_NAME} | rev | cut -d"-" -f1 | rev | sed 's/^0*//' | sed -e 's/^/\/dev\/loop/') - fi - bash ./compile.sh ${{ matrix.invocation }} REVISION="${{ needs.matrix_prep.outputs.version }}" USE_FIXED_LOOP_DEVICE="$USE_FIXED_LOOP_DEVICE" SHARE_LOG=yes MAKE_FOLDERS="archive" IMAGE_VERSION=${{ needs.matrix_prep.outputs.version }} ${{env.EXTRA_PARAMS_IMAGE}} ${{env.EXTRA_PARAMS_ALL_BUILDS}} - - - name: "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.build-one-image.outputs.logs_url }}" - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: Check API rate limits - run: | - - # install dependencies - if ! command -v "gh" > /dev/null 2>&1; then - sudo apt-get -y -qq install gh - fi - - while true - do - API_CALLS_TOTAL=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.limit') - API_CALLS_LEFT=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /rate_limit | jq -r '.rate.remaining') - PERCENT=$(( API_CALLS_LEFT * 100 / API_CALLS_TOTAL )) - if (( $PERCENT > 20 )); then - echo "API rate in good shape $PERCENT % free" - exit 0 - fi - echo "API rate lower then 20%, sleping 10m" - sleep 10m - done - - # show current api rate - curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" https://api.github.com/rate_limit - - - name: Import GPG key - env: - GPG_KEY1: ${{ secrets.GPG_KEY1 }} - if: env.GPG_KEY1 != null - uses: crazy-max/ghaction-import-gpg@v7 - with: - gpg_private_key: ${{ secrets.GPG_KEY1 }} - passphrase: ${{ secrets.GPG_PASSPHRASE1 }} - - - name: Sign - env: - GPG_PASSPHRASE1: ${{ secrets.GPG_PASSPHRASE1 }} - if: env.GPG_PASSPHRASE1 != null - run: | - - for extension in zip xz qcow2; do - if ls -l output/images/*/archive/*.$extension &>/dev/null; then - echo ${{ secrets.GPG_PASSPHRASE1 }} | gpg --passphrase-fd 0 --armor --detach-sign --pinentry-mode loopback --batch --yes output/images/*/archive/*.$extension - fi - done - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: output/info - - - name: Generate torrent - timeout-minutes: 3 - run: | - - set -euo pipefail - - # Build tracker list (ignore empty/whitespace-only lines) - TRACKERS=$( - grep -v '^[ ]*$' output/info/best-torrent-servers.txt \ - | sort -R \ - | sed 's/^/ --announce=/' - ) - - # Find BOARD and FILE (first zip/xz/qcow2 in output/images/*/archive/) - BOARD="" - FILE="" - first_match="" - - for ext in zip xz qcow2; do - if ls output/images/*/archive/*."$ext" >/dev/null 2>&1; then - first_match=$(ls -1 output/images/*/archive/*."$ext" | head -n1) - # first_match = output/images/BOARD/archive/file.ext - BOARD=$(basename "$(dirname "$(dirname "$first_match")")") # -> BOARD - FILE=$(basename "$first_match") - break - fi - done - - # Safety check - if [ -z "$BOARD" ] || [ -z "$FILE" ]; then - echo "No torrent source file found (zip/xz/qcow2) in output/images/*/archive" >&2 - exit 1 - fi - - # Nightly / stable logic (templated) - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - WEBSEEDS="" - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" != "distribution" ]; then - ################################################################## - # STABLE RELEASES - # Use download mirrors (servers-download.jq) - # URL: https:////archive/ - # download_path_images is already normalized & defaults to /dl in JSON. - ################################################################## - WEBSEEDS=$( - jq -r --arg board "$BOARD" --arg file "$FILE" ' - .[] - | "https://\(.host)\(.download_path_images)/\($board)/archive/\($file)" - ' output/info/servers-download.jq | paste -sd, - - ) - else - ################################################################## - # NIGHTLY BUILDS - # Use cache mirrors (servers-cache.jq) + GitHub as extra webseed - # Path is FIXED: https://SERVER/cache/os// - ################################################################## - WEBSEEDS=$( - jq -r \ - --arg repo "os" \ - --arg ver "${{ needs.matrix_prep.outputs.version }}" \ - --arg file "$FILE" ' - .[] - | "https://\(.host)/cache/\($repo)/\($ver)/\($file)" - ' output/info/servers-cache.jq | paste -sd, - - ) - - # Append GitHub webseed - if [ -n "$WEBSEEDS" ]; then - WEBSEEDS+="," - fi - WEBSEEDS+="https://github.com/armbian/os/releases/download/${{ needs.matrix_prep.outputs.version }}/${FILE}" - fi - - echo "WEBSEEDS: $WEBSEEDS" - - # Go to the archive directory that contains FILE - cd "$(dirname "$first_match")" || exit 1 - - mktorrent \ - --comment="Armbian torrent for ${FILE}" \ - --verbose \ - ${TRACKERS} \ - --web-seed="${WEBSEEDS}" \ - "${FILE}" - - # drop .txt helper files - rm -f *.txt - - - name: "Prepare release artifacts (exclude .asc, .sha, .torrent)" - run: | - # Start from a clean directory - rm -rf output/release - mkdir -p output/release - - # Copy wanted artifacts from output/images, preserving folder structure - # e.g. output/images/BOARDNAME/archive/Armbian_*.img -> output/release/output/images/BOARDNAME/archive/... - find output/images -type f -name 'Armbian_*.*' \ - ! -name '*.asc' \ - ! -name '*.sha' \ - ! -name '*.torrent' \ - -exec cp --parents {} output/release/ \; - - # debug - tree output/images - echo "# debug" - tree output/release - - - name: "Upload artefacts except .asc, .sha and .torrent" - timeout-minutes: 60 - if: ${{ ( github.event.inputs.nightlybuild || 'no' ) == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - #if: ${{ env.RELEASE_REPOSITORY == 'os' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} - uses: ncipollo/release-action@v1 - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ needs.matrix_prep.outputs.version }}" - artifacts: "output/release/output/images/*/*/Armbian_*.*" - omitBody: true - replacesArtifacts: true - omitName: true - makeLatest: false - omitPrereleaseDuringUpdate: true - allowUpdates: true - artifactErrorsFailBuild: true - token: "${{ env.GH_TOKEN }}" - - - name: "Upload to servers" - run: | - - # temp workspace for downloaded jsons (auto-cleanup) - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' EXIT - - curl -fL --retry 10 --retry-delay 5 --retry-all-errors \ - -o "$tmpdir/cache.jq" https://github.armbian.com/servers/cache.jq \ - -o "$tmpdir/upload.jq" https://github.armbian.com/servers/upload.jq - - # debug - echo "=== servers-cache.jq ===" - jq . output/info/servers-cache.jq || cat output/info/servers-cache.jq - echo "=== servers-upload.jq ===" - jq . output/info/servers-upload.jq || cat output/info/servers-upload.jq - - max_retries=3 - - sync_from_json() { - local json_file=$1 - local mode=$2 # "cache" or "upload" - - echo "== Processing ${json_file} (mode: ${mode}) ==" - - # Iterate over JSON array elements - while IFS= read -r server; do - # JSON structure (same for all files): - # { - # "host": "...", - # "upload_path": "...", - # "download_path_archive": "...", - # "download_path_images": "...", - # "download_path_debs": "...", - # "port": 22, - # "username": "mirror" - # } - - SERVER_URL=$(jq -r '.host // empty' <<<"$server") - SERVER_PATH=$(jq -r '.upload_path // ""' <<<"$server") - SERVER_PORT=$(jq -r '.port // 22' <<<"$server") - SERVER_USERNAME=$(jq -r '.username // "mirror"' <<<"$server") - - # skip empty host - [ -z "$SERVER_URL" ] && continue - - echo "Processing: $SERVER_URL:$SERVER_PORT (upload_path: $SERVER_PATH)" - - # Clean known_hosts entry (host:port form) - ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "[${SERVER_URL}]:${SERVER_PORT}" 2>/dev/null || true - - # Select rsync filters + remote subdir - if [ "$mode" = "cache" ]; then - # only.sha, .torrent .asc - RSYNC_FILTER=( - --include='*/' - --include='*.sha' - --include='*.asc' - --include='*.torrent' - --exclude='*' - ) - REMOTE_SUBDIR="cache/artifacts/" - else - # everything - RSYNC_FILTER=( - --include='*/' - --include='*' - ) - REMOTE_SUBDIR="incoming/${GITHUB_ACTOR}/" - fi - - # Retry loop - for attempt in $(seq 1 "$max_retries"); do - echo "[$SERVER_URL] rsync attempt ${attempt}/${max_retries}..." - - # Timeouts so an unreachable/stalled server fails fast - # instead of hanging the whole attempt: - # ssh ConnectTimeout - cap the TCP connect (unreachable host) - # ssh ServerAlive* - drop a dead connection mid-transfer (~60s) - # rsync --timeout - abort if no data moves for 300s - if rsync --progress \ - --timeout=300 \ - -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4" \ - -rvP \ - "${RSYNC_FILTER[@]}" \ - output/images/ \ - "${SERVER_USERNAME}@${SERVER_URL}:${SERVER_PATH}/${REMOTE_SUBDIR}" - then - echo "[$SERVER_URL] rsync successful." - break - fi - - if [ "$attempt" -eq "$max_retries" ]; then - echo "[$SERVER_URL] rsync FAILED after ${max_retries} attempts." - exit 1 - fi - - echo "[$SERVER_URL] rsync failed. Retrying in 10 seconds..." - sleep 10 - done - - done < <(jq -c '.[]' "$json_file") - } - - nightlybuild="${{ github.event.inputs.nightlybuild }}" - nightlybuild_default="'no'" - RELEASE_REPOSITORY="${{ env.RELEASE_REPOSITORY }}" - - effective_nightlybuild="${nightlybuild:-$nightlybuild_default}" - - # Upload to cache servers: only .sha/.asc/.torrent - sync_from_json "$tmpdir/cache.jq" cache - - if [ "$effective_nightlybuild" = "no" ] && [ "$RELEASE_REPOSITORY" = "os" ]; then - # Upload to servers: everything - sync_from_json "$tmpdir/upload.jq" upload - fi - - # cleaning self hosted runners - - name: "Runner clean ${{ needs.matrix_prep.outputs.version }}" - if: always() - uses: armbian/actions/runner-clean@main - -# template file: 750.single_repo.yaml - - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "${{ github.repository_owner }}" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} - -# template file: 950.single_footer.yaml - - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || 'no') == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-stable - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-stable - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || 'no') != 'yes' && (github.event.inputs.nightlybuild || 'no') == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || 'no') == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/.github/workflows/complete-artifact-matrix.yml b/.github/workflows/complete-artifact-matrix.yml new file mode 100644 index 00000000000..5048618b884 --- /dev/null +++ b/.github/workflows/complete-artifact-matrix.yml @@ -0,0 +1,652 @@ +# Reusable: the complete artifact + image build pipeline. +# +# This is the machinery, written once. Per-"track" workflows (build-nightly.yml, +# build-stable.yml, ...) call it via `uses:` with their own schedule + inputs. It used +# to be stamped out into six ~9000-line generated files; now it is one hand-maintained +# reusable workflow. +# +# Flow: team_check -> version_prep -> matrix_prep -> {artifact chunks} -> {image chunks} +# -> publish-debs-to-repo / closing. The per-chunk build bodies live in +# build-artifacts-chunk.yml and build-images-chunk.yml (each written once and fanned +# out here over a chunk matrix). +name: "Complete artifact matrix (reusable)" + +on: + workflow_call: + inputs: + # ---- per-track identity / routing ---- + release_repository: { required: true, type: string } # os | community | distribution + target_path: { required: true, type: string } # storage deploy subpath, e.g. "stable/" + team: { required: true, type: string } # team-check team name + repository_ref: { required: true, type: string } # download-artifact name suffix: stable|nightly|all + prepare_config: { required: true, type: string } # gha-matrix config: armbian-images|armbian-community + targets_filename: { required: true, type: string } # targets-release-*.yaml (fetched at runtime) + # ---- per-track menu defaults (used when a manual dispatch input is absent) ---- + skipimages_default: { required: true, type: string } + checkoci_default: { required: true, type: string } + nightlybuild_default: { required: true, type: string } + bumpversion_default: { required: true, type: boolean } + # ---- optional build ref forwarded from the caller ---- + ref: { required: false, type: string, default: "" } + +env: + # 'armbian/build' framework repo + BUILD_REPOSITORY: "armbian/build" + BUILD_REF: "${{ inputs.ref || github.event.inputs.branch || 'main' }}" + # 'armbian/os' userpatches repo + USERPATCHES_REPOSITORY: "armbian/os" + USERPATCHES_REF: "main" + USERPATCHES_DIR: "userpatches" + RELEASE_REPOSITORY: "${{ inputs.release_repository }}" + DOCKER_SKIP_UPDATE: "yes" + EXTRA_PARAMS_ALL_BUILDS: "${{ github.event.inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" + TARGET_PATH: "${{ inputs.target_path }}" + VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" + VERSION_BUMP: "${{ github.event.inputs.bumpversion || inputs.bumpversion_default }}" + GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" + EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " + +jobs: + + team_check: + permissions: + actions: write + name: "Team check" + runs-on: [ "ubuntu-latest" ] + steps: + - name: "Check membership" + uses: armbian/actions/team-check@main + with: + ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} + GITHUB_TOKEN: "${{ env.GH_TOKEN }}" + TEAM: "${{ inputs.team }}" + + version_prep: + needs: team_check + name: "Bump version" + runs-on: ubuntu-latest + steps: + + - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" + uses: actions/checkout@v6 + if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} + with: + repository: ${{ env.USERPATCHES_REPOSITORY }} + ref: ${{ env.USERPATCHES_REF }} + fetch-depth: 0 + clean: false + + - name: Determine version + id: versionfile + run: | + # file = where version is getting stored, different for stable and nightly + # skip_tag = we only upload nighlty to GH releases + echo "file=nightly" >> $GITHUB_OUTPUT + echo "skip_tag=false" >> $GITHUB_OUTPUT + echo "pre_release=true" >> $GITHUB_OUTPUT + if [ "${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }}" == "no" ]; then + echo "file=stable" >> $GITHUB_OUTPUT + echo "skip_tag=true" >> $GITHUB_OUTPUT + echo "pre_release=false" >> $GITHUB_OUTPUT + fi + + - name: Bump version + if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} + id: changelog + uses: TriPSs/conventional-changelog-action@v5.4.0 + with: + github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} + git-message: 'Bump release to {version}' + git-user-name: armbianworker + git-user-email: info@armbian.com + output-file: 'false' + skip-version-file: 'false' + skip-on-empty: 'false' + skip-commit: 'false' + skip-ci: 'false' + skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" + version-file: "${{ steps.versionfile.outputs.file }}.json" + pre-release: "${{ steps.versionfile.outputs.pre_release }}" + git-branch: 'main' + tag-prefix: '' + pre-release-identifier: 'trunk' + + - name: Read version from file if nor overriden + if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} + run: | + mkdir -p downloads + cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV + cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version + + - name: 'Upload Artifact' + uses: actions/upload-artifact@v7 + with: + name: assets-for-download-${{ inputs.repository_ref }} + path: downloads + retention-days: 5 + + - name: "Generate body file" + if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) != 'yes' }} + run: | + # Make html document + if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then + bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html + fi + + - uses: ncipollo/release-action@v1 + if: ${{ (github.event.inputs.nightlybuild || inputs.nightlybuild_default) == 'yes' && (github.event.inputs.skipImages || inputs.skipimages_default) != 'yes' }} + with: + repo: "${{ env.RELEASE_REPOSITORY }}" + tag: "${{ env.VERSION_OVERRIDE }}" + name: "${{ env.VERSION_OVERRIDE }}" + bodyFile: "body.html" + prerelease: "true" + allowUpdates: true + removeArtifacts: true + token: ${{ env.GH_TOKEN }} + + - name: Save + id: releases + run: | + + echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT + + outputs: + version: ${{ steps.releases.outputs.version }} + + matrix_prep: + name: "JSON matrix: 17 artifact chunks, 16 image chunks" + if: ${{ github.repository_owner == 'armbian' }} + needs: [ version_prep ] + runs-on: [ "self-hosted", "Linux", 'super' ] + steps: + + - name: Runner clean + uses: armbian/actions/runner-clean@main + + - name: Checkout build repo + uses: actions/checkout@v6 + with: + repository: ${{ env.BUILD_REPOSITORY }} + ref: ${{ env.BUILD_REF }} + fetch-depth: 0 + clean: false # true is default. it *will* delete the hosts /dev if mounted inside. + path: build + + - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" + uses: actions/checkout@v6 + if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} + with: + repository: ${{ env.USERPATCHES_REPOSITORY }} + ref: ${{ env.USERPATCHES_REF }} + fetch-depth: 0 + clean: false # true is default. + path: userpatches + + - name: Checkout JSON artifacts repository + uses: actions/checkout@v6 + with: + repository: armbian/armbian.github.io + ref: data + clean: false + path: armbian.github.io + + - name: "Checkout torrent lists" + uses: actions/checkout@v6 + with: + repository: XIU2/TrackersListCollection + clean: false + ref: master # true is default + path: trackerslist + fetch-depth: 1 + + - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" + id: latest-commit + run: | + cd build + echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + cd .. + + - name: "Put userpatches in place, and remove userpatches repo" + if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} + run: | + + mkdir -pv build/userpatches + rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ + + # Use new release YAML targets + rsync -av armbian.github.io/data/release-targets/. build/userpatches/ + + - name: GitHub cache + id: cache-restore + uses: actions/cache@v5 + with: + path: | + cache/memoize + cache/oci/positive + key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" + restore-keys: | + ${{ runner.os }}-matrix-cache- + + - name: Docker Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: "${{ github.repository_owner }}" # GitHub username or org + password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. + + - name: Prepare Info JSON and Matrices + id: prepare-matrix + run: | + + FILTERS="${{ github.event.inputs.targetsFilterInclude }}" + + if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then + FILTERS='BOARD:${{ github.event.inputs.board }}' + fi + + if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then + FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' + fi + + # this sets outputs "artifact-matrix" #and "image-matrix" + cd build + bash ./compile.sh gha-matrix ${{ inputs.prepare_config }} \ + REVISION="${{ needs.version_prep.outputs.version }}" \ + TARGETS_FILTER_INCLUDE="${FILTERS}" \ + BETA=${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }} \ + CLEAN_INFO=yes \ + CLEAN_MATRIX=yes \ + MATRIX_ARTIFACT_CHUNKS=17 \ + MATRIX_IMAGE_CHUNKS=16 \ + CHECK_OCI=${{ github.event.inputs.checkOci || inputs.nightlybuild_default }} \ + TARGETS_FILENAME="${{ inputs.targets_filename }}" \ + SKIP_IMAGES=${{ github.event.inputs.skipImages || inputs.skipimages_default }} \ + ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes + + - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" + if: always() + run: | + echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" + + - name: Generate server lists from NetBox (JSON) + id: prepare-urls + run: | + + set -euo pipefail + + mkdir -p build/output/info + + # copy trackers list + cp trackerslist/best.txt build/output/info/best-torrent-servers.txt + + BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" + + # One jq filter used for all kinds – same fields everywhere + JQ_FILTER=' + .results + | map( + select(.name != null) + | { + host: .name, + upload_path: (.custom_fields["path"] // ""), + + download_path_archive: + ((.custom_fields["download_path_archive"] // "/archive") + | if startswith("/") then . else "/" + . end), + + download_path_images: + ((.custom_fields["download_path_images"] // "/dl") + | if startswith("/") then . else "/" + . end), + + download_path_debs: + ((.custom_fields["download_path_debs"] // "/apt") + | if startswith("/") then . else "/" + . end), + + port: (.custom_fields["port"] // 22), + username: (.custom_fields["username"] // "mirror") + } + ) + | sort_by(.host) + ' + + for kind in servers servers-download servers-cache servers-upload; do + case "$kind" in + servers) + # All mirrors serving images + url="$BASE_URL&device_role=Mirror&tag=images" + outfile="build/output/info/servers.jq" + ;; + servers-download) + # Mirrors used as HTTP download sources (webseeds) + url="$BASE_URL&device_role=Mirror&tag=images" + outfile="build/output/info/servers-download.jq" + ;; + servers-cache) + # Cache mirrors (can be used for HTTP + rsync) + url="$BASE_URL&device_role=Mirror&tag=cache" + outfile="build/output/info/servers-cache.jq" + ;; + servers-upload) + # Upload targets (SSH) + url="$BASE_URL&tag=upload&tag=images" + outfile="build/output/info/servers-upload.jq" + ;; + esac + + echo "Generating $outfile from $url" + + # Fetch with retries; curl handles transient failures itself. + # --retry-all-errors covers connection timeouts as well as 5xx. + response="$(curl -s --fail \ + --connect-timeout 10 --max-time 30 \ + --retry 5 --retry-delay 2 --retry-all-errors \ + -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ + -H "Accept: application/json; indent=4" \ + "$url")" + + echo "$response" | jq "$JQ_FILTER" > "$outfile" + + # Guard against a valid-but-empty result set: NetBox returning no + # matching VMs would otherwise produce an empty server list and let + # the build proceed with nowhere to upload/download from. + count="$(jq 'length' "$outfile")" + if [ "$count" -eq 0 ]; then + echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" + exit 1 + fi + echo " -> $count server(s)" + + done + + - uses: actions/upload-artifact@v7 + name: Upload output/info as GitHub Artifact + with: + name: build-info-json + path: build/output/info + + - name: chown cache memoize/oci back to normal user + run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive + + outputs: + build-sha1: ${{ steps.latest-commit.outputs.sha1 }} + version: ${{ needs.version_prep.outputs.version }} + # Per-chunk matrices produced by `compile.sh gha-matrix`. + artifacts-chunk-json-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-1 }} + artifacts-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-1 }} + artifacts-chunk-json-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-2 }} + artifacts-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-2 }} + artifacts-chunk-json-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-3 }} + artifacts-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-3 }} + artifacts-chunk-json-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-4 }} + artifacts-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-4 }} + artifacts-chunk-json-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-5 }} + artifacts-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-5 }} + artifacts-chunk-json-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-6 }} + artifacts-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-6 }} + artifacts-chunk-json-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-7 }} + artifacts-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-7 }} + artifacts-chunk-json-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-8 }} + artifacts-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-8 }} + artifacts-chunk-json-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-9 }} + artifacts-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-9 }} + artifacts-chunk-json-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-10 }} + artifacts-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-10 }} + artifacts-chunk-json-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-11 }} + artifacts-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-11 }} + artifacts-chunk-json-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-12 }} + artifacts-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-12 }} + artifacts-chunk-json-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-13 }} + artifacts-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-13 }} + artifacts-chunk-json-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-14 }} + artifacts-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-14 }} + artifacts-chunk-json-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-15 }} + artifacts-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-15 }} + artifacts-chunk-json-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-16 }} + artifacts-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-16 }} + artifacts-chunk-json-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-17 }} + artifacts-chunk-not-empty-17: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-17 }} + images-chunk-json-1: ${{ steps.prepare-matrix.outputs.images-chunk-json-1 }} + images-chunk-not-empty-1: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-1 }} + images-chunk-json-2: ${{ steps.prepare-matrix.outputs.images-chunk-json-2 }} + images-chunk-not-empty-2: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-2 }} + images-chunk-json-3: ${{ steps.prepare-matrix.outputs.images-chunk-json-3 }} + images-chunk-not-empty-3: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-3 }} + images-chunk-json-4: ${{ steps.prepare-matrix.outputs.images-chunk-json-4 }} + images-chunk-not-empty-4: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-4 }} + images-chunk-json-5: ${{ steps.prepare-matrix.outputs.images-chunk-json-5 }} + images-chunk-not-empty-5: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-5 }} + images-chunk-json-6: ${{ steps.prepare-matrix.outputs.images-chunk-json-6 }} + images-chunk-not-empty-6: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-6 }} + images-chunk-json-7: ${{ steps.prepare-matrix.outputs.images-chunk-json-7 }} + images-chunk-not-empty-7: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-7 }} + images-chunk-json-8: ${{ steps.prepare-matrix.outputs.images-chunk-json-8 }} + images-chunk-not-empty-8: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-8 }} + images-chunk-json-9: ${{ steps.prepare-matrix.outputs.images-chunk-json-9 }} + images-chunk-not-empty-9: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-9 }} + images-chunk-json-10: ${{ steps.prepare-matrix.outputs.images-chunk-json-10 }} + images-chunk-not-empty-10: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-10 }} + images-chunk-json-11: ${{ steps.prepare-matrix.outputs.images-chunk-json-11 }} + images-chunk-not-empty-11: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-11 }} + images-chunk-json-12: ${{ steps.prepare-matrix.outputs.images-chunk-json-12 }} + images-chunk-not-empty-12: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-12 }} + images-chunk-json-13: ${{ steps.prepare-matrix.outputs.images-chunk-json-13 }} + images-chunk-not-empty-13: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-13 }} + images-chunk-json-14: ${{ steps.prepare-matrix.outputs.images-chunk-json-14 }} + images-chunk-not-empty-14: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-14 }} + images-chunk-json-15: ${{ steps.prepare-matrix.outputs.images-chunk-json-15 }} + images-chunk-not-empty-15: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-15 }} + images-chunk-json-16: ${{ steps.prepare-matrix.outputs.images-chunk-json-16 }} + images-chunk-not-empty-16: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-16 }} + + # ---- fan out artifact builds: one reusable call per chunk (body in build-artifacts-chunk.yml) ---- + build-artifacts: + needs: matrix_prep + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] + uses: ./.github/workflows/build-artifacts-chunk.yml + with: + chunk_json: ${{ needs.matrix_prep.outputs[format('artifacts-chunk-json-{0}', matrix.chunk)] }} + not_empty: ${{ needs.matrix_prep.outputs[format('artifacts-chunk-not-empty-{0}', matrix.chunk)] }} + version: ${{ needs.matrix_prep.outputs.version }} + build_sha1: ${{ needs.matrix_prep.outputs.build-sha1 }} + extra_params_all_builds: ${{ github.event.inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }} + secrets: inherit + + # ---- fan out image builds: one reusable call per chunk (body in build-images-chunk.yml) ---- + build-images: + needs: [ matrix_prep, build-artifacts ] + if: ${{ !failure() && !cancelled() }} + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + uses: ./.github/workflows/build-images-chunk.yml + with: + chunk_json: ${{ needs.matrix_prep.outputs[format('images-chunk-json-{0}', matrix.chunk)] }} + not_empty: ${{ needs.matrix_prep.outputs[format('images-chunk-not-empty-{0}', matrix.chunk)] }} + version: ${{ needs.matrix_prep.outputs.version }} + build_sha1: ${{ needs.matrix_prep.outputs.build-sha1 }} + release_repository: ${{ inputs.release_repository }} + nightlybuild: ${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }} + extra_params_all_builds: ${{ github.event.inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }} + secrets: inherit + + # ------ publish packages to repository ------- + publish-debs-to-repo: + name: "Download artifacts from ORAS cache" + runs-on: repository + if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 + needs: [ matrix_prep, build-artifacts ] + steps: + + - name: Docker Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: "${{ github.repository_owner }}" # GitHub username or org + password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. + + - name: Checkout build repo + uses: actions/checkout@v6 + with: + repository: ${{ env.BUILD_REPOSITORY }} + ref: ${{ needs.matrix_prep.outputs.build-sha1 }} + fetch-depth: 0 + path: build + clean: false + + - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" + uses: actions/checkout@v6 + if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} + with: + repository: ${{ env.USERPATCHES_REPOSITORY }} + ref: ${{ env.USERPATCHES_REF }} + fetch-depth: 0 + clean: false + path: os + + - name: "Put userpatches in place, and remove userpatches repo" + if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} + run: | + + rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ + + - name: Download artifacts + uses: actions/download-artifact@v8 + with: + name: build-info-json + path: build/output/info + + - name: List artifacts + run: | + + ls -laht build/output/info + + - name: Run debs-to-repo download + env: + BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} + run: | + + set -euo pipefail + cd build + ./compile.sh debs-to-repo-download \ + REVISION="${{ needs.matrix_prep.outputs.version }}" \ + BETA="$BETA" \ + SHARE_LOG=yes \ + ${{ env.EXTRA_PARAMS_ALL_BUILDS }} + + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.KEY_UPLOAD }} + known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} + if_key_exists: replace + + - name: "Upload artifacts" + run: | + + set -euo pipefail + + echo "Sync debs and debs-beta" + rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ + -rvc \ + --delete \ + --remove-source-files \ + --prune-empty-dirs \ + --include='debs/' \ + --include='debs-beta/' \ + --include='debs/***/' \ + --include='debs-beta/***/' \ + --include='debs/*.deb' \ + --include='debs-beta/*.deb' \ + --include='debs/**/*.deb' \ + --include='debs-beta/**/*.deb' \ + --exclude='*' \ + --omit-dir-times \ + --no-perms \ + --no-owner \ + --no-group \ + build/output/ \ + "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" + + - name: "Run repository update action" + if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) == 'yes' }} + uses: peter-evans/repository-dispatch@v4 + with: + token: ${{ secrets.DISPATCH }} + repository: armbian/armbian.github.io + event-type: "Repository update" + client-payload: > + { + "target": "${{ env.TARGET_PATH }}", + "download_external": true + } + + - name: "Logs debs-to-repo-download" + run: ':' + + outputs: + version: ${{ needs.matrix_prep.outputs.version }} + + # ------ finalize the run ------- + closing: + name: "Footer" + runs-on: ubuntu-latest + if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || inputs.nightlybuild_default) == 'yes' }} + needs: [ matrix_prep, build-artifacts, build-images ] + steps: + + - name: "Download all workflow run artifacts" + if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) != 'yes' }} + uses: actions/download-artifact@v8 + with: + name: assets-for-download-${{ inputs.repository_ref }} + path: downloads + + - name: "Read version" + run: | + + echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV + + - uses: geekyeggo/delete-artifact@v6 + with: + name: assets-for-download-${{ inputs.repository_ref }} + failOnError: false + + - name: "Keep only 30 days of workflow logs" + uses: igorjs/gh-actions-clean-workflow@v7 + with: + token: "${{ env.GH_TOKEN }}" + runs_older_than: 30 # optional + runs_to_keep: 0 # optional + + - uses: ncipollo/release-action@v1 + if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) != 'yes' && (github.event.inputs.nightlybuild || inputs.nightlybuild_default) == 'yes' }} + with: + repo: "${{ env.RELEASE_REPOSITORY }}" + tag: "${{ env.version }}" + omitBody: true + omitName: true + allowUpdates: true + makeLatest: true + token: "${{ env.GH_TOKEN }}" + + - name: "Run repository mirroring to CDN" + if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) == 'no' }} + uses: peter-evans/repository-dispatch@v4 + with: + token: ${{ secrets.DISPATCH }} + repository: armbian/armbian.github.io + event-type: "Infrastructure: Mirror artifacts" + client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' + + - name: "Run webindex update action" + if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) == 'no' }} + uses: peter-evans/repository-dispatch@v4 + with: + token: ${{ secrets.DISPATCH }} + repository: armbian/armbian.github.io + event-type: "Data: Update download index" diff --git a/.github/workflows/recreate-matrix.yml b/.github/workflows/recreate-matrix.yml deleted file mode 100644 index 11c3a198a89..00000000000 --- a/.github/workflows/recreate-matrix.yml +++ /dev/null @@ -1,523 +0,0 @@ -# -# This action recreate action for building stable images -# -name: Recreate Matrix (push) -on: - schedule: - - cron: '00 5 * * *' - push: - branches: - - 'main' - paths: - - "userpatches/*.template" - - "userpatches/*.map" - - "userpatches/*.blacklist" - - "userpatches/gha/**" - - "userpatches/gha/chunks/**" - - ".github/workflows/recreate-matrix.yml" - - workflow_dispatch: - repository_dispatch: - types: [Recreate Matrix] - -concurrency: - group: matrix-${{ github.ref }} - cancel-in-progress: true - -jobs: - - build: - name: Recreate action - if: ${{ github.repository_owner == 'Armbian' }} - runs-on: ubuntu-latest - steps: - - - name: Checkout Armbian Framework - uses: actions/checkout@v6 - with: - persist-credentials: false - repository: armbian/build - ref: main - fetch-depth: 0 - clean: false - path: build - - - name: Checkout Armbian OS Config - uses: actions/checkout@v6 - with: - persist-credentials: false - repository: armbian/os - ref: main - clean: false - fetch-depth: 0 - path: os - - - name: "Rsync userpatches" - run: | - - rsync -av os/userpatches/. build/userpatches/ - rm -f build/userpatches/targets.yaml - - - name: "Generate builds lists" - run: | - - cat <<- EOF > os/.github/workflows/complete-artifact-one-by-one.yml - name: "Build One by One (anyone)" - on: - workflow_dispatch: - inputs: - - armbian_target: - type: choice - description: 'Build' - required: false - options: - - kernel - - build - default: build - - armbian_kernel_branch: - type: choice - description: 'Kernel branch' - options: - - legacy - - current - - edge - default: 'current' - - armbian_release: - type: choice - description: 'Userspace' - options: - - jammy - - mantic - - bookworm - - trixie - - sid - default: 'jammy' - - armbian_ui: - type: choice - description: 'User interface (not all works)' - options: - - minimal - - server - - xfce - - gnome - - cinnamon - - i3-wm - - kde-plasma - default: 'minimal' - - armbian_version: - description: 'Version' - required: false - default: '' - - armbian_board: - type: choice - description: 'Board' - options: - EOF - # generate lists to include them - ls -1 build/config/boards/*.{conf,wip,csc} | cut -d"/" -f4 | cut -d"." -f1 | uniq | sed 's/.*/ - &/' >> os/.github/workflows/complete-artifact-one-by-one.yml - - cat <<- EOF >> os/.github/workflows/complete-artifact-one-by-one.yml - jobs: - - build: - name: "Build Armbian" - runs-on: ubuntu-latest - steps: - - - uses: armbian/build@v26.2.1 - with: - armbian_token: "${{ '\${{secrets.GITHUB_TOKEN}}' }}" - armbian_target: "${{ '\${{inputs.armbian_target}}' }}" - armbian_release: "${{ '\${{inputs.armbian_release}}' }}" - armbian_kernel_branch: "${{ '\${{inputs.armbian_kernel_branch}}' }}" - armbian_ui: "${{ '\${{inputs.armbian_ui}}' }}" - armbian_board: "${{ '\${{inputs.armbian_board}}' }}" - armbian_release_tittle: "Armbian SDK" - armbian_release_body: "Virtual images for x86 and arm64" - armbian_pgp_key: "${{ '\${{secrets.GPG_KEY1}}' }}" - armbian_pgp_password: "${{ '\${{secrets.GPG_PASSPHRASE1}}' }}" - EOF - - - name: "Generate builds lists" - run: | - - # generate lists to include them - find build/config/boards/*.{conf,wip,tvb,csc} ! '(' -name '*.eos' ')' | cut -d"/" -f4 | cut -d"." -f1 | uniq | sort | sed 's/.*/ - &/' > /tmp/standard-support.txt - cp /tmp/standard-support.txt /tmp/staging.txt - cp /tmp/standard-support.txt /tmp/community-maintained.txt - #ls -1 build/config/boards/*.{conf,wip} | cut -d"/" -f4 | cut -d"." -f1 | uniq | sed 's/.*/ - &/' > /tmp/staging.txt - #ls -1 build/config/boards/*.{tvb,csc} | cut -d"/" -f4 | cut -d"." -f1 | uniq | sed 's/.*/ - &/' > /tmp/community-maintained.txt - awk -F= ' - # skip full-line comments - /^[[:space:]]*#/ { next } - - # only process lines with BOARD_MAINTAINER= in the first field - $1 ~ /^[[:space:]]*BOARD_MAINTAINER[[:space:]]*$/ { - line = $2 - - sub(/#.*/, "", line) # strip trailing comments - gsub(/"/, "", line) # drop quotes - gsub(/[;,]/, " ", line) # commas/semicolons -> space - - # trim leading/trailing whitespace - sub(/^[[:space:]]+/, "", line) - sub(/[[:space:]]+$/, "", line) - - n = split(line, a, /[[:space:]]+/) - for (i = 1; i <= n; i++) - if (a[i] != "") - print a[i] - } - ' build/config/boards/*.{conf,eos,wip,csc} \ - | LC_ALL=C sort -u \ - | awk '{ printf " - \"%s\"\n", $0 }' > /tmp/maintainers.txt - - branches=$(git -C build/ for-each-ref \ - --sort=-committerdate \ - --count=20 \ - --format='%(refname:short)' \ - refs/remotes/origin) - - { - echo main - printf '%s\n' "$branches" \ - | sed 's#^origin/##' \ - | grep -v '^main$' - } | sed 's/.*/ - &/' > /tmp/branches.txt - - # debug - cat /tmp/branches.txt - echo "-" - cat /tmp/maintainers.txt - echo "-" - cat /tmp/standard-support.txt - echo "-" - cat /tmp/staging.txt - echo "-" - cat /tmp/community-maintained.txt - echo "-" - - function enable_extension() - { - : - } - - function display_alert() - { - : - } - - function make-targets() - { - - # cycle all board configs but .eos (end of support, which we swithc to when it breaks) - - # We are replacing in target yaml. copy from template - cp $2.template $2.yaml - - for SECTION in $1; do - - # debug - echo "Adding: $SECTION to $2" >> $GITHUB_STEP_SUMMARY - - OUTPUT=$(for board in $(ls -1 build/config/boards/*.{conf,csc,wip,tvb}); do - - # read board family and determine ARCH - unset KERNEL_TEST_TARGET - SRC=$(pwd)/build - source $board - source build/config/sources/families/${BOARDFAMILY}.conf 2>/dev/null || true - - # board status defines where it goes - if [[ $board == *.conf* ]]; then SUPPORT="standard-support"; - elif [[ $board == *.wip* ]]; then SUPPORT="staging"; - else SUPPORT="community-maintained"; fi - - # ... and board ARCH defines where it goes - TARGET_SECTION="${SUPPORT}-slow-hdmi" - if [[ $ARCH == riscv64 ]]; then TARGET_SECTION="${SUPPORT}-riscv64"; - elif grep -q 'HAS_VIDEO_OUTPUT="no"' $board; then TARGET_SECTION="${SUPPORT}-headless"; - elif [[ $ARCH == arm64 || $ARCH == amd64 ]]; then TARGET_SECTION="${SUPPORT}-fast-hdmi" - fi - - # exception. Lets not deal with this special target anymore, but leave it in configs - #KERNEL_TARGET=${KERNEL_TARGET/,collabora/} - - # if kernel test target is defined, override with it - [[ -n $KERNEL_TEST_TARGET ]] && KERNEL_TARGET=${KERNEL_TEST_TARGET} - - # read kernel target - if [[ $(echo $KERNEL_TARGET | cut -d '"' -f 2 | sed "s/,/ /g" | wc -w) -gt 2 ]]; then - KERNEL_TARGET=$(echo $KERNEL_TARGET | cut -d '"' -f 2 | awk -F, '{ print $2,$3 }') - else - KERNEL_TARGET=$(echo $KERNEL_TARGET | cut -d '"' -f 2 | sed "s/,/ /g") - fi - - # extract board name from config - board=$(echo $board | cut -d"/" -f4 | cut -d"." -f1) - - # skip boards listed in this file - if grep -qw "^${board}" os/userpatches/targets-automation.blacklist; then continue; fi - if [[ $2 == *nightly* ]] && grep -qw "^${board}" os/userpatches/targets-automation-nightly.blacklist; then continue; fi - - # our default is current build target. Use it or search further - if [[ $TARGET_SECTION == $SECTION ]]; then - VARIANTS=0 - for targets in ${KERNEL_TARGET}; do - - if [[ "${targets}" == current && $VARIANTS -lt $3 ]]; then - # check if we have extension mapping defined - EXTENSION=$(grep -P '^(?=.*'$board',)(?=.*current:)' os/userpatches/targets-extensions.map | cut -d"=" -f2) - if [[ -n $EXTENSION ]]; then EXTENSION=", ENABLE_EXTENSIONS: $EXTENSION"; fi - echo " - { BOARD: $board, BRANCH: current${EXTENSION} }" - VARIANTS=$((VARIANTS+1)) - fi - - if [[ "${targets}" == edge && $VARIANTS -lt $3 ]]; then - # check if we have extension mapping defined - EXTENSION=$(grep -P '^(?=.*'$board',)(?=.*edge:)' os/userpatches/targets-extensions.map | cut -d"=" -f2) - if [[ -n $EXTENSION ]]; then EXTENSION=", ENABLE_EXTENSIONS: $EXTENSION"; fi - echo " - { BOARD: $board, BRANCH: edge${EXTENSION} }" - VARIANTS=$((VARIANTS+1)) - fi - - if [[ "${targets}" == legacy && $VARIANTS -lt $3 ]]; then - # check if we have extension mapping defined - EXTENSION=$(grep -P '^(?=.*'$board',)(?=.*legacy:)' os/userpatches/targets-extensions.map | cut -d"=" -f2) - if [[ -n $EXTENSION ]]; then EXTENSION=", ENABLE_EXTENSIONS: $EXTENSION"; fi - echo " - { BOARD: $board, BRANCH: legacy${EXTENSION} }" - VARIANTS=$((VARIANTS+1)) - fi - - if [[ "${targets}" == vendor && $VARIANTS -lt $3 ]]; then - # check if we have extension mapping defined - EXTENSION=$(grep -P '^(?=.*'$board',)(?=.*vendor:)' os/userpatches/targets-extensions.map | cut -d"=" -f2) - if [[ -n $EXTENSION ]]; then EXTENSION=", ENABLE_EXTENSIONS: $EXTENSION"; fi - echo " - { BOARD: $board, BRANCH: vendor${EXTENSION} }" - VARIANTS=$((VARIANTS+1)) - fi - - #if [[ ! "$targets" =~ ^(legacy|vendor|current|edge)$ && $targets != collabora ]]; then - if [[ ! "$targets" =~ ^(legacy|vendor|current|edge)$ ]]; then - # otherwise it must be custom kernel target - EXTENSION=$(grep -P '^(?=.*'${board}',)(?=.*'${targets}':)' os/userpatches/targets-extensions.map | cut -d"=" -f2) - if [[ -n $EXTENSION ]]; then EXTENSION=", ENABLE_EXTENSIONS: $EXTENSION"; fi - echo " - { BOARD: $board, BRANCH: ${targets}${EXTENSION}}" - fi - - done - fi - - done) - - if [[ -n $OUTPUT ]]; then - TEMPFILE=$(mktemp) - ( - echo " $SECTION: &$SECTION" - echo " # auto generated section" - while IFS= read -r line - do - echo "$line" - done <<< $OUTPUT - # mark end of the section - echo -e " # end of auto generated section\n" - ) > $TEMPFILE - - # since its not empty, add to the yaml file - sed -i "/ # automated lists start/r $TEMPFILE" ${2}.yaml - - # add also to section - sed -i -e '/ automated-section/,/items/{/items/a \ \ \ \ \ \ - *'$SECTION'' -e '}' ${2}.yaml - else - # remove tags from manually made sections or it will exit with error - sed -i '/'$SECTION'/d' ${2}.yaml - echo "Removing $SECTION from $2" - fi - done - } - - # define which sections goes into which list - BUILD_SECTIONS_STANDARD_SUPPORT=" - standard-support-slow-hdmi - standard-support-fast-hdmi - standard-support-headless - standard-support-riscv64 - " - BUILD_SECTIONS_COMMUNITY_MAINTAINED=" - community-maintained-slow-hdmi - community-maintained-fast-hdmi - community-maintained-headless - community-maintained-riscv64 - " - BUILD_SECTIONS_NIGHTLY=" - standard-support-slow-hdmi - staging-slow-hdmi - standard-support-fast-hdmi - staging-fast-hdmi - standard-support-headless - staging-headless - standard-support-riscv64 - staging-riscv64 - " - - # $1 = parts to add - # $2 = filename that copy from .template to .yaml - # $3 = how many to build 1 or 2 (current,edge,legacy; current,legacy) - - make-targets "${BUILD_SECTIONS_STANDARD_SUPPORT}" "os/userpatches/targets-release-standard-support" "2" - make-targets "${BUILD_SECTIONS_STANDARD_SUPPORT}" "os/userpatches/targets-release-apps" "1" - make-targets "${BUILD_SECTIONS_COMMUNITY_MAINTAINED}" "os/userpatches/targets-release-community-maintained" "1" - make-targets "${BUILD_SECTIONS_NIGHTLY}" "os/userpatches/targets-release-nightly" "1" - - - name: "Generate Action Script" - run: | - - cd build - # Last stable branch - #LAST=$(git branch -r | grep "v2" | tail -1 | cut -d"/" -f2) - - # generate targets-release-community-maintained.yaml - #cp userpatches/targets-release-community-maintained.template userpatches/targets-release-community-maintained.yaml - #sed -i '/ # unsupported/r /tmp/unsupported.txt' userpatches/targets-release-community-maintained.yaml - #sed -i '/ # unsupported-headless/r /tmp/unsupported-headless.txt' userpatches/targets-release-community-maintained.yaml - - #git add userpatches/targets-release-community-maintained.yaml - #git add userpatches/targets-release-community-maintained.yaml - #git add -f userpatches/targets-release-community-maintained.yaml - - # - # copy nightly templates - # - cp userpatches/gha/gha_config_nightly.yaml userpatches/gha/gha_config.yaml - cp userpatches/targets-release-nightly.yaml userpatches/targets.yaml - bash ./compile.sh gha-template - # add boards, maintainers and branches - sed -i '/# boards/r /tmp/staging.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# branches/r /tmp/branches.txt' output/info/artifact-image-complete-matrix.yml - - # we need unique concurency id - sed -i "s/pipeline-/pipeline-nightly/" output/info/artifact-image-complete-matrix.yml - cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-nightly.yml - - # - # copy old stable templates - # - #cp userpatches/gha/gha_config_old_stable.yaml userpatches/gha/gha_config.yaml - #cp userpatches/targets-release.yaml userpatches/targets.yaml - #sed -i 's/build_ref: .*/build_ref: "'$LAST'"/' userpatches/gha/gha_config.yaml - #bash ./compile.sh gha-template - # add boards and maintainers - #sed -i '/# boards/r /tmp/boards.txt' output/info/artifact-image-complete-matrix.yml - #sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - # we need unique concurency id - #sed -i "s/pipeline-/pipeline-old-stable/" output/info/artifact-image-complete-matrix.yml - #cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-old-stable.yml - - - # - # copy standard support release templates - # - cp userpatches/gha/gha_config_release_standard_support.yaml userpatches/gha/gha_config.yaml - cp userpatches/targets-release-standard-support.yaml userpatches/targets.yaml - bash ./compile.sh gha-template - # add boards and maintainers - sed -i '/# boards/r /tmp/standard-support.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# branches/r /tmp/branches.txt' output/info/artifact-image-complete-matrix.yml - # we need unique concurency id - sed -i "s/pipeline-/standard-support/" output/info/artifact-image-complete-matrix.yml - cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-standard-support.yml - - - # - # copy apps release templates - # - cp userpatches/gha/gha_config_release_apps.yaml userpatches/gha/gha_config.yaml - cp userpatches/targets-release-apps.yaml userpatches/targets.yaml - bash ./compile.sh gha-template - # add boards and maintainers - sed -i '/# boards/r /tmp/standard-support.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# branches/r /tmp/branches.txt' output/info/artifact-image-complete-matrix.yml - # we need unique concurency id - sed -i "s/pipeline-/apps/" output/info/artifact-image-complete-matrix.yml - cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-apps.yml - - # - # copy unsupported release templates - # - cp userpatches/gha/gha_config_release_community_maintained.yaml userpatches/gha/gha_config.yaml - cp userpatches/targets-release-community-maintained.yaml userpatches/targets.yaml - bash ./compile.sh gha-template - # add boards and maintainers - sed -i '/# boards/r /tmp/community-maintained.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# branches/r /tmp/branches.txt' output/info/artifact-image-complete-matrix.yml - # we need unique concurency id - sed -i "s/pipeline-/unsupported/" output/info/artifact-image-complete-matrix.yml - cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-community-maintained.yml - - - # - # copy all templates - # - cp userpatches/gha/gha_config_all.yaml userpatches/gha/gha_config.yaml - cp userpatches/targets-all-not-eos.yaml userpatches/targets.yaml - bash ./compile.sh gha-template - # add boards and maintainers - sed -i '/# boards/r /tmp/boards.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# branches/r /tmp/branches.txt' output/info/artifact-image-complete-matrix.yml - # we need unique concurency id - sed -i "s/pipeline-/pipeline-all/" output/info/artifact-image-complete-matrix.yml - cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-all.yml - - # - # copy all stable templates - # - cp userpatches/gha/gha_config_all_stable.yaml userpatches/gha/gha_config.yaml - cp userpatches/targets-all-not-eos.yaml userpatches/targets.yaml - bash ./compile.sh gha-template - # add boards and maintainers - sed -i '/# boards/r /tmp/boards.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# maintainers/r /tmp/maintainers.txt' output/info/artifact-image-complete-matrix.yml - sed -i '/# branches/r /tmp/branches.txt' output/info/artifact-image-complete-matrix.yml - # we need unique concurency id - sed -i "s/pipeline-/pipeline-all/" output/info/artifact-image-complete-matrix.yml - cp output/info/artifact-image-complete-matrix.yml ../os/.github/workflows/complete-artifact-matrix-all-stable.yml - - cd ../os - git config --local user.email "info@armbian.com" - git config --local user.name "Armbianworker" - - # Update build list - git add userpatches/targets-release-standard-support.yaml - git add userpatches/targets-release-community-maintained.yaml - git add userpatches/targets-release-apps.yaml - git add userpatches/targets-release-nightly.yaml - - # Add Action Scritps - git add .github/workflows/complete-artifact-matrix-nightly.yml - #git add .github/workflows/complete-artifact-matrix-old-stable.yml - git add .github/workflows/complete-artifact-matrix-standard-support.yml - git add .github/workflows/complete-artifact-matrix-community-maintained.yml - git add .github/workflows/complete-artifact-matrix-apps.yml - git add .github/workflows/complete-artifact-matrix-all.yml - git add .github/workflows/complete-artifact-matrix-all-stable.yml - git add .github/workflows/complete-artifact-one-by-one.yml - git commit --allow-empty -m "Update generated GHA chunk workflow artifact-image-complete-matrix.yml" -a - - - name: Push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - repository: armbian/os - branch: ${{ github.ref }} - directory: os diff --git a/.github/workflows/watchdog.yml b/.github/workflows/watchdog.yml deleted file mode 100644 index 10aab172ef7..00000000000 --- a/.github/workflows/watchdog.yml +++ /dev/null @@ -1,46 +0,0 @@ -# -# This action recreate action for building stable images -# -name: Watchdog (cronjob) -on: - schedule: - - cron: '*/15 * * * *' - workflow_dispatch: - -env: - GH_TOKEN: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - -concurrency: - group: watchdog-${{ github.ref }} - cancel-in-progress: true - -jobs: - - gradle: - strategy: - fail-fast: false - max-parallel: 8 - matrix: - - # list scripts you want to watch and execute failed jobs x-times - script: ["repository-update","complete-artifact-matrix-all","complete-artifact-matrix-all-stable","complete-artifact-matrix-nightly","complete-artifact-matrix-standard-support","complete-artifact-matrix-community-maintained","complete-artifact-matrix-apps","full-distro-build-and-test"] - - name: rerun - runs-on: ubuntu-latest - steps: - - - name: "Restart ${{ matrix.script }}.yml" - run: | - - # Define variables here - OWNER_REPO="armbian/os" - ATTEMPTS="6" - SCRIPT="${{ matrix.script }}" - - WORKFLOW=$(gh api "/repos/${OWNER_REPO}/actions/workflows" | jq '.workflows[] | select(.path==".github/workflows/'${{ matrix.script }}'.yml")' | jq -r '.id') - read ID STATUS ATTEMPT <<< $(gh api "/repos/${OWNER_REPO}/actions/workflows/${WORKFLOW}/runs" | jq '.workflow_runs[]' | jq -r '.id,.conclusion,.run_attempt' | head -3 | xargs -n3 -d'\n') - - # if attempt is lower then 5 and status is "cancelled" or "failed", rerun failed jobs - if [ "${ATTEMPT}" -lt "${ATTEMPTS}" ] && ([ "$STATUS" == "failure" ] || [ "$STATUS" == "failure" ]); then - gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/${OWNER_REPO}/actions/runs/${ID}/rerun-failed-jobs - fi diff --git a/userpatches/gha/chunks/050.single_header.yaml b/userpatches/gha/chunks/050.single_header.yaml deleted file mode 100644 index 28ceb90435e..00000000000 --- a/userpatches/gha/chunks/050.single_header.yaml +++ /dev/null @@ -1,452 +0,0 @@ -name: "[[script_name]]" - -on: - [[cron_job]] - workflow_call: - inputs: - ref: # commit id - required: false - type: string - extraParamsAllBuilds: # addional build parameter - required: false - type: string - secrets: - ORG_MEMBERS: - required: true - workflow_dispatch: - inputs: - skipImages: - description: 'Skip building images? no = build images, yes = skip images' - required: true - options: [ 'yes', 'no' ] - type: choice - default: [[skipImagesDefaults]] - checkOci: - description: 'Check OCI for existing artifacts? yes = check OCI, no = always build everything' - required: true - options: [ 'yes', 'no' ] - type: choice - default: [[checkOciDefaults]] - extraParamsAllBuilds: - description: 'Extra params for all builds/jobs (prepare/artifact/image) (eg: DEBUG=yes)' - required: false - default: '' - type: string - branch: - type: choice - description: 'Framework build branch' - options: - # branches - default: 'main' - board: - type: choice - description: 'Board' - options: - # boards - - all - default: 'all' - maintainer: - type: choice - description: 'Maintainer' - options: - # maintainers - - all - default: 'all' - targetsFilterInclude: - description: 'TARGETS_FILTER_INCLUDE, example: "BOARD:odroidhc4,BOARD:odroidn2"' - required: false - default: '' - type: string - nightlybuild: - description: 'yes = nighlty, no = stable' - required: false - options: [ 'yes', 'no' ] - type: choice - default: [[nightlybuildDefaults]] - bumpversion: - type: boolean - description: "Bump version" - default: [[bumpVersion]] - versionOverride: - description: 'Version override' - required: false - default: '' - -env: - - # For easier reuse across the multiple chunks ('armbian/build' repo) - BUILD_REPOSITORY: "[[org_and_build_repo]]" - BUILD_REF: "${{ inputs.ref || inputs.branch || 'main' }}" # branch or tag or sha1 - - # For easier reuse across the multiple chunks ('armbian/os' repo) - USERPATCHES_REPOSITORY: "[[org_and_userpatches_repo]]" - USERPATCHES_REF: "[[userpatches_ref]]" # branch or tag or sha1 - USERPATCHES_DIR: "[[userpatches_dir]]" # folder inside USERPATCHES_REPOSITORY - - # Github repository for releases. Normally its the one where we executing script - RELEASE_REPOSITORY: "[[release_repo]]" - - # Armbian envs. Adjust to your needs. - # This makes builds faster, but only if the Docker images are up-to-date with all dependencies, Python, tools, etc. Otherwise it makes it... slower. - DOCKER_SKIP_UPDATE: "yes" # Do not apt update/install/requirements/etc during Dockerfile build, trust that Docker images are up-to-date. - - # Added to every build, even the prepare job. - EXTRA_PARAMS_ALL_BUILDS: "${{ inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}" - - # Storage deployment path - TARGET_PATH: "[[target_upload]]" - - # Version management - VERSION_OVERRIDE: "${{ github.event.inputs.versionOverride }}" - VERSION_BUMP: "${{ github.event.inputs.bumpversion || [[bumpVersion]] }}" - - # To use GitHub CLI in a GitHub Actions workflow - GH_TOKEN: "${{ secrets.ACCESS_TOKEN }}" - - # Added to every image build arguments. - EXTRA_PARAMS_IMAGE: "SHARE_LOG=yes " - -# To ensure that only a single workflow using the same concurrency group will run at a time -concurrency: - group: [[concurrency_group]] - cancel-in-progress: false - -jobs: - - # additional security check - team_check: - permissions: - actions: write - name: "Team check" - runs-on: [ "ubuntu-latest" ] - steps: - - - name: "Check membership" - uses: armbian/actions/team-check@main - with: - ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }} - GITHUB_TOKEN: "${{ env.GH_TOKEN }}" - TEAM: "[[team]]" - - version_prep: - needs: team_check - name: "Bump version" - runs-on: ubuntu-latest - steps: - - # Clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - - - name: Determine version - id: versionfile - run: | - # file = where version is getting stored, different for stable and nightly - # skip_tag = we only upload nighlty to GH releases - echo "file=nightly" >> $GITHUB_OUTPUT - echo "skip_tag=false" >> $GITHUB_OUTPUT - echo "pre_release=true" >> $GITHUB_OUTPUT - if [ "${{ github.event.inputs.nightlybuild || [[nightlybuildDefaults]] }}" == "no" ]; then - echo "file=stable" >> $GITHUB_OUTPUT - echo "skip_tag=true" >> $GITHUB_OUTPUT - echo "pre_release=false" >> $GITHUB_OUTPUT - fi - - # Bump version automatically - - name: Bump version - if: ${{ ( ! github.event.inputs.versionOverride ) && ( inputs.ref == '' ) && ( env.VERSION_BUMP == 'true' ) }} - id: changelog - uses: TriPSs/conventional-changelog-action@v5.4.0 - with: - github-token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }} - git-message: 'Bump release to {version}' - git-user-name: armbianworker - git-user-email: info@armbian.com - output-file: 'false' - skip-version-file: 'false' - skip-on-empty: 'false' - skip-commit: 'false' - skip-ci: 'false' - skip-tag: "${{ steps.versionfile.outputs.skip_tag }}" - version-file: "${{ steps.versionfile.outputs.file }}.json" - pre-release: "${{ steps.versionfile.outputs.pre_release }}" - git-branch: 'main' - tag-prefix: '' - pre-release-identifier: 'trunk' - - - name: Read version from file if nor overriden - if: ${{ ! github.event.inputs.versionOverride || env.VERSION_BUMP == 'false' }} - run: | - mkdir -p downloads - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" | sed 's/^/VERSION_OVERRIDE=/' >> $GITHUB_ENV - cat "${{ steps.versionfile.outputs.file }}.json" | jq '.version' | sed "s/\"//g" > downloads/version - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v7 - with: - name: assets-for-download-[[repository_ref]] - path: downloads - retention-days: 5 - - - name: "Generate body file" - if: ${{ (github.event.inputs.skipImages || [[skipImagesDefaults]]) != 'yes' }} - run: | - # Make html document - if [ -f release-headers/${{ env.RELEASE_REPOSITORY }}.sh ]; then - bash release-headers/${{ env.RELEASE_REPOSITORY }}.sh > body.html - fi - - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.nightlybuild || [[nightlybuildDefaults]]) == 'yes' && (github.event.inputs.skipImages || [[skipImagesDefaults]]) != 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.VERSION_OVERRIDE }}" - name: "${{ env.VERSION_OVERRIDE }}" - bodyFile: "body.html" - prerelease: "true" - allowUpdates: true - removeArtifacts: true - token: ${{ env.GH_TOKEN }} - - - name: Save - id: releases - run: | - - echo "version=${{ env.VERSION_OVERRIDE }}" >> $GITHUB_OUTPUT - - outputs: - - # not related to matrix - version: ${{ steps.releases.outputs.version }} - - matrix_prep: - name: "JSON matrix: [[num_chunks_artifacts]]/[[num_chunks_images]] :: [[num_chunks_artifacts]] artifact chunks, [[num_chunks_images]] image chunks" - if: ${{ github.repository_owner == '[[org]]' }} - needs: [ version_prep ] - runs-on: [ "self-hosted", "Linux", 'super' ] - steps: - - # Cleaning self hosted runners - - name: Runner clean - uses: armbian/actions/runner-clean@main - - # clone the build system repo (`armbian/build`) - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ env.BUILD_REF }} - fetch-depth: 0 - clean: false # true is default. it *will* delete the hosts /dev if mounted inside. - path: build - - # clone the userpatches repo (`armbian/os`) - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false # true is default. - path: userpatches - - - name: Checkout JSON artifacts repository - uses: actions/checkout@v6 - with: - repository: armbian/armbian.github.io - ref: data - clean: false - path: armbian.github.io - - # clone the torrent lists - - name: "Checkout torrent lists" - uses: actions/checkout@v6 - with: - repository: XIU2/TrackersListCollection - clean: false - ref: master # true is default - path: trackerslist - fetch-depth: 1 - - - name: "grab the sha1 of the latest commit of the build repo ${{ env.BUILD_REPOSITORY }}#${{ env.BUILD_REF }}" - id: latest-commit - run: | - cd build - echo "sha1=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - cd .. - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - mkdir -pv build/userpatches - rsync -av userpatches/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Use new release YAML targets - rsync -av armbian.github.io/data/release-targets/. build/userpatches/ - - - name: GitHub cache - id: cache-restore - uses: actions/cache@v5 - with: - path: | - cache/memoize - cache/oci/positive - key: ${{ runner.os }}-matrix-cache-${{ github.sha }}-${{ steps.latest-commit.outputs.sha1 }}" - restore-keys: | - ${{ runner.os }}-matrix-cache- - - # Login to ghcr.io, we're gonna do a lot of OCI lookups. - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "[[ghcr_user]]" # GitHub username or org - password: ${{ secrets.GITHUB_TOKEN }} # GitHub actions builtin token. repo has to have pkg access. - - - name: Prepare Info JSON and Matrices - id: prepare-matrix - run: | - - FILTERS="${{ github.event.inputs.targetsFilterInclude }}" - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.board }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD:${{ github.event.inputs.board }}' - fi - - if [ -z "${FILTERS}" ] && [ "${{ github.event.inputs.maintainer }}" != "all" ] && [ -n "${{ github.event.inputs.board }}" ]; then - FILTERS='BOARD_MAINTAINERS:${{ github.event.inputs.maintainer }}' - fi - - # this sets outputs "artifact-matrix" #and "image-matrix" - cd build - bash ./compile.sh gha-matrix [[userpatches_config_for_prepare_job]] \ - REVISION="${{ needs.version_prep.outputs.version }}" \ - TARGETS_FILTER_INCLUDE="${FILTERS}" \ - BETA=${{ github.event.inputs.nightlybuild || [[nightlybuildDefaults]] }} \ - CLEAN_INFO=yes \ - CLEAN_MATRIX=yes \ - MATRIX_ARTIFACT_CHUNKS=[[num_chunks_artifacts]] \ - MATRIX_IMAGE_CHUNKS=[[num_chunks_images]] \ - CHECK_OCI=${{ github.event.inputs.checkOci || [[nightlybuildDefaults]] }} \ - TARGETS_FILENAME="[[ targets_filename ]]" \ - SKIP_IMAGES=${{ github.event.inputs.skipImages || [[skipImagesDefaults]]}} \ - ${{env.EXTRA_PARAMS_ALL_BUILDS}} SHARE_LOG=yes # IMAGES_ONLY_OUTDATED_ARTIFACTS=yes - - - name: "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - if: always() - run: | - echo "Logs: ${{ steps.prepare-matrix.outputs.logs_url }}" - - - name: Generate server lists from NetBox (JSON) - id: prepare-urls - run: | - - set -euo pipefail - - mkdir -p build/output/info - - # copy trackers list - cp trackerslist/best.txt build/output/info/best-torrent-servers.txt - - BASE_URL="${{ secrets.NETBOX_API }}/virtualization/virtual-machines/?limit=500&name__empty=false&status=active" - - # One jq filter used for all kinds – same fields everywhere - JQ_FILTER=' - .results - | map( - select(.name != null) - | { - host: .name, - upload_path: (.custom_fields["path"] // ""), - - download_path_archive: - ((.custom_fields["download_path_archive"] // "/archive") - | if startswith("/") then . else "/" + . end), - - download_path_images: - ((.custom_fields["download_path_images"] // "/dl") - | if startswith("/") then . else "/" + . end), - - download_path_debs: - ((.custom_fields["download_path_debs"] // "/apt") - | if startswith("/") then . else "/" + . end), - - port: (.custom_fields["port"] // 22), - username: (.custom_fields["username"] // "mirror") - } - ) - | sort_by(.host) - ' - - for kind in servers servers-download servers-cache servers-upload; do - case "$kind" in - servers) - # All mirrors serving images - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers.jq" - ;; - servers-download) - # Mirrors used as HTTP download sources (webseeds) - url="$BASE_URL&device_role=Mirror&tag=images" - outfile="build/output/info/servers-download.jq" - ;; - servers-cache) - # Cache mirrors (can be used for HTTP + rsync) - url="$BASE_URL&device_role=Mirror&tag=cache" - outfile="build/output/info/servers-cache.jq" - ;; - servers-upload) - # Upload targets (SSH) - url="$BASE_URL&tag=upload&tag=images" - outfile="build/output/info/servers-upload.jq" - ;; - esac - - echo "Generating $outfile from $url" - - # Fetch with retries; curl handles transient failures itself. - # --retry-all-errors covers connection timeouts as well as 5xx. - response="$(curl -s --fail \ - --connect-timeout 10 --max-time 30 \ - --retry 5 --retry-delay 2 --retry-all-errors \ - -H "Authorization: Token ${{ secrets.NETBOX_TOKEN }}" \ - -H "Accept: application/json; indent=4" \ - "$url")" - - echo "$response" | jq "$JQ_FILTER" > "$outfile" - - # Guard against a valid-but-empty result set: NetBox returning no - # matching VMs would otherwise produce an empty server list and let - # the build proceed with nowhere to upload/download from. - count="$(jq 'length' "$outfile")" - if [ "$count" -eq 0 ]; then - echo "::error::$outfile is empty (NetBox returned no '$kind' servers from $url)" - exit 1 - fi - echo " -> $count server(s)" - - done - - # Store output/info folder in a GitHub Actions artifact - - uses: actions/upload-artifact@v7 - name: Upload output/info as GitHub Artifact - with: - name: build-info-json - path: build/output/info - - - name: chown cache memoize/oci back to normal user - run: sudo chown -R $USER:$USER build/cache/memoize build/cache/oci/positive - - outputs: - - # not related to matrix - build-sha1: ${{ steps.latest-commit.outputs.sha1 }} - version: ${{ needs.version_prep.outputs.version }} diff --git a/userpatches/gha/chunks/150.per-chunk-artifacts_prep-outputs.yaml b/userpatches/gha/chunks/150.per-chunk-artifacts_prep-outputs.yaml deleted file mode 100644 index eefc47eece7..00000000000 --- a/userpatches/gha/chunks/150.per-chunk-artifacts_prep-outputs.yaml +++ /dev/null @@ -1,7 +0,0 @@ -ignore: # - ignore: # - ignore: # - # artifacts-[[chunk]] of [[num_chunks_artifacts]] - artifacts-chunk-json-[[chunk]]: ${{ steps.prepare-matrix.outputs.artifacts-chunk-json-[[chunk]] }} - artifacts-chunk-not-empty-[[chunk]]: ${{ steps.prepare-matrix.outputs.artifacts-chunk-not-empty-[[chunk]] }} - artifacts-chunk-size-[[chunk]]: ${{ steps.prepare-matrix.outputs.artifacts-chunk-size-[[chunk]] }} diff --git a/userpatches/gha/chunks/151.per-chunk-images_prep-outputs.yaml b/userpatches/gha/chunks/151.per-chunk-images_prep-outputs.yaml deleted file mode 100644 index 65c05c14127..00000000000 --- a/userpatches/gha/chunks/151.per-chunk-images_prep-outputs.yaml +++ /dev/null @@ -1,7 +0,0 @@ -ignore: # - ignore: # - ignore: # - # artifacts-[[chunk]] of [[num_chunks_images]] - images-chunk-json-[[chunk]]: ${{ steps.prepare-matrix.outputs.images-chunk-json-[[chunk]] }} - images-chunk-not-empty-[[chunk]]: ${{ steps.prepare-matrix.outputs.images-chunk-not-empty-[[chunk]] }} - images-chunk-size-[[chunk]]: ${{ steps.prepare-matrix.outputs.images-chunk-size-[[chunk]] }} diff --git a/userpatches/gha/chunks/250.single_aggr-jobs.yaml b/userpatches/gha/chunks/250.single_aggr-jobs.yaml deleted file mode 100644 index f38341bbeb7..00000000000 --- a/userpatches/gha/chunks/250.single_aggr-jobs.yaml +++ /dev/null @@ -1,32 +0,0 @@ -on: # - workflow_dispatch: # -jobs: # - # ------ aggregate all artifact chunks into a single dependency ------- - - all-artifacts-ready: - name: "[[num_chunks_artifacts]] artifacts chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "[[ quoted_comma_list_artifact_chunk_jobs ]]" ] # <-- HERE: all artifact chunk numbers. - steps: - - name: fake step - run: uptime - - all-images-ready: - name: "[[num_chunks_images]] image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "[[ quoted_comma_list_image_chunk_jobs ]]" ] # <-- HERE: all image chunk numbers. - steps: - - name: fake step - run: uptime - - all-artifacts-and-images-ready: - name: "[[num_chunks_artifacts]] artifacts and [[num_chunks_images]] image chunks ready" - runs-on: ubuntu-latest # not going to run, anyway, but is required. - if: ${{ !cancelled() && ( 1 == 2 ) }} # eg: never run. - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready" ] - steps: - - name: fake step - run: uptime - diff --git a/userpatches/gha/chunks/750.single_repo.yaml b/userpatches/gha/chunks/750.single_repo.yaml deleted file mode 100644 index 90e1f4ecc2f..00000000000 --- a/userpatches/gha/chunks/750.single_repo.yaml +++ /dev/null @@ -1,126 +0,0 @@ -on: # - workflow_dispatch: # -jobs: # - # ------ publish packages to repository ------- - - publish-debs-to-repo: - name: "Download artifacts from ORAS cache" - #runs-on: ubuntu-latest - runs-on: repository - if: ${{ !failure() && !cancelled() && github.event.inputs.targetsFilterInclude == '' && inputs.ref == '' }} # eg: run if dependencies worked. See https://github.com/orgs/community/discussions/45058#discussioncomment-4817378 - needs: [ "matrix_prep", "all-artifacts-ready" ] - steps: - - # Login to ghcr.io, for later uploading rootfs to ghcr.io - - name: Docker Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: "[[ghcr_user]]" # GitHub username or org - password: "${{ secrets.GITHUB_TOKEN }}" # GitHub actions builtin token. repo has to have pkg access. - - - name: Checkout build repo - uses: actions/checkout@v6 - with: - repository: ${{ env.BUILD_REPOSITORY }} - ref: ${{ needs.matrix_prep.outputs.build-sha1 }} - fetch-depth: 0 - path: build - clean: false - - - name: "Checkout userpatches repo: ${{env.USERPATCHES_REPOSITORY}}#${{env.USERPATCHES_REF}}" - uses: actions/checkout@v6 - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - with: - repository: ${{ env.USERPATCHES_REPOSITORY }} - ref: ${{ env.USERPATCHES_REF }} - fetch-depth: 0 - clean: false - path: os - - - name: "Put userpatches in place, and remove userpatches repo" - if: ${{ ( env.USERPATCHES_REPOSITORY != '' ) && ( env.USERPATCHES_REF != '' ) }} - run: | - - rsync -av os/${{env.USERPATCHES_DIR}}/. build/userpatches/ - - # Download the artifacts (output/info) produced by the prepare-matrix job. - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: build-info-json - path: build/output/info - - # List the artifacts we downloaded - - name: List artifacts - run: | - - ls -laht build/output/info - - - name: Run debs-to-repo download - env: - BETA: ${{ github.event.inputs.nightlybuild == 'true' && 'yes' || 'no' }} - run: | - - set -euo pipefail - cd build - ./compile.sh debs-to-repo-download \ - REVISION="${{ needs.matrix_prep.outputs.version }}" \ - BETA="$BETA" \ - SHARE_LOG=yes \ - ${{ env.EXTRA_PARAMS_ALL_BUILDS }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY_UPLOAD }} - known_hosts: ${{ secrets.KNOWN_HOSTS_ARMBIAN_UPLOAD }} - if_key_exists: replace - - - name: "Upload artifacts" - run: | - - set -euo pipefail - - echo "Sync debs and debs-beta" - rsync -e "ssh -p ${{ secrets.HOST_UPLOAD_PORT }} -o StrictHostKeyChecking=accept-new" \ - -rvc \ - --delete \ - --remove-source-files \ - --prune-empty-dirs \ - --include='debs/' \ - --include='debs-beta/' \ - --include='debs/***/' \ - --include='debs-beta/***/' \ - --include='debs/*.deb' \ - --include='debs-beta/*.deb' \ - --include='debs/**/*.deb' \ - --include='debs-beta/**/*.deb' \ - --exclude='*' \ - --omit-dir-times \ - --no-perms \ - --no-owner \ - --no-group \ - build/output/ \ - "${{ secrets.HOST_UPLOAD_USER }}@${{ secrets.HOST_UPLOAD }}:storage/incoming/${{ env.TARGET_PATH }}" - - - name: "Run repository update action" - if: ${{ (github.event.inputs.skipImages || [[skipImagesDefaults]]) == 'yes' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Repository update" - client-payload: > - { - "target": "${{ env.TARGET_PATH }}", - "download_external": true - } - - - name: "Logs debs-to-repo-download: ${{ steps.download-debs.outputs.logs_url }}" - run: ':' - - outputs: - - # not related to matrix - version: ${{ needs.matrix_prep.outputs.version }} diff --git a/userpatches/gha/chunks/950.single_footer.yaml b/userpatches/gha/chunks/950.single_footer.yaml deleted file mode 100644 index 79946f89621..00000000000 --- a/userpatches/gha/chunks/950.single_footer.yaml +++ /dev/null @@ -1,70 +0,0 @@ -on: # - workflow_dispatch: # -jobs: # - # ------ aggregate all artifact chunks into a single dependency ------- - - closing: - name: "Footer" - runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() && inputs.ref == '' && (github.event.inputs.nightlybuild || [[nightlybuildDefaults]]) == 'yes' }} - needs: [ "matrix_prep", "all-artifacts-ready", "all-images-ready"] - steps: - - # Download workflow artifacts - - name: "Download all workflow run artifacts" - if: ${{ (github.event.inputs.skipImages || [[skipImagesDefaults]]) != 'yes' }} - uses: actions/download-artifact@v8 - with: - name: assets-for-download-[[repository_ref]] - path: downloads - - # Read version - - name: "Read version" - run: | - - echo "version=$(cat downloads/version 2>/dev/null || true)" >> $GITHUB_ENV - - # Delete artifacts - - uses: geekyeggo/delete-artifact@v6 - with: - name: assets-for-download-[[repository_ref]] - failOnError: false - - # Cleaning logs - - name: "Keep only 30 days of workflow logs" - uses: igorjs/gh-actions-clean-workflow@v7 - with: - token: "${{ env.GH_TOKEN }}" - runs_older_than: 30 # optional - runs_to_keep: 0 # optional - - # Switch pre-release to release - - uses: ncipollo/release-action@v1 - if: ${{ (github.event.inputs.skipImages || [[skipImagesDefaults]]) != 'yes' && (github.event.inputs.nightlybuild || [[nightlybuildDefaults]]) == 'yes' }} - with: - repo: "${{ env.RELEASE_REPOSITORY }}" - tag: "${{ env.version }}" - omitBody: true - omitName: true - allowUpdates: true - makeLatest: true - token: "${{ env.GH_TOKEN }}" - - # Run repository mirroring to CDN - - name: "Run repository mirroring to CDN" - if: ${{ (github.event.inputs.skipImages || [[skipImagesDefaults]]) == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "armbian/${{ env.RELEASE_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - - # Run webindex update action - - name: "Run webindex update action" - if: ${{ (github.event.inputs.skipImages || [[skipImagesDefaults]]) == 'no' }} - uses: peter-evans/repository-dispatch@v4 - with: - token: ${{ secrets.DISPATCH }} - repository: armbian/armbian.github.io - event-type: "Data: Update download index" diff --git a/userpatches/gha/gha_config.yaml b/userpatches/gha/gha_config.yaml deleted file mode 100644 index 5ba3d237e25..00000000000 --- a/userpatches/gha/gha_config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "os" -build_ref: "v23.05" -script_name: "Build Artifacts and Images" -repository_ref: "stable" -team: "Release manager" - -# menu defaults -skipImagesDefaults: "'yes'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'yes'" -bumpVersion: "'false'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets.yaml" # file with build definitions -target_upload: "generic/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -cron_job: "0 */4 * * *" -repository_runner: "[ \"self-hosted\", \"Linux\", \"fast\", \"repo\" ]" diff --git a/userpatches/gha/gha_config_all.yaml b/userpatches/gha/gha_config_all.yaml deleted file mode 100644 index 9bbc14d75e5..00000000000 --- a/userpatches/gha/gha_config_all.yaml +++ /dev/null @@ -1,32 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "os" -build_ref: "${{ inputs.ref || 'main' }}" -script_name: "Build All Artifacts (cronjob)" -concurrency_group: "pipeline" -repository_ref: "all" -team: "Board Maintainers" - -# menu defaults -skipImagesDefaults: "'yes'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'yes'" -bumpVersion: "'true'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-all-not-eos.yaml" -target_upload: "cron/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -repository_runner: "[ repository ]" - -# when this should run -cron_job: "schedule:\n - cron: '0 20-23/2,0-4/2 * * *'\n - cron: '0 8,14 * * *'" diff --git a/userpatches/gha/gha_config_all_stable.yaml b/userpatches/gha/gha_config_all_stable.yaml deleted file mode 100644 index 22b787dd97c..00000000000 --- a/userpatches/gha/gha_config_all_stable.yaml +++ /dev/null @@ -1,32 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "os" -build_ref: "${{ inputs.ref || 'main' }}" -script_name: "Build All Stable Artifacts (cronjob)" -concurrency_group: "pipeline" -repository_ref: "all" -team: "Board Maintainers" - -# menu defaults -skipImagesDefaults: "'yes'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'no'" -bumpVersion: "'false'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-all-not-eos.yaml" -target_upload: "stable/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -repository_runner: "[ repository ]" - -# when this should run -cron_job: "schedule:\n - cron: '0 2 * * *'" diff --git a/userpatches/gha/gha_config_nightly.yaml b/userpatches/gha/gha_config_nightly.yaml deleted file mode 100644 index bc061393ffb..00000000000 --- a/userpatches/gha/gha_config_nightly.yaml +++ /dev/null @@ -1,33 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "os" -build_ref: "main" -script_name: "Build Nightly Images (cronjob)" -concurrency_group: "pipeline" -#concurrency_group: "${{ github.run_id }}-pipeline-nightly" -repository_ref: "nightly" -team: "Release manager" - -# menu defaults -skipImagesDefaults: "'no'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'yes'" -bumpVersion: "'true'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-release-nightly.yaml" -target_upload: "nightly/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -repository_runner: "[ repository ]" - -# when this should run -cron_job: "schedule:\n - cron: '30 22 * * *'\n push:\n branches:\n - 'main'\n paths:\n - 'userpatches/targets-release-nightly.yaml'" diff --git a/userpatches/gha/gha_config_old_stable.yaml b/userpatches/gha/gha_config_old_stable.yaml deleted file mode 100644 index d13142da6ab..00000000000 --- a/userpatches/gha/gha_config_old_stable.yaml +++ /dev/null @@ -1,32 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "os" -build_ref: "v23.05.2" -script_name: "ARTIFACTS: old stable" -concurrency_group: "${{ github.run_id }}-" -repository_ref: "stable" -team: "Release manager" - -# menu defaults -skipImagesDefaults: "'no'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'no'" -bumpVersion: "'false'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-release.yaml" -target_upload: "generic/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -repository_runner: "[ stable ]" - -# when this should run -cron_job: "" diff --git a/userpatches/gha/gha_config_release_apps.yaml b/userpatches/gha/gha_config_release_apps.yaml deleted file mode 100644 index ee5cfe4ba62..00000000000 --- a/userpatches/gha/gha_config_release_apps.yaml +++ /dev/null @@ -1,33 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "distribution" -build_ref: "main" -script_name: "Build Apps Images (cronjob)" -concurrency_group: "${{ github.run_id }}-pipeline-apps" -repository_ref: "stable" -team: "Release manager" - -# menu defaults -skipImagesDefaults: "'no'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'no'" -bumpVersion: "'false'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-release-apps.yaml" -target_upload: "apps/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -repository_runner: "[ repository ]" - -# when this should run -#cron_job: "schedule:\n - cron: '0 0 * * THU'\n" -cron_job: "" diff --git a/userpatches/gha/gha_config_release_community_maintained.yaml b/userpatches/gha/gha_config_release_community_maintained.yaml deleted file mode 100644 index 33d393b49e1..00000000000 --- a/userpatches/gha/gha_config_release_community_maintained.yaml +++ /dev/null @@ -1,32 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "community" -build_ref: "main" -script_name: "Build Community Images (cronjob)" -concurrency_group: "${{ github.run_id }}-pipeline-stable" -repository_ref: "stable" -team: "Release manager" - -# menu defaults -skipImagesDefaults: "'no'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'yes'" -bumpVersion: "'true'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-release-community-maintained.yaml" -target_upload: "community/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-community" # use "" for no config file - -# repository runner -repository_runner: "[ repository ]" - -# when this should run -cron_job: "schedule:\n - cron: '0 23 * * THU'\n" diff --git a/userpatches/gha/gha_config_release_standard_support.yaml b/userpatches/gha/gha_config_release_standard_support.yaml deleted file mode 100644 index e595e10321a..00000000000 --- a/userpatches/gha/gha_config_release_standard_support.yaml +++ /dev/null @@ -1,32 +0,0 @@ -org: "armbian" # can't be an expression, only string. - -# armbian/build coordinates -org_and_build_repo: "armbian/build" -release_repo: "os" -build_ref: "v23.11" -script_name: "Build Standard Support Images (admin)" -concurrency_group: "${{ github.run_id }}-pipeline-stable" -repository_ref: "stable" -team: "Release manager" - -# menu defaults -skipImagesDefaults: "'no'" -checkOciDefaults: "'yes'" -nightlybuildDefaults: "'no'" -bumpVersion: "'false'" - -# armbian/os coordinates. set all to empty to not use userpatches. -org_and_userpatches_repo: "armbian/os" -userpatches_ref: "main" -userpatches_dir: "userpatches" # folder inside the userpatches repo; use "." if stuff is in the root of the repo -targets_filename: "targets-release-standard-support.yaml" # we need to split -target_upload: "stable/" - -ghcr_user: "${{ github.repository_owner }}" -userpatches_config_for_prepare_job: "armbian-images" # use "" for no config file - -# repository runner -repository_runner: "[ repository ]" - -# when this should run -cron_job: ""