From b581809dc144e3bc98cdea18c66136f8cdca60d6 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 30 Jul 2026 12:19:11 +0200 Subject: [PATCH 1/4] ci: community publishes to armbian/community with its own version series Community images were reading/bumping -trunk.N from the shared release repo (github.repository, = armbian/ci) and publishing there, so they reused nightly's version instead of getting their own, and the images landed next to nightly's. Add a release_target_repository input to complete-artifact-matrix.yml / build-images-chunk.yml (default empty -> github.repository, so nightly and every other track are unchanged) and route the version lookup, release creation, mark-latest, CDN mirror pull_repository, image upload and torrent GitHub webseed through it. build-community.yml sets it to armbian/community, so community now reads/bumps its own -trunk.N there and publishes its images and releases into armbian/community. Operational notes: - The release token (ACCESS_TOKEN) must have write access to armbian/community. - armbian/community needs a seed -trunk.N release; the first community run after this should pass versionOverride to start the series (same as any nightly track), otherwise version_prep errors with "no existing -trunk.N release". Signed-off-by: Igor Pecovnik --- .github/workflows/build-community.yml | 4 ++++ .github/workflows/build-images-chunk.yml | 6 ++++-- .github/workflows/complete-artifact-matrix.yml | 16 ++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-community.yml b/.github/workflows/build-community.yml index ed8d63a..575f2d4 100644 --- a/.github/workflows/build-community.yml +++ b/.github/workflows/build-community.yml @@ -34,6 +34,10 @@ jobs: uses: ./.github/workflows/complete-artifact-matrix.yml with: release_repository: "community" + # Community owns its version series + releases in armbian/community, so it + # reads/bumps its own -trunk.N and publishes there instead of reusing + # nightly's version out of the shared repo. + release_target_repository: "armbian/community" target_path: "community/" team: "Release manager" repository_ref: "stable" diff --git a/.github/workflows/build-images-chunk.yml b/.github/workflows/build-images-chunk.yml index 492e48d..6997d47 100644 --- a/.github/workflows/build-images-chunk.yml +++ b/.github/workflows/build-images-chunk.yml @@ -13,6 +13,7 @@ on: version: { required: true, type: string } build_sha1: { required: true, type: string } release_repository: { required: true, type: string } # os | community | distribution + release_target_repository: { required: false, type: string, default: "" } # repo that owns releases; empty -> github.repository nightlybuild: { required: true, type: string } # effective 'yes' | 'no' build_repository: { required: false, type: string, default: "armbian/build" } userpatches_repository: { required: true, type: string } @@ -33,6 +34,7 @@ env: USERPATCHES_REF: ${{ inputs.userpatches_ref }} USERPATCHES_DIR: ${{ inputs.userpatches_dir }} RELEASE_REPOSITORY: ${{ inputs.release_repository }} + RELEASE_TARGET_REPOSITORY: ${{ inputs.release_target_repository || github.repository }} EXTRA_PARAMS_ALL_BUILDS: ${{ inputs.extra_params_all_builds }} EXTRA_PARAMS_IMAGE: ${{ inputs.extra_params_image }} GH_TOKEN: ${{ secrets.ACCESS_TOKEN || github.token }} @@ -302,7 +304,7 @@ jobs: if [ -n "$WEBSEEDS" ]; then WEBSEEDS+="," fi - WEBSEEDS+="https://github.com/${{ github.repository }}/releases/download/${{ inputs.version }}/${FILE}" + WEBSEEDS+="https://github.com/${{ env.RELEASE_TARGET_REPOSITORY }}/releases/download/${{ inputs.version }}/${FILE}" fi echo "WEBSEEDS: $WEBSEEDS" @@ -344,7 +346,7 @@ jobs: if: ${{ inputs.nightlybuild == 'yes' || env.RELEASE_REPOSITORY == 'community' || env.RELEASE_REPOSITORY == 'distribution' }} uses: softprops/action-gh-release@v3 with: - repository: ${{ github.repository }} + repository: ${{ env.RELEASE_TARGET_REPOSITORY }} tag_name: "${{ inputs.version }}" files: "output/release/output/images/*/*/Armbian_*.*" prerelease: ${{ inputs.nightlybuild == 'yes' }} diff --git a/.github/workflows/complete-artifact-matrix.yml b/.github/workflows/complete-artifact-matrix.yml index d3ad855..d28149b 100644 --- a/.github/workflows/complete-artifact-matrix.yml +++ b/.github/workflows/complete-artifact-matrix.yml @@ -16,6 +16,11 @@ on: inputs: # ---- per-track identity / routing ---- release_repository: { required: true, type: string } # os | community | distribution + # GitHub repo that owns the version series + releases for this track. Defaults + # (empty -> github.repository) to this repo; community overrides to armbian/ + # community so it reads/bumps its OWN -trunk.N and publishes there instead of + # reusing nightly's version out of the shared repo. + release_target_repository: { required: false, type: string, default: "" } 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 @@ -37,6 +42,8 @@ env: USERPATCHES_REF: "main" USERPATCHES_DIR: "userpatches" RELEASE_REPOSITORY: "${{ inputs.release_repository }}" + # Repo that owns this track's version series + releases (see input above). + RELEASE_TARGET_REPOSITORY: "${{ inputs.release_target_repository || github.repository }}" # DOCKER_SKIP_UPDATE=yes skips pulling/updating the Docker image, risking very # outdated binaries; "no" pulls & rebuilds the Dockerfile (hits cache most of the # time, safer). @@ -89,7 +96,7 @@ jobs: GH_TOKEN: ${{ secrets.ACCESS_TOKEN || github.token }} run: | set -euo pipefail - REPO="${{ github.repository }}" + REPO="${{ env.RELEASE_TARGET_REPOSITORY }}" OVERRIDE="${{ github.event.inputs.versionOverride }}" NIGHTLY="${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }}" @@ -145,7 +152,7 @@ jobs: if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) != 'yes' }} uses: softprops/action-gh-release@v3 with: - repository: ${{ github.repository }} + repository: ${{ env.RELEASE_TARGET_REPOSITORY }} tag_name: "${{ steps.version.outputs.version }}" name: "${{ steps.version.outputs.version }}" body_path: body.html @@ -404,6 +411,7 @@ jobs: version: ${{ needs.matrix_prep.outputs.version }} build_sha1: ${{ needs.matrix_prep.outputs.build-sha1 }} release_repository: ${{ inputs.release_repository }} + release_target_repository: ${{ inputs.release_target_repository || github.repository }} nightlybuild: ${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }} userpatches_repository: ${{ github.repository }} extra_params_all_builds: ${{ github.event.inputs.extraParamsAllBuilds || 'UPLOAD_TO_OCI_ONLY=yes' }}${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.forceDockerPull != 'no') && ' DOCKER_FORCE_PULL=yes' || '' }} @@ -547,7 +555,7 @@ jobs: if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) != 'yes' && (github.event.inputs.nightlybuild || inputs.nightlybuild_default) == 'yes' }} uses: softprops/action-gh-release@v3 with: - repository: ${{ github.repository }} + repository: ${{ env.RELEASE_TARGET_REPOSITORY }} tag_name: "${{ needs.matrix_prep.outputs.version }}" prerelease: false make_latest: true @@ -560,7 +568,7 @@ jobs: token: ${{ secrets.DISPATCH }} repository: armbian/armbian.github.io event-type: "Infrastructure: Mirror artifacts" - client-payload: '{"pull_repository": "${{ github.repository }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' + client-payload: '{"pull_repository": "${{ env.RELEASE_TARGET_REPOSITORY }}", "cdn_tag": "${{ env.RELEASE_REPOSITORY }}"}' - name: "Run webindex update action" if: ${{ (github.event.inputs.skipImages || inputs.skipimages_default) == 'no' }} From 7df76243b98c556fcdec15c4a02b48e46b522bbe Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 30 Jul 2026 12:29:22 +0200 Subject: [PATCH 2/4] version_prep: pick highest -trunk.N by version, not createdAt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nightly-path version bump sorted trunk releases by createdAt and took the last. armbian/community's releases were bulk-imported with one shared timestamp, so the sort was arbitrary: it picked 26.8.0-trunk.170 and bumped to 171 — a tag that already exists — while the real max was trunk.417. A --limit 100 could also truncate a large series below its max. Sort by version instead ([major,minor,patch] then N) and raise --limit, so the next version is always max(N)+1 (e.g. community 417 -> 418) regardless of import timestamps. No effect on repos with normal per-release timestamps (armbian/ci), where highest-N and newest-createdAt coincide. Signed-off-by: Igor Pecovnik --- .github/workflows/complete-artifact-matrix.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/complete-artifact-matrix.yml b/.github/workflows/complete-artifact-matrix.yml index d28149b..14401d1 100644 --- a/.github/workflows/complete-artifact-matrix.yml +++ b/.github/workflows/complete-artifact-matrix.yml @@ -121,9 +121,17 @@ jobs: VERSION="$OVERRIDE" PRERELEASE=true else - # Nightly: newest "-trunk.N" release in the target repo, bump N. - LATEST="$(gh release list -R "$REPO" --limit 100 --json tagName,createdAt \ - --jq 'map(select(.tagName | test("-trunk\\.[0-9]+$"))) | sort_by(.createdAt) | last | .tagName // ""')" + # Nightly: highest "-trunk.N" release in the target repo, bump N. + # Sort by VERSION ([major,minor,patch] then N), NOT createdAt: releases + # bulk-imported into a repo (e.g. armbian/community) share one timestamp, + # so a createdAt sort is arbitrary and can pick a low N and bump onto an + # existing tag. High --limit so a big series isn't truncated below its max. + LATEST="$(gh release list -R "$REPO" --limit 1000 --json tagName \ + --jq 'map(.tagName) + | map(select(test("-trunk\\.[0-9]+$"))) + | sort_by(capture("^(?[0-9]+\\.[0-9]+\\.[0-9]+)-trunk\\.(?[0-9]+)$") + | [(.b | split(".") | map(tonumber)), (.n | tonumber)]) + | last // ""')" if [ -z "$LATEST" ]; then echo "::error::no existing -trunk.N release in $REPO; pass versionOverride to seed the series"; exit 1 fi From dedaed5a62909e752d889eebe957b93e176703fc Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 30 Jul 2026 12:34:10 +0200 Subject: [PATCH 3/4] ci: community tracks the current nightly version (reuse, don't bump) Refine the community targeting: the -trunk.N series lives only in armbian/ci and only nightly bumps it. Community reads that same series from armbian/ci and reuses the last trunk number as-is (reuse_latest_trunk=yes), so its images carry the current nightly version -- just published to armbian/community instead of armbian/ci. Version SOURCE is github.repository (armbian/ci) for every track again; only the release TARGET differs per track (release_target_repository). Drops the earlier "community bumps its own series out of armbian/community" behaviour, which reused stale bulk-imported tags. Signed-off-by: Igor Pecovnik --- .github/workflows/build-community.yml | 7 +++-- .../workflows/complete-artifact-matrix.yml | 28 ++++++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-community.yml b/.github/workflows/build-community.yml index 575f2d4..dcb854a 100644 --- a/.github/workflows/build-community.yml +++ b/.github/workflows/build-community.yml @@ -34,10 +34,11 @@ jobs: uses: ./.github/workflows/complete-artifact-matrix.yml with: release_repository: "community" - # Community owns its version series + releases in armbian/community, so it - # reads/bumps its own -trunk.N and publishes there instead of reusing - # nightly's version out of the shared repo. + # Community images publish to armbian/community, but track the CURRENT nightly + # version: read the -trunk.N series from armbian/ci and reuse it as-is (only + # nightly bumps). So the same tag lands in armbian/community, no separate series. release_target_repository: "armbian/community" + reuse_latest_trunk: "yes" target_path: "community/" team: "Release manager" repository_ref: "stable" diff --git a/.github/workflows/complete-artifact-matrix.yml b/.github/workflows/complete-artifact-matrix.yml index 14401d1..b8121cb 100644 --- a/.github/workflows/complete-artifact-matrix.yml +++ b/.github/workflows/complete-artifact-matrix.yml @@ -21,6 +21,10 @@ on: # community so it reads/bumps its OWN -trunk.N and publishes there instead of # reusing nightly's version out of the shared repo. release_target_repository: { required: false, type: string, default: "" } + # Reuse the current nightly -trunk.N as-is instead of bumping it (only nightly + # in armbian/ci cuts new trunk numbers). Community sets this so its images + # track the current nightly version rather than starting their own series. + reuse_latest_trunk: { required: false, type: string, default: "no" } 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 @@ -96,7 +100,12 @@ jobs: GH_TOKEN: ${{ secrets.ACCESS_TOKEN || github.token }} run: | set -euo pipefail - REPO="${{ env.RELEASE_TARGET_REPOSITORY }}" + # Version SERIES lives in this repo (armbian/ci) — that's where nightly + # cuts each -trunk.N. Community reads the same series and reuses the last + # trunk number as-is (REUSE_TRUNK=yes) so its images track the current + # nightly version; only their release TARGET differs (armbian/community). + REPO="${{ github.repository }}" + REUSE_TRUNK="${{ inputs.reuse_latest_trunk }}" OVERRIDE="${{ github.event.inputs.versionOverride }}" NIGHTLY="${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }}" @@ -121,11 +130,10 @@ jobs: VERSION="$OVERRIDE" PRERELEASE=true else - # Nightly: highest "-trunk.N" release in the target repo, bump N. - # Sort by VERSION ([major,minor,patch] then N), NOT createdAt: releases - # bulk-imported into a repo (e.g. armbian/community) share one timestamp, - # so a createdAt sort is arbitrary and can pick a low N and bump onto an - # existing tag. High --limit so a big series isn't truncated below its max. + # Highest "-trunk.N" release in armbian/ci (the nightly series). + # Sort by VERSION ([major,minor,patch] then N), NOT createdAt: a + # createdAt sort is arbitrary when releases share a timestamp and can + # pick a low N. High --limit so a big series isn't truncated below max. LATEST="$(gh release list -R "$REPO" --limit 1000 --json tagName \ --jq 'map(.tagName) | map(select(test("-trunk\\.[0-9]+$"))) @@ -137,7 +145,13 @@ jobs: fi BASE="${LATEST%-trunk.*}" N="${LATEST##*-trunk.}" - VERSION="${BASE}-trunk.$((N + 1))" + if [ "$REUSE_TRUNK" = "yes" ]; then + # Community: reuse the current nightly trunk number as-is (only nightly + # in armbian/ci bumps the series); community images just track it. + VERSION="$LATEST" + else + VERSION="${BASE}-trunk.$((N + 1))" + fi PRERELEASE=true fi From 0996721bf6cb5cf4ace489de74747fa321a65f9d Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Fri, 31 Jul 2026 09:41:10 +0200 Subject: [PATCH 4/4] version_prep: shared trunk counter across nightly (ci) + community Replace community's naive "always reuse" with a monotonic -trunk.N counter shared by armbian/ci (nightly, the leader) and armbian/community (peer): - LEADER (nightly) always cuts the next number: max(N across ci+community)+1, so a nightly bump always lands above community. - A non-leader target (community) reuses the leader's current number when the leader is ahead of it (community tracks the fresh nightly version); if it is already level with the leader, it cuts the next number instead. So a second community run with no nightly in between still bumps. Both callers list armbian/community in trunk_peer_repositories so either track sees the whole counter. Replaces the reuse_latest_trunk flag. Version compare is sort -V (handles base + N, and repos with bulk-imported equal timestamps). Signed-off-by: Igor Pecovnik --- .github/workflows/build-community.yml | 8 +-- .github/workflows/build-nightly.yml | 3 + .../workflows/complete-artifact-matrix.yml | 69 +++++++++++-------- 3 files changed, 47 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build-community.yml b/.github/workflows/build-community.yml index dcb854a..d504cec 100644 --- a/.github/workflows/build-community.yml +++ b/.github/workflows/build-community.yml @@ -34,11 +34,11 @@ jobs: uses: ./.github/workflows/complete-artifact-matrix.yml with: release_repository: "community" - # Community images publish to armbian/community, but track the CURRENT nightly - # version: read the -trunk.N series from armbian/ci and reuse it as-is (only - # nightly bumps). So the same tag lands in armbian/community, no separate series. + # Community images publish to armbian/community and share the -trunk.N counter + # with nightly (armbian/ci): when nightly is ahead, community reuses its number + # so the tags line up; a second community run with no nightly in between bumps. release_target_repository: "armbian/community" - reuse_latest_trunk: "yes" + trunk_peer_repositories: "armbian/community" target_path: "community/" team: "Release manager" repository_ref: "stable" diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index 60e59d2..3a8bdcc 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -34,6 +34,9 @@ jobs: uses: ./.github/workflows/complete-artifact-matrix.yml with: release_repository: "os" + # Nightly owns the -trunk.N counter here in armbian/ci, but community shares it + # (armbian/community); list it so a nightly bump lands above community's number. + trunk_peer_repositories: "armbian/community" target_path: "nightly/" team: "Release manager" repository_ref: "nightly" diff --git a/.github/workflows/complete-artifact-matrix.yml b/.github/workflows/complete-artifact-matrix.yml index b8121cb..fefe9a6 100644 --- a/.github/workflows/complete-artifact-matrix.yml +++ b/.github/workflows/complete-artifact-matrix.yml @@ -21,10 +21,12 @@ on: # community so it reads/bumps its OWN -trunk.N and publishes there instead of # reusing nightly's version out of the shared repo. release_target_repository: { required: false, type: string, default: "" } - # Reuse the current nightly -trunk.N as-is instead of bumping it (only nightly - # in armbian/ci cuts new trunk numbers). Community sets this so its images - # track the current nightly version rather than starting their own series. - reuse_latest_trunk: { required: false, type: string, default: "no" } + # Other repos that share this track's -trunk.N counter (space-separated). The + # counter is monotonic across all of them: any bump is max(all)+1. The leader + # (armbian/ci = github.repository) always bumps; a non-leader target (community) + # reuses the leader's number when the leader is ahead, else bumps. Both the + # nightly and community callers list "armbian/community" here. + trunk_peer_repositories: { required: false, type: string, default: "" } 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 @@ -100,15 +102,25 @@ jobs: GH_TOKEN: ${{ secrets.ACCESS_TOKEN || github.token }} run: | set -euo pipefail - # Version SERIES lives in this repo (armbian/ci) — that's where nightly - # cuts each -trunk.N. Community reads the same series and reuses the last - # trunk number as-is (REUSE_TRUNK=yes) so its images track the current - # nightly version; only their release TARGET differs (armbian/community). - REPO="${{ github.repository }}" - REUSE_TRUNK="${{ inputs.reuse_latest_trunk }}" + # The -trunk.N counter is shared across the leader repo (armbian/ci, where + # nightly cuts numbers) and its peers (armbian/community). LEADER always + # bumps to max(all)+1; a non-leader target (community) reuses the leader's + # number when the leader is ahead of the target, else bumps to max(all)+1. + REPO="${{ github.repository }}" # stable-path X.Y.Z source + LEADER="${{ github.repository }}" # armbian/ci — the counter leader + TARGET="${{ env.RELEASE_TARGET_REPOSITORY }}" # where THIS track publishes + PEERS="${{ inputs.trunk_peer_repositories }}" # other repos sharing the counter OVERRIDE="${{ github.event.inputs.versionOverride }}" NIGHTLY="${{ github.event.inputs.nightlybuild || inputs.nightlybuild_default }}" + # highest "-trunk.N" tag in a repo (version-sorted), or "" if none + trunk_latest() { + gh release list -R "$1" --limit 1000 --json tagName --jq '.[].tagName' 2>/dev/null \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-trunk\.[0-9]+$' | sort -V | tail -1 || true + } + trunk_n() { case "$1" in ?*-trunk.*) echo "${1##*-trunk.}";; *) echo -1;; esac; } + trunk_bump() { echo "${1%-trunk.*}-trunk.$(( ${1##*-trunk.} + 1 ))"; } + if [ "$NIGHTLY" = "no" ]; then # Stable (standard-support, apps): reuse the latest X.Y.Z release as-is # -- standard-support and apps rebuild into the current version, they do @@ -130,27 +142,26 @@ jobs: VERSION="$OVERRIDE" PRERELEASE=true else - # Highest "-trunk.N" release in armbian/ci (the nightly series). - # Sort by VERSION ([major,minor,patch] then N), NOT createdAt: a - # createdAt sort is arbitrary when releases share a timestamp and can - # pick a low N. High --limit so a big series isn't truncated below max. - LATEST="$(gh release list -R "$REPO" --limit 1000 --json tagName \ - --jq 'map(.tagName) - | map(select(test("-trunk\\.[0-9]+$"))) - | sort_by(capture("^(?[0-9]+\\.[0-9]+\\.[0-9]+)-trunk\\.(?[0-9]+)$") - | [(.b | split(".") | map(tonumber)), (.n | tonumber)]) - | last // ""')" - if [ -z "$LATEST" ]; then - echo "::error::no existing -trunk.N release in $REPO; pass versionOverride to seed the series"; exit 1 + # Trunk (nightly/community): shared monotonic -trunk.N counter across + # LEADER (armbian/ci) + PEERS (armbian/community). + leader_tag="$(trunk_latest "$LEADER")" + target_tag="$(trunk_latest "$TARGET")" + # global highest across leader + every peer (target is one of them) + global_tag="$( { trunk_latest "$LEADER"; for r in $PEERS; do trunk_latest "$r"; done; } \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-trunk\.[0-9]+$' | sort -V | tail -1 || true)" + if [ -z "$global_tag" ]; then + echo "::error::no existing -trunk.N release across [$LEADER $PEERS]; pass versionOverride to seed the series"; exit 1 fi - BASE="${LATEST%-trunk.*}" - N="${LATEST##*-trunk.}" - if [ "$REUSE_TRUNK" = "yes" ]; then - # Community: reuse the current nightly trunk number as-is (only nightly - # in armbian/ci bumps the series); community images just track it. - VERSION="$LATEST" + + if [ "$TARGET" = "$LEADER" ]; then + # Leader (nightly): always cut the next number, above every peer. + VERSION="$(trunk_bump "$global_tag")" + elif [ "$(trunk_n "$leader_tag")" -gt "$(trunk_n "$target_tag")" ]; then + # Community, leader is ahead: reuse the leader's current number. + VERSION="$leader_tag" else - VERSION="${BASE}-trunk.$((N + 1))" + # Community, already level with the leader: cut the next number. + VERSION="$(trunk_bump "$global_tag")" fi PRERELEASE=true fi