[code] add working role available by default #2
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: '"10 min" UNITTEST IMAGE on latest RPiOS Lite' | |
| on: | |
| push: | |
| branches-ignore: | |
| - main | |
| - master | |
| paths-ignore: | |
| - '**/*.md' | |
| - '**/*.rst' | |
| - '**/*.html' | |
| - '**/*.css' | |
| - '**/*.svg' | |
| - '**/*.jpg' | |
| - '**/*.png' | |
| - '**/*.php' | |
| - '**/*.js' | |
| pull_request: # needed to show up in Pull Request UI | |
| paths-ignore: | |
| - '**/*.md' | |
| - '**/*.rst' | |
| - '**/*.html' | |
| - '**/*.css' | |
| - '**/*.svg' | |
| - '**/*.jpg' | |
| - '**/*.png' | |
| - '**/*.php' | |
| - '**/*.js' | |
| workflow_dispatch: | |
| jobs: | |
| image: | |
| runs-on: ubuntu-24.04-arm | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| path: iiab | |
| fetch-depth: 0 # should be a full copy for updating later via git pull | |
| - uses: actions/checkout@v6 | |
| with: | |
| repository: chapmanjacobd/nspawn-loop | |
| path: nspawn-loop | |
| - name: Download RaspberryPi OS | |
| working-directory: nspawn-loop | |
| run: sudo ./mount.sh https://downloads.raspberrypi.org/raspios_lite_arm64_latest 7450 | |
| - name: Build image | |
| working-directory: nspawn-loop | |
| run: | | |
| sudo -E bash << 'EOF' | |
| set -euo pipefail | |
| STATE_FILE=$(find . -maxdepth 1 -name "*.state" | head -n 1) | |
| source "$STATE_FILE" | |
| VERSION=$(grep '^iiab_base_ver:' ../iiab/vars/default_vars.yml | awk '{print $2}') | |
| echo "IIAB_BASE_VERSION=$VERSION" >> $GITHUB_ENV | |
| OS="rpios" | |
| EDITION="unittest" | |
| HASH=$(git -C ../iiab rev-parse --short=7 HEAD) | |
| COMMIT_DATETIME_LOCAL=$(git -C ../iiab log -1 --format=%cd --date=iso-strict) | |
| # Convert the local time to UTC using the 'date' command | |
| COMMIT_DATETIME_UTC=$(date -d "$COMMIT_DATETIME_LOCAL" -u +"%Y-%m-%dT%H:%M:%SZ") | |
| # Extract date and time components from the UTC timestamp | |
| COMMIT_DATE=$(date -d "$COMMIT_DATETIME_UTC" +'%y%m%d') | |
| COMMIT_TIME=$(date -d "$COMMIT_DATETIME_UTC" +'%H%M') | |
| # Use GitHub context for PR number (works for pull_request events) | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| if [[ -z "$PR_NUM" ]]; then | |
| # Fallback: try to extract from merge commit message | |
| PR_NUM=$(git -C ../iiab log -1 --pretty=%B | grep -oP 'Merge pull request #\K[0-9]+' || echo "") | |
| fi | |
| PR_NUM="${PR_NUM%/*}" | |
| TAG_NAME="${{ github.ref_name }}" | |
| TAG_NAME="${TAG_NAME%/*}" | |
| if [[ "$TAG_NAME" =~ ^[0-9] ]]; then | |
| IMG_NAME="iiab-$VERSION-$OS-$EDITION-$TAG_NAME" | |
| elif [[ -n "$PR_NUM" ]]; then | |
| IMG_NAME="iiab-$VERSION-$OS-$EDITION-${COMMIT_DATE}-${COMMIT_TIME}-PR${PR_NUM}" | |
| else | |
| IMG_NAME="iiab-$VERSION-$OS-$EDITION-${COMMIT_DATE}-${COMMIT_TIME}-${HASH}" | |
| fi | |
| echo "IMG_NAME=$IMG_NAME" >> $GITHUB_ENV | |
| mkdir -p "$MOUNT_DIR/opt/iiab" | |
| cp -R --preserve=mode,timestamps,links ../iiab "$MOUNT_DIR/opt/iiab/" | |
| mkdir -p "$MOUNT_DIR/etc/iiab" | |
| cp --preserve=mode,timestamps ../iiab/vars/local_vars_unittest.yml "$MOUNT_DIR/etc/iiab/local_vars.yml" | |
| echo "rpi_image: True" >> "$MOUNT_DIR/etc/iiab/local_vars.yml" | |
| sed -i "s/^iiab_admin_user_install: True/iiab_admin_user_install: False/" "$MOUNT_DIR/etc/iiab/local_vars.yml" | |
| if ! command -v systemd-nspawn &> /dev/null; then | |
| apt-get update && apt-get install -y systemd-container | |
| fi | |
| systemctl is-active --quiet systemd-networkd || systemctl start systemd-networkd | |
| systemctl is-active --quiet systemd-resolved || systemctl start systemd-resolved | |
| # based on https://github.com/jvonau/mk-image/blob/main/mkarm-image.sh | |
| mount --bind /dev "${MOUNT_DIR}"/dev | |
| mount -t proc none "${MOUNT_DIR}"/proc | |
| mount -t sysfs none "${MOUNT_DIR}"/sys | |
| mount -t devpts none "${MOUNT_DIR}"/dev/pts | |
| mount -t tmpfs tmpfs "${MOUNT_DIR}"/tmp | |
| mount -t tmpfs tmpfs "${MOUNT_DIR}"/run | |
| mkdir -p "${MOUNT_DIR}"/run/lock | |
| sed 's/^ //' << 'EOF_CHROOT' > "$MOUNT_DIR/root/builder.sh" | |
| #!/bin/bash | |
| set -euo pipefail | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt update | |
| mkdir -p /etc/initramfs-tools/conf.d | |
| echo 'MODULES=most' > /etc/initramfs-tools/conf.d/nspawn.conf | |
| apt -y remove libcamera* | |
| apt -y full-upgrade | |
| apt install curl | |
| curl -fLo /usr/sbin/iiab https://raw.githubusercontent.com/iiab/iiab-factory/master/iiab | |
| chmod 0755 /usr/sbin/iiab | |
| echo "box" > /etc/hostname | |
| /usr/sbin/iiab --risky | |
| EOF_CHROOT | |
| chmod 0755 $MOUNT_DIR/root/builder.sh | |
| echo "Entering CHROOT" | |
| chroot $MOUNT_DIR /root/builder.sh | |
| echo "Leaving CHROOT" | |
| fuser -vk -m "${MOUNT_DIR}" || true | |
| umount "${MOUNT_DIR}"/run | |
| umount "${MOUNT_DIR}"/tmp | |
| umount "${MOUNT_DIR}"/dev/pts | |
| umount "${MOUNT_DIR}"/dev | |
| umount "${MOUNT_DIR}"/sys | |
| umount "${MOUNT_DIR}"/proc | |
| EOF | |
| - name: Validation | |
| working-directory: nspawn-loop | |
| run: | | |
| sudo -E bash << 'EOF' | |
| set -euo pipefail | |
| STATE_FILE=$(find . -maxdepth 1 -name "*.state" | head -n 1) | |
| source "$STATE_FILE" | |
| diff \ | |
| <(sort ../iiab/.github/workflows/tests/expected_state_unittest_image.yml) \ | |
| <(sort "$MOUNT_DIR/etc/iiab/iiab_state.yml") | |
| EOF | |
| - name: Shrink image | |
| working-directory: nspawn-loop | |
| run: | | |
| sudo -E bash << 'EOF' | |
| set -euo pipefail | |
| STATE_FILE=$(find . -maxdepth 1 -name "*.state" | head -n 1) | |
| source "$STATE_FILE" | |
| # Add image metadata | |
| echo "$IMG_NAME" > "$MOUNT_DIR/.iiab-image" | |
| echo "Build: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$MOUNT_DIR/.iiab-image" | |
| echo "" >> "$MOUNT_DIR/.iiab-image" | |
| git -C ../iiab rev-parse --abbrev-ref HEAD >> "$MOUNT_DIR/.iiab-image" | |
| git -C ../iiab remote -v >> "$MOUNT_DIR/.iiab-image" | |
| git -C ../iiab log -1 >> "$MOUNT_DIR/.iiab-image" | |
| # Remove image-specific configuration | |
| rm -f "$MOUNT_DIR/etc/initramfs-tools/conf.d/nspawn.conf" | |
| sed -i '/rpi_image: True/d' "$MOUNT_DIR/etc/iiab/local_vars.yml" | |
| echo uninitialized > "$MOUNT_DIR/etc/machine-id" | |
| rm -f "$MOUNT_DIR/etc/iiab/uuid" | |
| rm -f "$MOUNT_DIR/var/swap" | |
| rm -f "$MOUNT_DIR/root/builder.sh" | |
| rm -fr "$MOUNT_DIR/home/runner" | |
| ./shrink.sh "$STATE_FILE" 1800 | |
| mv "$IMG_FILE" "${IMG_NAME}.img" | |
| # Compress image with zstd | |
| zstd --long=27 -T0 -4 --stdout "${IMG_NAME}.img" > "${IMG_NAME}.img.zst" | |
| EOF | |
| - name: Generate rpi-imager manifest | |
| working-directory: nspawn-loop | |
| env: | |
| EDITION: unittest | |
| run: | | |
| cat << 'EOF' > gen_manifest.py | |
| import json | |
| import os | |
| import hashlib | |
| import datetime | |
| import sys | |
| img_name = os.environ.get('IMG_NAME') | |
| if not img_name: | |
| print("Error: IMG_NAME not set") | |
| sys.exit(1) | |
| zst_name = f"{img_name}.img.zst" | |
| if not os.path.exists(zst_name): | |
| print(f"Error: {zst_name} not found") | |
| sys.exit(1) | |
| # Get compressed file size (for download size) | |
| compressed_size = os.path.getsize(zst_name) | |
| print(f"Compressed size: {compressed_size} bytes") | |
| # Get uncompressed image size and hash (for extract verification) | |
| image_size = os.path.getsize(f"{img_name}.img") | |
| print(f"Image size: {image_size} bytes") | |
| print(f"Calculating SHA256 for {img_name}.img...") | |
| sha256_hash = hashlib.sha256() | |
| with open(f"{img_name}.img", "rb") as f: | |
| for byte_block in iter(lambda: f.read(4096), b""): | |
| sha256_hash.update(byte_block) | |
| extract_sha256 = sha256_hash.hexdigest() | |
| manifest_name = f"{img_name}.rpi-imager-manifest" | |
| manifest = { | |
| "imager": { | |
| "devices": [ | |
| { | |
| "name": "Raspberry Pi 5", | |
| "tags": ["pi5-64bit", "pi5-32bit"], | |
| "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_5.png", | |
| "description": "Raspberry Pi 5, 500 / 500+, and Compute Module 5", | |
| "matching_type": "exclusive", | |
| "capabilities": [] | |
| }, | |
| { | |
| "name": "Raspberry Pi 4", | |
| "tags": ["pi4-64bit", "pi4-32bit"], | |
| "default": False, | |
| "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": [] | |
| }, | |
| { | |
| "name": "Raspberry Pi 3", | |
| "tags": ["pi3-64bit", "pi3-32bit"], | |
| "default": False, | |
| "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": [] | |
| }, | |
| { | |
| "name": "Raspberry Pi Zero 2 W", | |
| "tags": ["pi3-64bit", "pi3-32bit"], | |
| "default": False, | |
| "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_Zero_2_W.png", | |
| "description": "Raspberry Pi Zero 2 W", | |
| "matching_type": "inclusive", | |
| "capabilities": [] | |
| }, | |
| { | |
| "name": "No filtering", | |
| "tags": [], | |
| "default": False, | |
| "description": "Show every possible image", | |
| "matching_type": "inclusive", | |
| "capabilities": [] | |
| } | |
| ] | |
| }, | |
| "os_list": [ | |
| { | |
| "name": f"Internet in a Box - {os.environ.get('EDITION', 'Custom')} (64-bit)", | |
| "description": f"IIAB {os.environ.get('IIAB_BASE_VERSION', '')} {os.environ.get('EDITION', '')} edition", | |
| "icon": "https://raw.githubusercontent.com/iiab/iiab-factory/master/box/rpi/iiab40.png", | |
| "url": f"file:///home/YOUR_USERNAME/Downloads/{zst_name}", | |
| "image_download_size": compressed_size, | |
| "extract_size": image_size, | |
| "extract_sha256": extract_sha256, | |
| "release_date": datetime.date.today().isoformat(), | |
| "init_format": "cloudinit-rpi", | |
| "devices": ["pi5-64bit", "pi4-64bit", "pi3-64bit"], | |
| "capabilities": ["rpi_connect"] | |
| } | |
| ] | |
| } | |
| with open(manifest_name, "w") as f: | |
| json.dump(manifest, f, indent=2) | |
| EOF | |
| python3 gen_manifest.py | |
| - name: Upload image artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| path: nspawn-loop/${{ env.IMG_NAME }}.img.zst | |
| retention-days: 10 | |
| archive: false | |
| - name: Upload manifest artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| path: nspawn-loop/${{ env.IMG_NAME }}.rpi-imager-manifest | |
| retention-days: 10 | |
| archive: false |