@@ -121,29 +121,98 @@ jobs:
121121 id : sha256
122122 run : |
123123 IMAGE_PATH="${{ steps.build.outputs.image-path }}"
124-
124+
125125 # Determine if image-path is a file
126126 if [ -f "$IMAGE_PATH" ]; then
127127 # Calculate SHA256 hash and extract only the hash value
128128 SHA256_HASH=$(sha256sum "$IMAGE_PATH" | awk '{print $1}')
129-
129+
130130 # Get file size in bytes
131131 FILE_SIZE=$(stat -c%s "$IMAGE_PATH")
132-
132+
133133 # Write hash to sha256 file
134134 echo "${SHA256_HASH} $(basename "$IMAGE_PATH")" > "${IMAGE_PATH}.sha256"
135-
135+
136136 # Set as GitHub Action outputs
137137 echo "sha256_hash=${SHA256_HASH}" >> $GITHUB_OUTPUT
138138 echo "file_size=${FILE_SIZE}" >> $GITHUB_OUTPUT
139-
139+ echo "image_path=${IMAGE_PATH}" >> $GITHUB_OUTPUT
140+ echo "image_basename=$(basename "$IMAGE_PATH")" >> $GITHUB_OUTPUT
141+
140142 echo "SHA256 checksum generated for: $IMAGE_PATH"
141143 echo "SHA256 hash: $SHA256_HASH"
142144 echo "File size: $FILE_SIZE bytes"
143145 else
144146 echo "::warning::Image path not found: $IMAGE_PATH"
145147 fi
146148
149+ # Generate a single-image rpi-imager.json so the user can load this
150+ # specific build directly via "Use custom image" / repo URL in Pi Imager.
151+ # Only runs when the build job opts in via enable-rpi-imager-snippet.
152+ - name : Generate single-image rpi-imager.json
153+ if : inputs.enable-rpi-imager-snippet && steps.sha256.outputs.image_path != ''
154+ id : imager-json
155+ run : |
156+ IMAGE_PATH="${{ steps.sha256.outputs.image_path }}"
157+ BASENAME="${{ steps.sha256.outputs.image_basename }}"
158+ DOWNLOAD_SIZE="${{ steps.sha256.outputs.file_size }}"
159+
160+ REF_NAME="${{ github.ref_name }}"
161+ REF_ENCODED="${REF_NAME//\//%2F}"
162+ IMAGE_URL="https://github.com/${{ github.repository }}/releases/download/${REF_ENCODED}/${BASENAME}"
163+
164+ # Pi Imager validates the decompressed image, not the .xz wrapper,
165+ # so the published sha256 must be of the underlying .img.
166+ if [[ "$BASENAME" == *.xz ]]; then
167+ EXTRACT_SHA256=$(xzcat "$IMAGE_PATH" | sha256sum | awk '{print $1}')
168+ EXTRACT_SIZE=$(xz --list --robot "$IMAGE_PATH" | awk '/^totals/ {print $5}')
169+ elif [[ "$BASENAME" == *.zip ]]; then
170+ EXTRACT_SHA256=$(unzip -p "$IMAGE_PATH" | sha256sum | awk '{print $1}')
171+ EXTRACT_SIZE=$(unzip -l "$IMAGE_PATH" | awk '/[0-9]+ +[0-9-]+ +[0-9:]+ +.+\.img$/ {print $1}' | head -1)
172+ else
173+ EXTRACT_SHA256="$(sha256sum "$IMAGE_PATH" | awk '{print $1}')"
174+ EXTRACT_SIZE="$DOWNLOAD_SIZE"
175+ fi
176+
177+ OS_NAME="${{ inputs.image-name }}"
178+ # raw.githubusercontent.com handles slashes in refs natively, so
179+ # we keep REF_NAME un-encoded here even though the releases URL
180+ # above needs the %2F form.
181+ ICON_URL="https://raw.githubusercontent.com/${{ github.repository }}/${REF_NAME}/docs/respeaker_2michats.webp"
182+
183+ cat > rpi-imager.json <<JSON
184+ {
185+ "imager": {
186+ "latest_version": "${REF_NAME}",
187+ "url": "https://www.raspberrypi.com/software/",
188+ "devices": [
189+ {"name": "Raspberry Pi 5", "tags": ["pi5-64bit", "pi5-32bit"], "default": true, "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_5.png", "description": "Raspberry Pi 5, 500 / 500+, and Compute Module 5", "matching_type": "exclusive", "capabilities": []},
190+ {"name": "Raspberry Pi 4", "tags": ["pi4-64bit", "pi4-32bit"], "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_4.png", "description": "Raspberry Pi 4 Model B, 400, and Compute Module 4 / 4S", "matching_type": "inclusive", "capabilities": []},
191+ {"name": "Raspberry Pi 3", "tags": ["pi3-64bit", "pi3-32bit"], "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_3.png", "description": "Raspberry Pi 3 Model A+ / B / B+ and Compute Module 3 / 3+", "matching_type": "inclusive", "capabilities": []},
192+ {"name": "Raspberry Pi Zero 2 W", "tags": ["pi3-64bit", "pi3-32bit"], "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_Zero_2_W.png", "description": "Raspberry Pi Zero 2 W", "matching_type": "inclusive", "capabilities": []}
193+ ]
194+ },
195+ "os_list": [
196+ {
197+ "name": "${OS_NAME} (${REF_NAME})",
198+ "description": "PiCompose image: 2-Mic HAT v2 + Linux Voice Assistant from feat/peripheral-led-light-entity (Light entity + Rainbow effect, ReSpeaker 2-Mic peripheral). Built ${{ github.sha }} on branch ${REF_NAME}.",
199+ "icon": "${ICON_URL}",
200+ "url": "${IMAGE_URL}",
201+ "init_format": "systemd",
202+ "image_download_size": ${DOWNLOAD_SIZE},
203+ "extract_size": ${EXTRACT_SIZE},
204+ "extract_sha256": "${EXTRACT_SHA256}",
205+ "devices": ["pi5-64bit", "pi4-64bit", "pi3-64bit"],
206+ "capabilities": ["ssh", "wifi", "hostname", "locale"]
207+ }
208+ ]
209+ }
210+ JSON
211+
212+ echo "rpi-imager.json:"
213+ cat rpi-imager.json
214+ echo "imager_json_path=$(pwd)/rpi-imager.json" >> $GITHUB_OUTPUT
215+
147216 # Build the image URL for releases
148217 - name : Build image URL
149218 id : image-url
@@ -175,9 +244,11 @@ jobs:
175244 generate_release_notes : true
176245 prerelease : false
177246 draft : false
247+ fail_on_unmatched_files : false
178248 files : |
179249 ${{ steps.build.outputs.image-path }}
180250 ${{ steps.build.outputs.image-path }}.sha256
251+ rpi-imager.json
181252 env :
182253 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
183254
@@ -190,9 +261,11 @@ jobs:
190261 generate_release_notes : true
191262 prerelease : true
192263 draft : false
264+ fail_on_unmatched_files : false
193265 files : |
194266 ${{ steps.build.outputs.image-path }}
195267 ${{ steps.build.outputs.image-path }}.sha256
268+ rpi-imager.json
196269 env :
197270 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
198271
0 commit comments