Skip to content

Commit 0a24225

Browse files
committed
feat: add reusable board definitions to image JSON generator
Adds ability to define virtual boards that reuse artifact sets from existing boards but with custom metadata (name, vendor, support level). Features: - Load board definitions from release-targets/reusable.yml - Support branch filtering (e.g., only edge branch images) - Apply custom vendor, company logos, and support levels - Independent platinum support tracking per virtual board - Match base board images and replicate with overridden metadata Use case: Boards without dedicated build configs can now appear in downloads with custom branding (e.g., minisforum-msr1 reuses uefi-arm64 images) Example configuration: boards: - board_slug: "minisforum-msr1" board_name: "Minisforum MS-R1" board_vendor: "minisforum" uses: "uefi-arm64" branch: "current"
1 parent f286a29 commit 0a24225

File tree

2 files changed

+287
-1
lines changed

2 files changed

+287
-1
lines changed

release-targets/reusable.yml

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# ==============================================================================
2+
# Reusable Board Definitions
3+
# ==============================================================================
4+
#
5+
# This file defines "virtual boards" that reuse artifact sets from existing
6+
# boards but with custom identity metadata (name, vendor, support level).
7+
#
8+
# Purpose
9+
# -------
10+
# Allows boards without dedicated build configurations to appear in the
11+
# Armbian downloads interface with custom branding and filtered image sets.
12+
#
13+
# Use Cases
14+
# ---------
15+
# - OEM systems that use generic UEFI images (e.g., Minisforum MS-R1)
16+
# - Hardware integrators with custom board branding
17+
# - Hardware variants that share identical builds with different branding
18+
# - System on Module (SoM) boards that use carrier board images
19+
#
20+
# How It Works
21+
# ------------
22+
# 1. Define a virtual board with its own identity (slug, name, vendor, support)
23+
# 2. Specify which existing board's artifacts to reuse (uses field)
24+
# 3. Optionally filter by branch and/or file extension
25+
# 4. The JSON generator will create download entries for all matching images
26+
# from the base board, but with the virtual board's metadata
27+
#
28+
# Field Reference
29+
# ---------------
30+
# Required Fields:
31+
# board_slug : Unique identifier for the virtual board (URL-friendly)
32+
# board_name : Human-readable display name
33+
# board_vendor : Vendor/company slug (must match Zoho Bigin company_slug)
34+
# board_support : Support level - "conf", "csc", "wip", or "tvb"
35+
# uses : Board slug to reuse artifacts from (e.g., "uefi-arm64")
36+
#
37+
# Optional Filters:
38+
# branch : Only include images from this branch (e.g., "current")
39+
# If omitted, all branches are included
40+
# file_extension : Only include images with this extension (e.g., "img.xz")
41+
# If omitted, all file types are included
42+
#
43+
# Support Levels:
44+
# conf : Configured - basic support, community maintained
45+
# csc : Community Supported - good community support
46+
# wip : Work In Progress - under active development
47+
# tvb : To Be Verified - not yet tested
48+
#
49+
# Common Base Boards:
50+
# uefi-arm64 : Generic UEFI ARM 64-bit images
51+
# uefi-x86 : Generic UEFI x86_64 images
52+
# uefi-loong64 : Generic UEFI LoongArch 64-bit images
53+
# uefi-riscv64 : Generic UEFI RISC-V 64-bit images
54+
#
55+
# Example 1: Simple OEM Board
56+
# -------------------------
57+
# Creates entries for all uefi-arm64 images with Minisforum branding:
58+
#
59+
# - board_slug: "minisforum-msr1"
60+
# board_name: "Minisforum MS-R1"
61+
# board_vendor: "minisforum"
62+
# board_support: "conf"
63+
# uses: "uefi-arm64"
64+
#
65+
# Example 2: Filtered by Branch
66+
# ---------------------------
67+
# Only includes "current" branch images:
68+
#
69+
# - board_slug: "khadasmind"
70+
# board_name: "Mind"
71+
# board_vendor: "khadas"
72+
# board_support: "conf"
73+
# uses: "uefi-x86"
74+
# branch: "current"
75+
#
76+
# Example 3: Filtered by Branch and Extension
77+
# ---------------------------------------------
78+
# Only includes "current" branch .img.xz files (no .qcow2, .zip, etc.):
79+
#
80+
# - board_slug: "orangepi6-plus"
81+
# board_name: "Orangepi 6 Plus"
82+
# board_vendor: "xunlong"
83+
# board_support: "csc"
84+
# uses: "uefi-arm64"
85+
# branch: "current"
86+
# file_extension: "img.xz"
87+
#
88+
# Important Notes
89+
# ---------------
90+
# - The 'uses' board MUST exist in the actual build/firmware infrastructure
91+
# - The board_vendor must match the company_slug in Zoho Bigin for company
92+
# enrichment (name, website, logo) to work correctly
93+
# - Virtual boards inherit ALL image properties from the base board except:
94+
# * board_slug, board_name, board_vendor, board_support (overridden)
95+
# * REDI URLs (generated with virtual board slug)
96+
# * Company information (looked up via virtual board's vendor)
97+
# - Platinum support tracking is independent per virtual board slug
98+
# - Promotion status is evaluated independently for each virtual board
99+
#
100+
# File Extension Filter
101+
# --------------------
102+
# The file_extension filter matches against the FILE_EXTENSION field in the
103+
# JSON output, which includes storage type prefixes. Examples:
104+
# - "img.xz" : Matches standard compressed images
105+
# - "img.qcow2" : Matches cloud images (QEMU/KVM format)
106+
# - "u-boot.rom.xz" : Matches U-Boot ROM artifacts
107+
# - "ufs.img.xz" : Matches UFS filesystem images
108+
#
109+
# Testing
110+
# -------
111+
# After modifying this file, regenerate armbian-images.json and verify:
112+
# 1. Virtual board appears in the JSON: jq '.assets[] | select(.board_slug=="minisforum-msr1")'
113+
# 2. Correct number of images (filtering working)
114+
# 3. Metadata is correct (name, vendor, company info)
115+
# 4. REDI URLs point to the virtual board slug, not the base board
116+
#
117+
# ==============================================================================
118+
119+
boards:
120+
- board_slug: "minisforum-msr1"
121+
board_name: "Minisforum MS-R1"
122+
board_vendor: "minisforum"
123+
board_support: "conf"
124+
125+
# The magic: reuse artifact set
126+
uses: "uefi-arm64"
127+
branch: "current"
128+
file_extension: "img.xz"
129+
130+
- board_slug: "khadasmind"
131+
board_name: "Khadas Mind Series"
132+
board_vendor: "khadas"
133+
board_support: "conf"
134+
135+
# The magic: reuse artifact set
136+
uses: "uefi-x86"
137+
branch: "current"
138+
file_extension: "img.xz"
139+
140+
- board_slug: "orangepi6-plus"
141+
board_name: "Orangepi 6 Plus"
142+
board_vendor: "xunlong"
143+
board_support: "csc"
144+
145+
# The magic: reuse artifact set
146+
uses: "uefi-arm64"
147+
branch: "current"
148+
file_extension: "img.xz"

scripts/generate-armbian-images-json.sh

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ IFS=$'\n\t'
88
SOURCE_OF_TRUTH="${SOURCE_OF_TRUTH:-rsync://fi.mirror.armbian.de}"
99
OS_DIR="${OS_DIR:-./os}"
1010
BOARD_DIR="${BOARD_DIR:-./build/config/boards}"
11+
REUSABLE_FILE="${REUSABLE_FILE:-./release-targets/reusable.yml}"
1112
OUT="${OUT:-armbian-images.json}"
1213

1314
# -----------------------------------------------------------------------------
@@ -32,7 +33,7 @@ BIGIN_PLATINUM_FIELDS="${BIGIN_PLATINUM_STATUS_FIELD},${BIGIN_PLATINUM_UNTIL_FIE
3233
# Requirements
3334
# -----------------------------------------------------------------------------
3435
need() { 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

Comments
 (0)