diff --git a/scripts/copy-kernel-packages.sh b/scripts/copy-kernel-packages.sh new file mode 100755 index 00000000..3ead7a4d --- /dev/null +++ b/scripts/copy-kernel-packages.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + +# ============================================================================= +# copy-armbian-kernel-and-uboot-debs.sh +# +# Copy a minimal, de-duplicated set of Armbian kernel and U-Boot .deb packages +# from SRC_DIR to DST_DIR based on image-info.json. +# +# Kernel packages are unique per (linuxfamily, branch). +# U-Boot packages are unique per (board, branch). +# +# SELECT syntax: +# SELECT='linuxfamily_glob[:branch_glob] ...' +# Examples: +# SELECT='meson64:current imx6:current' +# SELECT='meson64:* imx6:current' +# SELECT='meson64*' # shorthand for meson64*:* +# +# UBOOT_SELECT syntax: +# UBOOT_SELECT='board_glob[:branch_glob] ...' +# Examples: +# UBOOT_SELECT='odroidm1:current' +# UBOOT_SELECT='bananapim64:*' +# +# Other flags: +# DRY_RUN=true +# INCLUDE_LIBC_DEV=true +# COPY_UBOOT=false +# ============================================================================= + +# Default URL for image-info.json +DEFAULT_JSON_URL="https://github.armbian.com/image-info.json" +JSON_FILE="${1:-image-info.json}" + +SRC_DIR="${SRC_DIR:-./in}" +DST_DIR="${DST_DIR:-./out}" + +SELECT="${SELECT:-}" +INCLUDE_LIBC_DEV="${INCLUDE_LIBC_DEV:-false}" +DRY_RUN="${DRY_RUN:-false}" + +COPY_UBOOT="${COPY_UBOOT:-true}" +UBOOT_SELECT="${UBOOT_SELECT:-}" + +die() { echo "ERROR: $*" >&2; exit 1; } + +# Fetch JSON from URL if file doesn't exist +if [[ ! -f "$JSON_FILE" ]]; then + if [[ "$JSON_FILE" == "http://"* ]] || [[ "$JSON_FILE" == "https://"* ]]; then + # JSON parameter is a URL, download it + echo "Downloading JSON from: $JSON_FILE" + curl -fsSL "$JSON_FILE" -o image-info.json || die "Failed to download JSON from $JSON_FILE" + JSON_FILE="image-info.json" + else + # Try to download from default URL + echo "JSON file not found: $JSON_FILE" + echo "Attempting to download from: $DEFAULT_JSON_URL" + # Ensure target directory exists + JSON_DIR=$(dirname "$JSON_FILE") + [[ -n "$JSON_DIR" ]] && mkdir -p "$JSON_DIR" + curl -fsSL "$DEFAULT_JSON_URL" -o "$JSON_FILE" || die "Failed to download JSON from $DEFAULT_JSON_URL" + echo "Downloaded successfully to: $JSON_FILE" + fi +fi + +[[ -f "$JSON_FILE" ]] || die "JSON not found: $JSON_FILE" +[[ -d "$SRC_DIR" ]] || die "SRC_DIR not found: $SRC_DIR" +mkdir -p "$DST_DIR" + +# ----------------------------------------------------------------------------- +# Expand JSON to triplets (board, linuxfamily, branch) +# ----------------------------------------------------------------------------- +JQ_EXPAND=' + def norm_branches: + if . == null then [] + elif (type=="string") then ( gsub("[,\\s]+";" ") | split(" ") | map(select(length>0)) ) + elif (type=="array") then ( map(tostring) | map(select(length>0)) ) + else [] end; + + [ + .[] + | { + board: (.out.HOST // .in.inventory.BOARD // ""), + linuxfamily: (.out.LINUXFAMILY // .in.inventory.BOARDFAMILY // ""), + branches: ((.in.inventory.BOARD_POSSIBLE_BRANCHES + // .in.inventory.BOARD_TOP_LEVEL_VARS.BOARD_POSSIBLE_BRANCHES) + | norm_branches) + } + | select((.board|length>0) and (.linuxfamily|length>0) and (.branches|length>0)) + | . as $o + | $o.branches[] + | { board: $o.board, linuxfamily: $o.linuxfamily, branch: . } + ] + | unique_by([.linuxfamily,.branch,.board]) + | sort_by(.board, .branch, .linuxfamily) + | group_by([.board,.branch]) | map(.[0]) +' + +# Kernel pairs: dedupe boards for same (linuxfamily,branch) +JQ_KERNEL_PAIRS="$JQ_EXPAND"' + | sort_by(.linuxfamily, .branch, .board) + | group_by([.linuxfamily,.branch]) | map(.[0]) + | map([.linuxfamily, .branch] | @tsv) + | .[] +' + +# U-Boot triplets: keep all boards +JQ_UBOOT_TRIPLETS="$JQ_EXPAND"' + | map([.board, .branch] | @tsv) + | .[] +' + +mapfile -t KERNEL_PAIRS < <(jq -r "$JQ_KERNEL_PAIRS" "$JSON_FILE" | sort -u) +mapfile -t UBOOT_TRIPLETS < <(jq -r "$JQ_UBOOT_TRIPLETS" "$JSON_FILE" | sort -u) + +[[ ${#KERNEL_PAIRS[@]} -gt 0 ]] || die "No kernel pairs found" + +# ----------------------------------------------------------------------------- +# Selection helpers (IMPORTANT: local IFS includes space) +# ----------------------------------------------------------------------------- +pair_selected() { + local fam="$1" br="$2" + local token sf sb + local -a tokens=() + + [[ -z "${SELECT:-}" ]] && return 0 + + # Split SELECT on spaces/newlines/tabs regardless of global IFS + IFS=$' \n\t' read -r -a tokens <<< "${SELECT}" + + for token in "${tokens[@]}"; do + [[ -z "$token" ]] && continue + + if [[ "$token" == *:* ]]; then + sf="${token%%:*}" + sb="${token#*:}" + [[ -z "$sf" ]] && sf="*" + [[ -z "$sb" ]] && sb="*" + else + sf="$token" + sb="*" + fi + + # IMPORTANT: pattern side must be unquoted to enable glob matching (*) + if [[ $fam == $sf && $br == $sb ]]; then + return 0 + fi + done + return 1 +} + +uboot_selected() { + local board="$1" br="$2" + local token sb sbr + local -a tokens=() + + [[ -z "${UBOOT_SELECT:-}" ]] && return 0 + [[ "${UBOOT_SELECT}" == "*" ]] && return 0 + + # Split on spaces/newlines/tabs regardless of global IFS + IFS=$' \n\t' read -r -a tokens <<< "${UBOOT_SELECT}" + + for token in "${tokens[@]}"; do + [[ -z "$token" ]] && continue + + if [[ "$token" == *:* ]]; then + sb="${token%%:*}" + sbr="${token#*:}" + [[ -z "$sb" ]] && sb="*" + [[ -z "$sbr" ]] && sbr="*" + else + sb="$token" + sbr="*" + fi + + # IMPORTANT: unquoted to enable glob matching + if [[ $board == $sb && $br == $sbr ]]; then + return 0 + fi + done + + return 1 +} + + +# ----------------------------------------------------------------------------- +# Copy helpers +# ----------------------------------------------------------------------------- +shopt -s nullglob + +copy_flat() { + local glob="$1" + local files=( $glob ) + + [[ ${#files[@]} -eq 0 ]] && { echo " MISSING: $(basename "$glob")"; return 1; } + + for f in "${files[@]}"; do + if [[ "$DRY_RUN" == "true" ]]; then + echo " WOULD COPY: $(basename "$f")" + else + cp -a "$f" "$DST_DIR/" + echo " COPIED: $(basename "$f")" + fi + done +} + +copy_recursive() { + local pat="$1" + local files=() + + while IFS= read -r -d '' f; do files+=("$f"); done < <( + find "$SRC_DIR" -type f -name "$pat" -print0 2>/dev/null || true + ) + + [[ ${#files[@]} -eq 0 ]] && { echo " MISSING: $pat"; return 1; } + + for f in "${files[@]}"; do + if [[ "$DRY_RUN" == "true" ]]; then + echo " WOULD COPY: $(basename "$f")" + else + cp -a "$f" "$DST_DIR/" + echo " COPIED: $(basename "$f")" + fi + done +} + +# ----------------------------------------------------------------------------- +# Main +# ----------------------------------------------------------------------------- +copied=0 +missing=0 +selected_pairs=0 + +declare -A selected_pairs_set=() + +echo "JSON: $JSON_FILE" +echo "SRC_DIR: $SRC_DIR" +echo "DST_DIR: $DST_DIR" +echo "SELECT: ${SELECT:-}" +echo "COPY_UBOOT: $COPY_UBOOT" +echo "UBOOT_SELECT: ${UBOOT_SELECT:-}" +echo "DRY_RUN: $DRY_RUN" +echo + +# Kernels +for line in "${KERNEL_PAIRS[@]}"; do + fam="${line%%$'\t'*}" + br="${line#*$'\t'}" + + pair_selected "$fam" "$br" || continue + + selected_pairs=$((selected_pairs+1)) + selected_pairs_set["$fam:$br"]=1 + + echo "== Pair: linuxfamily=$fam branch=$br ==" + + for p in linux-image linux-dtb linux-headers; do + copy_flat "$SRC_DIR/${p}-${br}-${fam}_*.deb" && copied=$((copied+1)) || missing=$((missing+1)) + done + + if [[ "$INCLUDE_LIBC_DEV" == "true" ]]; then + copy_flat "$SRC_DIR/linux-libc-dev-${br}-${fam}_*.deb" && copied=$((copied+1)) || missing=$((missing+1)) + fi + + echo +done + +[[ $selected_pairs -gt 0 ]] || die "No kernel pairs selected" + +# U-Boot +if [[ "$COPY_UBOOT" == "true" ]]; then + echo "== U-Boot packages ==" + + for line in "${UBOOT_TRIPLETS[@]}"; do + board="${line%%$'\t'*}" + br="${line#*$'\t'}" + + uboot_selected "$board" "$br" || continue + + echo "-- board=$board branch=$br --" + copy_recursive "linux-u-boot-${board}-${br}_*.deb" \ + && copied=$((copied+1)) || missing=$((missing+1)) + done + echo +fi + +echo "Done." +echo "Selected (linuxfamily,branch) pairs: $selected_pairs" +echo "Matched file groups: $copied" +echo "Missing patterns: $missing"