summaryrefslogtreecommitdiffstats
path: root/classes
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-06 03:54:16 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-09 03:34:12 +0000
commitcd5081a5e9ff1c6f5eb74ab90326d602142248f9 (patch)
tree942b4954cb73e217d5c914d381d262b231ee8ab8 /classes
parentb4ad3f9eb2f022b6f69b2e78dbca80974d5bf84a (diff)
downloadmeta-virtualization-cd5081a5e9ff1c6f5eb74ab90326d602142248f9.tar.gz
container-cross-install: add CONTAINER_SERVICE_FILE support
Add support for custom systemd service files (Docker) or Quadlet container files (Podman) instead of auto-generated ones for container autostart. For containers requiring specific startup configuration (ports, volumes, capabilities, dependencies), users can now provide custom service files using the CONTAINER_SERVICE_FILE varflag: CONTAINER_SERVICE_FILE[container-name] = "${UNPACKDIR}/myservice.service" For BUNDLED_CONTAINERS in image recipes: SRC_URI += "file://myapp.service" BUNDLED_CONTAINERS = "myapp-container:docker:autostart" CONTAINER_SERVICE_FILE[myapp-container] = "${UNPACKDIR}/myapp.service" For container-bundle packages: SRC_URI = "file://myapp.service" CONTAINER_BUNDLES = "myapp-container:autostart" CONTAINER_SERVICE_FILE[myapp-container] = "${UNPACKDIR}/myapp.service" Implementation: - container-cross-install.bbclass: Add get_container_service_file_map() to build varflag map, install_custom_service() for BUNDLED_CONTAINERS, and install_custom_service_from_bundle() for bundle packages - container-bundle.bbclass: Install custom service files to ${datadir}/container-bundles/${runtime}/services/ Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'classes')
-rw-r--r--classes/container-bundle.bbclass80
-rw-r--r--classes/container-cross-install.bbclass157
2 files changed, 236 insertions, 1 deletions
diff --git a/classes/container-bundle.bbclass b/classes/container-bundle.bbclass
index 3d3f3a16..3c8ad030 100644
--- a/classes/container-bundle.bbclass
+++ b/classes/container-bundle.bbclass
@@ -141,6 +141,30 @@
141# The runtime subdirectory (docker/ vs podman/) tells container-cross-install 141# The runtime subdirectory (docker/ vs podman/) tells container-cross-install
142# which vrunner runtime to use for import. 142# which vrunner runtime to use for import.
143# 143#
144# ===========================================================================
145# Custom Service Files (CONTAINER_SERVICE_FILE)
146# ===========================================================================
147#
148# For containers requiring specific startup configuration, provide custom
149# service files instead of auto-generated ones:
150#
151# SRC_URI = "file://myapp.service file://mydb.container"
152#
153# CONTAINER_BUNDLES = "\
154# myapp-container:autostart \
155# mydb-container:autostart \
156# "
157#
158# CONTAINER_SERVICE_FILE[myapp-container] = "${UNPACKDIR}/myapp.service"
159# CONTAINER_SERVICE_FILE[mydb-container] = "${UNPACKDIR}/mydb.container"
160#
161# Custom files are installed to ${datadir}/container-bundles/${RUNTIME}/services/
162# and used by container-cross-install instead of generating default services.
163#
164# For Docker, provide a .service file; for Podman, provide a .container Quadlet.
165#
166# See docs/container-bundling.md for detailed examples.
167#
144# See also: container-cross-install.bbclass 168# See also: container-cross-install.bbclass
145 169
146CONTAINER_BUNDLES ?= "" 170CONTAINER_BUNDLES ?= ""
@@ -247,6 +271,29 @@ python __anonymous() {
247 d.setVar('_REMOTE_CONTAINERS', ' '.join(remote_urls)) 271 d.setVar('_REMOTE_CONTAINERS', ' '.join(remote_urls))
248 d.setVar('_PROCESSED_BUNDLES', ' '.join(processed_bundles)) 272 d.setVar('_PROCESSED_BUNDLES', ' '.join(processed_bundles))
249 d.setVar('_BUNDLE_RUNTIME', runtime) 273 d.setVar('_BUNDLE_RUNTIME', runtime)
274
275 # Build service file map for custom service files
276 # Format: container1=/path/to/file1;container2=/path/to/file2
277 service_mappings = []
278 for bundle in bundles:
279 # Extract container name (handle both local and remote formats)
280 if is_remote_container(bundle):
281 if bundle.endswith(':autostart') or bundle.endswith(':always') or \
282 bundle.endswith(':unless-stopped') or bundle.endswith(':on-failure') or \
283 bundle.endswith(':no'):
284 last_colon = bundle.rfind(':')
285 source = bundle[:last_colon]
286 else:
287 source = bundle
288 else:
289 parts = bundle.split(':')
290 source = parts[0]
291
292 custom_file = d.getVarFlag('CONTAINER_SERVICE_FILE', source)
293 if custom_file:
294 service_mappings.append(f"{source}={custom_file}")
295
296 d.setVar('_CONTAINER_SERVICE_FILE_MAP', ';'.join(service_mappings))
250} 297}
251 298
252# S must be a real directory 299# S must be a real directory
@@ -402,6 +449,39 @@ do_install() {
402 install -m 0644 ${B}/bundle-metadata.txt \ 449 install -m 0644 ${B}/bundle-metadata.txt \
403 ${D}${datadir}/container-bundles/${PN}.meta 450 ${D}${datadir}/container-bundles/${PN}.meta
404 fi 451 fi
452
453 # Install custom service files from CONTAINER_SERVICE_FILE varflags
454 # Format: container1=/path/to/file1;container2=/path/to/file2
455 if [ -n "${_CONTAINER_SERVICE_FILE_MAP}" ]; then
456 install -d ${D}${datadir}/container-bundles/${RUNTIME}/services
457 echo "${_CONTAINER_SERVICE_FILE_MAP}" | tr ';' '\n' | while IFS='=' read -r container_name service_file; do
458 [ -z "$container_name" ] && continue
459 [ -z "$service_file" ] && continue
460
461 if [ ! -f "$service_file" ]; then
462 bbwarn "Custom service file not found: $service_file (for container $container_name)"
463 continue
464 fi
465
466 # Sanitize container name for filename (replace / and : with _)
467 local sanitized_name=$(echo "$container_name" | sed 's|[/:]|_|g')
468
469 # Determine file extension based on runtime and source file
470 local dest_file
471 if [ "${RUNTIME}" = "docker" ]; then
472 dest_file="${sanitized_name}.service"
473 elif [ "${RUNTIME}" = "podman" ]; then
474 dest_file="${sanitized_name}.container"
475 else
476 # Keep original extension
477 dest_file="${sanitized_name}.$(echo "$service_file" | sed 's/.*\.//')"
478 fi
479
480 bbnote "Installing custom service file: $service_file -> services/${dest_file}"
481 install -m 0644 "$service_file" \
482 ${D}${datadir}/container-bundles/${RUNTIME}/services/${dest_file}
483 done
484 fi
405} 485}
406 486
407FILES:${PN} = "${datadir}/container-bundles" 487FILES:${PN} = "${datadir}/container-bundles"
diff --git a/classes/container-cross-install.bbclass b/classes/container-cross-install.bbclass
index d18c6436..bb2547e0 100644
--- a/classes/container-cross-install.bbclass
+++ b/classes/container-cross-install.bbclass
@@ -98,6 +98,27 @@
98# The runtime is determined by the subdirectory (docker/ vs podman/), 98# The runtime is determined by the subdirectory (docker/ vs podman/),
99# which is set by container-bundle.bbclass based on CONTAINER_BUNDLE_RUNTIME. 99# which is set by container-bundle.bbclass based on CONTAINER_BUNDLE_RUNTIME.
100# 100#
101# ===========================================================================
102# Custom Service Files (CONTAINER_SERVICE_FILE)
103# ===========================================================================
104#
105# For containers requiring specific startup configuration (ports, volumes,
106# capabilities, dependencies), provide custom service files instead of
107# auto-generated ones using the CONTAINER_SERVICE_FILE varflag:
108#
109# CONTAINER_SERVICE_FILE[container-name] = "${UNPACKDIR}/myservice.service"
110# CONTAINER_SERVICE_FILE[other-container] = "${UNPACKDIR}/other.container"
111#
112# Usage in image recipe:
113# SRC_URI += "file://myapp.service"
114# BUNDLED_CONTAINERS = "myapp-container:docker:autostart"
115# CONTAINER_SERVICE_FILE[myapp-container] = "${UNPACKDIR}/myapp.service"
116#
117# The custom file replaces the auto-generated service. For Docker, provide
118# a .service file; for Podman, provide a .container Quadlet file.
119#
120# See docs/container-bundling.md for detailed examples.
121#
101# See also: container-bundle.bbclass 122# See also: container-bundle.bbclass
102 123
103# Inherit shared functions for multiconfig/machine/arch mapping 124# Inherit shared functions for multiconfig/machine/arch mapping
@@ -164,6 +185,26 @@ python __anonymous() {
164 d.appendVarFlag('do_rootfs', 'depends', deps) 185 d.appendVarFlag('do_rootfs', 'depends', deps)
165} 186}
166 187
188# Build CONTAINER_SERVICE_FILE_MAP from varflags for shell access
189# Format: container1=/path/to/file1;container2=/path/to/file2
190def get_container_service_file_map(d):
191 """Build a semicolon-separated map of container names to custom service files"""
192 bundled = (d.getVar('BUNDLED_CONTAINERS') or "").split()
193 if not bundled:
194 return ""
195
196 mappings = []
197 for entry in bundled:
198 parts = entry.split(':')
199 container_name = parts[0]
200 custom_file = d.getVarFlag('CONTAINER_SERVICE_FILE', container_name)
201 if custom_file:
202 mappings.append(f"{container_name}={custom_file}")
203
204 return ";".join(mappings)
205
206CONTAINER_SERVICE_FILE_MAP = "${@get_container_service_file_map(d)}"
207
167# Path to vrunner.sh from vcontainer-native 208# Path to vrunner.sh from vcontainer-native
168VRUNNER_PATH = "${STAGING_BINDIR_NATIVE}/vrunner.sh" 209VRUNNER_PATH = "${STAGING_BINDIR_NATIVE}/vrunner.sh"
169 210
@@ -419,7 +460,21 @@ merge_installed_bundles() {
419 460
420 bbnote "Creating autostart service for $source ($runtime_type, restart=$restart_policy)" 461 bbnote "Creating autostart service for $source ($runtime_type, restart=$restart_policy)"
421 462
463 # Check for custom service file in bundle's services directory
464 # Custom files are stored as: services/<source-sanitized>.(service|container)
465 local source_sanitized=$(echo "$source" | sed 's|[/:]|_|g')
466 local custom_service_file=""
467
422 if [ "$runtime_type" = "docker" ]; then 468 if [ "$runtime_type" = "docker" ]; then
469 custom_service_file="${BUNDLES_DIR}/${runtime_type}/services/${source_sanitized}.service"
470 elif [ "$runtime_type" = "podman" ]; then
471 custom_service_file="${BUNDLES_DIR}/${runtime_type}/services/${source_sanitized}.container"
472 fi
473
474 if [ -n "$custom_service_file" ] && [ -f "$custom_service_file" ]; then
475 bbnote "Using custom service file from bundle: $custom_service_file"
476 install_custom_service_from_bundle "$source" "$service_name" "$runtime_type" "$custom_service_file"
477 elif [ "$runtime_type" = "docker" ]; then
423 generate_docker_service_from_bundle "$service_name" "$image_name" "$image_tag" "$restart_policy" 478 generate_docker_service_from_bundle "$service_name" "$image_name" "$image_tag" "$restart_policy"
424 elif [ "$runtime_type" = "podman" ]; then 479 elif [ "$runtime_type" = "podman" ]; then
425 generate_podman_service_from_bundle "$service_name" "$image_name" "$image_tag" "$restart_policy" 480 generate_podman_service_from_bundle "$service_name" "$image_name" "$image_tag" "$restart_policy"
@@ -434,6 +489,51 @@ merge_installed_bundles() {
434 return 0 489 return 0
435} 490}
436 491
492# Install a custom service file from a bundle package
493# Args: source service_name runtime_type custom_file
494install_custom_service_from_bundle() {
495 local source="$1"
496 local service_name="$2"
497 local runtime_type="$3"
498 local custom_file="$4"
499
500 if [ ! -f "$custom_file" ]; then
501 bbwarn "Custom service file not found: $custom_file (for container $source)"
502 return 1
503 fi
504
505 if [ "$runtime_type" = "docker" ]; then
506 # Docker: Install as systemd service
507 local service_dir="${IMAGE_ROOTFS}/lib/systemd/system"
508 local service_file="${service_dir}/${service_name}.service"
509
510 mkdir -p "$service_dir"
511 install -m 0644 "$custom_file" "$service_file"
512
513 # Enable the service via symlink
514 local wants_dir="${IMAGE_ROOTFS}/etc/systemd/system/multi-user.target.wants"
515 mkdir -p "$wants_dir"
516 ln -sf "/lib/systemd/system/${service_name}.service" "${wants_dir}/${service_name}.service"
517
518 bbnote "Installed custom service from bundle: $custom_file -> ${service_name}.service"
519
520 elif [ "$runtime_type" = "podman" ]; then
521 # Podman: Install as Quadlet container file
522 local quadlet_dir="${IMAGE_ROOTFS}/etc/containers/systemd"
523 local container_file="${quadlet_dir}/${service_name}.container"
524
525 mkdir -p "$quadlet_dir"
526 install -m 0644 "$custom_file" "$container_file"
527
528 bbnote "Installed custom Quadlet file from bundle: $custom_file -> ${service_name}.container"
529 else
530 bbwarn "Unknown runtime '$runtime_type' for custom service file from bundle, skipping"
531 return 1
532 fi
533
534 return 0
535}
536
437# Generate Docker systemd service (for bundle packages) 537# Generate Docker systemd service (for bundle packages)
438generate_docker_service_from_bundle() { 538generate_docker_service_from_bundle() {
439 local service_name="$1" 539 local service_name="$1"
@@ -648,6 +748,47 @@ EOF
648 bbnote "Created Quadlet file ${service_name}.container for Podman container ${image_name}:${image_tag}" 748 bbnote "Created Quadlet file ${service_name}.container for Podman container ${image_name}:${image_tag}"
649 } 749 }
650 750
751 # Install a custom service file provided by the user
752 # Args: container_name service_name runtime_type custom_file
753 install_custom_service() {
754 local container_name="$1"
755 local service_name="$2"
756 local runtime_type="$3"
757 local custom_file="$4"
758
759 if [ ! -f "$custom_file" ]; then
760 bbfatal "Custom service file not found: $custom_file (for container $container_name)"
761 fi
762
763 if [ "$runtime_type" = "docker" ]; then
764 # Docker: Install as systemd service
765 local service_dir="${IMAGE_ROOTFS}/lib/systemd/system"
766 local service_file="${service_dir}/${service_name}.service"
767
768 mkdir -p "$service_dir"
769 install -m 0644 "$custom_file" "$service_file"
770
771 # Enable the service via symlink
772 local wants_dir="${IMAGE_ROOTFS}/etc/systemd/system/multi-user.target.wants"
773 mkdir -p "$wants_dir"
774 ln -sf "/lib/systemd/system/${service_name}.service" "${wants_dir}/${service_name}.service"
775
776 bbnote "Installed custom service: $custom_file -> ${service_name}.service"
777
778 elif [ "$runtime_type" = "podman" ]; then
779 # Podman: Install as Quadlet container file
780 local quadlet_dir="${IMAGE_ROOTFS}/etc/containers/systemd"
781 local container_file="${quadlet_dir}/${service_name}.container"
782
783 mkdir -p "$quadlet_dir"
784 install -m 0644 "$custom_file" "$container_file"
785
786 bbnote "Installed custom Quadlet file: $custom_file -> ${service_name}.container"
787 else
788 bbwarn "Unknown runtime '$runtime_type' for custom service file, skipping"
789 fi
790 }
791
651 # Install autostart services for containers with autostart policy 792 # Install autostart services for containers with autostart policy
652 install_autostart_services() { 793 install_autostart_services() {
653 bbnote "Processing container autostart services..." 794 bbnote "Processing container autostart services..."
@@ -697,7 +838,21 @@ EOF
697 838
698 bbnote "Creating autostart service for $container_name ($runtime_type, restart=$restart_policy)" 839 bbnote "Creating autostart service for $container_name ($runtime_type, restart=$restart_policy)"
699 840
700 if [ "$runtime_type" = "docker" ]; then 841 # Check for custom service file via CONTAINER_SERVICE_FILE varflag
842 # This is evaluated at rootfs time, so we use a Python helper
843 local custom_service_file="${CONTAINER_SERVICE_FILE_MAP}"
844 local custom_file=""
845
846 # Parse the map to find this container's custom file
847 # Format: container1=/path/to/file1;container2=/path/to/file2
848 if [ -n "$custom_service_file" ]; then
849 custom_file=$(echo "$custom_service_file" | tr ';' '\n' | grep "^${container_name}=" | cut -d= -f2-)
850 fi
851
852 if [ -n "$custom_file" ] && [ -f "$custom_file" ]; then
853 bbnote "Using custom service file for $container_name: $custom_file"
854 install_custom_service "$container_name" "$service_name" "$runtime_type" "$custom_file"
855 elif [ "$runtime_type" = "docker" ]; then
701 generate_docker_service "$service_name" "${CONTAINER_IMAGE_NAME}" "${CONTAINER_IMAGE_TAG}" "$restart_policy" 856 generate_docker_service "$service_name" "${CONTAINER_IMAGE_NAME}" "${CONTAINER_IMAGE_TAG}" "$restart_policy"
702 elif [ "$runtime_type" = "podman" ]; then 857 elif [ "$runtime_type" = "podman" ]; then
703 generate_podman_service "$service_name" "${CONTAINER_IMAGE_NAME}" "${CONTAINER_IMAGE_TAG}" "$restart_policy" 858 generate_podman_service "$service_name" "${CONTAINER_IMAGE_NAME}" "${CONTAINER_IMAGE_TAG}" "$restart_policy"