@@ -8,6 +8,7 @@ IFS=$'\n\t'
88SOURCE_OF_TRUTH=" ${SOURCE_OF_TRUTH:- rsync:// fi.mirror.armbian.de} "
99OS_DIR=" ${OS_DIR:- ./ os} "
1010BOARD_DIR=" ${BOARD_DIR:- ./ build/ config/ boards} "
11+ REUSABLE_FILE=" ${REUSABLE_FILE:- ./ release-targets/ reusable.yml} "
1112OUT=" ${OUT:- armbian-images.json} "
1213
1314# -----------------------------------------------------------------------------
@@ -32,7 +33,7 @@ BIGIN_PLATINUM_FIELDS="${BIGIN_PLATINUM_STATUS_FIELD},${BIGIN_PLATINUM_UNTIL_FIE
3233# Requirements
3334# -----------------------------------------------------------------------------
3435need () { command -v " $1 " > /dev/null || { echo " ERROR: missing '$1 '" >&2 ; exit 1; }; }
35- need rsync gh jq jc find grep sed cut awk sort mktemp curl date
36+ need rsync gh jq jc python3 find grep sed cut awk sort mktemp curl date
3637
3738[[ -f " ${OS_DIR} /exposed.map" ]] || { echo " ERROR: ${OS_DIR} /exposed.map not found" >&2 ; exit 1; }
3839[[ -d " ${BOARD_DIR} " ]] || { echo " ERROR: board directory not found: ${BOARD_DIR} " >&2 ; exit 1; }
@@ -88,6 +89,62 @@ done < <(
8889 | sort
8990)
9091
92+ # -----------------------------------------------------------------------------
93+ # Load virtual boards from reusable.yml
94+ # Boards that reuse artifact sets from other boards but with custom metadata
95+ # -----------------------------------------------------------------------------
96+ declare -A REUSABLE_BOARD_USES=() # board_slug -> target_board_slug
97+ declare -A REUSABLE_BOARD_BRANCH=() # board_slug -> branch_filter (optional)
98+ declare -A REUSABLE_BOARD_EXT=() # board_slug -> file_extension_filter (optional)
99+ declare -A REUSABLE_BOARD_META=() # board_slug -> "name|vendor|support"
100+
101+ if [[ -f " $REUSABLE_FILE " ]]; then
102+ echo " ▶ Loading reusable board definitions from ${REUSABLE_FILE} …" >&2
103+
104+ while IFS=$' \t ' read -r slug name vendor support uses branch ext; do
105+ slug=" ${slug,,} "
106+ [[ -z " $slug " ]] && continue
107+
108+ # Store the reusable board mapping
109+ REUSABLE_BOARD_USES[" $slug " ]=" ${uses:- } "
110+ REUSABLE_BOARD_BRANCH[" $slug " ]=" ${branch:- } "
111+ REUSABLE_BOARD_EXT[" $slug " ]=" ${ext:- } "
112+ REUSABLE_BOARD_META[" $slug " ]=" ${name:- } |${vendor:- } |${support:- } "
113+
114+ # Add to board maps (overwrites if exists)
115+ [[ -n " $name " ]] && BOARD_NAME_MAP[" $slug " ]=" $name "
116+ [[ -n " $vendor " ]] && BOARD_VENDOR_MAP[" $slug " ]=" $vendor "
117+ [[ -n " $support " ]] && BOARD_SUPPORT_MAP[" $slug " ]=" $support "
118+
119+ ext_msg=" ${ext: + (ext: ${ext} )} "
120+ echo " - ${slug} → ${uses}${branch: + (branch: ${branch} )}${ext_msg} " >&2
121+ done < <(
122+ python3 -c "
123+ import yaml, sys
124+ try:
125+ with open('$REUSABLE_FILE ') as f:
126+ data = yaml.safe_load(f)
127+ for b in data.get('boards', []):
128+ print('\t'.join([
129+ str(b.get('board_slug', '')),
130+ str(b.get('board_name', '')),
131+ str(b.get('board_vendor', '')),
132+ str(b.get('board_support', '')),
133+ str(b.get('uses', '')),
134+ str(b.get('branch', '')),
135+ str(b.get('file_extension', ''))
136+ ]))
137+ except Exception as e:
138+ sys.stderr.write(f'Error loading reusable.yml: {e}\n')
139+ sys.exit(0)
140+ " 2> /dev/null || true
141+ )
142+
143+ echo " - Reusable boards loaded: ${# REUSABLE_BOARD_USES[@]} " >&2
144+ else
145+ echo " ℹ️ Reusable board file not found: ${REUSABLE_FILE} " >&2
146+ fi
147+
91148# -----------------------------------------------------------------------------
92149# Optional: Load company data from Bigin keyed by company_slug (matches board_vendor)
93150# -----------------------------------------------------------------------------
@@ -593,6 +650,87 @@ cat "$tmpdir/a.txt" "$tmpdir/bcd.txt" >"$feed"
593650 fi
594651 fi
595652 echo " ${BOARD_SLUG} |${BOARD_NAME_MAP[$BOARD_SLUG]:- } |${BOARD_VENDOR} |${BOARD_SUPPORT} |${C_NAME} |${C_WEB} |${C_LOGO} |${VER} |${FILE_URL} |${ASC} |${SHA} |${TOR} |${REDI_URL} |${REDI_URL} .asc|${REDI_URL} .sha|${REDI_URL} .torrent|${IMAGE_SIZE} |${DATE} |${DISTRO} |${BRANCH} |${VARIANT} |${APP} |${PROMOTED} |${REPO} |${FILE_EXTENSION} |${PLAT} |${PLAT_EXPIRED} |${PLAT_UNTIL} "
653+
654+ # Check if this board is used by any reusable boards
655+ for reusable_slug in " ${! REUSABLE_BOARD_USES[@]} " ; do
656+ base_slug=" ${REUSABLE_BOARD_USES[$reusable_slug]} "
657+
658+ # Match if this board is the base board
659+ if [[ " $BOARD_SLUG " == " $base_slug " ]]; then
660+ branch_filter=" ${REUSABLE_BOARD_BRANCH[$reusable_slug]:- } "
661+ ext_filter=" ${REUSABLE_BOARD_EXT[$reusable_slug]:- } "
662+
663+ # Apply branch filter if specified
664+ if [[ -n " $branch_filter " && " $BRANCH " != " $branch_filter " ]]; then
665+ continue
666+ fi
667+
668+ # Apply file extension filter if specified
669+ if [[ -n " $ext_filter " ]]; then
670+ # Remove leading dot if present for matching
671+ ext_filter=" ${ext_filter# .} "
672+ current_ext=" ${FILE_EXTENSION# .} "
673+ if [[ " $current_ext " != " $ext_filter " ]]; then
674+ continue
675+ fi
676+ fi
677+
678+ # Get reusable board metadata
679+ reusable_meta=" ${REUSABLE_BOARD_META[$reusable_slug]} "
680+ IFS=' |' read -r reusable_name reusable_vendor reusable_support <<< " $reusable_meta"
681+
682+ # Use reusable board's vendor for company lookup
683+ reusable_company_key=" ${reusable_vendor,,} "
684+ reusable_c_name=" "
685+ reusable_c_web=" "
686+ if [[ -n " $reusable_company_key " ]]; then
687+ reusable_c_name=" ${COMPANY_NAME_BY_SLUG[$reusable_company_key]:- } "
688+ reusable_c_web=" ${COMPANY_WEBSITE_BY_SLUG[$reusable_company_key]:- } "
689+ fi
690+
691+ reusable_c_logo=" "
692+ if [[ -n " $reusable_vendor " ]]; then
693+ reusable_c_logo=" https://cache.armbian.com/images/vendors/150/${reusable_vendor} .png"
694+ fi
695+
696+ # Get platinum support for reusable board
697+ reusable_plat_until=" ${PLATINUM_UNTIL_BY_BOARD[$reusable_slug]:- } "
698+ reusable_plat=" false"
699+ reusable_plat_expired=" false"
700+ if [[ -n " $reusable_plat_until " ]]; then
701+ if [[ " $reusable_plat_until " < " $TODAY_UTC " ]]; then
702+ reusable_plat=" false"
703+ reusable_plat_expired=" true"
704+ else
705+ reusable_plat=" true"
706+ reusable_plat_expired=" false"
707+ fi
708+ fi
709+
710+ # Update REDI URL with reusable board slug
711+ reusable_redi_url=" https://dl.armbian.com/${PREFIX}${reusable_slug} /${DISTRO^} _${REDI_BRANCH} _${REDI_VARIANT} "
712+
713+ # Update cache URLs for GitHub releases
714+ reusable_asc=" $ASC "
715+ reusable_sha=" $SHA "
716+ reusable_tor=" $TOR "
717+ if [[ " $URL " == https://github.com/armbian/* ]]; then
718+ reusable_cache=" https://cache.armbian.com/artifacts/${reusable_slug} /archive/${IMAGE_NAME} "
719+ reusable_asc=" ${reusable_cache} .asc"
720+ reusable_sha=" ${reusable_cache} .sha"
721+ reusable_tor=" ${reusable_cache} .torrent"
722+ fi
723+
724+ # Check if reusable board image should be promoted
725+ reusable_promoted=false
726+ if is_promoted " $IMAGE_NAME " " $reusable_slug " " $URL " ; then
727+ reusable_promoted=true
728+ fi
729+
730+ # Output for reusable board
731+ echo " ${reusable_slug} |${reusable_name} |${reusable_vendor} |${reusable_support} |${reusable_c_name} |${reusable_c_web} |${reusable_c_logo} |${VER} |${FILE_URL} |${reusable_asc} |${reusable_sha} |${reusable_tor} |${reusable_redi_url} |${reusable_redi_url} .asc|${reusable_redi_url} .sha|${reusable_redi_url} .torrent|${IMAGE_SIZE} |${DATE} |${DISTRO} |${BRANCH} |${VARIANT} |${APP} |${reusable_promoted} |${REPO} |${FILE_EXTENSION} |${reusable_plat} |${reusable_plat_expired} |${reusable_plat_until} "
732+ fi
733+ done
596734 done < " $feed "
597735
598736} | jc --csv | jq ' {assets:.}' > " $OUT "
0 commit comments