name: Firmware artifact
on:
push:
branches: [main]
paths:
- examples/esp32c3-ota/**
- tools/build-firmware-artifact.sh
- .github/workflows/firmware.yml
workflow_dispatch:
permissions:
contents: read
concurrency:
group: firmware
cancel-in-progress: false
env:
CHIP: esp32c3
TARGET: riscv32imc-unknown-none-elf
IMAGE: ghcr.io/pawelchcki/oci-zero-firmware
ESPFLASH_VERSION: "4.5.0"
ESPFLASH_SHA256: "dcd7fd822f247df18966935bbb10c7cb4b700c87172349633e9e323ee2542717"
CRANE_VERSION: "0.21.7"
CRANE_SHA256: "1a57bc98207fa1c0d04bf760699099e26f8383499bfd55b99c1b919a928a7230"
jobs:
publish:
name: Build and publish
runs-on: ubuntu-latest
permissions:
contents: write packages: write steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: dtolnay/rust-toolchain@d1031067263f94b142dd6c0ce24c5eb9d02d52a0 with:
toolchain: "1.95.0"
targets: ${{ env.TARGET }}
- name: Install espflash and crane
run: |
set -eu
curl -fsSLo /tmp/espflash.zip \
"https://github.com/esp-rs/espflash/releases/download/v${ESPFLASH_VERSION}/espflash-x86_64-unknown-linux-gnu.zip"
echo "${ESPFLASH_SHA256} /tmp/espflash.zip" | sha256sum --check
unzip -j -o /tmp/espflash.zip espflash -d /tmp
sudo install -m 0755 /tmp/espflash /usr/local/bin/espflash
espflash --version
curl -fsSLo /tmp/crane.tar.gz \
"https://github.com/google/go-containerregistry/releases/download/v${CRANE_VERSION}/go-containerregistry_Linux_x86_64.tar.gz"
echo "${CRANE_SHA256} /tmp/crane.tar.gz" | sha256sum --check
tar -xzf /tmp/crane.tar.gz -C /tmp crane
sudo install -m 0755 /tmp/crane /usr/local/bin/crane
crane version
- name: Resolve version
id: version
run: |
set -eu
crate=$(cargo metadata --manifest-path examples/esp32c3-ota/Cargo.toml \
--no-deps --format-version 1 | jq -r '.packages[0].version')
short=$(git rev-parse --short=7 HEAD)
echo "version=${crate}+sha.${short}" >> "$GITHUB_OUTPUT"
echo "short=${short}" >> "$GITHUB_OUTPUT"
- name: Build firmware
working-directory: examples/esp32c3-ota
env:
OCI_ZERO_FIRMWARE_VERSION: ${{ steps.version.outputs.version }}
run: cargo build --release --target "$TARGET" --features full
- name: Build the flashable images
run: |
set -eu
elf="examples/esp32c3-ota/target/${TARGET}/release/oci-zero-esp32c3-ota"
# Padded, not --skip-padding: the splitter reads flash offsets as file
# offsets, and skipping padding compacts the file so they stop matching.
espflash save-image --chip "$CHIP" --merge \
--partition-table examples/esp32c3-ota/partitions.csv \
"$elf" merged.bin
python3 tools/split-flash-image.py merged.bin images \
| tee split.log
grep '^IMAGE_ARGS=' split.log >> "$GITHUB_ENV"
- name: Assemble OCI layout
id: layout
run: |
set -eu
# $IMAGE_ARGS carries bootloader/partition-table/firmware and their
# offsets, straight from the image that was just built, so the offsets
# cannot drift from partitions.csv.
./tools/build-firmware-artifact.sh \
$IMAGE_ARGS \
--version '${{ steps.version.outputs.version }}' \
--revision "$GITHUB_SHA" \
--chip "$CHIP" \
--target "$TARGET" \
--output layout \
--summary layout.env
# Appended, never redirected: $GITHUB_OUTPUT is shared by every step in
# the job, so `>` would discard the outputs written above.
cat layout.env >> "$GITHUB_OUTPUT"
- name: The artifact can provision a blank board
run: |
set -eu
config='${{ steps.layout.outputs.config_digest }}'
entries=$(jq -r '.entries[].path' "layout/blobs/sha256/${config#sha256:}")
echo "$entries"
for required in bootloader.bin partition-table.bin firmware.bin; do
echo "$entries" | grep -qx "$required" \
|| { echo "the artifact is missing $required" >&2; exit 1; }
done
- name: Push the OCI artifact to GHCR
run: |
set -eu
crane auth login ghcr.io -u '${{ github.actor }}' \
--password-stdin <<< '${{ secrets.GITHUB_TOKEN }}'
crane push layout "${IMAGE}:sha-${{ steps.version.outputs.short }}"
crane push layout "${IMAGE}:latest"
- name: Verify the registry digest matches the local layout
run: |
set -eu
remote=$(crane digest "${IMAGE}:sha-${{ steps.version.outputs.short }}")
expected='${{ steps.layout.outputs.manifest_digest }}'
echo "remote: $remote"
echo "expected: $expected"
test "$remote" = "$expected"
- name: Pack the release tar
id: tar
run: |
set -eu
name="firmware-${{ steps.version.outputs.version }}.oci.tar"
tar -cf "$name" -C layout .
echo "name=$name" >> "$GITHUB_OUTPUT"
sha256sum "$name"
- name: Publish to the rolling firmware release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
if ! gh release view firmware-latest >/dev/null 2>&1; then
gh release create firmware-latest --prerelease \
--title "ESP32-C3 firmware (rolling)" \
--notes "OCI layout tars built from main. Flash them from the browser: https://pawelchcki.github.io/oci-zero/flash.html"
fi
gh release upload firmware-latest "${{ steps.tar.outputs.name }}" --clobber