Skip to content

[code] add working role available by default #2

[code] add working role available by default

[code] add working role available by default #2

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