Skip to content

[kolibri] fix shell indenting #7

[kolibri] fix shell indenting

[kolibri] fix shell indenting #7

name: 'IIAB image on RPiOS Lite Latest'
# cancel-in-progress: false
# concurrency:
# group: ${{ github.ref }}-${{ github.workflow }}
on: [push, pull_request, workflow_dispatch]
jobs:
build:
runs-on: ubuntu-24.04-arm
# environment:
# name: deploy
strategy:
# fail-fast: true
# max-parallel: 1
matrix:
include:
# - edition: medical
# config: vars/local_vars_none.yml
# # Maybe only possible self-hosted
# # https://github.com/marketplace/actions/maximize-build-disk-space-only-remove-unwanted-software#how-it-works
# working_image_size: 75000
# preset_id: en-medical
- edition: large
config: vars/local_vars_large.yml
working_image_size: 20000
# - edition: medium
# config: vars/local_vars_medium.yml
# working_image_size: 15000
# - edition: small
# config: vars/local_vars_small.yml
# working_image_size: 15000
- edition: tiny
config: vars/local_vars_tiny.yml
working_image_size: 7450
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 ${{ matrix.working_image_size || 10000 }}
- 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
DATE=$(date +'%y%m%d')
OS="raspios"
EDITION="${{ matrix.edition }}"
HASH=$(git -C ../iiab rev-parse --short HEAD)
IMG_NAME="iiab-$VERSION-$DATE-$OS-$EDITION-$HASH.img"
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/${{ matrix.config }} "$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 expect &>/dev/null; then
apt-get update && apt-get install -y expect
fi
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
# Explicitly allow forwarding for systemd-nspawn virtual interfaces
sysctl -w net.ipv4.ip_forward=1
EXT_IF=$(ip route | grep default | awk '{print $5}' | head -n1)
iptables -t nat -A POSTROUTING -o "$EXT_IF" -j MASQUERADE
iptables -A FORWARD -i ve-+ -o "$EXT_IF" -j ACCEPT
iptables -A FORWARD -i "$EXT_IF" -o ve-+ -m state --state RELATED,ESTABLISHED -j ACCEPT
# Manually setting DNS can help avoid host loopback/127.0.0.53 issues
# echo "nameserver 1.1.1.1" >> "$MOUNT_DIR/etc/resolv.conf"
sed 's/^ //' << 'EOF_PRESET' > "$MOUNT_DIR/root/install_preset.sh"
#!/bin/bash
set -euo pipefail
cd /opt/admin/cmdsrv
scripts/get_kiwix_catalog
scripts/get_oer2go_catalog
if [ -n "${{ matrix.preset_id }}" ]; then
iiab-cmdsrv-ctl 'INST-PRESETS {"preset_id":"${{ matrix.preset_id }}"}'
fi
rm -f /root/install_preset.sh
EOF_PRESET
chmod +x "$MOUNT_DIR/root/install_preset.sh"
systemd-firstboot --root="$MOUNT_DIR" --delete-root-password --force
export MOUNT_DIR
sed 's/^ //' << 'EXPECT_EOF' | expect
set timeout 7200
spawn systemd-nspawn -q --network-veth --resolv-conf=off -D $env(MOUNT_DIR) -M box --boot
expect "login: " { send "root\r" }
expect -re {#\s?$} { send "apt update\r" }
expect -re {#\s?$} { send "mkdir -p /etc/initramfs-tools/conf.d && echo 'MODULES=most' > /etc/initramfs-tools/conf.d/nspawn.conf\r" }
expect -re {#\s?$} { send "DEBIAN_FRONTEND=noninteractive apt upgrade -y\r" }
expect -re {#\s?$} { send "curl -fLo /usr/sbin/iiab https://raw.githubusercontent.com/iiab/iiab-factory/master/iiab\r" }
expect -re {#\s?$} { send "chmod 0755 /usr/sbin/iiab\r" }
expect -re {#\s?$} { send "/usr/sbin/iiab --risky\r" }
expect {
timeout { puts "\nTimed out waiting for final confirmation prompt"; exit 1 }
"photographed" { send "\r" }
}
# forced reboot
expect "login: " { send "root\r" }
expect -re {#\s?$} { send "/root/install_preset.sh\r" }
expect -re {#\s?$} { send "usermod --lock --expiredate=1 root\r" }
# legacy whiptail TUI OOBE, replaced by cloud-init / rpi-imager
# expect -re {#\s?$} { send "systemctl disable userconfig.service\r" }
expect -re {#\s?$} { send "shutdown now\r" }
expect eof
EXPECT_EOF
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_${{ matrix.edition }}_image.yml) \
<(sort "$MOUNT_DIR/etc/iiab/iiab_state.yml")
EOF
- name: Shrink image
if: ${{ matrix.edition == 'tiny' }}
working-directory: nspawn-loop
env:
GH_TOKEN: ${{ github.token }}
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"
gh pr list --repo ${{ github.repository }} --commit $(git -C ../iiab rev-parse HEAD) --json number,url --jq '.[0] | "PR #\(.number): \(.url)"' >> "$MOUNT_DIR/.iiab-image" 2>/dev/null || true
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"
touch "$MOUNT_DIR/.resize-rootfs"
./shrink.sh "$STATE_FILE" 1800
mv "$IMG_FILE" "$IMG_NAME"
EOF
- name: Generate rpi-imager manifest
if: ${{ matrix.edition == 'tiny' }}
working-directory: nspawn-loop
env:
EDITION: ${{ matrix.edition }}
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)
if not os.path.exists(img_name):
print(f"Error: {img_name} not found")
sys.exit(1)
file_size = os.path.getsize(img_name)
print(f"Calculating SHA256 for {img_name} ({file_size} bytes)...")
sha256_hash = hashlib.sha256()
with open(img_name, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
extract_sha256 = sha256_hash.hexdigest()
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://downloads.raspberrypi.com/raspios_arm64/Raspberry_Pi_OS_(64-bit).png",
"url": f"file:///home/YOUR_USERNAME/Downloads/{img_name}.zip",
"image_download_size": file_size,
"extract_size": file_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("iiab.rpi-imager-manifest", "w") as f:
json.dump(manifest, f, indent=2)
EOF
python3 gen_manifest.py
- name: Upload iiab.rpi-imager-manifest
if: ${{ matrix.edition == 'tiny' }}
uses: actions/upload-artifact@v6
with:
name: rpi-imager-manifest
path: nspawn-loop/iiab.rpi-imager-manifest
if-no-files-found: error
retention-days: 3
compression-level: 9
- name: Upload image
if: ${{ matrix.edition == 'tiny' }}
uses: actions/upload-artifact@v6
with:
name: ${{ env.IMG_NAME }}
path: nspawn-loop/${{ env.IMG_NAME }}
if-no-files-found: error
retention-days: 3
compression-level: 9
# - name: Compress image
# working-directory: nspawn-loop
# run: xz -v -9 -T0 --stdout "$IMG_NAME" > "$IMG_NAME.xz"