Skip to content

Commit 9b88933

Browse files
committed
Add enable_overlayfs option to set up Raspberry Pi OverlayFS root (#122)
raspi-config nonint do_overlayfs 0 fails under qemu-user emulation because uname -r returns the host kernel, so update-initramfs produces no initramfs. enable_overlayfs regenerates the initramfs against the image kernel after the user commands run and configures cmdline.txt and config.txt for overlay boot. Signed-off-by: Paul Guyot <[email protected]>
1 parent 546cee1 commit 9b88933

4 files changed

Lines changed: 155 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Test enable_overlayfs
2+
on:
3+
push:
4+
branches:
5+
- 'main'
6+
- 'releases/**'
7+
pull_request:
8+
workflow_dispatch:
9+
10+
jobs:
11+
test_enable_overlayfs:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- uses: ./ # pguyot/arm-runner-action@HEAD
17+
id: build_image
18+
with:
19+
base_image: raspios_lite:latest
20+
# optimize_image shrinks the rootfs, which is unnecessary for this test.
21+
optimize_image: 'no'
22+
enable_overlayfs: 'yes'
23+
commands: |
24+
# raspi-config nonint do_overlayfs 0 fails to generate a working
25+
# initramfs under qemu-user (uname -r returns the host kernel).
26+
# enable_overlayfs runs after these commands and regenerates the
27+
# initramfs against the image kernel; the test below verifies the
28+
# produced image really is bootable in overlay mode.
29+
sudo raspi-config nonint do_overlayfs 0
30+
31+
- name: Verify overlay configuration in produced image
32+
run: |
33+
set -euxo pipefail
34+
sudo apt-get update
35+
# unmkinitramfs (from initramfs-tools-core) handles the
36+
# early-microcode + main archive concatenation produced by
37+
# mkinitramfs, which a plain cpio/zstd/gunzip cannot.
38+
sudo apt-get install -y file initramfs-tools-core
39+
40+
IMAGE="${{ steps.build_image.outputs.image }}"
41+
LOOP=$(sudo losetup --find --show --partscan "$IMAGE")
42+
sudo partprobe -s "$LOOP" || true
43+
44+
TIMEOUT=60; i=0
45+
until sudo test -e "${LOOP}p1" || [ $i -ge $TIMEOUT ]; do sleep 1; i=$((i+1)); done
46+
sudo test -e "${LOOP}p1" || { echo "${LOOP}p1 did not appear after ${TIMEOUT}s" >&2; exit 1; }
47+
48+
BOOT_MNT=$(mktemp -d)
49+
sudo mount "${LOOP}p1" "$BOOT_MNT"
50+
51+
echo "::group::cmdline.txt"
52+
sudo cat "$BOOT_MNT/cmdline.txt"
53+
echo "::endgroup::"
54+
sudo grep -q "boot=overlay" "$BOOT_MNT/cmdline.txt"
55+
56+
echo "::group::config.txt initramfs lines"
57+
sudo grep -n "^initramfs" "$BOOT_MNT/config.txt" || true
58+
echo "::endgroup::"
59+
sudo grep -q "^initramfs initrd.img followkernel" "$BOOT_MNT/config.txt"
60+
61+
sudo test -s "$BOOT_MNT/initrd.img"
62+
echo "initrd.img size: $(sudo stat -c %s "$BOOT_MNT/initrd.img") bytes"
63+
64+
WORK=$(mktemp -d)
65+
sudo cp "$BOOT_MNT/initrd.img" "$WORK/initrd.img"
66+
cd "$WORK"
67+
sudo file initrd.img
68+
sudo unmkinitramfs initrd.img extracted
69+
# overlayroot ships init scripts under /scripts/*overlayroot*
70+
sudo find extracted -path '*overlayroot*' | tee /tmp/overlayroot_files
71+
test -s /tmp/overlayroot_files
72+
73+
cd /
74+
sudo umount "$BOOT_MNT"
75+
sudo losetup --detach "$LOOP"
76+
sudo rm -rf "$WORK" "$BOOT_MNT"

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,31 @@ environment.
322322
Note this parameter does not enable importing any contents written to
323323
`$GITHUB_ENV` ahead of running the commands. For that, use `import_github_env`.
324324

325+
#### `enable_overlayfs`
326+
327+
Enable Raspberry Pi OverlayFS (read-only root with tmpfs overlay) on the
328+
produced image. Default is `no`.
329+
330+
Calling `raspi-config nonint do_overlayfs 0` from the `commands` block does
331+
not work on its own under qemu-user emulation: raspi-config invokes
332+
`update-initramfs -c -k "$(uname -r)"`, but `uname -r` reports the host
333+
kernel rather than the image kernel, so no initramfs is produced. The
334+
resulting image has `boot=overlay` in `cmdline.txt` but no
335+
`/boot/initrd.img`, and will not boot in overlay mode.
336+
337+
When `enable_overlayfs` is set to `yes`, the action regenerates the
338+
initramfs against the image's actual kernel (detected from `/lib/modules`)
339+
after the `commands` step, places `/boot/initrd.img`, and ensures
340+
`cmdline.txt` and `config.txt` are configured for overlay boot. It is
341+
idempotent: it composes with `commands` that already invoke
342+
`raspi-config nonint do_overlayfs 0`.
343+
344+
The script installs the `overlayroot` package into the image if it is not
345+
already present, so this option works with Raspberry Pi OS and custom images
346+
that have `apt` available. See
347+
[overlayfs test](.github/workflows/test-overlayfs.yml) for a verified
348+
example.
349+
325350
### Outputs
326351

327352
#### `image`

action.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ inputs:
8888
description: 'Exports $GITHUB_ENV from the image environment to subsequent tasks'
8989
required: false
9090
default: 'no'
91+
enable_overlayfs:
92+
description: 'Enable Raspberry Pi OverlayFS root in the produced image (regenerates initramfs against the image kernel and updates cmdline.txt / config.txt)'
93+
required: false
94+
default: 'no'
9195
outputs:
9296
image:
9397
description: "Path to image"
@@ -296,6 +300,11 @@ runs:
296300
cat ${script_dir}/github_env.sh >> $GITHUB_ENV
297301
exit $rc
298302
shell: bash
303+
- name: Enable overlayfs
304+
if: ${{ !cancelled() && steps.runcmd.conclusion == 'success' && (inputs.enable_overlayfs == 'yes' || inputs.enable_overlayfs == 'true') }}
305+
run: |
306+
sudo bash ${GITHUB_ACTION_PATH}/enable_overlayfs.sh ${{ steps.mount_image.outputs.mount }}
307+
shell: bash
299308
- name: Copy artifacts within image
300309
if: ${{ always() && !cancelled() && (inputs.copy_artifacts_on_fail == 'yes' || steps.runcmd.conclusion == 'success') }}
301310
run: |

enable_overlayfs.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
# Enable Raspberry Pi OverlayFS root in a mounted image.
3+
#
4+
# raspi-config's do_overlayfs uses `update-initramfs -c -k "$(uname -r)"`,
5+
# but under qemu-user chroot `uname -r` returns the host kernel, so the
6+
# command fails silently and the image ships without /boot/initrd.img.
7+
# This script regenerates the initramfs against the image's actual kernel
8+
# and ensures cmdline.txt / config.txt are set up for overlay boot.
9+
set -euxo pipefail
10+
11+
mount=$1
12+
13+
kver=$(find "${mount}/lib/modules" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | sort -V | tail -n1 || true)
14+
if [ -z "${kver}" ]; then
15+
echo "enable_overlayfs: no kernel found under ${mount}/lib/modules/" >&2
16+
echo "enable_overlayfs requires an image with a kernel installed (e.g. Raspberry Pi OS)." >&2
17+
exit 1
18+
fi
19+
echo "enable_overlayfs: detected image kernel ${kver}"
20+
21+
chroot "${mount}" sh -c '
22+
set -e
23+
if ! command -v overlayroot-chroot >/dev/null 2>&1; then
24+
DEBIAN_FRONTEND=noninteractive apt-get update
25+
DEBIAN_FRONTEND=noninteractive apt-get install -y overlayroot
26+
fi
27+
'
28+
29+
# Use mkinitramfs directly rather than update-initramfs: the
30+
# z50-raspi-firmware post-update hook copies to /boot/firmware/, which does
31+
# not exist in this mount layout (the FAT firmware partition is mounted at
32+
# ${mount}/boot, not ${mount}/boot/firmware). We place the file ourselves.
33+
chroot "${mount}" mkinitramfs -o "/boot/initrd.img-${kver}" "${kver}"
34+
35+
cp -f "${mount}/boot/initrd.img-${kver}" "${mount}/boot/initrd.img"
36+
37+
cmdline="${mount}/boot/cmdline.txt"
38+
if [ -f "${cmdline}" ] && ! grep -q "boot=overlay" "${cmdline}"; then
39+
sed -i 's/[[:space:]]*$/ boot=overlay/' "${cmdline}"
40+
fi
41+
42+
config="${mount}/boot/config.txt"
43+
if [ -f "${config}" ] && ! grep -q "^initramfs initrd.img followkernel" "${config}"; then
44+
printf '\ninitramfs initrd.img followkernel\n' >> "${config}"
45+
fi

0 commit comments

Comments
 (0)