build-image.sh RawHistoryFolder
1#!/usr/bin/env bash
2# SPDX-License-Identifier: MIT
3#
4# Build the ermite-runner-ubuntu-26.04 image as an OCI image (container
5# isolation) and as a sparse ext4 base image (vm isolation). No root needed:
6# the OCI build uses docker or podman (rootless is fine), the ext4 image is
7# written by `mkfs.ext4 -d` from the exported rootfs tarball.
8#
9# scripts/build-image.sh [OUT_DIR]
10#
11# Environment:
12# ENGINE docker or podman (default: first found)
13# IMAGE_TAG OCI tag (default ermite-runner-ubuntu-26.04:latest, the
14# default of ERMITE_RUNNER_IMAGE in container mode)
15# EXT4_SLACK free space added to the base image in MiB (default 512)
16#
17# Output: OUT_DIR/base.ext4 (read-only shared base disk; its UUID and size are
18# stable per build) and the OCI image IMAGE_TAG in the engine's store.
19set -euo pipefail
20
21root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
22out=${1:-$root/tmp/image}
23tag=${IMAGE_TAG:-ermite-runner-ubuntu-26.04:latest}
24slack=${EXT4_SLACK:-512}
25engine=${ENGINE:-}
26
27if [ -z "$engine" ]; then
28 for e in docker podman; do
29 if command -v "$e" >/dev/null 2>&1; then engine=$e; break; fi
30 done
31fi
32[ -n "$engine" ] || { echo "need docker or podman" >&2; exit 1; }
33command -v mkfs.ext4 >/dev/null || { echo "need mkfs.ext4 (e2fsprogs >= 1.47.1 for tarball input)" >&2; exit 1; }
34
35# mkfs.ext4 -d with a tarball needs e2fsprogs 1.47.1; older releases only take a
36# directory. mke2fs prints its version on stderr and may exit non-zero on -V.
37ver=$(mkfs.ext4 -V 2>&1 | sed -n 's/^mke2fs \([0-9]*\.[0-9]*\.[0-9]*\).*/\1/p' | head -n1)
38[ -n "$ver" ] || ver=$(mkfs.ext4 -V 2>&1 | sed -n 's/^mke2fs \([0-9]*\.[0-9]*\).*/\1.0/p' | head -n1)
39if [ -z "$ver" ] || [ "$(printf '%s\n1.47.1\n' "$ver" | sort -V | head -n1)" != "1.47.1" ]; then
40 echo "mkfs.ext4 is e2fsprogs ${ver:-of unknown version}, need >= 1.47.1 (mkfs.ext4 -d with a tarball)" >&2
41 exit 1
42fi
43
44mkdir -p "$out"
45out=$(cd "$out" && pwd)
46"$engine" build -t "$tag" "$root/images/ubuntu-26.04"
47
48cid=$("$engine" create "$tag")
49tmp=$(mktemp -d)
50cleanup() { "$engine" rm -f "$cid" >/dev/null 2>&1 || true; rm -rf "$tmp"; }
51trap cleanup EXIT
52
53"$engine" export "$cid" > "$tmp/rootfs.tar"
54bytes=$(stat -c %s "$tmp/rootfs.tar")
55# ext4 metadata overhead is about 5 %; the slack leaves room for the guest's
56# own writes into the (read-only mounted) base only when someone remounts it.
57mib=$(( bytes / 1024 / 1024 * 110 / 100 + slack ))
58
59img="$out/base.ext4.part"
60rm -f "$img"
61truncate -s "${mib}M" "$img"
62# No journal: the base is mounted read-only and shared between VMs.
63mkfs.ext4 -q -F -L ermite-base -O ^has_journal -E lazy_itable_init=1,nodiscard \
64 -d "$tmp/rootfs.tar" "$img"
65mv "$img" "$out/base.ext4"
66echo "base image: $out/base.ext4 (${mib} MiB sparse), OCI image: $tag"