diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-06 20:45:13 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-09 03:32:52 +0000 |
| commit | 8f6f746bb6075157fa175a2081e6b3dcd833a8a2 (patch) | |
| tree | cc0ed3530abeda331b74af3325e85542edabd703 | |
| parent | 18074e0efe255a43ab9155171d08aa6e0d736b5f (diff) | |
| download | meta-virtualization-8f6f746bb6075157fa175a2081e6b3dcd833a8a2.tar.gz | |
vcontainer: add vpdmn Podman support
Add vpdmn - Podman CLI wrapper for cross-architecture container operations:
Scripts:
- vpdmn.sh: Podman CLI entry point (vpdmn-x86_64, vpdmn-aarch64)
- vpdmn-init.sh: Podman init script for QEMU guest
Recipes:
- vpdmn-native: Installs vpdmn CLI wrappers
- vpdmn-rootfs-image: Builds Podman rootfs with crun, netavark, skopeo
- vpdmn-initramfs-create: Creates bootable initramfs blob
The vpdmn CLI provides Podman-compatible commands executed inside a
QEMU-emulated environment. Unlike vdkr, Podman is daemonless which
simplifies the guest init process.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
7 files changed, 610 insertions, 0 deletions
diff --git a/recipes-containers/vcontainer/files/blobs/vpdmn/aarch64/README b/recipes-containers/vcontainer/files/blobs/vpdmn/aarch64/README new file mode 100644 index 00000000..a4197779 --- /dev/null +++ b/recipes-containers/vcontainer/files/blobs/vpdmn/aarch64/README | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | vpdmn aarch64 Blobs | ||
| 2 | ==================== | ||
| 3 | |||
| 4 | This directory should contain the boot blobs for vpdmn aarch64: | ||
| 5 | |||
| 6 | Required files: | ||
| 7 | - Image : Linux kernel for aarch64 | ||
| 8 | - initramfs.cpio.gz : Tiny initramfs for switch_root | ||
| 9 | - rootfs.img : Ext4 root filesystem with Podman tools | ||
| 10 | |||
| 11 | Build instructions: | ||
| 12 | 1. Set MACHINE for aarch64: | ||
| 13 | MACHINE=qemuarm64 | ||
| 14 | |||
| 15 | 2. Build the blobs: | ||
| 16 | bitbake vpdmn-initramfs-create | ||
| 17 | |||
| 18 | 3. Copy from deploy directory: | ||
| 19 | cp tmp/deploy/images/qemuarm64/vpdmn/aarch64/Image . | ||
| 20 | cp tmp/deploy/images/qemuarm64/vpdmn/aarch64/initramfs.cpio.gz . | ||
| 21 | cp tmp/deploy/images/qemuarm64/vpdmn/aarch64/rootfs.img . | ||
| 22 | |||
| 23 | Once these files are present, vpdmn-native will automatically include them. | ||
| 24 | |||
| 25 | Alternatively, set VPDMN_USE_DEPLOY = "1" in local.conf to use blobs | ||
| 26 | directly from DEPLOY_DIR without copying to the layer. | ||
diff --git a/recipes-containers/vcontainer/files/blobs/vpdmn/x86_64/README b/recipes-containers/vcontainer/files/blobs/vpdmn/x86_64/README new file mode 100644 index 00000000..30ddb445 --- /dev/null +++ b/recipes-containers/vcontainer/files/blobs/vpdmn/x86_64/README | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | vpdmn x86_64 Blobs | ||
| 2 | =================== | ||
| 3 | |||
| 4 | This directory should contain the boot blobs for vpdmn x86_64: | ||
| 5 | |||
| 6 | Required files: | ||
| 7 | - bzImage : Linux kernel for x86_64 | ||
| 8 | - initramfs.cpio.gz : Tiny initramfs for switch_root | ||
| 9 | - rootfs.img : Ext4 root filesystem with Podman tools | ||
| 10 | |||
| 11 | Build instructions: | ||
| 12 | 1. Set MACHINE for x86_64: | ||
| 13 | MACHINE=qemux86-64 | ||
| 14 | |||
| 15 | 2. Build the blobs: | ||
| 16 | bitbake vpdmn-initramfs-create | ||
| 17 | |||
| 18 | 3. Copy from deploy directory: | ||
| 19 | cp tmp/deploy/images/qemux86-64/vpdmn/x86_64/bzImage . | ||
| 20 | cp tmp/deploy/images/qemux86-64/vpdmn/x86_64/initramfs.cpio.gz . | ||
| 21 | cp tmp/deploy/images/qemux86-64/vpdmn/x86_64/rootfs.img . | ||
| 22 | |||
| 23 | Once these files are present, vpdmn-native will automatically include them. | ||
| 24 | |||
| 25 | Alternatively, set VPDMN_USE_DEPLOY = "1" in local.conf to use blobs | ||
| 26 | directly from DEPLOY_DIR without copying to the layer. | ||
diff --git a/recipes-containers/vcontainer/files/vpdmn-init.sh b/recipes-containers/vcontainer/files/vpdmn-init.sh new file mode 100755 index 00000000..52aa9129 --- /dev/null +++ b/recipes-containers/vcontainer/files/vpdmn-init.sh | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 3 | # | ||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | # | ||
| 6 | # vpdmn-init.sh | ||
| 7 | # Init script for vpdmn: execute arbitrary podman commands in QEMU | ||
| 8 | # | ||
| 9 | # This script runs on a real ext4 filesystem after switch_root from initramfs. | ||
| 10 | # The preinit script mounted /dev/vda (rootfs.img) and did switch_root to us. | ||
| 11 | # | ||
| 12 | # Drive layout (rootfs.img is always /dev/vda, mounted as /): | ||
| 13 | # /dev/vda = rootfs.img (this script runs from here, mounted as /) | ||
| 14 | # /dev/vdb = input disk (optional, OCI/tar/dir data) | ||
| 15 | # /dev/vdc = state disk (optional, persistent Podman storage) | ||
| 16 | # | ||
| 17 | # Kernel parameters: | ||
| 18 | # podman_cmd=<base64> Base64-encoded podman command + args | ||
| 19 | # podman_input=<type> Input type: none, oci, tar, dir (default: none) | ||
| 20 | # podman_output=<type> Output type: text, tar, storage (default: text) | ||
| 21 | # podman_state=<type> State type: none, disk (default: none) | ||
| 22 | # podman_network=1 Enable networking (configure eth0, DNS) | ||
| 23 | # | ||
| 24 | # Version: 1.1.0 | ||
| 25 | # | ||
| 26 | # Note: Podman is daemonless - no containerd/dockerd required! | ||
| 27 | |||
| 28 | # Set runtime-specific parameters before sourcing common code | ||
| 29 | VCONTAINER_RUNTIME_NAME="vpdmn" | ||
| 30 | VCONTAINER_RUNTIME_CMD="podman" | ||
| 31 | VCONTAINER_RUNTIME_PREFIX="podman" | ||
| 32 | VCONTAINER_STATE_DIR="/var/lib/containers/storage" | ||
| 33 | VCONTAINER_SHARE_NAME="vpdmn_share" | ||
| 34 | VCONTAINER_VERSION="1.1.0" | ||
| 35 | |||
| 36 | # Source common init functions | ||
| 37 | # When installed as /init, common file is at /vcontainer-init-common.sh | ||
| 38 | . /vcontainer-init-common.sh | ||
| 39 | |||
| 40 | # ============================================================================ | ||
| 41 | # Podman-Specific Functions | ||
| 42 | # ============================================================================ | ||
| 43 | |||
| 44 | setup_podman_environment() { | ||
| 45 | # Podman needs XDG_RUNTIME_DIR | ||
| 46 | export XDG_RUNTIME_DIR="/run/user/0" | ||
| 47 | mkdir -p "$XDG_RUNTIME_DIR" | ||
| 48 | chmod 700 "$XDG_RUNTIME_DIR" | ||
| 49 | } | ||
| 50 | |||
| 51 | setup_podman_mounts() { | ||
| 52 | # Podman needs /dev/shm | ||
| 53 | mkdir -p /dev/shm | ||
| 54 | mount -t tmpfs tmpfs /dev/shm | ||
| 55 | |||
| 56 | # Mount /var/volatile for Yocto's volatile symlinks (/var/tmp -> volatile/tmp, etc.) | ||
| 57 | mkdir -p /var/volatile | ||
| 58 | mount -t tmpfs tmpfs /var/volatile | ||
| 59 | mkdir -p /var/volatile/tmp /var/volatile/log /var/volatile/run /var/volatile/cache | ||
| 60 | |||
| 61 | # Also mount /var/cache directly (not a symlink) | ||
| 62 | mount -t tmpfs tmpfs /var/cache | ||
| 63 | } | ||
| 64 | |||
| 65 | setup_podman_storage() { | ||
| 66 | mkdir -p /run/lock | ||
| 67 | |||
| 68 | # /var/lib/containers exists in rootfs.img (read-only), mount tmpfs over it | ||
| 69 | mount -t tmpfs tmpfs /var/lib/containers | ||
| 70 | mkdir -p /var/lib/containers/storage | ||
| 71 | |||
| 72 | # Handle Podman storage | ||
| 73 | if [ -n "$STATE_DISK" ] && [ -b "$STATE_DISK" ]; then | ||
| 74 | log "Mounting state disk $STATE_DISK as /var/lib/containers/storage..." | ||
| 75 | if mount -t ext4 "$STATE_DISK" /var/lib/containers/storage 2>&1; then | ||
| 76 | log "SUCCESS: Mounted $STATE_DISK as Podman storage" | ||
| 77 | log "Podman storage contents:" | ||
| 78 | [ "$QUIET_BOOT" = "0" ] && ls -la /var/lib/containers/storage/ 2>/dev/null || log "(empty)" | ||
| 79 | else | ||
| 80 | log "WARNING: Failed to mount state disk, using tmpfs fallback" | ||
| 81 | RUNTIME_STATE="none" | ||
| 82 | fi | ||
| 83 | else | ||
| 84 | log "Using tmpfs for Podman storage (ephemeral)..." | ||
| 85 | fi | ||
| 86 | } | ||
| 87 | |||
| 88 | verify_podman() { | ||
| 89 | # Podman is daemonless - just verify it's available | ||
| 90 | if [ -x "/usr/bin/podman" ]; then | ||
| 91 | log "Podman available: $(podman --version 2>/dev/null || echo 'version unknown')" | ||
| 92 | else | ||
| 93 | echo "===ERROR===" | ||
| 94 | echo "Podman not found at /usr/bin/podman" | ||
| 95 | sleep 2 | ||
| 96 | reboot -f | ||
| 97 | fi | ||
| 98 | } | ||
| 99 | |||
| 100 | # Podman is daemonless - nothing to stop | ||
| 101 | stop_runtime_daemons() { | ||
| 102 | : | ||
| 103 | } | ||
| 104 | |||
| 105 | handle_storage_output() { | ||
| 106 | # Export entire podman storage | ||
| 107 | # Tar from inside /var/lib/containers/storage so paths are vfs-images/... directly | ||
| 108 | echo "Packaging Podman storage..." | ||
| 109 | if ! cd /var/lib/containers/storage; then | ||
| 110 | echo "===ERROR===" | ||
| 111 | echo "Failed to cd to /var/lib/containers/storage" | ||
| 112 | echo "Contents of /var/lib/containers:" | ||
| 113 | ls -la /var/lib/containers/ 2>&1 || echo "(not found)" | ||
| 114 | poweroff -f | ||
| 115 | exit 1 | ||
| 116 | fi | ||
| 117 | tar -cf /tmp/storage.tar . | ||
| 118 | |||
| 119 | STORAGE_SIZE=$(stat -c%s /tmp/storage.tar 2>/dev/null || echo "0") | ||
| 120 | echo "Storage size: $STORAGE_SIZE bytes" | ||
| 121 | |||
| 122 | if [ "$STORAGE_SIZE" -gt 1000 ]; then | ||
| 123 | dmesg -n 1 | ||
| 124 | echo "===STORAGE_START===" | ||
| 125 | base64 /tmp/storage.tar | ||
| 126 | echo "===STORAGE_END===" | ||
| 127 | echo "===EXIT_CODE=$EXEC_EXIT_CODE===" | ||
| 128 | else | ||
| 129 | echo "===ERROR===" | ||
| 130 | echo "Storage too small" | ||
| 131 | fi | ||
| 132 | } | ||
| 133 | |||
| 134 | # ============================================================================ | ||
| 135 | # Main | ||
| 136 | # ============================================================================ | ||
| 137 | |||
| 138 | # Initialize base environment | ||
| 139 | setup_base_environment | ||
| 140 | setup_podman_environment | ||
| 141 | mount_base_filesystems | ||
| 142 | |||
| 143 | # Check for quiet boot mode | ||
| 144 | check_quiet_boot | ||
| 145 | |||
| 146 | log "=== vpdmn Init ===" | ||
| 147 | log "Version: $VCONTAINER_VERSION" | ||
| 148 | |||
| 149 | # Mount tmpfs directories and Podman-specific mounts | ||
| 150 | mount_tmpfs_dirs | ||
| 151 | setup_podman_mounts | ||
| 152 | setup_cgroups | ||
| 153 | |||
| 154 | # Parse kernel command line | ||
| 155 | parse_cmdline | ||
| 156 | |||
| 157 | # Detect and configure disks | ||
| 158 | detect_disks | ||
| 159 | |||
| 160 | # Set up Podman storage (Podman-specific) | ||
| 161 | setup_podman_storage | ||
| 162 | |||
| 163 | # Mount input disk | ||
| 164 | mount_input_disk | ||
| 165 | |||
| 166 | # Configure networking | ||
| 167 | configure_networking | ||
| 168 | |||
| 169 | # Verify podman is available (no daemon to start) | ||
| 170 | verify_podman | ||
| 171 | |||
| 172 | # Handle daemon mode or single command execution | ||
| 173 | if [ "$RUNTIME_DAEMON" = "1" ]; then | ||
| 174 | run_daemon_mode | ||
| 175 | else | ||
| 176 | prepare_input_path | ||
| 177 | execute_command | ||
| 178 | fi | ||
| 179 | |||
| 180 | # Graceful shutdown | ||
| 181 | graceful_shutdown | ||
diff --git a/recipes-containers/vcontainer/files/vpdmn.sh b/recipes-containers/vcontainer/files/vpdmn.sh new file mode 100755 index 00000000..30775d35 --- /dev/null +++ b/recipes-containers/vcontainer/files/vpdmn.sh | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 3 | # | ||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | # | ||
| 6 | # vpdmn: Podman-like interface for cross-architecture container operations | ||
| 7 | # | ||
| 8 | # This provides a familiar podman-like CLI that executes commands inside | ||
| 9 | # a QEMU-emulated environment with the target architecture's Podman. | ||
| 10 | # | ||
| 11 | # Command naming convention: | ||
| 12 | # - Commands matching Podman's syntax/semantics use Podman's name (import, load, save, etc.) | ||
| 13 | # - Extended commands with non-Podman behavior use 'v' prefix (vimport) | ||
| 14 | |||
| 15 | # Set runtime-specific parameters before sourcing common code | ||
| 16 | VCONTAINER_RUNTIME_NAME="vpdmn" | ||
| 17 | VCONTAINER_RUNTIME_CMD="podman" | ||
| 18 | VCONTAINER_RUNTIME_PREFIX="VPDMN" | ||
| 19 | VCONTAINER_IMPORT_TARGET="containers-storage:" | ||
| 20 | VCONTAINER_STATE_FILE="podman-state.img" | ||
| 21 | VCONTAINER_OTHER_PREFIX="VDKR" | ||
| 22 | VCONTAINER_VERSION="1.2.0" | ||
| 23 | |||
| 24 | # Source common implementation | ||
| 25 | source "$(dirname "${BASH_SOURCE[0]}")/vcontainer-common.sh" "$@" | ||
diff --git a/recipes-containers/vcontainer/vpdmn-initramfs-create_1.0.bb b/recipes-containers/vcontainer/vpdmn-initramfs-create_1.0.bb new file mode 100644 index 00000000..2c500b30 --- /dev/null +++ b/recipes-containers/vcontainer/vpdmn-initramfs-create_1.0.bb | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 2 | # | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # vpdmn-initramfs-create_1.0.bb | ||
| 6 | # =========================================================================== | ||
| 7 | # Builds QEMU boot blobs for vpdmn (Podman CLI) | ||
| 8 | # =========================================================================== | ||
| 9 | # | ||
| 10 | # This recipe packages the boot blobs for vpdmn: | ||
| 11 | # - A tiny initramfs with just busybox for switch_root | ||
| 12 | # - The rootfs.img squashfs image with Podman (built via multiconfig) | ||
| 13 | # - The kernel | ||
| 14 | # | ||
| 15 | # Boot flow: | ||
| 16 | # QEMU boots kernel + tiny initramfs | ||
| 17 | # -> preinit mounts rootfs.img from /dev/vda | ||
| 18 | # -> switch_root into rootfs.img | ||
| 19 | # -> vpdmn-init.sh runs with a real root filesystem | ||
| 20 | # -> Podman executes container commands | ||
| 21 | # | ||
| 22 | # =========================================================================== | ||
| 23 | # BUILD INSTRUCTIONS | ||
| 24 | # =========================================================================== | ||
| 25 | # | ||
| 26 | # For aarch64 (multiconfig dependency is automatic): | ||
| 27 | # MACHINE=qemuarm64 bitbake vpdmn-initramfs-create | ||
| 28 | # | ||
| 29 | # For x86_64: | ||
| 30 | # MACHINE=qemux86-64 bitbake vpdmn-initramfs-create | ||
| 31 | # | ||
| 32 | # Blobs are deployed to: tmp-vruntime-*/deploy/images/${MACHINE}/vpdmn/ | ||
| 33 | # | ||
| 34 | # To build the complete standalone tarball (recommended): | ||
| 35 | # MACHINE=qemux86-64 bitbake vcontainer-native -c create_tarball | ||
| 36 | # | ||
| 37 | # =========================================================================== | ||
| 38 | |||
| 39 | SUMMARY = "Build QEMU blobs for vpdmn" | ||
| 40 | DESCRIPTION = "Packages a tiny initramfs for switch_root and bundles the \ | ||
| 41 | rootfs.img with Podman from multiconfig build for vpdmn." | ||
| 42 | |||
| 43 | # Set the runtime before including shared code | ||
| 44 | VCONTAINER_RUNTIME = "vpdmn" | ||
| 45 | |||
| 46 | require vcontainer-initramfs-create.inc | ||
diff --git a/recipes-containers/vcontainer/vpdmn-native_1.0.bb b/recipes-containers/vcontainer/vpdmn-native_1.0.bb new file mode 100644 index 00000000..7aaa8b28 --- /dev/null +++ b/recipes-containers/vcontainer/vpdmn-native_1.0.bb | |||
| @@ -0,0 +1,177 @@ | |||
| 1 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 2 | # | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # vpdmn-native_1.0.bb | ||
| 6 | # =========================================================================== | ||
| 7 | # Emulated Podman for cross-architecture container operations | ||
| 8 | # =========================================================================== | ||
| 9 | # | ||
| 10 | # vpdmn provides a Podman-like CLI that executes arbitrary podman commands | ||
| 11 | # inside a QEMU-emulated environment with the target architecture's Podman. | ||
| 12 | # Commands like "podman load", "podman images", etc. are passed through to | ||
| 13 | # Podman running inside QEMU and results streamed back. | ||
| 14 | # | ||
| 15 | # vpdmn shares the vrunner.sh runner with vdkr (it's runtime-agnostic). | ||
| 16 | # | ||
| 17 | # USAGE: | ||
| 18 | # vpdmn images # Uses detected default arch | ||
| 19 | # vpdmn -a aarch64 images # Explicit arch | ||
| 20 | # vpdmn-aarch64 images # Symlink (backwards compatible) | ||
| 21 | # vpdmn-x86_64 load -i myimage.tar | ||
| 22 | # | ||
| 23 | # Architecture detection (in priority order): | ||
| 24 | # 1. --arch / -a flag | ||
| 25 | # 2. Executable name (vpdmn-aarch64, vpdmn-x86_64) | ||
| 26 | # 3. VPDMN_ARCH environment variable | ||
| 27 | # 4. Config file: ~/.config/vpdmn/arch | ||
| 28 | # 5. Host architecture (uname -m) | ||
| 29 | # | ||
| 30 | # DEPENDENCIES: | ||
| 31 | # - Kernel/initramfs blobs from vpdmn-initramfs-create | ||
| 32 | # - QEMU system emulator (qemu-system-native) | ||
| 33 | # - vrunner.sh (shared runner from vdkr-native) | ||
| 34 | # | ||
| 35 | # =========================================================================== | ||
| 36 | |||
| 37 | SUMMARY = "Emulated Podman for cross-architecture container operations" | ||
| 38 | DESCRIPTION = "Provides vpdmn CLI that executes podman commands inside \ | ||
| 39 | QEMU-emulated environment. Useful for building/manipulating \ | ||
| 40 | containers for target architectures on a different host." | ||
| 41 | HOMEPAGE = "https://github.com/anthropics/meta-virtualization" | ||
| 42 | LICENSE = "MIT" | ||
| 43 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" | ||
| 44 | |||
| 45 | inherit native | ||
| 46 | |||
| 47 | # Dependencies - we use vdkr's runner script | ||
| 48 | DEPENDS = "qemu-system-native coreutils-native socat-native vdkr-native" | ||
| 49 | |||
| 50 | SRC_URI = "\ | ||
| 51 | file://vpdmn.sh \ | ||
| 52 | " | ||
| 53 | |||
| 54 | # Pre-built blobs are optional - they're checked into the layer after being | ||
| 55 | # built by vpdmn-initramfs-create. If not present, vpdmn will still build | ||
| 56 | # but will require --blob-dir at runtime. | ||
| 57 | # | ||
| 58 | # To build blobs: | ||
| 59 | # MACHINE=qemuarm64 bitbake vpdmn-initramfs-create | ||
| 60 | # MACHINE=qemux86-64 bitbake vpdmn-initramfs-create | ||
| 61 | # Then copy from tmp/deploy/images/<machine>/vpdmn-initramfs/ to files/blobs/vpdmn/<arch>/ | ||
| 62 | |||
| 63 | FILESEXTRAPATHS:prepend := "${THISDIR}/files:" | ||
| 64 | |||
| 65 | # Layer directory containing optional blobs | ||
| 66 | VPDMN_LAYER_BLOBS = "${THISDIR}/files/blobs/vpdmn" | ||
| 67 | |||
| 68 | # Deploy directories (used when VPDMN_USE_DEPLOY = "1") | ||
| 69 | VPDMN_DEPLOY_AARCH64 = "${DEPLOY_DIR}/images/qemuarm64/vpdmn-initramfs" | ||
| 70 | VPDMN_DEPLOY_X86_64 = "${DEPLOY_DIR}/images/qemux86-64/vpdmn-initramfs" | ||
| 71 | |||
| 72 | # Set to "1" in local.conf to prefer DEPLOY_DIR blobs over layer | ||
| 73 | VPDMN_USE_DEPLOY ?= "0" | ||
| 74 | |||
| 75 | S = "${UNPACKDIR}" | ||
| 76 | |||
| 77 | do_install() { | ||
| 78 | # Install vpdmn main script and architecture symlinks | ||
| 79 | install -d ${D}${bindir} | ||
| 80 | install -d ${D}${bindir}/vpdmn-blobs/aarch64 | ||
| 81 | install -d ${D}${bindir}/vpdmn-blobs/x86_64 | ||
| 82 | |||
| 83 | # Install main vpdmn script (arch detected at runtime) | ||
| 84 | install -m 0755 ${S}/vpdmn.sh ${D}${bindir}/vpdmn | ||
| 85 | |||
| 86 | # Create backwards-compatible symlinks | ||
| 87 | ln -sf vpdmn ${D}${bindir}/vpdmn-aarch64 | ||
| 88 | ln -sf vpdmn ${D}${bindir}/vpdmn-x86_64 | ||
| 89 | |||
| 90 | # Note: vpdmn uses vrunner.sh from vdkr-native (it's runtime-agnostic) | ||
| 91 | |||
| 92 | # Determine blob source directories based on VPDMN_USE_DEPLOY | ||
| 93 | if [ "${VPDMN_USE_DEPLOY}" = "1" ]; then | ||
| 94 | AARCH64_SRC="${VPDMN_DEPLOY_AARCH64}" | ||
| 95 | X86_64_SRC="${VPDMN_DEPLOY_X86_64}" | ||
| 96 | bbwarn "============================================================" | ||
| 97 | bbwarn "VPDMN_USE_DEPLOY=1: Using blobs from DEPLOY_DIR" | ||
| 98 | bbwarn "This is for development only. For permanent use, copy blobs:" | ||
| 99 | bbwarn "" | ||
| 100 | bbwarn " # For aarch64:" | ||
| 101 | bbwarn " cp ${VPDMN_DEPLOY_AARCH64}/Image \\" | ||
| 102 | bbwarn " ${VPDMN_LAYER_BLOBS}/aarch64/" | ||
| 103 | bbwarn " cp ${VPDMN_DEPLOY_AARCH64}/initramfs.cpio.gz \\" | ||
| 104 | bbwarn " ${VPDMN_LAYER_BLOBS}/aarch64/" | ||
| 105 | bbwarn "" | ||
| 106 | bbwarn " # For x86_64:" | ||
| 107 | bbwarn " cp ${VPDMN_DEPLOY_X86_64}/bzImage \\" | ||
| 108 | bbwarn " ${VPDMN_LAYER_BLOBS}/x86_64/" | ||
| 109 | bbwarn " cp ${VPDMN_DEPLOY_X86_64}/initramfs.cpio.gz \\" | ||
| 110 | bbwarn " ${VPDMN_LAYER_BLOBS}/x86_64/" | ||
| 111 | bbwarn "" | ||
| 112 | bbwarn "Then remove VPDMN_USE_DEPLOY from local.conf" | ||
| 113 | bbwarn "============================================================" | ||
| 114 | else | ||
| 115 | AARCH64_SRC="${VPDMN_LAYER_BLOBS}/aarch64" | ||
| 116 | X86_64_SRC="${VPDMN_LAYER_BLOBS}/x86_64" | ||
| 117 | fi | ||
| 118 | |||
| 119 | # Install aarch64 blobs (if available) | ||
| 120 | # Requires: Image, initramfs.cpio.gz, rootfs.img | ||
| 121 | if [ -f "$AARCH64_SRC/Image" ] && [ -f "$AARCH64_SRC/rootfs.img" ]; then | ||
| 122 | install -m 0644 "$AARCH64_SRC/Image" ${D}${bindir}/vpdmn-blobs/aarch64/ | ||
| 123 | install -m 0644 "$AARCH64_SRC/initramfs.cpio.gz" ${D}${bindir}/vpdmn-blobs/aarch64/ | ||
| 124 | install -m 0644 "$AARCH64_SRC/rootfs.img" ${D}${bindir}/vpdmn-blobs/aarch64/ | ||
| 125 | bbnote "Installed aarch64 blobs from $AARCH64_SRC" | ||
| 126 | else | ||
| 127 | bbnote "No aarch64 blobs found at $AARCH64_SRC" | ||
| 128 | bbnote "Required: Image, initramfs.cpio.gz, rootfs.img" | ||
| 129 | fi | ||
| 130 | |||
| 131 | # Install x86_64 blobs (if available) | ||
| 132 | # Requires: bzImage, initramfs.cpio.gz, rootfs.img | ||
| 133 | if [ -f "$X86_64_SRC/bzImage" ] && [ -f "$X86_64_SRC/rootfs.img" ]; then | ||
| 134 | install -m 0644 "$X86_64_SRC/bzImage" ${D}${bindir}/vpdmn-blobs/x86_64/ | ||
| 135 | install -m 0644 "$X86_64_SRC/initramfs.cpio.gz" ${D}${bindir}/vpdmn-blobs/x86_64/ | ||
| 136 | install -m 0644 "$X86_64_SRC/rootfs.img" ${D}${bindir}/vpdmn-blobs/x86_64/ | ||
| 137 | bbnote "Installed x86_64 blobs from $X86_64_SRC" | ||
| 138 | else | ||
| 139 | bbnote "No x86_64 blobs found at $X86_64_SRC" | ||
| 140 | bbnote "Required: bzImage, initramfs.cpio.gz, rootfs.img" | ||
| 141 | fi | ||
| 142 | } | ||
| 143 | |||
| 144 | # Make available in native sysroot | ||
| 145 | SYSROOT_DIRS += "${bindir}" | ||
| 146 | |||
| 147 | # Task to print usage instructions | ||
| 148 | # Run with: bitbake vpdmn-native -c print_usage | ||
| 149 | python do_print_usage() { | ||
| 150 | import os | ||
| 151 | native_sysroot = d.getVar('STAGING_DIR_NATIVE') | ||
| 152 | |||
| 153 | bb.plain("") | ||
| 154 | bb.plain("=" * 70) | ||
| 155 | bb.plain("vpdmn Usage Instructions") | ||
| 156 | bb.plain("=" * 70) | ||
| 157 | bb.plain("") | ||
| 158 | bb.plain("Option 1: Add to PATH (recommended)") | ||
| 159 | bb.plain("-" * 40) | ||
| 160 | bb.plain("export PATH=\"%s:$PATH\"" % (native_sysroot + d.getVar('bindir'))) | ||
| 161 | bb.plain("") | ||
| 162 | bb.plain("Then use:") | ||
| 163 | bb.plain(" vpdmn images # Uses default arch (host or config)") | ||
| 164 | bb.plain(" vpdmn -a aarch64 images # Explicit arch") | ||
| 165 | bb.plain(" vpdmn-x86_64 images # Symlink (backwards compatible)") | ||
| 166 | bb.plain("") | ||
| 167 | bb.plain("Option 2: Set default architecture") | ||
| 168 | bb.plain("-" * 40) | ||
| 169 | bb.plain("mkdir -p ~/.config/vpdmn && echo 'aarch64' > ~/.config/vpdmn/arch") | ||
| 170 | bb.plain("") | ||
| 171 | bb.plain("Note: QEMU must be in PATH. If not found, also add:") | ||
| 172 | bb.plain("export PATH=\"%s:$PATH\"" % (d.getVar('STAGING_BINDIR_NATIVE'))) | ||
| 173 | bb.plain("") | ||
| 174 | bb.plain("=" * 70) | ||
| 175 | } | ||
| 176 | addtask print_usage | ||
| 177 | do_print_usage[nostamp] = "1" | ||
diff --git a/recipes-containers/vcontainer/vpdmn-rootfs-image.bb b/recipes-containers/vcontainer/vpdmn-rootfs-image.bb new file mode 100644 index 00000000..33647202 --- /dev/null +++ b/recipes-containers/vcontainer/vpdmn-rootfs-image.bb | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 2 | # | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # vpdmn-rootfs-image.bb | ||
| 6 | # Minimal Podman-capable image for vpdmn QEMU environment | ||
| 7 | # | ||
| 8 | # This image is built via multiconfig and used by vpdmn-initramfs-create | ||
| 9 | # to provide a proper rootfs for running Podman in QEMU. | ||
| 10 | # | ||
| 11 | # Build with: | ||
| 12 | # bitbake mc:vpdmn-aarch64:vpdmn-rootfs-image | ||
| 13 | # bitbake mc:vpdmn-x86-64:vpdmn-rootfs-image | ||
| 14 | |||
| 15 | SUMMARY = "Minimal Podman rootfs for vpdmn" | ||
| 16 | DESCRIPTION = "A minimal image containing Podman tools for use with vpdmn. \ | ||
| 17 | This image runs inside QEMU to provide Podman command execution." | ||
| 18 | |||
| 19 | LICENSE = "MIT" | ||
| 20 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" | ||
| 21 | |||
| 22 | # Track init script changes via file-checksums | ||
| 23 | # This adds the file content hash to the task signature | ||
| 24 | do_rootfs[file-checksums] += "${THISDIR}/files/vpdmn-init.sh:True" | ||
| 25 | do_rootfs[file-checksums] += "${THISDIR}/files/vcontainer-init-common.sh:True" | ||
| 26 | |||
| 27 | # Force do_rootfs to always run (no stamp caching) | ||
| 28 | # Combined with file-checksums, this ensures init script changes are picked up | ||
| 29 | do_rootfs[nostamp] = "1" | ||
| 30 | |||
| 31 | # Inherit from core-image-minimal for a minimal base | ||
| 32 | inherit core-image | ||
| 33 | |||
| 34 | # Use crun as the OCI runtime (not runc) - this prevents the conflict where | ||
| 35 | # both crun (which creates /usr/bin/runc symlink) and runc package are installed | ||
| 36 | VIRTUAL-RUNTIME_container_runtime = "crun" | ||
| 37 | |||
| 38 | # Use netavark for container networking (pulled in via podman's RDEPENDS) | ||
| 39 | VIRTUAL-RUNTIME_container_networking = "netavark" | ||
| 40 | |||
| 41 | # Use aardvark-dns for container DNS | ||
| 42 | VIRTUAL-RUNTIME_container_dns = "aardvark-dns" | ||
| 43 | |||
| 44 | # We need Podman and container tools | ||
| 45 | # Podman is daemonless - no containerd required! | ||
| 46 | IMAGE_INSTALL = " \ | ||
| 47 | packagegroup-core-boot \ | ||
| 48 | podman \ | ||
| 49 | skopeo \ | ||
| 50 | conmon \ | ||
| 51 | netavark \ | ||
| 52 | aardvark-dns \ | ||
| 53 | busybox \ | ||
| 54 | iproute2 \ | ||
| 55 | iptables \ | ||
| 56 | util-linux \ | ||
| 57 | ca-certificates \ | ||
| 58 | " | ||
| 59 | |||
| 60 | # No extra features needed | ||
| 61 | IMAGE_FEATURES = "" | ||
| 62 | |||
| 63 | # Keep the image small | ||
| 64 | IMAGE_ROOTFS_SIZE = "524288" | ||
| 65 | IMAGE_ROOTFS_EXTRA_SPACE = "0" | ||
| 66 | |||
| 67 | # Use squashfs for smaller size (~3x compression) | ||
| 68 | # The preinit mounts squashfs read-only with tmpfs overlay for writes | ||
| 69 | IMAGE_FSTYPES = "squashfs" | ||
| 70 | |||
| 71 | # Install our init script | ||
| 72 | ROOTFS_POSTPROCESS_COMMAND += "install_vpdmn_init;" | ||
| 73 | |||
| 74 | install_vpdmn_init() { | ||
| 75 | # Install vpdmn-init.sh as /init and vcontainer-init-common.sh alongside it | ||
| 76 | install -m 0755 ${THISDIR}/files/vpdmn-init.sh ${IMAGE_ROOTFS}/init | ||
| 77 | install -m 0755 ${THISDIR}/files/vcontainer-init-common.sh ${IMAGE_ROOTFS}/vcontainer-init-common.sh | ||
| 78 | |||
| 79 | # Create required directories | ||
| 80 | install -d ${IMAGE_ROOTFS}/mnt/input | ||
| 81 | install -d ${IMAGE_ROOTFS}/mnt/state | ||
| 82 | install -d ${IMAGE_ROOTFS}/var/lib/containers | ||
| 83 | install -d ${IMAGE_ROOTFS}/run/containers | ||
| 84 | |||
| 85 | # Create skopeo/podman policy | ||
| 86 | install -d ${IMAGE_ROOTFS}/etc/containers | ||
| 87 | echo '{"default":[{"type":"insecureAcceptAnything"}]}' > ${IMAGE_ROOTFS}/etc/containers/policy.json | ||
| 88 | |||
| 89 | # Create registries.conf for podman | ||
| 90 | cat > ${IMAGE_ROOTFS}/etc/containers/registries.conf << 'EOF' | ||
| 91 | # Search registries | ||
| 92 | unqualified-search-registries = ["docker.io", "quay.io"] | ||
| 93 | |||
| 94 | # Short name aliases | ||
| 95 | [aliases] | ||
| 96 | "alpine" = "docker.io/library/alpine" | ||
| 97 | "busybox" = "docker.io/library/busybox" | ||
| 98 | "nginx" = "docker.io/library/nginx" | ||
| 99 | "ubuntu" = "docker.io/library/ubuntu" | ||
| 100 | "debian" = "docker.io/library/debian" | ||
| 101 | EOF | ||
| 102 | |||
| 103 | # Create storage.conf for podman | ||
| 104 | # IMPORTANT: Must use VFS driver, not overlay, because: | ||
| 105 | # - The storage tar is extracted into Yocto rootfs under pseudo (fakeroot) | ||
| 106 | # - Overlay storage has special files/symlinks that fail under pseudo | ||
| 107 | # - VFS extracts cleanly (simpler structure, no special filesystem features) | ||
| 108 | install -d ${IMAGE_ROOTFS}/etc/containers/storage.conf.d | ||
| 109 | cat > ${IMAGE_ROOTFS}/etc/containers/storage.conf << 'EOF' | ||
| 110 | [storage] | ||
| 111 | driver = "vfs" | ||
| 112 | runroot = "/run/containers/storage" | ||
| 113 | graphroot = "/var/lib/containers/storage" | ||
| 114 | |||
| 115 | [storage.options] | ||
| 116 | additionalimagestores = [] | ||
| 117 | EOF | ||
| 118 | |||
| 119 | # Create containers.conf for podman engine settings | ||
| 120 | cat > ${IMAGE_ROOTFS}/etc/containers/containers.conf << 'EOF' | ||
| 121 | [engine] | ||
| 122 | # Location of helper binaries (netavark, aardvark-dns) | ||
| 123 | helper_binaries_dir = ["/usr/libexec/podman"] | ||
| 124 | |||
| 125 | [network] | ||
| 126 | # Use netavark as the network backend | ||
| 127 | network_backend = "netavark" | ||
| 128 | EOF | ||
| 129 | } | ||
