diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-10 04:33:48 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-10 21:04:20 +0000 |
| commit | 6f53d7763906f24d86e89da4da744421c52d591c (patch) | |
| tree | f37ff082e424b1e4aaa820c8e2db07b31146f091 /recipes-extended | |
| parent | a71e7b0499c51c74686e41e7810e2f202d851ce6 (diff) | |
| download | meta-virtualization-6f53d7763906f24d86e89da4da744421c52d591c.tar.gz | |
container-yocto-builder: add Yocto build container with systemd
Multi-layer OCI container image that can compile the Yocto Project.
Three layers: systemd-base, build-tools, yocto-extras. Features
CROPS-style dynamic user creation matching /workdir volume owner
UID/GID.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-extended')
3 files changed, 108 insertions, 0 deletions
diff --git a/recipes-extended/builder-container-config/builder-container-config_1.0.bb b/recipes-extended/builder-container-config/builder-container-config_1.0.bb new file mode 100644 index 00000000..6b023caa --- /dev/null +++ b/recipes-extended/builder-container-config/builder-container-config_1.0.bb | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | SUMMARY = "Entrypoint and user configuration for Yocto builder container" | ||
| 2 | DESCRIPTION = "CROPS-style entrypoint script that creates a builder user \ | ||
| 3 | matching the /workdir mount owner, then hands off to systemd." | ||
| 4 | LICENSE = "MIT" | ||
| 5 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" | ||
| 6 | |||
| 7 | SRC_URI = "file://builder-entry.sh" | ||
| 8 | |||
| 9 | S = "${UNPACKDIR}" | ||
| 10 | RDEPENDS:${PN} = "bash sudo shadow" | ||
| 11 | |||
| 12 | inherit allarch | ||
| 13 | |||
| 14 | do_install() { | ||
| 15 | # Entrypoint script (CROPS-style user creation -> exec /sbin/init) | ||
| 16 | install -d ${D}${bindir} | ||
| 17 | install -m 0755 ${UNPACKDIR}/builder-entry.sh ${D}${bindir}/ | ||
| 18 | |||
| 19 | # Create /workdir mount point | ||
| 20 | install -d ${D}/workdir | ||
| 21 | } | ||
| 22 | |||
| 23 | FILES:${PN} = "${bindir}/builder-entry.sh /workdir" | ||
diff --git a/recipes-extended/builder-container-config/files/builder-entry.sh b/recipes-extended/builder-container-config/files/builder-entry.sh new file mode 100644 index 00000000..a23d8dae --- /dev/null +++ b/recipes-extended/builder-container-config/files/builder-entry.sh | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | # builder-entry.sh - Container entrypoint for Yocto builder | ||
| 3 | # Creates a 'builder' user matching the /workdir owner's UID/GID, | ||
| 4 | # then exec's /sbin/init (systemd). | ||
| 5 | # | ||
| 6 | # Usage: | ||
| 7 | # docker run --privileged -v /home/user/yocto:/workdir builder-image | ||
| 8 | # docker run --privileged -e BUILDER_UID=1000 -e BUILDER_GID=1000 builder-image | ||
| 9 | |||
| 10 | WORKDIR="/workdir" | ||
| 11 | DEFAULT_UID=1000 | ||
| 12 | DEFAULT_GID=1000 | ||
| 13 | |||
| 14 | # Determine UID/GID | ||
| 15 | if [ -n "$BUILDER_UID" ] && [ -n "$BUILDER_GID" ]; then | ||
| 16 | TARGET_UID="$BUILDER_UID" | ||
| 17 | TARGET_GID="$BUILDER_GID" | ||
| 18 | elif [ -d "$WORKDIR" ] && [ "$(stat -c %u "$WORKDIR")" != "0" ]; then | ||
| 19 | TARGET_UID=$(stat -c %u "$WORKDIR") | ||
| 20 | TARGET_GID=$(stat -c %g "$WORKDIR") | ||
| 21 | else | ||
| 22 | TARGET_UID=$DEFAULT_UID | ||
| 23 | TARGET_GID=$DEFAULT_GID | ||
| 24 | fi | ||
| 25 | |||
| 26 | # Refuse UID/GID 0 (root) | ||
| 27 | [ "$TARGET_UID" = "0" ] && TARGET_UID=$DEFAULT_UID | ||
| 28 | [ "$TARGET_GID" = "0" ] && TARGET_GID=$DEFAULT_GID | ||
| 29 | |||
| 30 | # Create group and user if they don't exist | ||
| 31 | if ! getent group builder >/dev/null 2>&1; then | ||
| 32 | groupadd -g "$TARGET_GID" builder 2>/dev/null || groupadd builder | ||
| 33 | fi | ||
| 34 | if ! getent passwd builder >/dev/null 2>&1; then | ||
| 35 | useradd -m -u "$TARGET_UID" -g builder -s /bin/bash builder | ||
| 36 | fi | ||
| 37 | |||
| 38 | # Ensure /workdir is accessible | ||
| 39 | [ -d "$WORKDIR" ] && chown builder:builder "$WORKDIR" | ||
| 40 | |||
| 41 | # Grant passwordless sudo | ||
| 42 | if [ -d /etc/sudoers.d ]; then | ||
| 43 | echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder | ||
| 44 | chmod 0440 /etc/sudoers.d/builder | ||
| 45 | fi | ||
| 46 | |||
| 47 | # Hand off to systemd | ||
| 48 | exec /sbin/init "$@" | ||
diff --git a/recipes-extended/images/container-yocto-builder.bb b/recipes-extended/images/container-yocto-builder.bb new file mode 100644 index 00000000..0dba4c31 --- /dev/null +++ b/recipes-extended/images/container-yocto-builder.bb | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | SUMMARY = "Yocto Project builder container with systemd" | ||
| 2 | DESCRIPTION = "A self-hosting Yocto build container. Includes compiler \ | ||
| 3 | toolchain, Python 3, Git, and all tools needed to compile the Yocto \ | ||
| 4 | Project. Uses systemd init and supports CROPS-style dynamic user \ | ||
| 5 | creation for volume-mounted builds." | ||
| 6 | LICENSE = "MIT" | ||
| 7 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" | ||
| 8 | |||
| 9 | IMAGE_FSTYPES = "container oci" | ||
| 10 | inherit image | ||
| 11 | inherit image-oci | ||
| 12 | |||
| 13 | # Multi-layer OCI image | ||
| 14 | OCI_LAYER_MODE = "multi" | ||
| 15 | OCI_LAYERS = "\ | ||
| 16 | systemd-base:packages:packagegroup-yocto-builder-base \ | ||
| 17 | build-tools:packages:packagegroup-yocto-builder-toolchain \ | ||
| 18 | yocto-extras:packages:packagegroup-yocto-builder-extras \ | ||
| 19 | " | ||
| 20 | |||
| 21 | # Entrypoint: user setup script -> systemd | ||
| 22 | OCI_IMAGE_ENTRYPOINT = "/usr/bin/builder-entry.sh" | ||
| 23 | |||
| 24 | # OCI metadata | ||
| 25 | OCI_IMAGE_AUTHOR ?= "meta-virtualization" | ||
| 26 | OCI_IMAGE_TAG ?= "latest" | ||
| 27 | |||
| 28 | # All packages listed here to trigger builds (multi-layer requirement) | ||
| 29 | IMAGE_INSTALL = "packagegroup-yocto-builder" | ||
| 30 | |||
| 31 | # No kernel needed for container | ||
| 32 | IMAGE_CONTAINER_NO_DUMMY = "1" | ||
| 33 | |||
| 34 | # Minimize image | ||
| 35 | IMAGE_FEATURES = "" | ||
| 36 | IMAGE_LINGUAS = "" | ||
| 37 | NO_RECOMMENDATIONS = "1" | ||
