From 4da8638949e3d20a6f2bba3fef32abff33691d5b Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:34:00 +0200 Subject: [PATCH 1/8] repository update: select stable kernels from a config file The stable/ branch of Copy operations never moved anything: it ran copy-kernel-packages.sh with DRY_RUN=true, reading incoming/nightly/debs-beta/ and writing to /tmp/x, while guarding on incoming/stable. A stable dispatch reported success and ingested nothing. Wire it for real, driven by config/repository-kernel-selection.conf. Kernels are a selection rather than a sweep - there is one linux-image per (linuxfamily, branch) - so the file names which families the repository carries, and copy-kernel-packages.sh resolves what exists from image-info.json. Seeded with the three that are stable enough to carry between releases: KERNEL_SELECT="x86 arm64 bcm2711" # uefi-x86, uefi-arm64, Raspberry Pi linux-libc-dev travels with them; u-boot does not - none of the three boot through Armbian's u-boot, so COPY_UBOOT is false. Note the tokens are LINUXFAMILY values, which differ from the board and BOARDFAMILY names: uefi-x86 builds linuxfamily x86, uefi-arm64 builds arm64, rpi4b builds bcm2711. Verified against the live image-info.json with a fixture: the x86, arm64 and bcm2711 image/dtb/headers/libc-dev debs are copied, a rockchip64 kernel and a u-boot deb in the same directory are left behind. Signed-off-by: Igor Pecovnik --- .../infrastructure-repository-update.yml | 34 ++++++++++++++--- config/repository-kernel-selection.conf | 37 +++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 config/repository-kernel-selection.conf diff --git a/.github/workflows/infrastructure-repository-update.yml b/.github/workflows/infrastructure-repository-update.yml index 875fd3fcf5..5938a458c1 100644 --- a/.github/workflows/infrastructure-repository-update.yml +++ b/.github/workflows/infrastructure-repository-update.yml @@ -137,13 +137,35 @@ jobs: case "$TARGET" in "stable/") + # Kernels are a selection, not a sweep: there is one linux-image + # per (linuxfamily, branch) and the repository only carries the + # families listed in config/repository-kernel-selection.conf. + # copy-kernel-packages.sh resolves what actually exists from + # image-info.json and copies the matching debs. if [ -d "${INCOMING_PATH}/stable" ]; then - COPY_UBOOT=false \ - DRY_RUN=true \ - SELECT=':edge' \ - SRC_DIR="${{ env.INCOMING_PATH }}"/nightly/debs-beta/ \ - DST_DIR=/tmp/x \ - scripts/copy-kernel-packages.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY + # shellcheck source=config/repository-kernel-selection.conf + . "${GITHUB_WORKSPACE}/config/repository-kernel-selection.conf" + echo "## Stable kernel selection: ${KERNEL_SELECT}" >> $GITHUB_STEP_SUMMARY + for ST_REPO in debs debs-beta; do + ST_SRC="${INCOMING_PATH}/stable/${ST_REPO}" + if [ ! -d "${ST_SRC}" ]; then + echo "## No stable/${ST_REPO} in incoming, skipping" >> $GITHUB_STEP_SUMMARY + continue + fi + SELECT="${KERNEL_SELECT}" \ + UBOOT_SELECT="${UBOOT_SELECT}" \ + COPY_UBOOT="${COPY_UBOOT}" \ + INCLUDE_LIBC_DEV="${INCLUDE_LIBC_DEV}" \ + DRY_RUN=false \ + SRC_DIR="${ST_SRC}" \ + DST_DIR="${STORAGE_PATH}/${ST_REPO}" \ + scripts/copy-kernel-packages.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY + ST_RC=${PIPESTATUS[0]} + if [ "${ST_RC}" -ne 0 ]; then + echo "::error::copy-kernel-packages.sh failed for stable/${ST_REPO} (exit ${ST_RC})" + exit 1 + fi + done else echo "## Source folder INCOMING/stable does not exist, skipping" >> $GITHUB_STEP_SUMMARY fi diff --git a/config/repository-kernel-selection.conf b/config/repository-kernel-selection.conf new file mode 100644 index 0000000000..8e7f113b07 --- /dev/null +++ b/config/repository-kernel-selection.conf @@ -0,0 +1,37 @@ +# +# Which kernel packages reach the apt repositories, for the targets that publish +# a curated selection rather than everything they built. +# +# Sourced by the "stable/" branch of Infrastructure: APT repositories update and +# handed to scripts/copy-kernel-packages.sh, which resolves what exists from +# image-info.json and copies the matching debs out of the incoming drop box. +# +# KERNEL_SELECT tokens are linuxfamily[:branch] , space separated, globs +# allowed. A bare family means every branch that family builds: +# +# x86 -> all branches of linuxfamily x86 (uefi-x86) +# x86:current -> only the current branch +# bcm27* -> every family whose name starts bcm27 +# +# The family is the LINUXFAMILY, which is not always the board or the +# BOARDFAMILY name - uefi-x86 builds linuxfamily "x86", uefi-arm64 builds +# "arm64", and the Raspberry Pi boards build "bcm2711". +# +# Copied for each selected (family, branch): linux-image, linux-dtb, +# linux-headers, plus linux-libc-dev when INCLUDE_LIBC_DEV is true. +# + +# Weekly stable update: the generic UEFI kernels and the Raspberry Pi one. All +# three move slowly enough to be worth carrying in the repository between +# stable releases. +KERNEL_SELECT="x86 arm64 bcm2711" + +# linux-libc-dev-- alongside the kernel. +INCLUDE_LIBC_DEV="true" + +# None of the three boot through Armbian's u-boot - the UEFI targets boot via +# firmware and GRUB, the Pi through its own bootloader - so there is no u-boot +# package to carry. UBOOT_SELECT is board[:branch], and only read when +# COPY_UBOOT is true. +COPY_UBOOT="false" +UBOOT_SELECT="" From a6865638f005c8f0b257b61e1a425752e4cdbd84 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:39:13 +0200 Subject: [PATCH 2/8] repository kernel selection: add udoo with its u-boot Gives the weekly update one ordinary, non-critical board that gets the complete set - kernel, dtb, headers, libc-dev and u-boot - so a regression in the full path shows up on something harmless rather than on a flagship board. udoo builds linuxfamily imx6 (current and edge), so KERNEL_SELECT gains imx6. U-Boot is per BOARD rather than per family, so it needs COPY_UBOOT=true plus an explicit UBOOT_SELECT: an empty UBOOT_SELECT means EVERY board, not none - the filter is skipped when it is empty - which with COPY_UBOOT on would drag the whole fleet's u-boot into the repository. Verified with a fixture: both imx6 branches' image/dtb/headers/libc-dev and both udoo u-boot debs are copied, while a rockchip64 kernel and the rpi4b and orangepi5 u-boot debs sitting beside them are left behind. Signed-off-by: Igor Pecovnik --- config/repository-kernel-selection.conf | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/config/repository-kernel-selection.conf b/config/repository-kernel-selection.conf index 8e7f113b07..f0d8d0740b 100644 --- a/config/repository-kernel-selection.conf +++ b/config/repository-kernel-selection.conf @@ -21,17 +21,22 @@ # linux-headers, plus linux-libc-dev when INCLUDE_LIBC_DEV is true. # -# Weekly stable update: the generic UEFI kernels and the Raspberry Pi one. All -# three move slowly enough to be worth carrying in the repository between -# stable releases. -KERNEL_SELECT="x86 arm64 bcm2711" +# Weekly stable update: the generic UEFI kernels, the Raspberry Pi one, and imx6 +# for udoo. The first three move slowly enough to be worth carrying in the +# repository between stable releases; udoo is here to give the weekly update one +# ordinary, non-critical board that gets the complete set - kernel and u-boot - +# so a regression in the full path shows up on something harmless. +KERNEL_SELECT="x86 arm64 bcm2711 imx6" # linux-libc-dev-- alongside the kernel. INCLUDE_LIBC_DEV="true" -# None of the three boot through Armbian's u-boot - the UEFI targets boot via -# firmware and GRUB, the Pi through its own bootloader - so there is no u-boot -# package to carry. UBOOT_SELECT is board[:branch], and only read when -# COPY_UBOOT is true. -COPY_UBOOT="false" -UBOOT_SELECT="" +# U-Boot is per BOARD, not per family, so it needs its own list. +# +# CAREFUL: an empty UBOOT_SELECT means EVERY board, not none - the filter is +# skipped when it is empty. With COPY_UBOOT true it must always name boards. +# The UEFI targets boot via firmware and GRUB and the Pi through its own +# bootloader, so none of them have a u-boot package to carry; udoo does, and it +# is the only one wanted here. +COPY_UBOOT="true" +UBOOT_SELECT="udoo" From 02acb517db5b4d60c6510f3b35965095a4762ddb Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:41:15 +0200 Subject: [PATCH 3/8] repository kernel selection: add the cubox-i bootloader cubox-i is imx6 like udoo, so it already gets the kernel selected above; adding its u-boot makes it the second complete, updatable board in the weekly update. Verified with a fixture: both cubox-i and both udoo u-boot debs are copied, while cubieboard and rpi4b u-boot debs sitting beside them are left behind. Signed-off-by: Igor Pecovnik --- config/repository-kernel-selection.conf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/repository-kernel-selection.conf b/config/repository-kernel-selection.conf index f0d8d0740b..1d3fce32c3 100644 --- a/config/repository-kernel-selection.conf +++ b/config/repository-kernel-selection.conf @@ -36,7 +36,8 @@ INCLUDE_LIBC_DEV="true" # CAREFUL: an empty UBOOT_SELECT means EVERY board, not none - the filter is # skipped when it is empty. With COPY_UBOOT true it must always name boards. # The UEFI targets boot via firmware and GRUB and the Pi through its own -# bootloader, so none of them have a u-boot package to carry; udoo does, and it -# is the only one wanted here. +# bootloader, so none of them have a u-boot package to carry. udoo and cubox-i +# do, and both are imx6 - so the imx6 kernel above and these two bootloaders +# together give two complete, updatable boards. COPY_UBOOT="true" -UBOOT_SELECT="udoo" +UBOOT_SELECT="udoo cubox-i" From e99d924332cc2264c893c960c38d548363f83dfc Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:41:39 +0200 Subject: [PATCH 4/8] repository kernel selection: document u-boot's per-branch packaging The package is linux-u-boot--, so a board building current and edge produces two of them. A bare board token takes every branch, which is what makes udoo and cubox-i completely updatable - the kernel selection covers each branch and the bootloader has to match it. Signed-off-by: Igor Pecovnik --- config/repository-kernel-selection.conf | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/config/repository-kernel-selection.conf b/config/repository-kernel-selection.conf index 1d3fce32c3..507b1e3a00 100644 --- a/config/repository-kernel-selection.conf +++ b/config/repository-kernel-selection.conf @@ -31,7 +31,15 @@ KERNEL_SELECT="x86 arm64 bcm2711 imx6" # linux-libc-dev-- alongside the kernel. INCLUDE_LIBC_DEV="true" -# U-Boot is per BOARD, not per family, so it needs its own list. +# U-Boot is per BOARD *and* per branch - the package is +# linux-u-boot-- - so it needs its own list, and a board building +# current and edge produces two of them. Tokens are board[:branch]: +# +# udoo -> every branch udoo builds (current and edge) +# udoo:current -> only current +# +# A bare board name is what makes a board completely updatable: the kernel it +# gets from KERNEL_SELECT covers each branch, and the bootloader has to match. # # CAREFUL: an empty UBOOT_SELECT means EVERY board, not none - the filter is # skipped when it is empty. With COPY_UBOOT true it must always name boards. From 9da06200c5a24e72a5bfa15aa3797d156bea9422 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:44:25 +0200 Subject: [PATCH 5/8] repository update: ingest the stable kernels regardless of dispatch target Keying the kernel selection on target == stable/ made it unreachable on the path that actually produces those packages: the weekly chain builds them (uploading to incoming/stable/) and then runs base-files, and base-files is what fires the repository update - so the payload says base-files/ and the stable/ arm never runs. Treat it like the generic packages: incoming is a drop box, so whatever is in it gets ingested, whoever rang the bell. The stable/ case arm is now a no-op and the selection runs unconditionally, guarded on the directory existing. Signed-off-by: Igor Pecovnik --- .../infrastructure-repository-update.yml | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/.github/workflows/infrastructure-repository-update.yml b/.github/workflows/infrastructure-repository-update.yml index 5938a458c1..a887222a35 100644 --- a/.github/workflows/infrastructure-repository-update.yml +++ b/.github/workflows/infrastructure-repository-update.yml @@ -137,38 +137,9 @@ jobs: case "$TARGET" in "stable/") - # Kernels are a selection, not a sweep: there is one linux-image - # per (linuxfamily, branch) and the repository only carries the - # families listed in config/repository-kernel-selection.conf. - # copy-kernel-packages.sh resolves what actually exists from - # image-info.json and copies the matching debs. - if [ -d "${INCOMING_PATH}/stable" ]; then - # shellcheck source=config/repository-kernel-selection.conf - . "${GITHUB_WORKSPACE}/config/repository-kernel-selection.conf" - echo "## Stable kernel selection: ${KERNEL_SELECT}" >> $GITHUB_STEP_SUMMARY - for ST_REPO in debs debs-beta; do - ST_SRC="${INCOMING_PATH}/stable/${ST_REPO}" - if [ ! -d "${ST_SRC}" ]; then - echo "## No stable/${ST_REPO} in incoming, skipping" >> $GITHUB_STEP_SUMMARY - continue - fi - SELECT="${KERNEL_SELECT}" \ - UBOOT_SELECT="${UBOOT_SELECT}" \ - COPY_UBOOT="${COPY_UBOOT}" \ - INCLUDE_LIBC_DEV="${INCLUDE_LIBC_DEV}" \ - DRY_RUN=false \ - SRC_DIR="${ST_SRC}" \ - DST_DIR="${STORAGE_PATH}/${ST_REPO}" \ - scripts/copy-kernel-packages.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY - ST_RC=${PIPESTATUS[0]} - if [ "${ST_RC}" -ne 0 ]; then - echo "::error::copy-kernel-packages.sh failed for stable/${ST_REPO} (exit ${ST_RC})" - exit 1 - fi - done - else - echo "## Source folder INCOMING/stable does not exist, skipping" >> $GITHUB_STEP_SUMMARY - fi + # Handled unconditionally below, like the generic packages: the + # drop box is ingested whichever pipeline triggered the update. + : ;; "cron/") # move what is inside incoming @@ -200,6 +171,45 @@ jobs: ;; esac + # Always ingest the stable kernel selection, whatever the target was. + # + # Not keyed on the dispatch target on purpose. The weekly chain builds + # the packages (uploading to incoming/stable/) and then runs + # base-files, and it is base-files that fires the repository update - + # so the payload says "base-files/" and a target-keyed branch would + # never see the kernels sitting next door. incoming is a drop box: + # whatever is in it gets ingested, whoever rang the bell. + # + # Kernels are a selection rather than a sweep - there is one + # linux-image per (linuxfamily, branch) - so which families the + # repository carries comes from config/repository-kernel-selection.conf + # and copy-kernel-packages.sh resolves what exists from image-info.json. + if [ -d "${INCOMING_PATH}/stable" ]; then + # shellcheck source=config/repository-kernel-selection.conf + . "${GITHUB_WORKSPACE}/config/repository-kernel-selection.conf" + echo "## Stable kernel selection: ${KERNEL_SELECT}" >> "$GITHUB_STEP_SUMMARY" + for ST_REPO in debs debs-beta; do + ST_SRC="${INCOMING_PATH}/stable/${ST_REPO}" + if [ ! -d "${ST_SRC}" ]; then + echo "## No stable/${ST_REPO} in incoming, skipping" >> "$GITHUB_STEP_SUMMARY" + continue + fi + SELECT="${KERNEL_SELECT}" \ + UBOOT_SELECT="${UBOOT_SELECT}" \ + COPY_UBOOT="${COPY_UBOOT}" \ + INCLUDE_LIBC_DEV="${INCLUDE_LIBC_DEV}" \ + DRY_RUN=false \ + SRC_DIR="${ST_SRC}" \ + DST_DIR="${STORAGE_PATH}/${ST_REPO}" \ + scripts/copy-kernel-packages.sh 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" + ST_RC=${PIPESTATUS[0]} + if [ "${ST_RC}" -ne 0 ]; then + echo "::error::copy-kernel-packages.sh failed for stable/${ST_REPO} (exit ${ST_RC})" + exit 1 + fi + done + fi + # Always ingest the release-generic packages, whatever the target was. # # armbian/ci builds these in their own pipeline and uploads them to From f164061b7dbc3bc542459528efc681955e7931bb Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:51:24 +0200 Subject: [PATCH 6/8] repository update: gate the kernel selection on stable/ and base-files/ Better than running it unconditionally. base-files/ is the weekly chain's completion signal - build-all-stable uploads to incoming/stable/, then runs base-files, and base-files fires the repository update - so gating there catches the kernels while still being an explicit target, and a manual stable/ dispatch keeps working through the same function. It also removes the cost of the unconditional version: copy-kernel-packages.sh copies rather than moves and nothing drains incoming/stable, so running on every dispatch meant the eight daily cron updates re-copied the same debs. Now the selection runs once per weekly chain. The generic packages stay unconditional: they are moved, so they are ingested once by whichever update sees them first, and they should never wait for a particular pipeline to finish. Signed-off-by: Igor Pecovnik --- .../infrastructure-repository-update.yml | 97 +++++++++++-------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/.github/workflows/infrastructure-repository-update.yml b/.github/workflows/infrastructure-repository-update.yml index a887222a35..721ee9f4eb 100644 --- a/.github/workflows/infrastructure-repository-update.yml +++ b/.github/workflows/infrastructure-repository-update.yml @@ -135,11 +135,63 @@ jobs: TARGET="cron/" fi + # Ingest the curated kernel selection out of incoming/stable. + # + # Kernels are a selection rather than a sweep - there is one + # linux-image per (linuxfamily, branch) - so which families the + # repository carries comes from config/repository-kernel-selection.conf, + # and copy-kernel-packages.sh resolves what exists from image-info.json. + # + # Called from both the stable/ and base-files/ targets: the weekly + # chain builds the packages into incoming/stable/ and then runs + # base-files, and it is base-files that fires the repository update - + # so base-files/ is the signal that the chain finished. Gating it there + # rather than running unconditionally also keeps the eight daily cron + # updates from re-copying the same debs, since this copies rather than + # moves. + ingest_stable_kernels() { + [ -d "${INCOMING_PATH}/stable" ] || { + echo "## No stable/ in incoming, skipping kernel selection" >> "$GITHUB_STEP_SUMMARY" + return 0 + } + # shellcheck source=config/repository-kernel-selection.conf + . "${GITHUB_WORKSPACE}/config/repository-kernel-selection.conf" + echo "## Stable kernel selection: ${KERNEL_SELECT}" >> "$GITHUB_STEP_SUMMARY" + local ST_REPO ST_SRC ST_RC + for ST_REPO in debs debs-beta; do + ST_SRC="${INCOMING_PATH}/stable/${ST_REPO}" + if [ ! -d "${ST_SRC}" ]; then + echo "## No stable/${ST_REPO} in incoming, skipping" >> "$GITHUB_STEP_SUMMARY" + continue + fi + SELECT="${KERNEL_SELECT}" \ + UBOOT_SELECT="${UBOOT_SELECT}" \ + COPY_UBOOT="${COPY_UBOOT}" \ + INCLUDE_LIBC_DEV="${INCLUDE_LIBC_DEV}" \ + DRY_RUN=false \ + SRC_DIR="${ST_SRC}" \ + DST_DIR="${STORAGE_PATH}/${ST_REPO}" \ + scripts/copy-kernel-packages.sh 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" + ST_RC=${PIPESTATUS[0]} + if [ "${ST_RC}" -ne 0 ]; then + echo "::error::copy-kernel-packages.sh failed for stable/${ST_REPO} (exit ${ST_RC})" + return 1 + fi + done + } + case "$TARGET" in "stable/") - # Handled unconditionally below, like the generic packages: the - # drop box is ingested whichever pipeline triggered the update. - : + # A stable build dispatching for itself (manual, or if the chain + # is ever unpicked). + ingest_stable_kernels || exit 1 + ;; + + "base-files/") + # The weekly chain's completion signal: build-all-stable uploads + # to incoming/stable/, then runs base-files, and base-files fires + # this update. So this is where the stable drop box is drained. + ingest_stable_kernels || exit 1 ;; "cron/") # move what is inside incoming @@ -171,45 +223,6 @@ jobs: ;; esac - # Always ingest the stable kernel selection, whatever the target was. - # - # Not keyed on the dispatch target on purpose. The weekly chain builds - # the packages (uploading to incoming/stable/) and then runs - # base-files, and it is base-files that fires the repository update - - # so the payload says "base-files/" and a target-keyed branch would - # never see the kernels sitting next door. incoming is a drop box: - # whatever is in it gets ingested, whoever rang the bell. - # - # Kernels are a selection rather than a sweep - there is one - # linux-image per (linuxfamily, branch) - so which families the - # repository carries comes from config/repository-kernel-selection.conf - # and copy-kernel-packages.sh resolves what exists from image-info.json. - if [ -d "${INCOMING_PATH}/stable" ]; then - # shellcheck source=config/repository-kernel-selection.conf - . "${GITHUB_WORKSPACE}/config/repository-kernel-selection.conf" - echo "## Stable kernel selection: ${KERNEL_SELECT}" >> "$GITHUB_STEP_SUMMARY" - for ST_REPO in debs debs-beta; do - ST_SRC="${INCOMING_PATH}/stable/${ST_REPO}" - if [ ! -d "${ST_SRC}" ]; then - echo "## No stable/${ST_REPO} in incoming, skipping" >> "$GITHUB_STEP_SUMMARY" - continue - fi - SELECT="${KERNEL_SELECT}" \ - UBOOT_SELECT="${UBOOT_SELECT}" \ - COPY_UBOOT="${COPY_UBOOT}" \ - INCLUDE_LIBC_DEV="${INCLUDE_LIBC_DEV}" \ - DRY_RUN=false \ - SRC_DIR="${ST_SRC}" \ - DST_DIR="${STORAGE_PATH}/${ST_REPO}" \ - scripts/copy-kernel-packages.sh 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" - ST_RC=${PIPESTATUS[0]} - if [ "${ST_RC}" -ne 0 ]; then - echo "::error::copy-kernel-packages.sh failed for stable/${ST_REPO} (exit ${ST_RC})" - exit 1 - fi - done - fi - # Always ingest the release-generic packages, whatever the target was. # # armbian/ci builds these in their own pipeline and uploads them to From 9ecf188a57693c3841fed368dbdd29dd3eb8814d Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 17:52:29 +0200 Subject: [PATCH 7/8] repository update: drop incoming/stable once the selection is copied What was wanted has been copied; the rest is every other family's kernel and every other board's u-boot, which this repository does not carry. Leaving them means incoming grows without bound and every weekly run re-copies the same debs over the last. Only reached when every copy above succeeded, so nothing is dropped on the strength of a failed run, and the .deb count is written to the step summary first - widening KERNEL_SELECT means next week's build supplies them again, but this week's are gone. The rm is guarded on INCOMING_PATH being under the storage path, matching the checks the other destructive steps in this workflow already make. Signed-off-by: Igor Pecovnik --- .../infrastructure-repository-update.yml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/infrastructure-repository-update.yml b/.github/workflows/infrastructure-repository-update.yml index 721ee9f4eb..5587d1b2c9 100644 --- a/.github/workflows/infrastructure-repository-update.yml +++ b/.github/workflows/infrastructure-repository-update.yml @@ -178,6 +178,28 @@ jobs: return 1 fi done + + # Drain the drop box: what was wanted has been copied, the rest is + # every other family's kernel and every other board's u-boot, which + # this repository does not carry. Leaving them means incoming grows + # without bound and each weekly run re-copies over the last. + # + # Only reached when every selection above succeeded, so nothing is + # dropped on the strength of a failed copy. The count is reported + # first: widening KERNEL_SELECT means next week's build supplies + # them again, but this week's are gone. + local ST_TOTAL + ST_TOTAL=$(find "${INCOMING_PATH}/stable" -type f -name '*.deb' 2>/dev/null | wc -l) + echo "## Dropping incoming/stable (${ST_TOTAL} .deb, selected ones already copied)" >> "$GITHUB_STEP_SUMMARY" + case "${INCOMING_PATH}" in + *"/armbian/openssh-server/storage"*) + rm -rf "${INCOMING_PATH}/stable" + ;; + *) + echo "::error::refusing to remove ${INCOMING_PATH}/stable - not under the storage path" + return 1 + ;; + esac } case "$TARGET" in From 763a80b86df4be35da55d2b180f1909db629c77f Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Thu, 20 Aug 2026 18:14:36 +0200 Subject: [PATCH 8/8] repository update: keep the generic packages off the cron path cron/ is the nightly repository: build-all runs with nightlybuild=yes, so its debs land in debs-beta. Sweeping the base-files drop box on that dispatch mixed stable-side packages into a run that has no business carrying them. The generic move becomes a function called from the two stable-side targets, stable/ and base-files/, alongside the kernel selection. cron/ now does exactly what it did before this branch: rsync incoming/cron into storage and drop the source. Signed-off-by: Igor Pecovnik --- .../infrastructure-repository-update.yml | 148 +++++++++--------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/.github/workflows/infrastructure-repository-update.yml b/.github/workflows/infrastructure-repository-update.yml index 5587d1b2c9..a4d91cd4bd 100644 --- a/.github/workflows/infrastructure-repository-update.yml +++ b/.github/workflows/infrastructure-repository-update.yml @@ -202,11 +202,81 @@ jobs: esac } + # Ingest the release-generic packages out of incoming/base-files. + # + # armbian/ci builds these in their own pipeline and uploads them there, + # decoupled so they publish even when a package build did not run + # (armbian/build#9476). The drop box carries more than base-files: + # everything the general packages build produces lands in it and all of + # it belongs in the repository - published today as base-files, + # armbian-firmware, armbian-firmware-full, armbian-plymouth-theme, + # armbian-zsh, fake-ubuntu-advantage-tools and armbian-bsp-cli-. + # So this matches on *.deb only: no package list to keep in sync, and + # repo.sh decides which component each one belongs to. + # + # Moved, not copied, with --remove-source-files rather than a following + # rm -rf, so rsync deletes exactly the files it confirmed on the + # receiving side - a partial transfer (exit 23) leaves whatever did not + # land in place for the next run instead of dropping it. + # + # Called only from the stable-side targets. cron/ builds with + # nightlybuild=yes and feeds debs-beta: the nightly repository has no + # business pulling in stable packages. + # + # Add another drop box by naming it here. + GENERIC_INCOMING="base-files" + + ingest_generic_packages() { + local GP_DIR GP_REPO GP_RC + for GP_DIR in ${GENERIC_INCOMING}; do + for GP_REPO in debs debs-beta; do + if [ -d "${INCOMING_PATH}/${GP_DIR}/${GP_REPO}" ]; then + echo "## Move ${GP_DIR}/${GP_REPO} into storage" >> "$GITHUB_STEP_SUMMARY" + rsync -av --remove-source-files \ + --include='*/' \ + --include='*.deb' \ + --exclude='*' \ + --omit-dir-times --no-perms --no-group \ + "${INCOMING_PATH}/${GP_DIR}/${GP_REPO}/" "${STORAGE_PATH}/${GP_REPO}/" \ + 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" + # PIPESTATUS, not $?: the pipe through tee would otherwise + # report tee's status and hide every rsync failure. 23 is + # expected here - --no-perms/--no-group on the shared-group + # storage means some attributes legitimately do not transfer. + # Anything else is real, and must stop the run: + # update-repository would happily publish a storage tree that + # is missing these packages. + GP_RC=${PIPESTATUS[0]} + case "${GP_RC}" in + 0) ;; + 23) + echo "Warning: some files/attrs were not transferred (rsync 23)" >> "$GITHUB_STEP_SUMMARY" + ;; + *) + echo "::error::rsync of ${GP_DIR}/${GP_REPO} failed (rsync exit ${GP_RC})" + echo "**rsync of ${GP_DIR}/${GP_REPO} failed (exit ${GP_RC})**" >> "$GITHUB_STEP_SUMMARY" + return 1 + ;; + esac + # --remove-source-files only removes files; clear the + # directories it emptied, and the per-repo dir once it is empty. + find "${INCOMING_PATH}/${GP_DIR}/${GP_REPO}" -type d -empty -delete + else + echo "## No ${GP_DIR}/${GP_REPO} in incoming, skipping" >> "$GITHUB_STEP_SUMMARY" + fi + done + # Drop the now-empty drop box. Left alone if anything remains, so a + # partial transfer stays visible for the next run. + rmdir "${INCOMING_PATH}/${GP_DIR}" 2>/dev/null || true + done + } + case "$TARGET" in "stable/") # A stable build dispatching for itself (manual, or if the chain # is ever unpicked). ingest_stable_kernels || exit 1 + ingest_generic_packages || exit 1 ;; "base-files/") @@ -214,6 +284,7 @@ jobs: # to incoming/stable/, then runs base-files, and base-files fires # this update. So this is where the stable drop box is drained. ingest_stable_kernels || exit 1 + ingest_generic_packages || exit 1 ;; "cron/") # move what is inside incoming @@ -245,83 +316,6 @@ jobs: ;; esac - # Always ingest the release-generic packages, whatever the target was. - # - # armbian/ci builds these in their own pipeline and uploads them to - # incoming/base-files/ - decoupled so they publish even when a package - # build did not run (armbian/build#9476). Nothing used to consume that - # directory: the target fell through to the no-op *) branch, so the - # debs piled up in incoming and never reached a repository. - # - # The drop box carries more than base-files. Everything the general - # packages build produces lands here and all of it belongs in the - # repository - published today as base-files, armbian-firmware, - # armbian-firmware-full, armbian-plymouth-theme, armbian-zsh, - # fake-ubuntu-advantage-tools and armbian-bsp-cli-. So this - # matches on *.deb only: no package list to keep in sync, and a new - # generic package needs no change here. repo.sh decides which - # component each one belongs to. - # - # Moved, not copied: incoming is a drop box, so what has been taken - # into storage is removed, the same way incoming/cron is. The move is - # done with --remove-source-files rather than a following rm -rf, so - # rsync deletes exactly the files it confirmed on the receiving side - - # a partial transfer (exit 23) leaves whatever did not land in place - # for the next run instead of dropping it. - # - # Only the generic packages move wholesale. kernel, linux-dtb, - # linux-libc-dev and u-boot are deliberately NOT in here: there is one - # of each per (linuxfamily, branch) and per (board, branch), so which - # of them belong in the repository is a selection, not a sweep. - # scripts/copy-kernel-packages.sh already does that selection - # (SELECT / UBOOT_SELECT / INCLUDE_LIBC_DEV / COPY_UBOOT, deduplicating - # against image-info.json); driving it from a config file is the next - # step, and belongs in the per-target branches above. - # - # Add another drop box by naming it here. - GENERIC_INCOMING="base-files" - - for GP_DIR in ${GENERIC_INCOMING}; do - for GP_REPO in debs debs-beta; do - if [ -d "${INCOMING_PATH}/${GP_DIR}/${GP_REPO}" ]; then - echo "## Move ${GP_DIR}/${GP_REPO} into storage" >> "$GITHUB_STEP_SUMMARY" - rsync -av --remove-source-files \ - --include='*/' \ - --include='*.deb' \ - --exclude='*' \ - --omit-dir-times --no-perms --no-group \ - "${INCOMING_PATH}/${GP_DIR}/${GP_REPO}/" "${STORAGE_PATH}/${GP_REPO}/" \ - 2>&1 | tee -a "$GITHUB_STEP_SUMMARY" - # PIPESTATUS, not $?: the pipe through tee would otherwise report - # tee's status and hide every rsync failure. 23 is expected here - - # --no-perms/--no-group on the shared-group storage means some - # attributes legitimately do not transfer. Anything else is real, - # and must stop the run: update-repository would happily publish - # a storage tree that is missing these packages. - GP_RC=${PIPESTATUS[0]} - case "${GP_RC}" in - 0) ;; - 23) - echo "Warning: some files/attrs were not transferred (rsync 23)" >> "$GITHUB_STEP_SUMMARY" - ;; - *) - echo "::error::rsync of ${GP_DIR}/${GP_REPO} failed (rsync exit ${GP_RC})" - echo "**rsync of ${GP_DIR}/${GP_REPO} failed (exit ${GP_RC})**" >> "$GITHUB_STEP_SUMMARY" - exit 1 - ;; - esac - # --remove-source-files only removes files; clear the directories - # it emptied, and the per-repo dir itself once it is empty. - find "${INCOMING_PATH}/${GP_DIR}/${GP_REPO}" -type d -empty -delete - else - echo "## No ${GP_DIR}/${GP_REPO} in incoming, skipping" >> "$GITHUB_STEP_SUMMARY" - fi - done - # Drop the now-empty drop box. Left alone if anything remains, so a - # partial transfer stays visible for the next run. - rmdir "${INCOMING_PATH}/${GP_DIR}" 2>/dev/null || true - done - # Always sync external if [ -d "${INCOMING_PATH}/external/debs" ]; then echo "## Copy external debs "