Skip to content

SD Image

SD Image #49

name: SD Image
# Build NixOS SD card image for Orange Pi 5 (aarch64).
#
# Triggered AFTER the Release workflow completes — the aarch64 mesh-player
# binary is already in the GitHub Pages Nix cache, so this job fetches it
# instead of building from source.
#
# Only runs for minor/major releases (patch == 0, e.g. v0.9.0, v1.0.0).
# Patch releases update the device via nixos-rebuild (pulling from cache).
#
# Uses Nix derivation hash for deduplication: the drvPath encodes ALL inputs.
# If nothing affecting the image changed, the hash is identical and the build
# is skipped entirely.
on:
workflow_run:
workflows: ["Release"]
types: [completed]
workflow_dispatch:
inputs:
version:
description: 'Version tag (e.g., v0.9.0)'
required: true
type: string
force_sd_image:
description: 'Force SD image rebuild (ignore cache)'
required: false
type: boolean
default: false
permissions:
contents: write
jobs:
sd-image:
name: Build SD Image
runs-on: ubuntu-24.04-arm
# Only run when:
# 1. Release workflow succeeded (workflow_run), OR
# 2. Manual trigger (workflow_dispatch)
if: >-
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- name: Get version and check if minor release
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
FORCE="${{ github.event.inputs.force_sd_image }}"
else
# workflow_run: head_branch contains the tag that triggered Release
VERSION="${{ github.event.workflow_run.head_branch }}"
FORCE="false"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Detect minor/major version bumps (patch == 0)
SEMVER="${VERSION#v}"
SEMVER="${SEMVER%%-*}"
PATCH=$(echo "$SEMVER" | cut -d. -f3)
if [ "$FORCE" = "true" ]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "Force build requested."
elif [ "$PATCH" = "0" ]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "Minor/major release (patch=0), building SD image."
else
echo "skip=true" >> $GITHUB_OUTPUT
echo "Patch release (patch=$PATCH), skipping SD image."
fi
- name: Checkout repository
if: steps.version.outputs.skip != 'true'
uses: actions/checkout@v4
with:
ref: ${{ steps.version.outputs.version }}
- name: Install Nix
if: steps.version.outputs.skip != 'true'
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
extra-substituters = https://datao1.github.io/Mesh/
extra-trusted-public-keys = mesh-embedded:vLo1l3Abp0Uzcn21wR3oXvmZxZb1Z1rbk+ggTOIGmeQ=
- name: Nix store cache
if: steps.version.outputs.skip != 'true'
uses: DeterminateSystems/magic-nix-cache-action@v8
with:
use-flakehub: false
# Compute the Nix derivation hash — captures ALL inputs to the image.
# The hash changes if and only if something affecting the image changed.
- name: Compute derivation hash
if: steps.version.outputs.skip != 'true'
id: hash
run: |
echo "Evaluating SD image derivation (no build yet)..."
drv=$(nix eval --raw .#packages.aarch64-linux.sdImage.drvPath)
hash=$(basename "$drv" | cut -c1-32)
echo "hash=$hash" >> $GITHUB_OUTPUT
echo "Derivation: $drv"
echo "Hash: $hash"
# Check if we already uploaded an image with this exact hash
- name: Check for existing release
if: steps.version.outputs.skip != 'true'
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="sdimage-${{ steps.hash.outputs.hash }}"
force="${{ github.event.inputs.force_sd_image }}"
if [ "$force" = "true" ]; then
echo "exists=false" >> $GITHUB_OUTPUT
echo "Force rebuild requested, skipping cache check."
elif gh release view "$tag" &>/dev/null; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "SD image already exists for this configuration (tag: $tag), skipping build."
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "No existing release found (tag: $tag), building SD image..."
fi
# Build the full NixOS SD card image (kernel + system + mesh-player)
# mesh-player is fetched from the GitHub Pages binary cache (published
# by the Release workflow). System packages come from cache.nixos.org.
- name: Build SD image
if: steps.version.outputs.skip != 'true' && steps.check.outputs.exists != 'true'
run: |
echo "Building NixOS SD image for Orange Pi 5..."
echo "mesh-player should be fetched from binary cache (not built from source)"
nix build -L .#packages.aarch64-linux.sdImage
echo ""
echo "Image output:"
ls -lh result/sd-image/
- name: Upload to GitHub Release
if: steps.version.outputs.skip != 'true' && steps.check.outputs.exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version="${{ steps.version.outputs.version }}"
tag="sdimage-${{ steps.hash.outputs.hash }}"
image=$(ls result/sd-image/*.img* | head -1)
filename=$(basename "$image")
gh release create "$tag" \
--title "SD Image — ${version}" \
--notes "$(cat <<EOF
NixOS SD card image for Orange Pi 5 (aarch64).
Includes embedded U-Boot bootloader — boots on a fresh board with no SPI flash setup.
**Version:** ${version}
**Derivation hash:** \`${{ steps.hash.outputs.hash }}\`
**U-Boot:** Orange Pi vendor v1.1.8 (prebuilt, idbloader @ sector 64 + u-boot.itb @ sector 16384)
### Flash to SD card
\`\`\`bash
# Decompress and flash (replace /dev/sdX with your device)
zstdcat ${filename} | sudo dd of=/dev/sdX bs=4M status=progress
\`\`\`
### After first boot
The device auto-starts mesh-player in kiosk mode.
Update via: \`nixos-rebuild switch --flake github:${{ github.repository }}/${version}#mesh-embedded\`
EOF
)" \
"$image"
- name: Summary
if: always()
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "## SD Image — $VERSION" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "Skipped — patch release (SD image only builds for minor/major versions)." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The embedded device updates via:" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "nixos-rebuild switch --flake github:${{ github.repository }}/$VERSION#mesh-embedded --no-write-lock-file" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.exists }}" = "true" ]; then
echo "Skipped — image already exists for derivation hash \`${{ steps.hash.outputs.hash }}\`" >> $GITHUB_STEP_SUMMARY
else
echo "Built and uploaded as release \`sdimage-${{ steps.hash.outputs.hash }}\`" >> $GITHUB_STEP_SUMMARY
fi