From 485e35da38150388b86dc7d86840368d2fca1bfa Mon Sep 17 00:00:00 2001 From: Bruce Ashfield Date: Mon, 12 Jan 2026 16:09:12 +0000 Subject: container-registry: add local OCI registry infrastructure Add container registry support for Yocto container workflows: - container-registry.bbclass with helper functions - container-registry-index.bb generates helper script with baked paths - docker-registry-config.bb for Docker daemon on targets - container-oci-registry-config.bb for Podman/Skopeo/Buildah targets - IMAGE_FEATURES container-registry for easy target configuration Signed-off-by: Bruce Ashfield --- classes/container-registry.bbclass | 203 ++++++++++ conf/layer.conf | 6 + recipes-containers/container-registry/README.md | 140 +++++++ .../container-oci-registry-config.bb | 110 ++++++ .../container-registry/container-registry-index.bb | 433 +++++++++++++++++++++ .../container-registry-populate.bb | 109 ++++++ .../container-registry/docker-registry-config.bb | 84 ++++ .../files/container-registry-dev.yml | 61 +++ .../container-registry/files/container-registry.sh | 268 +++++++++++++ recipes-extended/images/container-image-host.bb | 8 + 10 files changed, 1422 insertions(+) create mode 100644 classes/container-registry.bbclass create mode 100644 recipes-containers/container-registry/README.md create mode 100644 recipes-containers/container-registry/container-oci-registry-config.bb create mode 100644 recipes-containers/container-registry/container-registry-index.bb create mode 100644 recipes-containers/container-registry/container-registry-populate.bb create mode 100644 recipes-containers/container-registry/docker-registry-config.bb create mode 100644 recipes-containers/container-registry/files/container-registry-dev.yml create mode 100644 recipes-containers/container-registry/files/container-registry.sh diff --git a/classes/container-registry.bbclass b/classes/container-registry.bbclass new file mode 100644 index 00000000..f5a2d0c3 --- /dev/null +++ b/classes/container-registry.bbclass @@ -0,0 +1,203 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield +# +# SPDX-License-Identifier: MIT +# +# container-registry.bbclass +# =========================================================================== +# Container registry operations for pushing OCI images to registries +# =========================================================================== +# +# This class provides functions to push OCI images from the deploy directory +# to a container registry. It works with docker-distribution, Docker Hub, +# or any OCI-compliant registry. +# +# Usage: +# inherit container-registry +# +# # In do_populate_registry task: +# container_registry_push(d, oci_path, image_name) +# +# Configuration: +# CONTAINER_REGISTRY_URL = "localhost:5000" # Registry endpoint +# CONTAINER_REGISTRY_NAMESPACE = "yocto" # Image namespace +# CONTAINER_REGISTRY_TLS_VERIFY = "false" # TLS verification +# CONTAINER_REGISTRY_TAG_STRATEGY = "timestamp latest" # Tag generation +# CONTAINER_REGISTRY_STORAGE = "${TOPDIR}/container-registry" # Persistent storage +# +# =========================================================================== + +# Registry configuration +CONTAINER_REGISTRY_URL ?= "localhost:5000" +CONTAINER_REGISTRY_NAMESPACE ?= "yocto" +CONTAINER_REGISTRY_TLS_VERIFY ?= "false" +CONTAINER_REGISTRY_TAG_STRATEGY ?= "timestamp latest" + +# Storage location for registry data (default: outside tmp/, persists across builds) +# Set in local.conf to customize, e.g.: +# CONTAINER_REGISTRY_STORAGE = "/data/container-registry" +# CONTAINER_REGISTRY_STORAGE = "${TOPDIR}/../container-registry" +CONTAINER_REGISTRY_STORAGE ?= "${TOPDIR}/container-registry" + +# Require skopeo-native for registry operations +DEPENDS += "skopeo-native" + +def container_registry_generate_tags(d, image_name): + """Generate tags based on CONTAINER_REGISTRY_TAG_STRATEGY. + + Strategies: + timestamp - YYYYMMDD-HHMMSS format + git - Short git hash if in git repo + version - PV from recipe or image name + latest - Always includes 'latest' tag + arch - Appends architecture suffix + + Returns list of tags to apply. + """ + import datetime + import subprocess + + strategy = (d.getVar('CONTAINER_REGISTRY_TAG_STRATEGY') or 'latest').split() + tags = [] + + for strat in strategy: + if strat == 'timestamp': + ts = datetime.datetime.now().strftime('%Y%m%d-%H%M%S') + tags.append(ts) + elif strat == 'git': + try: + git_hash = subprocess.check_output( + ['git', 'rev-parse', '--short', 'HEAD'], + stderr=subprocess.DEVNULL, + cwd=d.getVar('TOPDIR') + ).decode().strip() + if git_hash: + tags.append(git_hash) + except (subprocess.CalledProcessError, FileNotFoundError): + pass + elif strat == 'version': + pv = d.getVar('PV') + if pv and pv != '1.0': + tags.append(pv) + elif strat == 'latest': + tags.append('latest') + elif strat == 'arch': + arch = d.getVar('TARGET_ARCH') or d.getVar('BUILD_ARCH') + if arch: + # Add arch suffix to existing tags + arch_tags = [f"{t}-{arch}" for t in tags if t != 'latest'] + tags.extend(arch_tags) + + # Ensure at least one tag + if not tags: + tags = ['latest'] + + return tags + +def container_registry_push(d, oci_path, image_name, tags=None): + """Push an OCI image to the configured registry. + + Args: + d: BitBake datastore + oci_path: Path to OCI directory (containing index.json) + image_name: Name for the image (without registry/namespace) + tags: Optional list of tags (default: generated from strategy) + + Returns: + List of pushed image references (registry/namespace/name:tag) + """ + import os + import subprocess + + registry = d.getVar('CONTAINER_REGISTRY_URL') + namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE') + tls_verify = d.getVar('CONTAINER_REGISTRY_TLS_VERIFY') + + # Find skopeo in native sysroot + staging_sbindir = d.getVar('STAGING_SBINDIR_NATIVE') + skopeo = os.path.join(staging_sbindir, 'skopeo') + + if not os.path.exists(skopeo): + bb.fatal(f"skopeo not found at {skopeo} - ensure skopeo-native is built") + + # Validate OCI directory + index_json = os.path.join(oci_path, 'index.json') + if not os.path.exists(index_json): + bb.fatal(f"Invalid OCI directory: {oci_path} (missing index.json)") + + # Generate tags if not provided + if tags is None: + tags = container_registry_generate_tags(d, image_name) + + pushed = [] + src = f"oci:{oci_path}" + + for tag in tags: + dest = f"docker://{registry}/{namespace}/{image_name}:{tag}" + + cmd = [skopeo, 'copy'] + if tls_verify == 'false': + cmd.append('--dest-tls-verify=false') + cmd.extend([src, dest]) + + bb.note(f"Pushing {image_name}:{tag} to {registry}/{namespace}/") + + try: + subprocess.check_call(cmd) + pushed.append(f"{registry}/{namespace}/{image_name}:{tag}") + bb.note(f"Successfully pushed {dest}") + except subprocess.CalledProcessError as e: + bb.error(f"Failed to push {dest}: {e}") + + return pushed + +def container_registry_discover_oci_images(d): + """Discover OCI images in the deploy directory. + + Finds directories matching *-oci or *-latest-oci patterns + that contain valid OCI layouts (index.json). + + Returns: + List of tuples: (oci_path, image_name) + """ + import os + + deploy_dir = d.getVar('DEPLOY_DIR_IMAGE') + if not deploy_dir or not os.path.isdir(deploy_dir): + return [] + + images = [] + + for entry in os.listdir(deploy_dir): + # Match *-oci or *-latest-oci directories + if not (entry.endswith('-oci') or entry.endswith('-latest-oci')): + continue + + oci_path = os.path.join(deploy_dir, entry) + if not os.path.isdir(oci_path): + continue + + # Verify valid OCI layout + if not os.path.exists(os.path.join(oci_path, 'index.json')): + continue + + # Extract image name from directory name + # container-base-qemux86-64.rootfs-20260108.rootfs-oci -> container-base + # container-base-latest-oci -> container-base + name = entry + for suffix in ['-latest-oci', '-oci']: + if name.endswith(suffix): + name = name[:-len(suffix)] + break + + # Remove machine suffix if present (e.g., -qemux86-64) + machine = d.getVar('MACHINE') + if machine and f'-{machine}' in name: + name = name.split(f'-{machine}')[0] + + # Remove rootfs timestamp suffix (e.g., .rootfs-20260108) + if '.rootfs-' in name: + name = name.split('.rootfs-')[0] + + images.append((oci_path, name)) + + return images diff --git a/conf/layer.conf b/conf/layer.conf index 5ec45ee0..85694344 100644 --- a/conf/layer.conf +++ b/conf/layer.conf @@ -50,6 +50,12 @@ CONTAINER_PROFILE ?= "default" # virt profile can be: kvm, xen, runx VIRTUALIZATION_PROFILE ?= "default" +# Custom IMAGE_FEATURES for container images +# container-registry: Install registry config based on container engine +# Requires: CONTAINER_REGISTRY_URL (for OCI) or DOCKER_REGISTRY_INSECURE (for Docker) +# Usage: IMAGE_FEATURES:append = " container-registry" +IMAGE_FEATURES[validitems] += "container-registry" + # Sanity check for meta-virtualization layer. # Setting SKIP_META_VIRT_SANITY_CHECK to "1" would skip the bbappend files check. INHERIT += "sanity-meta-virt" diff --git a/recipes-containers/container-registry/README.md b/recipes-containers/container-registry/README.md new file mode 100644 index 00000000..11db39bb --- /dev/null +++ b/recipes-containers/container-registry/README.md @@ -0,0 +1,140 @@ +# Container Registry Infrastructure + +Local container registry for Yocto/OE builds - analogous to package-index for containers. + +## Quick Start + +```bash +# 1. Configure in local.conf +CONTAINER_REGISTRY_URL = "localhost:5000" +CONTAINER_REGISTRY_NAMESPACE = "yocto" +CONTAINER_REGISTRY_INSECURE = "1" + +# 2. Generate the helper script +bitbake container-registry-index -c generate_registry_script + +# 3. Start registry, push images +$TOPDIR/container-registry/container-registry.sh start +$TOPDIR/container-registry/container-registry.sh push + +# 4. Import 3rd party images +$TOPDIR/container-registry/container-registry.sh import docker.io/library/alpine:latest + +# 5. Use with vdkr (10.0.2.2 is QEMU slirp gateway to localhost) +vdkr vconfig registry 10.0.2.2:5000/yocto +vdkr pull container-base +``` + +## Helper Script Commands + +Script location: `${TOPDIR}/container-registry/container-registry.sh` (outside tmp/, persists) + +| Command | Description | +|---------|-------------| +| `start` | Start the container registry server | +| `stop` | Stop the container registry server | +| `status` | Check if registry is running | +| `push` | Push all OCI images from deploy/ to registry | +| `import [name]` | Import 3rd party image to registry | +| `list` | List all images with their tags | +| `tags ` | List tags for a specific image | +| `catalog` | Raw API catalog output | + +## Configuration (local.conf) + +```bitbake +# Registry endpoint (host-side) +CONTAINER_REGISTRY_URL = "localhost:5000" + +# Image namespace +CONTAINER_REGISTRY_NAMESPACE = "yocto" + +# Mark as insecure (HTTP) +CONTAINER_REGISTRY_INSECURE = "1" + +# For Docker targets +DOCKER_REGISTRY_INSECURE = "localhost:5000" + +# Persistent storage (default: ${TOPDIR}/container-registry) +CONTAINER_REGISTRY_STORAGE = "/data/container-registry" +``` + +## vdkr Registry Usage + +### Pull Behavior with Registry Fallback + +When a registry is configured, vdkr uses **registry-first, Docker Hub fallback** for pulls: + +1. Try configured registry first (e.g., `10.0.2.2:5000/yocto/alpine`) +2. If not found, fall back to Docker Hub (`docker.io/library/alpine`) + +This allows you to override images with local builds while still pulling public images normally. + +```bash +# One-off +vdkr --registry 10.0.2.2:5000/yocto pull alpine + +# Persistent config +vdkr vconfig registry 10.0.2.2:5000/yocto +vdkr pull alpine # Tries registry first, falls back to Docker Hub +vdkr pull container-base # Pulls from registry (your Yocto-built image) +vdkr run alpine echo hello + +# Clear config +vdkr vconfig registry --reset + +# Image management (all commands use registry prefix for stored images) +vdkr image ls +vdkr image inspect alpine # Works for both registry and Docker Hub images +vdkr image rm +vdkr image rm e7b39c54cdec # Image IDs work without transformation +``` + +### Registry Transform + +When a registry is configured: +- `pull`, `run` - Use fallback (registry first, then Docker Hub) +- `inspect`, `history`, `rmi`, `tag`, `images` - No transform (use actual local image names) +- Image IDs (hex strings like `e7b39c54cdec`) - Never transformed + +## Baking Registry Config into Target Images + +Use `IMAGE_FEATURES` to auto-select the right package based on `CONTAINER_PROFILE`: + +```bitbake +# In local.conf +CONTAINER_REGISTRY_URL = "localhost:5000" +CONTAINER_REGISTRY_INSECURE = "1" +DOCKER_REGISTRY_INSECURE = "localhost:5000" + +# Enable the feature +IMAGE_FEATURES:append = " container-registry" +``` + +This installs: +- **Docker profile** → `docker-registry-config` → `/etc/docker/daemon.json` +- **Podman profile** → `container-oci-registry-config` → `/etc/containers/registries.conf.d/` + +## Files + +| File | Description | +|------|-------------| +| `container-registry-index.bb` | Generates helper script with baked-in paths | +| `container-registry-populate.bb` | Alternative bitbake-driven push | +| `container-oci-registry-config.bb` | OCI tools config (Podman/Skopeo/Buildah/CRI-O) | +| `docker-registry-config.bb` | Docker daemon config | +| `files/container-registry-dev.yml` | Development registry config | + +## Storage + +Registry data and script are stored at `${TOPDIR}/container-registry/` by default: +- Outside tmp/, persists across builds and cleanall +- Imported and pushed images are copied here +- Script regenerates with same paths after tmp/ cleanup + +## Localhost to 10.0.2.2 Translation + +For vdkr baked configs, `localhost` URLs are auto-translated to `10.0.2.2` (QEMU slirp gateway): +- Set `CONTAINER_REGISTRY_URL = "localhost:5000"` in local.conf +- Host-side operations use localhost directly +- vdkr inside QEMU accesses via 10.0.2.2 automatically diff --git a/recipes-containers/container-registry/container-oci-registry-config.bb b/recipes-containers/container-registry/container-oci-registry-config.bb new file mode 100644 index 00000000..ee6760f4 --- /dev/null +++ b/recipes-containers/container-registry/container-oci-registry-config.bb @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield +# +# SPDX-License-Identifier: MIT +# +# container-oci-registry-config.bb +# =========================================================================== +# Configure custom container registry for OCI runtimes (OPT-IN) +# =========================================================================== +# +# FOR OCI-COMPATIBLE RUNTIMES (use /etc/containers/registries.conf.d/): +# - Podman +# - Skopeo +# - Buildah +# - CRI-O +# +# NOT FOR DOCKER - Docker uses /etc/docker/daemon.json +# See: docker-registry-config.bb for Docker configuration +# +# This recipe creates a drop-in configuration file for accessing a custom +# container registry. It is completely OPT-IN and does not modify any +# existing configuration files. +# +# IMPORTANT: This recipe: +# - Does NOT modify docker-distribution or container-host-config +# - Does NOT install automatically - user must add to IMAGE_INSTALL +# - Does NOT clobber public registry access (docker.io, quay.io, etc.) +# - Uses drop-in files in /etc/containers/registries.conf.d/ +# - Skips entirely if CONTAINER_REGISTRY_URL is not set +# +# Usage: +# # In local.conf or image recipe - BOTH required: +# CONTAINER_REGISTRY_URL = "localhost:5000" +# CONTAINER_REGISTRY_INSECURE = "1" +# IMAGE_INSTALL:append = " container-oci-registry-config" +# +# =========================================================================== + +SUMMARY = "Configure custom container registry for Podman/Skopeo/Buildah (opt-in)" +DESCRIPTION = "Adds drop-in configuration for Podman, Skopeo, Buildah, and CRI-O. \ +NOT for Docker (use docker-registry-config for Docker). \ +Does NOT modify existing registries.conf - creates a separate file in \ +registries.conf.d/ that is merged at runtime. Public registries remain accessible. \ +This recipe is opt-in: requires CONTAINER_REGISTRY_URL to be set. \ +Use IMAGE_FEATURES container-registry to auto-select based on container engine." + +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" + +# User MUST set these - recipe skips otherwise +CONTAINER_REGISTRY_URL ?= "" +CONTAINER_REGISTRY_INSECURE ?= "0" +CONTAINER_REGISTRY_SEARCH_FIRST ?= "1" + +inherit allarch + +# Skip recipe entirely if not configured +# User must explicitly set CONTAINER_REGISTRY_URL to enable +python() { + registry = d.getVar('CONTAINER_REGISTRY_URL') + if not registry: + raise bb.parse.SkipRecipe("CONTAINER_REGISTRY_URL not set - recipe is opt-in only") +} + +python do_install() { + import os + + registry = d.getVar('CONTAINER_REGISTRY_URL') + insecure = d.getVar('CONTAINER_REGISTRY_INSECURE') == "1" + search_first = d.getVar('CONTAINER_REGISTRY_SEARCH_FIRST') == "1" + + dest = d.getVar('D') + confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'), + 'containers', 'registries.conf.d') + os.makedirs(confdir, exist_ok=True) + + # Generate drop-in config + # Filename starts with 50- so it's processed after base config but + # can be overridden by higher-numbered files + config_path = os.path.join(confdir, '50-custom-registry.conf') + + with open(config_path, 'w') as f: + f.write(f"# Custom container registry: {registry}\n") + f.write(f"# Generated by container-registry-config recipe\n") + f.write(f"# This is ADDITIVE - base registries.conf is unchanged\n") + f.write(f"# Public registries (docker.io, quay.io) remain accessible\n") + f.write(f"#\n") + f.write(f"# To remove: uninstall container-registry-config package\n") + f.write(f"# or delete this file\n\n") + + if search_first: + # Add to unqualified-search-registries + # This means short names like "myapp:latest" will search here first + f.write(f"# Search this registry for unqualified image names\n") + f.write(f'unqualified-search-registries = ["{registry}"]\n\n') + + if insecure: + # Mark registry as insecure (HTTP or self-signed TLS) + f.write(f"# Registry uses HTTP or has untrusted TLS certificate\n") + f.write(f'[[registry]]\n') + f.write(f'location = "{registry}"\n') + f.write(f'insecure = true\n') + + bb.note(f"Created registry config for {registry} (insecure={insecure})") +} + +FILES:${PN} = "${sysconfdir}/containers/registries.conf.d" + +# Soft dependency - works with or without container-host-config +# If container-host-config is installed, our drop-in extends it +RRECOMMENDS:${PN} = "container-host-config" diff --git a/recipes-containers/container-registry/container-registry-index.bb b/recipes-containers/container-registry/container-registry-index.bb new file mode 100644 index 00000000..c3a48e94 --- /dev/null +++ b/recipes-containers/container-registry/container-registry-index.bb @@ -0,0 +1,433 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield +# +# SPDX-License-Identifier: MIT +# +# container-registry-index.bb +# =========================================================================== +# Push OCI container images to a registry (like package-index for containers) +# =========================================================================== +# +# This is the container equivalent of meta/recipes-core/meta/package-index.bb +# It discovers OCI images in DEPLOY_DIR_IMAGE and pushes them to a registry. +# +# Usage: +# # Start registry first (separate terminal): +# oe-run-native docker-distribution-native registry serve config.yml +# +# # Push all container images to registry: +# bitbake container-registry-index +# +# # Or use the helper script: +# oe-run-native container-registry-index +# +# Configuration (in local.conf): +# CONTAINER_REGISTRY_URL = "localhost:5000" +# CONTAINER_REGISTRY_NAMESPACE = "yocto" +# CONTAINER_REGISTRY_IMAGES = "container-base container-app" # optional filter +# +# =========================================================================== + +SUMMARY = "Populate container registry with OCI images" +LICENSE = "MIT" + +INHIBIT_DEFAULT_DEPS = "1" +PACKAGES = "" + +inherit nopackages container-registry + +deltask do_fetch +deltask do_unpack +deltask do_patch +deltask do_configure +deltask do_compile +deltask do_install +deltask do_populate_lic +deltask do_populate_sysroot + +do_container_registry_index[nostamp] = "1" +do_container_registry_index[network] = "1" +do_container_registry_index[depends] += "skopeo-native:do_populate_sysroot" + +python do_container_registry_index() { + import os + + registry = d.getVar('CONTAINER_REGISTRY_URL') + namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE') + specific_images = (d.getVar('CONTAINER_REGISTRY_IMAGES') or '').split() + + bb.plain(f"Container Registry Index: {registry}/{namespace}/") + + # Discover OCI images + all_images = container_registry_discover_oci_images(d) + + if not all_images: + bb.warn("No OCI images found in deploy directory") + bb.plain(f"Deploy directory: {d.getVar('DEPLOY_DIR_IMAGE')}") + bb.plain("Build container images first: bitbake container-base") + return + + bb.plain(f"Found {len(all_images)} OCI images") + + # Filter if specific images requested + if specific_images: + images = [(path, name) for path, name in all_images if name in specific_images] + else: + images = all_images + + # Push each image + pushed_refs = [] + for oci_path, image_name in images: + bb.plain(f"Pushing: {image_name}") + refs = container_registry_push(d, oci_path, image_name) + pushed_refs.extend(refs) + + bb.plain(f"Pushed {len(pushed_refs)} image references to {registry}") +} + +addtask do_container_registry_index before do_build + +# Generate a helper script with paths baked in +# Script is placed alongside registry storage (outside tmp/) so it persists +CONTAINER_REGISTRY_SCRIPT = "${CONTAINER_REGISTRY_STORAGE}/container-registry.sh" + +python do_generate_registry_script() { + import os + import stat + + script_path = d.getVar('CONTAINER_REGISTRY_SCRIPT') + deploy_dir = d.getVar('DEPLOY_DIR') + deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE') + + # Find registry binary path + native_sysroot = d.getVar('STAGING_DIR_NATIVE') or '' + registry_bin = os.path.join(native_sysroot, 'usr', 'sbin', 'registry') + + # Find skopeo binary path + skopeo_bin = os.path.join(d.getVar('STAGING_SBINDIR_NATIVE') or '', 'skopeo') + + # Config file path + config_file = os.path.join(d.getVar('THISDIR'), 'files', 'container-registry-dev.yml') + + # Registry settings + registry_url = d.getVar('CONTAINER_REGISTRY_URL') + registry_namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE') + registry_storage = d.getVar('CONTAINER_REGISTRY_STORAGE') + + os.makedirs(deploy_dir, exist_ok=True) + + script = f'''#!/bin/bash +# Container Registry Helper Script +# Generated by: bitbake container-registry-index -c generate_registry_script +# +# This script has all paths pre-configured for your build. +# +# Usage: +# {script_path} start # Start registry server +# {script_path} stop # Stop registry server +# {script_path} status # Check if running +# {script_path} push # Push OCI images to registry +# {script_path} import # Import 3rd party image +# {script_path} list # List all images with tags +# {script_path} tags # List tags for an image +# {script_path} catalog # List image names (raw API) + +set -e + +# Pre-configured paths from bitbake +REGISTRY_BIN="{registry_bin}" +SKOPEO_BIN="{skopeo_bin}" +REGISTRY_CONFIG="{config_file}" +REGISTRY_STORAGE="{registry_storage}" +REGISTRY_URL="{registry_url}" +REGISTRY_NAMESPACE="{registry_namespace}" +DEPLOY_DIR_IMAGE="{deploy_dir_image}" + +PID_FILE="/tmp/container-registry.pid" +LOG_FILE="/tmp/container-registry.log" + +cmd_start() {{ + if [ -f "$PID_FILE" ] && kill -0 "$(cat $PID_FILE)" 2>/dev/null; then + echo "Registry already running (PID: $(cat $PID_FILE))" + return 0 + fi + + if [ ! -x "$REGISTRY_BIN" ]; then + echo "Error: Registry binary not found at $REGISTRY_BIN" + echo "Build it with: bitbake docker-distribution-native" + return 1 + fi + + mkdir -p "$REGISTRY_STORAGE" + export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY="$REGISTRY_STORAGE" + + echo "Starting container registry..." + echo " URL: http://$REGISTRY_URL" + echo " Storage: $REGISTRY_STORAGE" + echo " Config: $REGISTRY_CONFIG" + + nohup "$REGISTRY_BIN" serve "$REGISTRY_CONFIG" > "$LOG_FILE" 2>&1 & + echo $! > "$PID_FILE" + sleep 2 + + if kill -0 "$(cat $PID_FILE)" 2>/dev/null; then + echo "Registry started (PID: $(cat $PID_FILE))" + echo "Logs: $LOG_FILE" + else + echo "Failed to start registry. Check $LOG_FILE" + cat "$LOG_FILE" + return 1 + fi +}} + +cmd_stop() {{ + if [ ! -f "$PID_FILE" ]; then + echo "Registry not running" + return 0 + fi + + local pid=$(cat "$PID_FILE") + if kill -0 "$pid" 2>/dev/null; then + echo "Stopping registry (PID: $pid)..." + kill "$pid" + rm -f "$PID_FILE" + echo "Registry stopped" + else + rm -f "$PID_FILE" + echo "Registry not running (stale PID file removed)" + fi +}} + +cmd_status() {{ + if [ -f "$PID_FILE" ] && kill -0 "$(cat $PID_FILE)" 2>/dev/null; then + echo "Registry running (PID: $(cat $PID_FILE))" + echo "URL: http://$REGISTRY_URL" + if curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then + echo "Status: healthy" + else + echo "Status: not responding" + fi + else + echo "Registry not running" + return 1 + fi +}} + +cmd_push() {{ + if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then + echo "Registry not responding at http://$REGISTRY_URL" + echo "Start it first: $0 start" + return 1 + fi + + echo "Pushing OCI images from $DEPLOY_DIR_IMAGE" + echo "To registry: $REGISTRY_URL/$REGISTRY_NAMESPACE/" + echo "" + + for oci_dir in "$DEPLOY_DIR_IMAGE"/*-oci; do + [ -d "$oci_dir" ] || continue + [ -f "$oci_dir/index.json" ] || continue + + name=$(basename "$oci_dir" | sed 's/-latest-oci$//' | sed 's/-oci$//') + # Remove machine suffix + name=$(echo "$name" | sed 's/-qemux86-64//' | sed 's/-qemuarm64//') + # Remove rootfs timestamp + name=$(echo "$name" | sed 's/\\.rootfs-[0-9]*//') + + echo "Pushing: $name" + "$SKOPEO_BIN" copy --dest-tls-verify=false \\ + "oci:$oci_dir" \\ + "docker://$REGISTRY_URL/$REGISTRY_NAMESPACE/$name:latest" + done + + echo "" + echo "Done. Catalog:" + cmd_catalog +}} + +cmd_catalog() {{ + curl -s "http://$REGISTRY_URL/v2/_catalog" | python3 -m json.tool 2>/dev/null || \\ + curl -s "http://$REGISTRY_URL/v2/_catalog" +}} + +cmd_tags() {{ + local image="${{2:-}}" + + if [ -z "$image" ]; then + echo "Usage: $0 tags " + echo "" + echo "Examples:" + echo " $0 tags alpine" + echo " $0 tags yocto/container-base" + return 1 + fi + + # Add namespace if not already qualified + if ! echo "$image" | grep -q '/'; then + image="$REGISTRY_NAMESPACE/$image" + fi + + local result=$(curl -s "http://$REGISTRY_URL/v2/$image/tags/list") + + # Check for errors or empty result + if [ -z "$result" ]; then + echo "Image not found: $image" + return 1 + fi + + if echo "$result" | grep -qE '"errors"|NAME_UNKNOWN|MANIFEST_UNKNOWN'; then + echo "Image not found: $image" + return 1 + fi + + # Check if tags array is null or empty + if echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('tags') else 1)" 2>/dev/null; then + echo "$result" | python3 -m json.tool 2>/dev/null || echo "$result" + else + echo "Image not found: $image" + return 1 + fi +}} + +cmd_list() {{ + if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then + echo "Registry not responding at http://$REGISTRY_URL" + return 1 + fi + + echo "Images in $REGISTRY_URL:" + echo "" + + local repos=$(curl -s "http://$REGISTRY_URL/v2/_catalog" | python3 -c "import sys,json; print('\\n'.join(json.load(sys.stdin).get('repositories',[])))" 2>/dev/null) + + if [ -z "$repos" ]; then + echo " (none)" + return 0 + fi + + for repo in $repos; do + local tags=$(curl -s "http://$REGISTRY_URL/v2/$repo/tags/list" | python3 -c "import sys,json; print(' '.join(json.load(sys.stdin).get('tags',[])))" 2>/dev/null) + if [ -n "$tags" ]; then + echo " $repo: $tags" + else + echo " $repo: (no tags)" + fi + done +}} + +cmd_import() {{ + local source="${{2:-}}" + local dest_name="${{3:-}}" + + if [ -z "$source" ]; then + echo "Usage: $0 import [local-name]" + echo "" + echo "Examples:" + echo " $0 import docker.io/library/alpine:latest" + echo " $0 import docker.io/library/alpine:latest my-alpine" + echo " $0 import quay.io/podman/hello:latest hello" + echo " $0 import ghcr.io/owner/image:tag" + return 1 + fi + + if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then + echo "Registry not responding at http://$REGISTRY_URL" + echo "Start it first: $0 start" + return 1 + fi + + # Extract image name if not provided + if [ -z "$dest_name" ]; then + # docker.io/library/alpine:latest -> alpine + # quay.io/podman/hello:latest -> hello + dest_name=$(echo "$source" | rev | cut -d'/' -f1 | rev | cut -d':' -f1) + fi + + # Extract tag from source, default to latest + local tag="latest" + if echo "$source" | grep -q ':'; then + tag=$(echo "$source" | rev | cut -d':' -f1 | rev) + fi + + echo "Importing: $source" + echo " To: $REGISTRY_URL/$REGISTRY_NAMESPACE/$dest_name:$tag" + echo "" + + "$SKOPEO_BIN" copy \\ + --dest-tls-verify=false \\ + "docker://$source" \\ + "docker://$REGISTRY_URL/$REGISTRY_NAMESPACE/$dest_name:$tag" + + echo "" + echo "Import complete. Pull with:" + echo " vdkr --registry $REGISTRY_URL/$REGISTRY_NAMESPACE pull $dest_name" + echo " # or configure: vdkr vconfig registry $REGISTRY_URL/$REGISTRY_NAMESPACE" + echo " # then: vdkr pull $dest_name" +}} + +cmd_help() {{ + echo "Usage: $0 " + echo "" + echo "Commands:" + echo " start Start the container registry server" + echo " stop Stop the container registry server" + echo " status Check if registry is running" + echo " push Push all OCI images to registry" + echo " import [name] Import 3rd party image to registry" + echo " list List all images with tags" + echo " tags List tags for an image" + echo " catalog List image names (raw API)" + echo " help Show this help" + echo "" + echo "Examples:" + echo " $0 start" + echo " $0 push" + echo " $0 import docker.io/library/alpine:latest" + echo " $0 import docker.io/library/busybox:latest my-busybox" + echo " $0 list" + echo " $0 tags container-base" + echo "" + echo "Configuration:" + echo " Registry URL: $REGISTRY_URL" + echo " Namespace: $REGISTRY_NAMESPACE" + echo " Storage: $REGISTRY_STORAGE" + echo " Deploy images: $DEPLOY_DIR_IMAGE" +}} + +case "${{1:-help}}" in + start) cmd_start ;; + stop) cmd_stop ;; + status) cmd_status ;; + push) cmd_push ;; + import) cmd_import "$@" ;; + list) cmd_list ;; + tags) cmd_tags "$@" ;; + catalog) cmd_catalog ;; + help|--help|-h) cmd_help ;; + *) echo "Unknown command: $1"; cmd_help; exit 1 ;; +esac +''' + + with open(script_path, 'w') as f: + f.write(script) + + # Make executable + os.chmod(script_path, os.stat(script_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + bb.plain("") + bb.plain("=" * 70) + bb.plain("Generated container registry helper script:") + bb.plain(f" {script_path}") + bb.plain("") + bb.plain("Usage:") + bb.plain(f" {script_path} start # Start registry server") + bb.plain(f" {script_path} push # Push OCI images to registry") + bb.plain(f" {script_path} catalog # List images in registry") + bb.plain(f" {script_path} stop # Stop registry server") + bb.plain("=" * 70) + bb.plain("") +} + +do_generate_registry_script[depends] += "docker-distribution-native:do_populate_sysroot skopeo-native:do_populate_sysroot" +addtask do_generate_registry_script + +EXCLUDE_FROM_WORLD = "1" diff --git a/recipes-containers/container-registry/container-registry-populate.bb b/recipes-containers/container-registry/container-registry-populate.bb new file mode 100644 index 00000000..d44b1051 --- /dev/null +++ b/recipes-containers/container-registry/container-registry-populate.bb @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield +# +# SPDX-License-Identifier: MIT +# +# container-registry-populate.bb +# =========================================================================== +# Push OCI container images from deploy directory to a container registry +# =========================================================================== +# +# This recipe discovers OCI images in DEPLOY_DIR_IMAGE and pushes them +# to the configured container registry using skopeo. +# +# Usage: +# # Set registry URL (default: localhost:5000) +# CONTAINER_REGISTRY_URL = "localhost:5000" +# +# # Push all discovered images +# bitbake container-registry-populate +# +# # Push specific images only +# CONTAINER_REGISTRY_IMAGES = "container-base container-app" +# bitbake container-registry-populate +# +# Prerequisites: +# - docker-distribution-native built and running +# - Container images built (bitbake container-base) +# +# =========================================================================== + +SUMMARY = "Push container images to registry" +DESCRIPTION = "Discovers OCI images in the deploy directory and pushes them \ +to the configured container registry using skopeo. Works with docker-distribution, \ +Docker Hub, or any OCI-compliant registry." + +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" + +inherit container-registry + +# Additional dependencies +DEPENDS += "docker-distribution-native" + +# Specific images to push (empty = auto-discover all) +CONTAINER_REGISTRY_IMAGES ?= "" + +# Work directory +S = "${WORKDIR}/sources" + +do_unpack[noexec] = "1" +do_patch[noexec] = "1" +do_configure[noexec] = "1" +do_compile[noexec] = "1" +do_install[noexec] = "1" + +python do_populate_registry() { + """Push OCI images to the configured registry.""" + import os + + registry = d.getVar('CONTAINER_REGISTRY_URL') + namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE') + specific_images = (d.getVar('CONTAINER_REGISTRY_IMAGES') or '').split() + + bb.note(f"Container Registry: {registry}/{namespace}/") + bb.note(f"Tag Strategy: {d.getVar('CONTAINER_REGISTRY_TAG_STRATEGY')}") + + # Discover OCI images + all_images = container_registry_discover_oci_images(d) + + if not all_images: + bb.warn("No OCI images found in deploy directory") + bb.note(f"Deploy directory: {d.getVar('DEPLOY_DIR_IMAGE')}") + bb.note("Build container images first: bitbake container-base") + return + + bb.note(f"Discovered {len(all_images)} OCI images") + + # Filter if specific images requested + if specific_images: + images = [(path, name) for path, name in all_images if name in specific_images] + if not images: + bb.warn(f"None of the requested images found: {specific_images}") + bb.note(f"Available images: {[name for _, name in all_images]}") + return + else: + images = all_images + + # Push each image + pushed_refs = [] + for oci_path, image_name in images: + bb.note(f"Processing: {image_name} from {oci_path}") + refs = container_registry_push(d, oci_path, image_name) + pushed_refs.extend(refs) + + # Summary + bb.note("=" * 60) + bb.note(f"Pushed {len(pushed_refs)} image references:") + for ref in pushed_refs: + bb.note(f" {ref}") + bb.note("=" * 60) +} + +# Run after prepare_recipe_sysroot so skopeo-native is available +addtask populate_registry after do_prepare_recipe_sysroot before do_build + +# Allow network access for pushing to registry +do_populate_registry[network] = "1" + +# Don't cache - always push fresh +do_populate_registry[nostamp] = "1" diff --git a/recipes-containers/container-registry/docker-registry-config.bb b/recipes-containers/container-registry/docker-registry-config.bb new file mode 100644 index 00000000..eee74c98 --- /dev/null +++ b/recipes-containers/container-registry/docker-registry-config.bb @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield +# +# SPDX-License-Identifier: MIT +# +# docker-registry-config.bb +# =========================================================================== +# Configure custom container registry for Docker daemon (OPT-IN) +# =========================================================================== +# +# FOR DOCKER ONLY - creates /etc/docker/daemon.json +# +# NOT for Podman/Skopeo/Buildah - they use /etc/containers/registries.conf.d/ +# See: container-registry-config.bb for Podman/Skopeo/Buildah +# +# This recipe creates daemon.json for Docker to access insecure registries. +# It is completely OPT-IN and requires explicit configuration. +# +# NOTE: Docker does not support "default registry" like our vdkr transform. +# Users must still use fully qualified image names unless using Docker Hub. +# This config only handles insecure registry trust. +# +# IMPORTANT: This recipe: +# - Does NOT install automatically - user must add to IMAGE_INSTALL +# - Skips entirely if DOCKER_REGISTRY_INSECURE is not set +# - Creates /etc/docker/daemon.json (will be merged if docker recipe +# also creates one, or may need RCONFLICTS handling) +# +# Usage: +# # In local.conf or image recipe: +# DOCKER_REGISTRY_INSECURE = "10.0.2.2:5000 myregistry.local:5000" +# IMAGE_INSTALL:append = " docker-registry-config" +# +# =========================================================================== + +SUMMARY = "Configure insecure container registries for Docker daemon (opt-in)" +DESCRIPTION = "Creates /etc/docker/daemon.json with insecure-registries config. \ +FOR DOCKER ONLY - not for Podman/Skopeo (use container-oci-registry-config for those). \ +This recipe is opt-in: requires DOCKER_REGISTRY_INSECURE to be set. \ +Use IMAGE_FEATURES container-registry to auto-select based on container engine." + +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" + +# Space-separated list of insecure registries +# Example: "10.0.2.2:5000 myregistry.local:5000" +DOCKER_REGISTRY_INSECURE ?= "" + +inherit allarch + +# Skip recipe entirely if not configured +python() { + registries = d.getVar('DOCKER_REGISTRY_INSECURE') + if not registries or not registries.strip(): + raise bb.parse.SkipRecipe("DOCKER_REGISTRY_INSECURE not set - recipe is opt-in only") +} + +python do_install() { + import os + import json + + registries = d.getVar('DOCKER_REGISTRY_INSECURE').split() + + dest = d.getVar('D') + confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'), 'docker') + os.makedirs(confdir, exist_ok=True) + + config_path = os.path.join(confdir, 'daemon.json') + + # Create daemon.json + config = { + "insecure-registries": registries + } + + with open(config_path, 'w') as f: + json.dump(config, f, indent=2) + f.write("\n") + + bb.note(f"Created Docker config with insecure registries: {registries}") +} + +FILES:${PN} = "${sysconfdir}/docker/daemon.json" + +# Docker must be installed for this to be useful +RDEPENDS:${PN} = "docker" diff --git a/recipes-containers/container-registry/files/container-registry-dev.yml b/recipes-containers/container-registry/files/container-registry-dev.yml new file mode 100644 index 00000000..ed0a7c88 --- /dev/null +++ b/recipes-containers/container-registry/files/container-registry-dev.yml @@ -0,0 +1,61 @@ +# Container Registry Development Configuration +# ============================================ +# +# This is a simple configuration for running a local container registry +# for development purposes. It uses filesystem storage and listens on +# port 5000 without TLS. +# +# Usage: +# oe-run-native docker-distribution-native registry serve \ +# /path/to/container-registry-dev.yml +# +# Or with explicit paths: +# /path/to/sysroot-native/usr/sbin/registry serve \ +# /path/to/container-registry-dev.yml +# +# For production, consider: +# - Enabling TLS +# - Adding authentication +# - Using cloud storage (S3, GCS, Azure) +# - Setting up garbage collection +# +# See: https://distribution.github.io/distribution/about/configuration/ + +version: 0.1 + +log: + level: info + formatter: text + fields: + service: container-registry + +storage: + filesystem: + # Storage directory - override with REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY env var + rootdirectory: /tmp/container-registry + # Don't redirect to external storage + redirect: + disable: true + # Maintenance settings + maintenance: + uploadpurging: + enabled: true + age: 168h # 1 week + interval: 24h + dryrun: false + +http: + addr: :5000 + headers: + X-Content-Type-Options: [nosniff] + # For development - allow HTTP. In production, use TLS. + # tls: + # certificate: /path/to/cert.pem + # key: /path/to/key.pem + +# Health check endpoint +health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 diff --git a/recipes-containers/container-registry/files/container-registry.sh b/recipes-containers/container-registry/files/container-registry.sh new file mode 100644 index 00000000..14684c9a --- /dev/null +++ b/recipes-containers/container-registry/files/container-registry.sh @@ -0,0 +1,268 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield +# SPDX-License-Identifier: MIT +# +# container-registry.sh +# ============================================ +# Helper script to start/stop a local container registry +# ============================================ +# +# This script manages a local docker-distribution registry server +# for development purposes. +# +# Usage: +# container-registry.sh start [config.yml] [storage-dir] +# container-registry.sh stop +# container-registry.sh status +# container-registry.sh logs +# +# Examples: +# # Start with defaults (port 5000, storage in /tmp/container-registry) +# container-registry.sh start +# +# # Start with custom config +# container-registry.sh start /path/to/config.yml +# +# # Start with custom storage +# container-registry.sh start /path/to/config.yml /var/lib/registry +# +# Environment: +# REGISTRY_BIN Path to registry binary (auto-detected from oe-run-native) +# REGISTRY_CONFIG Path to config file +# REGISTRY_STORAGE Storage directory +# REGISTRY_PORT Port to listen on (default: 5000) +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PID_FILE="/tmp/container-registry.pid" +LOG_FILE="/tmp/container-registry.log" + +# Default configuration +REGISTRY_PORT="${REGISTRY_PORT:-5000}" +REGISTRY_STORAGE="${REGISTRY_STORAGE:-/tmp/container-registry}" + +# Find registry binary +find_registry_bin() { + # Check if provided via environment + if [ -n "$REGISTRY_BIN" ] && [ -x "$REGISTRY_BIN" ]; then + echo "$REGISTRY_BIN" + return 0 + fi + + # Try to find in Yocto native sysroot + local builddir="${BUILDDIR:-$(pwd)}" + local native_sysroot="$builddir/tmp/work/x86_64-linux/docker-distribution-native" + + if [ -d "$native_sysroot" ]; then + local registry=$(find "$native_sysroot" -name "registry" -type f -executable 2>/dev/null | head -1) + if [ -n "$registry" ]; then + echo "$registry" + return 0 + fi + fi + + # Try system PATH + if command -v registry &>/dev/null; then + command -v registry + return 0 + fi + + return 1 +} + +# Find config file +find_config() { + local config="$1" + + if [ -n "$config" ] && [ -f "$config" ]; then + echo "$config" + return 0 + fi + + # Check environment + if [ -n "$REGISTRY_CONFIG" ] && [ -f "$REGISTRY_CONFIG" ]; then + echo "$REGISTRY_CONFIG" + return 0 + fi + + # Check script directory + if [ -f "$SCRIPT_DIR/container-registry-dev.yml" ]; then + echo "$SCRIPT_DIR/container-registry-dev.yml" + return 0 + fi + + return 1 +} + +cmd_start() { + local config="$1" + local storage="${2:-$REGISTRY_STORAGE}" + + if [ -f "$PID_FILE" ]; then + local pid=$(cat "$PID_FILE") + if kill -0 "$pid" 2>/dev/null; then + echo "Registry already running (PID: $pid)" + return 1 + fi + rm -f "$PID_FILE" + fi + + local registry_bin + if ! registry_bin=$(find_registry_bin); then + echo "Error: Cannot find registry binary" + echo "Build it with: bitbake docker-distribution-native" + return 1 + fi + + local config_file + if ! config_file=$(find_config "$config"); then + echo "Error: Cannot find config file" + echo "Provide config file as argument or set REGISTRY_CONFIG" + return 1 + fi + + # Create storage directory + mkdir -p "$storage" + + echo "Starting container registry..." + echo " Binary: $registry_bin" + echo " Config: $config_file" + echo " Storage: $storage" + echo " Port: $REGISTRY_PORT" + + # Export storage directory for config + export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY="$storage" + + # Start registry in background + nohup "$registry_bin" serve "$config_file" > "$LOG_FILE" 2>&1 & + local pid=$! + echo "$pid" > "$PID_FILE" + + # Wait for startup + sleep 2 + + if kill -0 "$pid" 2>/dev/null; then + echo "Registry started (PID: $pid)" + echo "Access at: http://localhost:$REGISTRY_PORT" + echo "Logs at: $LOG_FILE" + else + echo "Failed to start registry. Check logs: $LOG_FILE" + cat "$LOG_FILE" + return 1 + fi +} + +cmd_stop() { + if [ ! -f "$PID_FILE" ]; then + echo "Registry not running (no PID file)" + return 0 + fi + + local pid=$(cat "$PID_FILE") + + if kill -0 "$pid" 2>/dev/null; then + echo "Stopping registry (PID: $pid)..." + kill "$pid" + sleep 2 + + if kill -0 "$pid" 2>/dev/null; then + echo "Force killing..." + kill -9 "$pid" 2>/dev/null || true + fi + fi + + rm -f "$PID_FILE" + echo "Registry stopped" +} + +cmd_status() { + if [ ! -f "$PID_FILE" ]; then + echo "Registry not running" + return 1 + fi + + local pid=$(cat "$PID_FILE") + + if kill -0 "$pid" 2>/dev/null; then + echo "Registry running (PID: $pid)" + echo "Port: $REGISTRY_PORT" + + # Check if responding + if curl -s "http://localhost:$REGISTRY_PORT/v2/" >/dev/null 2>&1; then + echo "Status: healthy" + + # List images + local catalog=$(curl -s "http://localhost:$REGISTRY_PORT/v2/_catalog" 2>/dev/null) + if [ -n "$catalog" ]; then + echo "Catalog: $catalog" + fi + else + echo "Status: not responding" + fi + else + echo "Registry not running (stale PID file)" + rm -f "$PID_FILE" + return 1 + fi +} + +cmd_logs() { + if [ -f "$LOG_FILE" ]; then + tail -f "$LOG_FILE" + else + echo "No log file found" + return 1 + fi +} + +cmd_help() { + cat << EOF +Usage: $(basename "$0") [options] + +Commands: + start [config] [storage] Start the registry + stop Stop the registry + status Show registry status + logs Tail registry logs + help Show this help + +Environment: + REGISTRY_BIN Path to registry binary + REGISTRY_CONFIG Path to config file + REGISTRY_STORAGE Storage directory (default: /tmp/container-registry) + REGISTRY_PORT Port to listen on (default: 5000) + BUILDDIR Yocto build directory (for finding native binaries) + +Examples: + $(basename "$0") start + $(basename "$0") start /path/to/config.yml + $(basename "$0") status + $(basename "$0") stop +EOF +} + +# Main +case "${1:-help}" in + start) + cmd_start "$2" "$3" + ;; + stop) + cmd_stop + ;; + status) + cmd_status + ;; + logs) + cmd_logs + ;; + help|--help|-h) + cmd_help + ;; + *) + echo "Unknown command: $1" + cmd_help + exit 1 + ;; +esac diff --git a/recipes-extended/images/container-image-host.bb b/recipes-extended/images/container-image-host.bb index 454fcd45..723f0cf5 100644 --- a/recipes-extended/images/container-image-host.bb +++ b/recipes-extended/images/container-image-host.bb @@ -84,6 +84,14 @@ REQUIRED_DISTRO_FEATURES:append = " ${@bb.utils.contains('VIRTUAL-RUNTIME_contai # of the host name to make it unique IMAGE_FEATURES[validitems] += "virt-unique-hostname" IMAGE_FEATURES[validitems] += "container-tools" +IMAGE_FEATURES[validitems] += "container-registry" + +# Container registry configuration packages (opt-in via IMAGE_FEATURES += "container-registry") +# Requires CONTAINER_REGISTRY_URL and/or DOCKER_REGISTRY_INSECURE to be set +FEATURE_PACKAGES_container-registry = "\ + ${@bb.utils.contains_any('VIRTUAL-RUNTIME_container_engine', 'docker docker-moby', 'docker-registry-config', '', d)} \ + ${@bb.utils.contains_any('VIRTUAL-RUNTIME_container_engine', 'podman containerd cri-o', 'container-oci-registry-config', '', d)} \ +" IMAGE_FEATURES += "ssh-server-openssh" IMAGE_FEATURES += "package-management" -- cgit v1.2.3-54-g00ecf