SD Image #49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |