summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-01-06 20:45:13 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-09 03:32:52 +0000
commit8f6f746bb6075157fa175a2081e6b3dcd833a8a2 (patch)
treecc0ed3530abeda331b74af3325e85542edabd703
parent18074e0efe255a43ab9155171d08aa6e0d736b5f (diff)
downloadmeta-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>
-rw-r--r--recipes-containers/vcontainer/files/blobs/vpdmn/aarch64/README26
-rw-r--r--recipes-containers/vcontainer/files/blobs/vpdmn/x86_64/README26
-rwxr-xr-xrecipes-containers/vcontainer/files/vpdmn-init.sh181
-rwxr-xr-xrecipes-containers/vcontainer/files/vpdmn.sh25
-rw-r--r--recipes-containers/vcontainer/vpdmn-initramfs-create_1.0.bb46
-rw-r--r--recipes-containers/vcontainer/vpdmn-native_1.0.bb177
-rw-r--r--recipes-containers/vcontainer/vpdmn-rootfs-image.bb129
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 @@
1vpdmn aarch64 Blobs
2====================
3
4This directory should contain the boot blobs for vpdmn aarch64:
5
6Required 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
11Build 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
23Once these files are present, vpdmn-native will automatically include them.
24
25Alternatively, set VPDMN_USE_DEPLOY = "1" in local.conf to use blobs
26directly 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 @@
1vpdmn x86_64 Blobs
2===================
3
4This directory should contain the boot blobs for vpdmn x86_64:
5
6Required 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
11Build 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
23Once these files are present, vpdmn-native will automatically include them.
24
25Alternatively, set VPDMN_USE_DEPLOY = "1" in local.conf to use blobs
26directly 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
29VCONTAINER_RUNTIME_NAME="vpdmn"
30VCONTAINER_RUNTIME_CMD="podman"
31VCONTAINER_RUNTIME_PREFIX="podman"
32VCONTAINER_STATE_DIR="/var/lib/containers/storage"
33VCONTAINER_SHARE_NAME="vpdmn_share"
34VCONTAINER_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
44setup_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
51setup_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
65setup_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
88verify_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
101stop_runtime_daemons() {
102 :
103}
104
105handle_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
139setup_base_environment
140setup_podman_environment
141mount_base_filesystems
142
143# Check for quiet boot mode
144check_quiet_boot
145
146log "=== vpdmn Init ==="
147log "Version: $VCONTAINER_VERSION"
148
149# Mount tmpfs directories and Podman-specific mounts
150mount_tmpfs_dirs
151setup_podman_mounts
152setup_cgroups
153
154# Parse kernel command line
155parse_cmdline
156
157# Detect and configure disks
158detect_disks
159
160# Set up Podman storage (Podman-specific)
161setup_podman_storage
162
163# Mount input disk
164mount_input_disk
165
166# Configure networking
167configure_networking
168
169# Verify podman is available (no daemon to start)
170verify_podman
171
172# Handle daemon mode or single command execution
173if [ "$RUNTIME_DAEMON" = "1" ]; then
174 run_daemon_mode
175else
176 prepare_input_path
177 execute_command
178fi
179
180# Graceful shutdown
181graceful_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
16VCONTAINER_RUNTIME_NAME="vpdmn"
17VCONTAINER_RUNTIME_CMD="podman"
18VCONTAINER_RUNTIME_PREFIX="VPDMN"
19VCONTAINER_IMPORT_TARGET="containers-storage:"
20VCONTAINER_STATE_FILE="podman-state.img"
21VCONTAINER_OTHER_PREFIX="VDKR"
22VCONTAINER_VERSION="1.2.0"
23
24# Source common implementation
25source "$(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
39SUMMARY = "Build QEMU blobs for vpdmn"
40DESCRIPTION = "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
44VCONTAINER_RUNTIME = "vpdmn"
45
46require 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
37SUMMARY = "Emulated Podman for cross-architecture container operations"
38DESCRIPTION = "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."
41HOMEPAGE = "https://github.com/anthropics/meta-virtualization"
42LICENSE = "MIT"
43LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
44
45inherit native
46
47# Dependencies - we use vdkr's runner script
48DEPENDS = "qemu-system-native coreutils-native socat-native vdkr-native"
49
50SRC_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
63FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
64
65# Layer directory containing optional blobs
66VPDMN_LAYER_BLOBS = "${THISDIR}/files/blobs/vpdmn"
67
68# Deploy directories (used when VPDMN_USE_DEPLOY = "1")
69VPDMN_DEPLOY_AARCH64 = "${DEPLOY_DIR}/images/qemuarm64/vpdmn-initramfs"
70VPDMN_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
73VPDMN_USE_DEPLOY ?= "0"
74
75S = "${UNPACKDIR}"
76
77do_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
145SYSROOT_DIRS += "${bindir}"
146
147# Task to print usage instructions
148# Run with: bitbake vpdmn-native -c print_usage
149python 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}
176addtask print_usage
177do_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
15SUMMARY = "Minimal Podman rootfs for vpdmn"
16DESCRIPTION = "A minimal image containing Podman tools for use with vpdmn. \
17 This image runs inside QEMU to provide Podman command execution."
18
19LICENSE = "MIT"
20LIC_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
24do_rootfs[file-checksums] += "${THISDIR}/files/vpdmn-init.sh:True"
25do_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
29do_rootfs[nostamp] = "1"
30
31# Inherit from core-image-minimal for a minimal base
32inherit 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
36VIRTUAL-RUNTIME_container_runtime = "crun"
37
38# Use netavark for container networking (pulled in via podman's RDEPENDS)
39VIRTUAL-RUNTIME_container_networking = "netavark"
40
41# Use aardvark-dns for container DNS
42VIRTUAL-RUNTIME_container_dns = "aardvark-dns"
43
44# We need Podman and container tools
45# Podman is daemonless - no containerd required!
46IMAGE_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
61IMAGE_FEATURES = ""
62
63# Keep the image small
64IMAGE_ROOTFS_SIZE = "524288"
65IMAGE_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
69IMAGE_FSTYPES = "squashfs"
70
71# Install our init script
72ROOTFS_POSTPROCESS_COMMAND += "install_vpdmn_init;"
73
74install_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
92unqualified-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"
101EOF
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]
111driver = "vfs"
112runroot = "/run/containers/storage"
113graphroot = "/var/lib/containers/storage"
114
115[storage.options]
116additionalimagestores = []
117EOF
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)
123helper_binaries_dir = ["/usr/libexec/podman"]
124
125[network]
126# Use netavark as the network backend
127network_backend = "netavark"
128EOF
129}