diff --git a/board/common/rootfs/usr/sbin/container b/board/common/rootfs/usr/sbin/container index d39ad217..c3a5bda4 100755 --- a/board/common/rootfs/usr/sbin/container +++ b/board/common/rootfs/usr/sbin/container @@ -230,6 +230,18 @@ load_archive() err 1 "failed tagging image as $tag" fi + # Save archive SHA256 to sidecar file for local archives to enable optimization + if [ -f "$file" ]; then + img_sha256=$(sha256sum "$file" | cut -f1 -d' ') + archive_basename=$(basename "$file") + sha_file="$DOWNLOADS/${archive_basename}.sha256" + + mkdir -p "$DOWNLOADS" + + echo "$img_sha256" > "$sha_file" + log "Saved archive checksum to $sha_file" + fi + # Clean up after ourselves if [ -n "$extracted" ]; then log "Cleaning up extracted $dir" @@ -295,6 +307,20 @@ create() args="$args --network=none" fi + # Add optimization labels for meta-sha256 and config checksum + script="/run/containers/${name}.sh" + if [ -f "$script" ]; then + # Extract meta-sha256 from script if present + meta_sha=$(awk '/^# meta-sha256:/ {print $3}' "$script" 2>/dev/null) + if [ -n "$meta_sha" ]; then + args="$args --label meta-sha256=$meta_sha" + fi + + # Add config checksum label + config_sha=$(sha256sum "$script" | cut -f1 -d' ') + args="$args --label config-sha256=$config_sha" + fi + # shellcheck disable=SC2048 log "podman create --name $name --conmon-pidfile=$pidfile $args $image $*" if podman create --name "$name" --conmon-pidfile="$pidfile" $args "$image" $*; then @@ -785,6 +811,78 @@ case $cmd in script=/run/containers/${name}.sh [ -x "$script" ] || err 1 "setup: $script does not exist or is not executable." + # Early exit optimization: check if container is already up-to-date + meta_sha=$(awk '/^# meta-sha256:/ {print $3}' "$script" 2>/dev/null) + if [ -n "$meta_sha" ]; then + # Local image optimization: check if archive SHA matches stored sidecar file + # Extract the image path from the script + img=$(awk '/^# meta-image:/ {print $3}' "$script" 2>/dev/null) + if [ -n "$img" ]; then + # Determine the archive file path + case "$img" in + oci-archive:*) + archive_path="${img#oci-archive:}" + ;; + *) + archive_path="$img" + ;; + esac + + # Handle relative paths - check BUILTIN and DOWNLOADS + if [ ! -e "$archive_path" ]; then + if [ -e "$DOWNLOADS/$(basename "$archive_path")" ]; then + archive_path="$DOWNLOADS/$(basename "$archive_path")" + elif [ -e "$BUILTIN/$(basename "$archive_path")" ]; then + archive_path="$BUILTIN/$(basename "$archive_path")" + fi + fi + + # Check if the archive exists and compare SHA with sidecar file + if [ -f "$archive_path" ]; then + archive_basename=$(basename "$archive_path") + sha_file="$DOWNLOADS/${archive_basename}.sha256" + + # Check if sidecar file exists + if [ -f "$sha_file" ]; then + stored_sha=$(cat "$sha_file" 2>/dev/null) + current_sha=$(sha256sum "$archive_path" | cut -f1 -d' ') + + # If SHA matches, check container instance + if [ "$stored_sha" = "$current_sha" ]; then + if podman container exists "$name"; then + # Check if container has matching labels + config_sha=$(sha256sum "$script" | cut -f1 -d' ') + container_meta=$(podman inspect "$name" --format '{{index .Config.Labels "meta-sha256"}}' 2>/dev/null) + container_config=$(podman inspect "$name" --format '{{index .Config.Labels "config-sha256"}}' 2>/dev/null) + + if [ "$container_meta" = "$meta_sha" ] && [ "$container_config" = "$config_sha" ]; then + log "Container $name is up-to-date (archive and config unchanged), skipping setup" + exit 0 + fi + fi + else + # Archive changed (e.g., rootfs upgrade) - need to reload + log "Archive SHA changed, will reload image and recreate container" + fi + fi + fi + fi + else + # Remote image optimization: check config-sha256 only + if podman container exists "$name"; then + # Get current config checksum from script + config_sha=$(sha256sum "$script" | cut -f1 -d' ') + + # Get container's stored config checksum + container_config=$(podman inspect "$name" --format '{{index .Config.Labels "config-sha256"}}' 2>/dev/null) + + if [ -n "$container_config" ] && [ "$container_config" = "$config_sha" ]; then + log "Container $name config unchanged, skipping setup" + exit 0 + fi + fi + fi + # Save our PID in case we get stuck here and someone wants to # stop us, e.g., due to reconfiguration or reboot. pidfile=$(pidfn "${name}") diff --git a/src/confd/src/infix-containers.c b/src/confd/src/infix-containers.c index 2a108109..b4e37a89 100644 --- a/src/confd/src/infix-containers.c +++ b/src/confd/src/infix-containers.c @@ -22,6 +22,30 @@ #define _PATH_CONT "/run/containers" +/* + * Check if image is a local archive and return the offset to the file path. + * Returns 0 if not a recognized local archive format. + */ +static int archive_offset(const char *image) +{ + static const struct { + const char *prefix; + int offset; + } prefixes[] = { + { "docker-archive:", 15 }, + { "oci-archive:", 12 }, + { NULL, 0 } + }; + int i; + + for (i = 0; prefixes[i].prefix; i++) { + if (!strncmp(image, prefixes[i].prefix, prefixes[i].offset)) + return prefixes[i].offset; + } + + return 0; +} + /* * Create a setup/create/upgrade script and instantiate a new instance * that Finit will start when all networking and other dependencies are @@ -37,6 +61,7 @@ static int add(const char *name, struct lyd_node *cif) struct lyd_node *node, *nets, *caps; char script[strlen(name) + 5]; FILE *fp, *ap; + int offset; snprintf(script, sizeof(script), "%s.sh", name); fp = fopenf("w", "%s/%s", _PATH_CONT, script); @@ -58,9 +83,26 @@ static int add(const char *name, struct lyd_node *cif) image = lydx_get_cattr(cif, "image"); fprintf(fp, "#!/bin/sh\n" "# meta-name: %s\n" - "# meta-image: %s\n" - "container --quiet delete %s >/dev/null\n" - "container --quiet", name, image, name); + "# meta-image: %s\n", name, image); + + offset = archive_offset(image); + if (offset) { + const char *path = image + offset; + char sha256[65] = { 0 }; + FILE *pp; + + pp = popenf("r", "sha256sum %s | cut -f1 -d' '", path); + if (pp) { + if (fgets(sha256, sizeof(sha256), pp)) { + chomp(sha256); + fprintf(fp, "# meta-sha256: %s\n", sha256); + } + pclose(pp); + } + } + + fprintf(fp, "container --quiet delete %s >/dev/null\n" + "container --quiet", name); LYX_LIST_FOR_EACH(lyd_child(cif), node, "dns") fprintf(fp, " --dns %s", lyd_get_value(node));