|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | +IFS=$'\n\t' |
| 4 | + |
| 5 | +# ----------------------------------------------------------------------------- |
| 6 | +# Configuration (override via env) |
| 7 | +# ----------------------------------------------------------------------------- |
| 8 | +SOURCE_OF_TRUTH="${SOURCE_OF_TRUTH:-rsync://fi.mirror.armbian.de}" |
| 9 | +OS_DIR="${OS_DIR:-./os}" |
| 10 | +BOARD_DIR="${BOARD_DIR:-./build/config/boards}" |
| 11 | +OUT="${OUT:-armbian-images.json}" |
| 12 | + |
| 13 | +# ----------------------------------------------------------------------------- |
| 14 | +# Requirements |
| 15 | +# ----------------------------------------------------------------------------- |
| 16 | +need() { command -v "$1" >/dev/null || { echo "ERROR: missing '$1'" >&2; exit 1; }; } |
| 17 | +need rsync gh jq jc find grep sed cut rev awk |
| 18 | + |
| 19 | +[[ -f "${OS_DIR}/exposed.map" ]] || { echo "ERROR: ${OS_DIR}/exposed.map not found" >&2; exit 1; } |
| 20 | +[[ -d "${BOARD_DIR}" ]] || { echo "ERROR: board directory not found: ${BOARD_DIR}" >&2; exit 1; } |
| 21 | + |
| 22 | +# ----------------------------------------------------------------------------- |
| 23 | +# Helpers: parse first assignment of VAR=... from a board config file. |
| 24 | +# Supports: |
| 25 | +# BOARD_NAME="X" |
| 26 | +# export BOARD_NAME="X" |
| 27 | +# declare -g BOARD_NAME="X" |
| 28 | +# declare -g -r BOARD_NAME='\''X'\'' |
| 29 | +# BOARD_VENDOR=generic |
| 30 | +# ----------------------------------------------------------------------------- |
| 31 | +extract_cfg_var() { |
| 32 | + local file="$1" var="$2" |
| 33 | + |
| 34 | + awk -v var="$var" ' |
| 35 | + function ltrim(s){ sub(/^[ \t\r\n]+/, "", s); return s } |
| 36 | + function rtrim(s){ sub(/[ \t\r\n]+$/, "", s); return s } |
| 37 | + function trim(s){ return rtrim(ltrim(s)) } |
| 38 | +
|
| 39 | + { |
| 40 | + line=$0 |
| 41 | + sub(/[ \t]*#.*/, "", line) |
| 42 | + line=trim(line) |
| 43 | + if (line == "") next |
| 44 | +
|
| 45 | + if (line ~ /^export[ \t]+/) { |
| 46 | + sub(/^export[ \t]+/, "", line) |
| 47 | + } else if (line ~ /^declare[ \t]+-/) { |
| 48 | + sub(/^declare[ \t]+(-[A-Za-z]+[ \t]+)+/, "", line) |
| 49 | + } else if (line ~ /^declare[ \t]+/) { |
| 50 | + sub(/^declare[ \t]+/, "", line) |
| 51 | + } |
| 52 | +
|
| 53 | + pattern="^" var "[ \t]*=" |
| 54 | + if (line ~ pattern) { |
| 55 | + sub(pattern, "", line) |
| 56 | + line=trim(line) |
| 57 | +
|
| 58 | + if (match(line, /^".*"$/)) { |
| 59 | + sub(/^"/, "", line); sub(/"$/, "", line) |
| 60 | + } else if (match(line, /^\047.*\047$/)) { |
| 61 | + sub(/^\047/, "", line); sub(/\047$/, "", line) |
| 62 | + } |
| 63 | +
|
| 64 | + print line |
| 65 | + exit 0 |
| 66 | + } |
| 67 | + } |
| 68 | + ' "$file" 2>/dev/null || true |
| 69 | +} |
| 70 | + |
| 71 | +# ----------------------------------------------------------------------------- |
| 72 | +# Load board metadata |
| 73 | +# ----------------------------------------------------------------------------- |
| 74 | +declare -A BOARD_NAME_MAP |
| 75 | +declare -A BOARD_VENDOR_MAP |
| 76 | + |
| 77 | +while IFS= read -r cfg; do |
| 78 | + slug="$(basename "$cfg")" |
| 79 | + slug="${slug%.*}" |
| 80 | + slug="${slug,,}" |
| 81 | + |
| 82 | + name="$(extract_cfg_var "$cfg" "BOARD_NAME")" |
| 83 | + vendor="$(extract_cfg_var "$cfg" "BOARD_VENDOR")" |
| 84 | + |
| 85 | + [[ -n "$name" ]] && BOARD_NAME_MAP["$slug"]="$name" |
| 86 | + [[ -n "$vendor" ]] && BOARD_VENDOR_MAP["$slug"]="$vendor" |
| 87 | +done < <( |
| 88 | + find "$BOARD_DIR" -maxdepth 1 -type f \ |
| 89 | + \( -name "*.conf" -o -name "*.csc" -o -name "*.wip" -o -name "*.tvb" \) | sort |
| 90 | +) |
| 91 | + |
| 92 | +# ----------------------------------------------------------------------------- |
| 93 | +# Warn once per board if metadata is missing |
| 94 | +# ----------------------------------------------------------------------------- |
| 95 | +declare -A BOARD_WARNED |
| 96 | + |
| 97 | +warn_missing_board_meta() { |
| 98 | + local slug="$1" name="$2" vendor="$3" |
| 99 | + |
| 100 | + [[ -n "${BOARD_WARNED[$slug]:-}" ]] && return 0 |
| 101 | + BOARD_WARNED["$slug"]=1 |
| 102 | + |
| 103 | + if [[ -z "$name" || -z "$vendor" ]]; then |
| 104 | + echo "WARNING: Missing board metadata for slug '${slug}' (name='${name}', vendor='${vendor}')" >&2 |
| 105 | + fi |
| 106 | +} |
| 107 | + |
| 108 | +# ----------------------------------------------------------------------------- |
| 109 | +# Build feeds (drop .asc/.sha/.torrent as standalone entries) |
| 110 | +# ----------------------------------------------------------------------------- |
| 111 | +tmpdir="$(mktemp -d)" |
| 112 | +trap 'rm -rf "$tmpdir"' EXIT |
| 113 | +feed="${tmpdir}/feed.txt" |
| 114 | + |
| 115 | +echo "▶ Building feeds…" >&2 |
| 116 | + |
| 117 | +# Feed A: mirror |
| 118 | +rsync --recursive --list-only "${SOURCE_OF_TRUTH}/dl/" \ |
| 119 | +| awk '{ |
| 120 | + gsub(",", "", $2) |
| 121 | + gsub("/", "-", $3) |
| 122 | + url = "https://dl.armbian.com/" $5 |
| 123 | + if (url ~ /\/[^\/]+\/archive\/Armbian/ && |
| 124 | + url !~ /\.txt|homeassistant|openhab|kali|omv/ && |
| 125 | + url !~ /\.(asc|sha|torrent)$/) { |
| 126 | + print $2 "|" url "|" $3 "T" $4 "Z" |
| 127 | + } |
| 128 | +}' > "${tmpdir}/a.txt" |
| 129 | + |
| 130 | +# Feed B/C/D: GitHub releases |
| 131 | +: > "${tmpdir}/bcd.txt" |
| 132 | +for repo in community os distribution; do |
| 133 | + gh release view --json assets --repo "github.com/armbian/${repo}" \ |
| 134 | + | jq -r '.assets[] |
| 135 | + | select(.url | test("\\.(asc|sha|torrent)$") | not) |
| 136 | + | select(.url | test("\\.txt$") | not) |
| 137 | + | "\(.size)|\(.url)|\(.createdAt)"' \ |
| 138 | + >> "${tmpdir}/bcd.txt" |
| 139 | +done |
| 140 | + |
| 141 | +cat "${tmpdir}/a.txt" "${tmpdir}/bcd.txt" > "$feed" |
| 142 | + |
| 143 | +# ----------------------------------------------------------------------------- |
| 144 | +# JSON generation |
| 145 | +# ----------------------------------------------------------------------------- |
| 146 | +{ |
| 147 | + echo '"board_slug"|"board_name"|"board_vendor"|"armbian_version"|"file_url"|"file_url_asc"|"file_url_sha"|"file_url_torrent"|"redi_url"|"redi_url_asc"|"redi_url_sha"|"redi_url_torrent"|"file_updated"|"file_size"|"distro_release"|"kernel_branch"|"image_variant"|"preinstalled_application"|"promoted"|"download_repository"|"file_extension"' |
| 148 | + |
| 149 | + while IFS= read -r line; do |
| 150 | + [[ -z "$line" ]] && continue |
| 151 | + |
| 152 | + IMAGE_URL="$(cut -d"|" -f2 <<<"$line")" |
| 153 | + IMAGE_SIZE="$(cut -d"|" -f1 <<<"$line" | sed 's/[.,]//g')" |
| 154 | + IMAGE_CREATED="$(cut -d"|" -f3 <<<"$line" | sed 's/\//-/g')" |
| 155 | + |
| 156 | + BOARD="$(echo "$IMAGE_URL" | grep -Po 'Armbian.*[0-9][0-9]\.[0-9].*' | grep -Po '[0-9][0-9]\.[0-9].*' | cut -d_ -f2)" |
| 157 | + ARMBIAN_VERSION="$(echo "$IMAGE_URL" | grep -Po 'Armbian.*[0-9][0-9]\.[0-9].*' | grep -Po '[0-9][0-9]\.[0-9].*' | cut -d_ -f1)" |
| 158 | + IMAGE_RELEASE="$(echo "$IMAGE_URL" | grep -Po 'Armbian.*[0-9][0-9]\.[0-9].*' | grep -Po '[0-9][0-9]\.[0-9].*' | cut -d_ -f3)" |
| 159 | + KERNEL_BRANCH="$(echo "$IMAGE_URL" | grep -Po 'Armbian.*[0-9][0-9]\.[0-9].*' | grep -Po '[0-9][0-9]\.[0-9].*' | cut -d_ -f4)" |
| 160 | + |
| 161 | + BOARD_SLUG="${BOARD,,}" |
| 162 | + BOARD_NAME="${BOARD_NAME_MAP[$BOARD_SLUG]:-}" |
| 163 | + BOARD_VENDOR="${BOARD_VENDOR_MAP[$BOARD_SLUG]:-}" |
| 164 | + |
| 165 | + warn_missing_board_meta "$BOARD_SLUG" "$BOARD_NAME" "$BOARD_VENDOR" |
| 166 | + |
| 167 | + STEP_A=1 |
| 168 | + STEP_B=2 |
| 169 | + [[ $IMAGE_URL == *i3-wm* || $IMAGE_URL == *kde-* || $IMAGE_URL == *-rc* ]] && STEP_A=2 && STEP_B=3 |
| 170 | + |
| 171 | + IMAGE_VARIANT="$( |
| 172 | + echo "$IMAGE_URL" \ |
| 173 | + | grep "minimal\|desktop" \ |
| 174 | + | grep -Po '[0-9][0-9]\.[0-9].*' \ |
| 175 | + | cut -d_ -f4- \ |
| 176 | + | grep -Po '_[a-z].*' \ |
| 177 | + | cut -d. -f1 \ |
| 178 | + | sed 's/^_//; s/_desktop//; s/-kisak//; s/-backported-mesa//' \ |
| 179 | + | cut -d- -f1,${STEP_A} || true |
| 180 | + )" |
| 181 | + [[ -z "$IMAGE_VARIANT" ]] && IMAGE_VARIANT="server" |
| 182 | + |
| 183 | + FILE_EXTENSION="$( |
| 184 | + echo "$IMAGE_URL" \ |
| 185 | + | grep -Po 'Armbian.*[0-9][0-9]\.[0-9].*' \ |
| 186 | + | grep -Po '[0-9][0-9]\.[0-9].*' \ |
| 187 | + | sed 's/_sm8250-xiaomi-umi\|_sm8250-xiaomi-elish//g' \ |
| 188 | + | sed 's/-rc[0-9]//g' \ |
| 189 | + | rev | cut -d_ -f1 | rev \ |
| 190 | + | sed 's/.*[^0-9]\([0-9]*\.[0-9]*\.[0-9]*\)//' \ |
| 191 | + | sed 's/desktop.\|minimal.//' |
| 192 | + )" |
| 193 | + |
| 194 | + IMAGE_TYPE="$(cut -d/ -f5 <<<"$IMAGE_URL")" |
| 195 | + PREFIX="" |
| 196 | + [[ "$IMAGE_TYPE" == "os" ]] && PREFIX="nightly/" |
| 197 | + |
| 198 | + REDI_URL="https://dl.armbian.com/${PREFIX}${BOARD_SLUG}/${IMAGE_RELEASE^}_${KERNEL_BRANCH}_${IMAGE_VARIANT}" |
| 199 | + |
| 200 | + IMAGE_NAME="${IMAGE_URL##*/}" |
| 201 | + |
| 202 | + if [[ "$IMAGE_URL" == https://github.com/armbian/* ]]; then |
| 203 | + BASE_CACHE="https://cache.armbian.com/artifacts/${BOARD_SLUG}/archive/${IMAGE_NAME}" |
| 204 | + FILE_URL_ASC="${BASE_CACHE}.asc" |
| 205 | + FILE_URL_SHA="${BASE_CACHE}.sha" |
| 206 | + FILE_URL_TORRENT="${BASE_CACHE}.torrent" |
| 207 | + |
| 208 | + REDI_URL_ASC="$FILE_URL_ASC" |
| 209 | + REDI_URL_SHA="$FILE_URL_SHA" |
| 210 | + REDI_URL_TORRENT="$FILE_URL_TORRENT" |
| 211 | + else |
| 212 | + FILE_URL_ASC="${IMAGE_URL}.asc" |
| 213 | + FILE_URL_SHA="${IMAGE_URL}.sha" |
| 214 | + FILE_URL_TORRENT="${IMAGE_URL}.torrent" |
| 215 | + |
| 216 | + REDI_URL_ASC="${REDI_URL}.asc" |
| 217 | + REDI_URL_SHA="${REDI_URL}.sha" |
| 218 | + REDI_URL_TORRENT="${REDI_URL}.torrent" |
| 219 | + fi |
| 220 | + |
| 221 | + EXPOSED=false |
| 222 | + while IFS= read -r exposed; do |
| 223 | + [[ -z "$exposed" ]] && continue |
| 224 | + [[ $IMAGE_URL =~ $exposed ]] && EXPOSED=true |
| 225 | + done < "${OS_DIR}/exposed.map" |
| 226 | + |
| 227 | + echo "${BOARD_SLUG}|${BOARD_NAME}|${BOARD_VENDOR}|$ARMBIAN_VERSION|$IMAGE_URL|$FILE_URL_ASC|$FILE_URL_SHA|$FILE_URL_TORRENT|$REDI_URL|$REDI_URL_ASC|$REDI_URL_SHA|$REDI_URL_TORRENT|$IMAGE_CREATED|$IMAGE_SIZE|$IMAGE_RELEASE|$KERNEL_BRANCH|$IMAGE_VARIANT||$EXPOSED|$IMAGE_TYPE|$FILE_EXTENSION" |
| 228 | + done < "$feed" |
| 229 | + |
| 230 | +} | jc --csv | jq '{"assets": .}' > "$OUT" |
| 231 | + |
| 232 | +echo "✔ Generated $OUT" |
| 233 | +echo "✔ Assets: $(jq '.assets | length' "$OUT")" |
0 commit comments