diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-06 20:42:46 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-09 03:32:52 +0000 |
| commit | 18074e0efe255a43ab9155171d08aa6e0d736b5f (patch) | |
| tree | 25411491f3a6c3ed7b3460a0bd9f3d4d1c561bc6 | |
| parent | c32db56912b06a89490fda5d554468dbc12a39a2 (diff) | |
| download | meta-virtualization-18074e0efe255a43ab9155171d08aa6e0d736b5f.tar.gz | |
vcontainer: add vdkr Docker support
Add vdkr - Docker CLI wrapper for cross-architecture container operations:
Scripts:
- vdkr.sh: Docker CLI entry point (vdkr-x86_64, vdkr-aarch64)
- vdkr-init.sh: Docker init script for QEMU guest
Recipes:
- vdkr-native: Installs vdkr CLI wrappers
- vdkr-rootfs-image: Builds Docker rootfs with containerd, runc, skopeo
- vdkr-initramfs-create: Creates bootable initramfs blob
The vdkr CLI provides Docker-compatible commands executed inside a
QEMU-emulated environment, enabling cross-architecture container
operations during Yocto builds.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
| -rw-r--r-- | recipes-containers/vcontainer/files/blobs/vdkr/aarch64/README | 26 | ||||
| -rw-r--r-- | recipes-containers/vcontainer/files/blobs/vdkr/x86_64/README | 26 | ||||
| -rwxr-xr-x | recipes-containers/vcontainer/files/vdkr-init.sh | 238 | ||||
| -rwxr-xr-x | recipes-containers/vcontainer/files/vdkr.sh | 25 | ||||
| -rw-r--r-- | recipes-containers/vcontainer/vdkr-initramfs-create_1.0.bb | 46 | ||||
| -rw-r--r-- | recipes-containers/vcontainer/vdkr-native_1.0.bb | 191 | ||||
| -rw-r--r-- | recipes-containers/vcontainer/vdkr-rootfs-image.bb | 74 |
7 files changed, 626 insertions, 0 deletions
diff --git a/recipes-containers/vcontainer/files/blobs/vdkr/aarch64/README b/recipes-containers/vcontainer/files/blobs/vdkr/aarch64/README new file mode 100644 index 00000000..cabb5100 --- /dev/null +++ b/recipes-containers/vcontainer/files/blobs/vdkr/aarch64/README | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | vdkr aarch64 Blobs | ||
| 2 | ==================== | ||
| 3 | |||
| 4 | This directory should contain the boot blobs for vdkr 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 Docker tools | ||
| 10 | |||
| 11 | Build instructions: | ||
| 12 | 1. Set MACHINE for aarch64: | ||
| 13 | MACHINE=qemuarm64 | ||
| 14 | |||
| 15 | 2. Build the blobs: | ||
| 16 | bitbake vdkr-initramfs-create | ||
| 17 | |||
| 18 | 3. Copy from deploy directory: | ||
| 19 | cp tmp/deploy/images/qemuarm64/vdkr/aarch64/Image . | ||
| 20 | cp tmp/deploy/images/qemuarm64/vdkr/aarch64/initramfs.cpio.gz . | ||
| 21 | cp tmp/deploy/images/qemuarm64/vdkr/aarch64/rootfs.img . | ||
| 22 | |||
| 23 | Once these files are present, vdkr-native will automatically include them. | ||
| 24 | |||
| 25 | Alternatively, set VDKR_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/vdkr/x86_64/README b/recipes-containers/vcontainer/files/blobs/vdkr/x86_64/README new file mode 100644 index 00000000..6902f729 --- /dev/null +++ b/recipes-containers/vcontainer/files/blobs/vdkr/x86_64/README | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | vdkr x86_64 Blobs | ||
| 2 | =================== | ||
| 3 | |||
| 4 | This directory should contain the boot blobs for vdkr 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 Docker tools | ||
| 10 | |||
| 11 | Build instructions: | ||
| 12 | 1. Set MACHINE for x86_64: | ||
| 13 | MACHINE=qemux86-64 | ||
| 14 | |||
| 15 | 2. Build the blobs: | ||
| 16 | bitbake vdkr-initramfs-create | ||
| 17 | |||
| 18 | 3. Copy from deploy directory: | ||
| 19 | cp tmp/deploy/images/qemux86-64/vdkr/x86_64/bzImage . | ||
| 20 | cp tmp/deploy/images/qemux86-64/vdkr/x86_64/initramfs.cpio.gz . | ||
| 21 | cp tmp/deploy/images/qemux86-64/vdkr/x86_64/rootfs.img . | ||
| 22 | |||
| 23 | Once these files are present, vdkr-native will automatically include them. | ||
| 24 | |||
| 25 | Alternatively, set VDKR_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/vdkr-init.sh b/recipes-containers/vcontainer/files/vdkr-init.sh new file mode 100755 index 00000000..7cfe12af --- /dev/null +++ b/recipes-containers/vcontainer/files/vdkr-init.sh | |||
| @@ -0,0 +1,238 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 3 | # | ||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | # | ||
| 6 | # vdkr-init.sh | ||
| 7 | # Init script for vdkr: execute arbitrary docker 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 Docker storage) | ||
| 16 | # | ||
| 17 | # Kernel parameters: | ||
| 18 | # docker_cmd=<base64> Base64-encoded docker command + args | ||
| 19 | # docker_input=<type> Input type: none, oci, tar, dir (default: none) | ||
| 20 | # docker_output=<type> Output type: text, tar, storage (default: text) | ||
| 21 | # docker_state=<type> State type: none, disk (default: none) | ||
| 22 | # docker_network=1 Enable networking (configure eth0, DNS) | ||
| 23 | # | ||
| 24 | # Version: 2.3.0 | ||
| 25 | |||
| 26 | # Set runtime-specific parameters before sourcing common code | ||
| 27 | VCONTAINER_RUNTIME_NAME="vdkr" | ||
| 28 | VCONTAINER_RUNTIME_CMD="docker" | ||
| 29 | VCONTAINER_RUNTIME_PREFIX="docker" | ||
| 30 | VCONTAINER_STATE_DIR="/var/lib/docker" | ||
| 31 | VCONTAINER_SHARE_NAME="vdkr_share" | ||
| 32 | VCONTAINER_VERSION="2.3.0" | ||
| 33 | |||
| 34 | # Source common init functions | ||
| 35 | # When installed as /init, common file is at /vcontainer-init-common.sh | ||
| 36 | . /vcontainer-init-common.sh | ||
| 37 | |||
| 38 | # ============================================================================ | ||
| 39 | # Docker-Specific Functions | ||
| 40 | # ============================================================================ | ||
| 41 | |||
| 42 | setup_docker_storage() { | ||
| 43 | mkdir -p /run/containerd /run/lock | ||
| 44 | mkdir -p /var/lib/docker | ||
| 45 | mkdir -p /var/lib/containerd | ||
| 46 | |||
| 47 | # Handle Docker storage | ||
| 48 | if [ -n "$STATE_DISK" ] && [ -b "$STATE_DISK" ]; then | ||
| 49 | log "Mounting state disk $STATE_DISK as /var/lib/docker..." | ||
| 50 | if mount -t ext4 "$STATE_DISK" /var/lib/docker 2>&1; then | ||
| 51 | log "SUCCESS: Mounted $STATE_DISK as Docker storage" | ||
| 52 | log "Docker storage contents:" | ||
| 53 | [ "$QUIET_BOOT" = "0" ] && ls -la /var/lib/docker/ 2>/dev/null || log "(empty)" | ||
| 54 | else | ||
| 55 | log "WARNING: Failed to mount state disk, using tmpfs" | ||
| 56 | RUNTIME_STATE="none" | ||
| 57 | fi | ||
| 58 | fi | ||
| 59 | |||
| 60 | # If no state disk, use tmpfs for Docker storage | ||
| 61 | if [ "$RUNTIME_STATE" != "disk" ]; then | ||
| 62 | log "Using tmpfs for Docker storage (ephemeral)..." | ||
| 63 | mount -t tmpfs -o size=1G tmpfs /var/lib/docker | ||
| 64 | fi | ||
| 65 | } | ||
| 66 | |||
| 67 | start_containerd() { | ||
| 68 | CONTAINERD_READY=false | ||
| 69 | if [ -x "/usr/bin/containerd" ]; then | ||
| 70 | log "Starting containerd..." | ||
| 71 | mkdir -p /var/lib/containerd | ||
| 72 | mkdir -p /run/containerd | ||
| 73 | /usr/bin/containerd --log-level info --root /var/lib/containerd --state /run/containerd >/tmp/containerd.log 2>&1 & | ||
| 74 | CONTAINERD_PID=$! | ||
| 75 | # Wait for containerd socket | ||
| 76 | for i in 1 2 3 4 5 6 7 8 9 10; do | ||
| 77 | if [ -S /run/containerd/containerd.sock ]; then | ||
| 78 | log "Containerd running (PID: $CONTAINERD_PID)" | ||
| 79 | CONTAINERD_READY=true | ||
| 80 | break | ||
| 81 | fi | ||
| 82 | sleep 1 | ||
| 83 | done | ||
| 84 | if [ "$CONTAINERD_READY" != "true" ]; then | ||
| 85 | log "WARNING: Containerd failed to start, check /tmp/containerd.log" | ||
| 86 | [ -f /tmp/containerd.log ] && cat /tmp/containerd.log >&2 | ||
| 87 | fi | ||
| 88 | fi | ||
| 89 | } | ||
| 90 | |||
| 91 | start_dockerd() { | ||
| 92 | log "Starting Docker daemon..." | ||
| 93 | DOCKER_OPTS="--data-root=/var/lib/docker" | ||
| 94 | DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2" | ||
| 95 | DOCKER_OPTS="$DOCKER_OPTS --iptables=false" | ||
| 96 | DOCKER_OPTS="$DOCKER_OPTS --userland-proxy=false" | ||
| 97 | DOCKER_OPTS="$DOCKER_OPTS --bridge=none" | ||
| 98 | DOCKER_OPTS="$DOCKER_OPTS --host=unix:///var/run/docker.sock" | ||
| 99 | DOCKER_OPTS="$DOCKER_OPTS --exec-opt native.cgroupdriver=cgroupfs" | ||
| 100 | DOCKER_OPTS="$DOCKER_OPTS --log-level=info" | ||
| 101 | |||
| 102 | if [ "$CONTAINERD_READY" = "true" ]; then | ||
| 103 | DOCKER_OPTS="$DOCKER_OPTS --containerd=/run/containerd/containerd.sock" | ||
| 104 | fi | ||
| 105 | |||
| 106 | /usr/bin/dockerd $DOCKER_OPTS >/dev/null 2>&1 & | ||
| 107 | DOCKER_PID=$! | ||
| 108 | log "Docker daemon started (PID: $DOCKER_PID)" | ||
| 109 | |||
| 110 | # Wait for Docker to be ready | ||
| 111 | log "Waiting for Docker daemon..." | ||
| 112 | DOCKER_READY=false | ||
| 113 | |||
| 114 | sleep 5 | ||
| 115 | |||
| 116 | for i in $(seq 1 60); do | ||
| 117 | if ! kill -0 $DOCKER_PID 2>/dev/null; then | ||
| 118 | echo "===ERROR===" | ||
| 119 | echo "Docker daemon died after $i iterations" | ||
| 120 | cat /var/log/docker.log 2>/dev/null || true | ||
| 121 | dmesg | tail -20 2>/dev/null || true | ||
| 122 | sleep 2 | ||
| 123 | reboot -f | ||
| 124 | fi | ||
| 125 | |||
| 126 | if /usr/bin/docker info >/dev/null 2>&1; then | ||
| 127 | log "Docker daemon is ready!" | ||
| 128 | DOCKER_READY=true | ||
| 129 | break | ||
| 130 | fi | ||
| 131 | |||
| 132 | log "Waiting... ($i/60)" | ||
| 133 | sleep 2 | ||
| 134 | done | ||
| 135 | |||
| 136 | if [ "$DOCKER_READY" != "true" ]; then | ||
| 137 | echo "===ERROR===" | ||
| 138 | echo "Docker failed to start" | ||
| 139 | sleep 2 | ||
| 140 | reboot -f | ||
| 141 | fi | ||
| 142 | } | ||
| 143 | |||
| 144 | stop_runtime_daemons() { | ||
| 145 | # Stop Docker daemon | ||
| 146 | if [ -n "$DOCKER_PID" ]; then | ||
| 147 | log "Stopping Docker daemon..." | ||
| 148 | kill $DOCKER_PID 2>/dev/null || true | ||
| 149 | for i in $(seq 1 10); do | ||
| 150 | if ! kill -0 $DOCKER_PID 2>/dev/null; then | ||
| 151 | log "Docker daemon stopped" | ||
| 152 | break | ||
| 153 | fi | ||
| 154 | sleep 1 | ||
| 155 | done | ||
| 156 | fi | ||
| 157 | |||
| 158 | # Stop containerd | ||
| 159 | if [ -n "$CONTAINERD_PID" ]; then | ||
| 160 | log "Stopping containerd..." | ||
| 161 | kill $CONTAINERD_PID 2>/dev/null || true | ||
| 162 | sleep 2 | ||
| 163 | fi | ||
| 164 | } | ||
| 165 | |||
| 166 | handle_storage_output() { | ||
| 167 | echo "Stopping Docker gracefully..." | ||
| 168 | /usr/bin/docker system prune -f >/dev/null 2>&1 || true | ||
| 169 | kill $DOCKER_PID 2>/dev/null || true | ||
| 170 | [ -n "$CONTAINERD_PID" ] && kill $CONTAINERD_PID 2>/dev/null || true | ||
| 171 | sleep 3 | ||
| 172 | |||
| 173 | echo "Packaging Docker storage..." | ||
| 174 | cd /var/lib | ||
| 175 | tar -cf /tmp/storage.tar docker/ | ||
| 176 | |||
| 177 | STORAGE_SIZE=$(stat -c%s /tmp/storage.tar 2>/dev/null || echo "0") | ||
| 178 | echo "Storage size: $STORAGE_SIZE bytes" | ||
| 179 | |||
| 180 | if [ "$STORAGE_SIZE" -gt 1000 ]; then | ||
| 181 | dmesg -n 1 | ||
| 182 | echo "===STORAGE_START===" | ||
| 183 | base64 /tmp/storage.tar | ||
| 184 | echo "===STORAGE_END===" | ||
| 185 | echo "===EXIT_CODE=$EXEC_EXIT_CODE===" | ||
| 186 | else | ||
| 187 | echo "===ERROR===" | ||
| 188 | echo "Storage too small" | ||
| 189 | fi | ||
| 190 | } | ||
| 191 | |||
| 192 | # ============================================================================ | ||
| 193 | # Main | ||
| 194 | # ============================================================================ | ||
| 195 | |||
| 196 | # Initialize base environment | ||
| 197 | setup_base_environment | ||
| 198 | mount_base_filesystems | ||
| 199 | |||
| 200 | # Check for quiet boot mode | ||
| 201 | check_quiet_boot | ||
| 202 | |||
| 203 | log "=== vdkr Init ===" | ||
| 204 | log "Version: $VCONTAINER_VERSION" | ||
| 205 | |||
| 206 | # Mount tmpfs directories and cgroups | ||
| 207 | mount_tmpfs_dirs | ||
| 208 | setup_cgroups | ||
| 209 | |||
| 210 | # Parse kernel command line | ||
| 211 | parse_cmdline | ||
| 212 | |||
| 213 | # Detect and configure disks | ||
| 214 | detect_disks | ||
| 215 | |||
| 216 | # Set up Docker storage (Docker-specific) | ||
| 217 | setup_docker_storage | ||
| 218 | |||
| 219 | # Mount input disk | ||
| 220 | mount_input_disk | ||
| 221 | |||
| 222 | # Configure networking | ||
| 223 | configure_networking | ||
| 224 | |||
| 225 | # Start containerd and dockerd (Docker-specific) | ||
| 226 | start_containerd | ||
| 227 | start_dockerd | ||
| 228 | |||
| 229 | # Handle daemon mode or single command execution | ||
| 230 | if [ "$RUNTIME_DAEMON" = "1" ]; then | ||
| 231 | run_daemon_mode | ||
| 232 | else | ||
| 233 | prepare_input_path | ||
| 234 | execute_command | ||
| 235 | fi | ||
| 236 | |||
| 237 | # Graceful shutdown | ||
| 238 | graceful_shutdown | ||
diff --git a/recipes-containers/vcontainer/files/vdkr.sh b/recipes-containers/vcontainer/files/vdkr.sh new file mode 100755 index 00000000..818c831e --- /dev/null +++ b/recipes-containers/vcontainer/files/vdkr.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 | # vdkr: Docker-like interface for cross-architecture container operations | ||
| 7 | # | ||
| 8 | # This provides a familiar docker-like CLI that executes commands inside | ||
| 9 | # a QEMU-emulated environment with the target architecture's Docker. | ||
| 10 | # | ||
| 11 | # Command naming convention: | ||
| 12 | # - Commands matching Docker's syntax/semantics use Docker's name (import, load, save, etc.) | ||
| 13 | # - Extended commands with non-Docker behavior use 'v' prefix (vimport) | ||
| 14 | |||
| 15 | # Set runtime-specific parameters before sourcing common code | ||
| 16 | VCONTAINER_RUNTIME_NAME="vdkr" | ||
| 17 | VCONTAINER_RUNTIME_CMD="docker" | ||
| 18 | VCONTAINER_RUNTIME_PREFIX="VDKR" | ||
| 19 | VCONTAINER_IMPORT_TARGET="docker-daemon:" | ||
| 20 | VCONTAINER_STATE_FILE="docker-state.img" | ||
| 21 | VCONTAINER_OTHER_PREFIX="VPDMN" | ||
| 22 | VCONTAINER_VERSION="3.4.0" | ||
| 23 | |||
| 24 | # Source common implementation | ||
| 25 | source "$(dirname "${BASH_SOURCE[0]}")/vcontainer-common.sh" "$@" | ||
diff --git a/recipes-containers/vcontainer/vdkr-initramfs-create_1.0.bb b/recipes-containers/vcontainer/vdkr-initramfs-create_1.0.bb new file mode 100644 index 00000000..0d665f4a --- /dev/null +++ b/recipes-containers/vcontainer/vdkr-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 | # vdkr-initramfs-create_1.0.bb | ||
| 6 | # =========================================================================== | ||
| 7 | # Builds QEMU boot blobs for vdkr (Docker CLI) | ||
| 8 | # =========================================================================== | ||
| 9 | # | ||
| 10 | # This recipe packages the boot blobs for vdkr: | ||
| 11 | # - A tiny initramfs with just busybox for switch_root | ||
| 12 | # - The rootfs.img squashfs image (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 | # -> vdkr-init.sh runs with a real root filesystem | ||
| 20 | # -> Docker can use pivot_root properly | ||
| 21 | # | ||
| 22 | # =========================================================================== | ||
| 23 | # BUILD INSTRUCTIONS | ||
| 24 | # =========================================================================== | ||
| 25 | # | ||
| 26 | # For aarch64 (multiconfig dependency is automatic): | ||
| 27 | # MACHINE=qemuarm64 bitbake vdkr-initramfs-create | ||
| 28 | # | ||
| 29 | # For x86_64: | ||
| 30 | # MACHINE=qemux86-64 bitbake vdkr-initramfs-create | ||
| 31 | # | ||
| 32 | # Blobs are deployed to: tmp-vruntime-*/deploy/images/${MACHINE}/vdkr/ | ||
| 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 vdkr" | ||
| 40 | DESCRIPTION = "Packages a tiny initramfs for switch_root and bundles the \ | ||
| 41 | rootfs.img from multiconfig build for vdkr." | ||
| 42 | |||
| 43 | # Set the runtime before including shared code | ||
| 44 | VCONTAINER_RUNTIME = "vdkr" | ||
| 45 | |||
| 46 | require vcontainer-initramfs-create.inc | ||
diff --git a/recipes-containers/vcontainer/vdkr-native_1.0.bb b/recipes-containers/vcontainer/vdkr-native_1.0.bb new file mode 100644 index 00000000..a6a56ecb --- /dev/null +++ b/recipes-containers/vcontainer/vdkr-native_1.0.bb | |||
| @@ -0,0 +1,191 @@ | |||
| 1 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 2 | # | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # vdkr-native_1.0.bb | ||
| 6 | # =========================================================================== | ||
| 7 | # Emulated Docker for cross-architecture container operations | ||
| 8 | # =========================================================================== | ||
| 9 | # | ||
| 10 | # vdkr provides a Docker-like CLI that executes arbitrary docker commands | ||
| 11 | # inside a QEMU-emulated environment with the target architecture's Docker | ||
| 12 | # daemon. Commands like "docker load", "docker export", "docker images" etc | ||
| 13 | # are passed through to Docker running inside QEMU and results streamed back. | ||
| 14 | # | ||
| 15 | # vdkr uses its own initramfs (built by vdkr-initramfs-create) which | ||
| 16 | # has vdkr-init.sh baked in. This is separate from container-cross-install. | ||
| 17 | # | ||
| 18 | # USAGE: | ||
| 19 | # vdkr images # Uses detected default arch | ||
| 20 | # vdkr -a aarch64 images # Explicit arch | ||
| 21 | # vdkr-aarch64 images # Symlink (backwards compatible) | ||
| 22 | # vdkr-x86_64 load -i myimage.tar | ||
| 23 | # | ||
| 24 | # Architecture detection (in priority order): | ||
| 25 | # 1. --arch / -a flag | ||
| 26 | # 2. Executable name (vdkr-aarch64, vdkr-x86_64) | ||
| 27 | # 3. VDKR_ARCH environment variable | ||
| 28 | # 4. Config file: ~/.config/vdkr/arch | ||
| 29 | # 5. Host architecture (uname -m) | ||
| 30 | # | ||
| 31 | # DEPENDENCIES: | ||
| 32 | # - Kernel/initramfs blobs from vdkr-initramfs-create | ||
| 33 | # - QEMU system emulator (qemu-system-native) | ||
| 34 | # | ||
| 35 | # =========================================================================== | ||
| 36 | |||
| 37 | SUMMARY = "Emulated Docker for cross-architecture container operations" | ||
| 38 | DESCRIPTION = "Provides vdkr CLI that executes docker 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 | ||
| 48 | DEPENDS = "qemu-system-native coreutils-native socat-native" | ||
| 49 | |||
| 50 | # vdkr-init.sh is now baked into the initramfs, not installed separately | ||
| 51 | SRC_URI = "\ | ||
| 52 | file://vdkr.sh \ | ||
| 53 | file://vrunner.sh \ | ||
| 54 | " | ||
| 55 | |||
| 56 | # Pre-built blobs are optional - they're checked into the layer after being | ||
| 57 | # built by vdkr-initramfs-build. If not present, vdkr will still build | ||
| 58 | # but will require --blob-dir at runtime. | ||
| 59 | # | ||
| 60 | # To build blobs: | ||
| 61 | # MACHINE=qemuarm64 bitbake vdkr-initramfs-build | ||
| 62 | # MACHINE=qemux86-64 bitbake vdkr-initramfs-build | ||
| 63 | # Then copy from tmp/deploy/images/<machine>/vdkr-initramfs/ to files/blobs/vdkr/<arch>/ | ||
| 64 | # | ||
| 65 | # For development, set VDKR_USE_DEPLOY = "1" in local.conf to use blobs | ||
| 66 | # directly from DEPLOY_DIR instead of copying to layer. | ||
| 67 | |||
| 68 | FILESEXTRAPATHS:prepend := "${THISDIR}/files:" | ||
| 69 | |||
| 70 | # Layer directory containing optional blobs | ||
| 71 | VDKR_LAYER_BLOBS = "${THISDIR}/files/blobs/vdkr" | ||
| 72 | |||
| 73 | # Deploy directories (used when VDKR_USE_DEPLOY = "1") | ||
| 74 | VDKR_DEPLOY_AARCH64 = "${DEPLOY_DIR}/images/qemuarm64/vdkr-initramfs" | ||
| 75 | VDKR_DEPLOY_X86_64 = "${DEPLOY_DIR}/images/qemux86-64/vdkr-initramfs" | ||
| 76 | |||
| 77 | # Set to "1" in local.conf to prefer DEPLOY_DIR blobs over layer | ||
| 78 | VDKR_USE_DEPLOY ?= "0" | ||
| 79 | |||
| 80 | S = "${UNPACKDIR}" | ||
| 81 | |||
| 82 | do_install() { | ||
| 83 | # Install vdkr main script and architecture symlinks | ||
| 84 | install -d ${D}${bindir} | ||
| 85 | install -d ${D}${bindir}/vdkr-blobs/aarch64 | ||
| 86 | install -d ${D}${bindir}/vdkr-blobs/x86_64 | ||
| 87 | |||
| 88 | # Install main vdkr script (arch detected at runtime) | ||
| 89 | install -m 0755 ${S}/vdkr.sh ${D}${bindir}/vdkr | ||
| 90 | |||
| 91 | # Create backwards-compatible symlinks | ||
| 92 | ln -sf vdkr ${D}${bindir}/vdkr-aarch64 | ||
| 93 | ln -sf vdkr ${D}${bindir}/vdkr-x86_64 | ||
| 94 | |||
| 95 | # Install runner script | ||
| 96 | install -m 0755 ${S}/vrunner.sh ${D}${bindir}/ | ||
| 97 | |||
| 98 | # Determine blob source directories based on VDKR_USE_DEPLOY | ||
| 99 | if [ "${VDKR_USE_DEPLOY}" = "1" ]; then | ||
| 100 | AARCH64_SRC="${VDKR_DEPLOY_AARCH64}" | ||
| 101 | X86_64_SRC="${VDKR_DEPLOY_X86_64}" | ||
| 102 | bbwarn "============================================================" | ||
| 103 | bbwarn "VDKR_USE_DEPLOY=1: Using blobs from DEPLOY_DIR" | ||
| 104 | bbwarn "This is for development only. For permanent use, copy blobs:" | ||
| 105 | bbwarn "" | ||
| 106 | bbwarn " # For aarch64:" | ||
| 107 | bbwarn " cp ${VDKR_DEPLOY_AARCH64}/Image \\" | ||
| 108 | bbwarn " ${VDKR_LAYER_BLOBS}/aarch64/" | ||
| 109 | bbwarn " cp ${VDKR_DEPLOY_AARCH64}/initramfs.cpio.gz \\" | ||
| 110 | bbwarn " ${VDKR_LAYER_BLOBS}/aarch64/" | ||
| 111 | bbwarn "" | ||
| 112 | bbwarn " # For x86_64:" | ||
| 113 | bbwarn " cp ${VDKR_DEPLOY_X86_64}/bzImage \\" | ||
| 114 | bbwarn " ${VDKR_LAYER_BLOBS}/x86_64/" | ||
| 115 | bbwarn " cp ${VDKR_DEPLOY_X86_64}/initramfs.cpio.gz \\" | ||
| 116 | bbwarn " ${VDKR_LAYER_BLOBS}/x86_64/" | ||
| 117 | bbwarn "" | ||
| 118 | bbwarn "Then remove VDKR_USE_DEPLOY from local.conf" | ||
| 119 | bbwarn "============================================================" | ||
| 120 | else | ||
| 121 | AARCH64_SRC="${VDKR_LAYER_BLOBS}/aarch64" | ||
| 122 | X86_64_SRC="${VDKR_LAYER_BLOBS}/x86_64" | ||
| 123 | fi | ||
| 124 | |||
| 125 | # Install aarch64 blobs (if available) | ||
| 126 | # Requires: Image, initramfs.cpio.gz, rootfs.img | ||
| 127 | if [ -f "$AARCH64_SRC/Image" ] && [ -f "$AARCH64_SRC/rootfs.img" ]; then | ||
| 128 | install -m 0644 "$AARCH64_SRC/Image" ${D}${bindir}/vdkr-blobs/aarch64/ | ||
| 129 | install -m 0644 "$AARCH64_SRC/initramfs.cpio.gz" ${D}${bindir}/vdkr-blobs/aarch64/ | ||
| 130 | install -m 0644 "$AARCH64_SRC/rootfs.img" ${D}${bindir}/vdkr-blobs/aarch64/ | ||
| 131 | bbnote "Installed aarch64 blobs from $AARCH64_SRC" | ||
| 132 | else | ||
| 133 | bbnote "No aarch64 blobs found at $AARCH64_SRC" | ||
| 134 | bbnote "Required: Image, initramfs.cpio.gz, rootfs.img" | ||
| 135 | fi | ||
| 136 | |||
| 137 | # Install x86_64 blobs (if available) | ||
| 138 | # Requires: bzImage, initramfs.cpio.gz, rootfs.img | ||
| 139 | if [ -f "$X86_64_SRC/bzImage" ] && [ -f "$X86_64_SRC/rootfs.img" ]; then | ||
| 140 | install -m 0644 "$X86_64_SRC/bzImage" ${D}${bindir}/vdkr-blobs/x86_64/ | ||
| 141 | install -m 0644 "$X86_64_SRC/initramfs.cpio.gz" ${D}${bindir}/vdkr-blobs/x86_64/ | ||
| 142 | install -m 0644 "$X86_64_SRC/rootfs.img" ${D}${bindir}/vdkr-blobs/x86_64/ | ||
| 143 | bbnote "Installed x86_64 blobs from $X86_64_SRC" | ||
| 144 | else | ||
| 145 | bbnote "No x86_64 blobs found at $X86_64_SRC" | ||
| 146 | bbnote "Required: bzImage, initramfs.cpio.gz, rootfs.img" | ||
| 147 | fi | ||
| 148 | } | ||
| 149 | |||
| 150 | # Make available in native sysroot | ||
| 151 | SYSROOT_DIRS += "${bindir}" | ||
| 152 | |||
| 153 | # Task to print usage instructions for using vdkr from current location | ||
| 154 | # Run with: bitbake vdkr-native -c print_usage | ||
| 155 | python do_print_usage() { | ||
| 156 | import os | ||
| 157 | bindir = d.getVar('D') + d.getVar('bindir') | ||
| 158 | |||
| 159 | # Find the actual install location | ||
| 160 | image_dir = d.getVar('D') | ||
| 161 | native_sysroot = d.getVar('STAGING_DIR_NATIVE') | ||
| 162 | |||
| 163 | bb.plain("") | ||
| 164 | bb.plain("=" * 70) | ||
| 165 | bb.plain("vdkr Usage Instructions") | ||
| 166 | bb.plain("=" * 70) | ||
| 167 | bb.plain("") | ||
| 168 | bb.plain("Option 1: Add to PATH (recommended)") | ||
| 169 | bb.plain("-" * 40) | ||
| 170 | bb.plain("export PATH=\"%s:$PATH\"" % (native_sysroot + d.getVar('bindir'))) | ||
| 171 | bb.plain("") | ||
| 172 | bb.plain("Then use:") | ||
| 173 | bb.plain(" vdkr images # Uses default arch (host or config)") | ||
| 174 | bb.plain(" vdkr -a aarch64 images # Explicit arch") | ||
| 175 | bb.plain(" vdkr-x86_64 images # Symlink (backwards compatible)") | ||
| 176 | bb.plain("") | ||
| 177 | bb.plain("Option 2: Direct invocation") | ||
| 178 | bb.plain("-" * 40) | ||
| 179 | bb.plain("%s/vdkr images" % (native_sysroot + d.getVar('bindir'))) | ||
| 180 | bb.plain("") | ||
| 181 | bb.plain("Option 3: Set default architecture") | ||
| 182 | bb.plain("-" * 40) | ||
| 183 | bb.plain("mkdir -p ~/.config/vdkr && echo 'aarch64' > ~/.config/vdkr/arch") | ||
| 184 | bb.plain("") | ||
| 185 | bb.plain("Note: QEMU must be in PATH. If not found, also add:") | ||
| 186 | bb.plain("export PATH=\"%s:$PATH\"" % (d.getVar('STAGING_BINDIR_NATIVE'))) | ||
| 187 | bb.plain("") | ||
| 188 | bb.plain("=" * 70) | ||
| 189 | } | ||
| 190 | addtask print_usage | ||
| 191 | do_print_usage[nostamp] = "1" | ||
diff --git a/recipes-containers/vcontainer/vdkr-rootfs-image.bb b/recipes-containers/vcontainer/vdkr-rootfs-image.bb new file mode 100644 index 00000000..a9bbb9fa --- /dev/null +++ b/recipes-containers/vcontainer/vdkr-rootfs-image.bb | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 2 | # | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # vdkr-rootfs-image.bb | ||
| 6 | # Minimal Docker-capable image for vdkr QEMU environment | ||
| 7 | # | ||
| 8 | # This image is built via multiconfig and used by vdkr-initramfs-create | ||
| 9 | # to provide a proper rootfs for running Docker in QEMU. | ||
| 10 | # | ||
| 11 | # Build with: | ||
| 12 | # bitbake mc:vdkr-aarch64:vdkr-rootfs-image | ||
| 13 | # bitbake mc:vdkr-x86-64:vdkr-rootfs-image | ||
| 14 | |||
| 15 | SUMMARY = "Minimal Docker rootfs for vdkr" | ||
| 16 | DESCRIPTION = "A minimal image containing Docker tools for use with vdkr. \ | ||
| 17 | This image runs inside QEMU to provide Docker 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/vdkr-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 | # We need Docker and container tools | ||
| 35 | IMAGE_INSTALL = " \ | ||
| 36 | packagegroup-core-boot \ | ||
| 37 | docker-moby \ | ||
| 38 | containerd \ | ||
| 39 | runc \ | ||
| 40 | skopeo \ | ||
| 41 | busybox \ | ||
| 42 | iproute2 \ | ||
| 43 | util-linux \ | ||
| 44 | " | ||
| 45 | |||
| 46 | # No extra features needed | ||
| 47 | IMAGE_FEATURES = "" | ||
| 48 | |||
| 49 | # Keep the image small | ||
| 50 | IMAGE_ROOTFS_SIZE = "524288" | ||
| 51 | IMAGE_ROOTFS_EXTRA_SPACE = "0" | ||
| 52 | |||
| 53 | # Use squashfs for smaller size (~3x compression) | ||
| 54 | # The preinit mounts squashfs read-only with tmpfs overlay for writes | ||
| 55 | IMAGE_FSTYPES = "squashfs" | ||
| 56 | |||
| 57 | # Install our init script | ||
| 58 | ROOTFS_POSTPROCESS_COMMAND += "install_vdkr_init;" | ||
| 59 | |||
| 60 | install_vdkr_init() { | ||
| 61 | # Install vdkr-init.sh as /init and vcontainer-init-common.sh alongside it | ||
| 62 | install -m 0755 ${THISDIR}/files/vdkr-init.sh ${IMAGE_ROOTFS}/init | ||
| 63 | install -m 0755 ${THISDIR}/files/vcontainer-init-common.sh ${IMAGE_ROOTFS}/vcontainer-init-common.sh | ||
| 64 | |||
| 65 | # Create required directories | ||
| 66 | install -d ${IMAGE_ROOTFS}/mnt/input | ||
| 67 | install -d ${IMAGE_ROOTFS}/mnt/state | ||
| 68 | install -d ${IMAGE_ROOTFS}/var/lib/docker | ||
| 69 | install -d ${IMAGE_ROOTFS}/run/containerd | ||
| 70 | |||
| 71 | # Create skopeo policy | ||
| 72 | install -d ${IMAGE_ROOTFS}/etc/containers | ||
| 73 | echo '{"default":[{"type":"insecureAcceptAnything"}]}' > ${IMAGE_ROOTFS}/etc/containers/policy.json | ||
| 74 | } | ||
