@@ -93,6 +93,17 @@ pub(crate) async fn new_importer(
9393 Ok ( imp)
9494}
9595
96+ /// Wrapper for pulling a container image with a custom proxy config (e.g. for unified storage).
97+ pub ( crate ) async fn new_importer_with_config (
98+ repo : & ostree:: Repo ,
99+ imgref : & ostree_container:: OstreeImageReference ,
100+ config : ostree_ext:: containers_image_proxy:: ImageProxyConfig ,
101+ ) -> Result < ostree_container:: store:: ImageImporter > {
102+ let mut imp = ostree_container:: store:: ImageImporter :: new ( repo, imgref, config) . await ?;
103+ imp. require_bootable ( ) ;
104+ Ok ( imp)
105+ }
106+
96107pub ( crate ) fn check_bootc_label ( config : & ostree_ext:: oci_spec:: image:: ImageConfiguration ) {
97108 if let Some ( label) =
98109 labels_of_config ( config) . and_then ( |labels| labels. get ( crate :: metadata:: BOOTC_COMPAT_LABEL ) )
@@ -316,6 +327,16 @@ pub(crate) async fn prune_container_store(sysroot: &Storage) -> Result<()> {
316327 for deployment in deployments {
317328 let bound = crate :: boundimage:: query_bound_images_for_deployment ( ostree, & deployment) ?;
318329 all_bound_images. extend ( bound. into_iter ( ) ) ;
330+ // Also include the host image itself
331+ if let Some ( host_image) = crate :: status:: boot_entry_from_deployment ( ostree, & deployment) ?
332+ . image
333+ . map ( |i| i. image )
334+ {
335+ all_bound_images. push ( crate :: boundimage:: BoundImage {
336+ image : crate :: utils:: imageref_to_container_ref ( & host_image) ,
337+ auth_file : None ,
338+ } ) ;
339+ }
319340 }
320341 // Convert to a hashset of just the image names
321342 let image_names = HashSet :: from_iter ( all_bound_images. iter ( ) . map ( |img| img. image . as_str ( ) ) ) ;
@@ -381,6 +402,151 @@ pub(crate) async fn prepare_for_pull(
381402 Ok ( PreparedPullResult :: Ready ( Box :: new ( prepared_image) ) )
382403}
383404
405+ /// Unified approach: Use bootc's CStorage to pull the image, then prepare from containers-storage.
406+ /// This reuses the same infrastructure as LBIs.
407+ ///
408+ /// The `sysroot_path` parameter specifies the path to the sysroot where bootc storage is located.
409+ /// During install, this should be the path to the target disk's mount point.
410+ /// During upgrade/switch on a running system, pass `None` to use the default `/sysroot`.
411+ pub ( crate ) async fn prepare_for_pull_unified (
412+ repo : & ostree:: Repo ,
413+ imgref : & ImageReference ,
414+ target_imgref : Option < & OstreeImageReference > ,
415+ store : & Storage ,
416+ sysroot_path : Option < & camino:: Utf8Path > ,
417+ ) -> Result < PreparedPullResult > {
418+ // Get or initialize the bootc container storage (same as used for LBIs)
419+ let imgstore = store. get_ensure_imgstore ( ) ?;
420+
421+ let image_ref_str = crate :: utils:: imageref_to_container_ref ( imgref) ;
422+
423+ // Check if image already exists in bootc storage - if so, skip the pull
424+ // This is important for localhost images which can't be pulled from a registry
425+ let image_exists = imgstore. exists ( & image_ref_str) . await . unwrap_or ( false ) ;
426+
427+ if image_exists {
428+ tracing:: info!(
429+ "Unified pull: image '{}' already exists in bootc storage, skipping pull" ,
430+ & image_ref_str
431+ ) ;
432+ } else {
433+ // Log the original transport being used for the pull
434+ tracing:: info!(
435+ "Unified pull: pulling from transport '{}' to bootc storage" ,
436+ & imgref. transport
437+ ) ;
438+
439+ // Pull the image to bootc storage using the same method as LBIs
440+ imgstore
441+ . pull ( & image_ref_str, crate :: podstorage:: PullMode :: Always )
442+ . await ?;
443+ }
444+
445+ // Now create a containers-storage reference to read from bootc storage
446+ tracing:: info!( "Unified pull: now importing from containers-storage transport" ) ;
447+ let containers_storage_imgref = ImageReference {
448+ transport : "containers-storage" . to_string ( ) ,
449+ image : imgref. image . clone ( ) ,
450+ signature : imgref. signature . clone ( ) ,
451+ } ;
452+ let ostree_imgref = OstreeImageReference :: from ( containers_storage_imgref) ;
453+
454+ // Configure the importer to use bootc storage as an additional image store
455+ use std:: process:: Command ;
456+ let mut config = ostree_ext:: containers_image_proxy:: ImageProxyConfig :: default ( ) ;
457+ let mut cmd = Command :: new ( "skopeo" ) ;
458+ // Use the actual physical path to bootc storage
459+ // During install, this is the target disk's mount point; otherwise default to /sysroot
460+ let sysroot_base = sysroot_path
461+ . map ( |p| p. to_string ( ) )
462+ . unwrap_or_else ( || "/sysroot" . to_string ( ) ) ;
463+ let storage_path = format ! ( "{}/{}" , sysroot_base, crate :: podstorage:: CStorage :: subpath( ) ) ;
464+ crate :: podstorage:: set_additional_image_store ( & mut cmd, & storage_path) ;
465+ config. skopeo_cmd = Some ( cmd) ;
466+
467+ // Use the preparation flow with the custom config
468+ let mut imp = new_importer_with_config ( repo, & ostree_imgref, config) . await ?;
469+ if let Some ( target) = target_imgref {
470+ imp. set_target ( target) ;
471+ }
472+ let prep = match imp. prepare ( ) . await ? {
473+ PrepareResult :: AlreadyPresent ( c) => {
474+ println ! ( "No changes in {imgref:#} => {}" , c. manifest_digest) ;
475+ return Ok ( PreparedPullResult :: AlreadyPresent ( Box :: new ( ( * c) . into ( ) ) ) ) ;
476+ }
477+ PrepareResult :: Ready ( p) => p,
478+ } ;
479+ check_bootc_label ( & prep. config ) ;
480+ if let Some ( warning) = prep. deprecated_warning ( ) {
481+ ostree_ext:: cli:: print_deprecated_warning ( warning) . await ;
482+ }
483+ ostree_ext:: cli:: print_layer_status ( & prep) ;
484+ let layers_to_fetch = prep. layers_to_fetch ( ) . collect :: < Result < Vec < _ > > > ( ) ?;
485+
486+ // Log that we're importing a new image from containers-storage
487+ const PULLING_NEW_IMAGE_ID : & str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0" ;
488+ tracing:: info!(
489+ message_id = PULLING_NEW_IMAGE_ID ,
490+ bootc. image. reference = & imgref. image,
491+ bootc. image. transport = "containers-storage" ,
492+ bootc. original_transport = & imgref. transport,
493+ bootc. status = "importing_from_storage" ,
494+ "Importing image from bootc storage: {}" ,
495+ ostree_imgref
496+ ) ;
497+
498+ let prepared_image = PreparedImportMeta {
499+ imp,
500+ n_layers_to_fetch : layers_to_fetch. len ( ) ,
501+ layers_total : prep. all_layers ( ) . count ( ) ,
502+ bytes_to_fetch : layers_to_fetch. iter ( ) . map ( |( l, _) | l. layer . size ( ) ) . sum ( ) ,
503+ bytes_total : prep. all_layers ( ) . map ( |l| l. layer . size ( ) ) . sum ( ) ,
504+ digest : prep. manifest_digest . clone ( ) ,
505+ prep,
506+ } ;
507+
508+ Ok ( PreparedPullResult :: Ready ( Box :: new ( prepared_image) ) )
509+ }
510+
511+ /// Unified pull: Use podman to pull to containers-storage, then read from there
512+ ///
513+ /// The `sysroot_path` parameter specifies the path to the sysroot where bootc storage is located.
514+ /// For normal upgrade/switch operations, pass `None` to use the default `/sysroot`.
515+ pub ( crate ) async fn pull_unified (
516+ repo : & ostree:: Repo ,
517+ imgref : & ImageReference ,
518+ target_imgref : Option < & OstreeImageReference > ,
519+ quiet : bool ,
520+ prog : ProgressWriter ,
521+ store : & Storage ,
522+ sysroot_path : Option < & camino:: Utf8Path > ,
523+ ) -> Result < Box < ImageState > > {
524+ match prepare_for_pull_unified ( repo, imgref, target_imgref, store, sysroot_path) . await ? {
525+ PreparedPullResult :: AlreadyPresent ( existing) => {
526+ // Log that the image was already present (Debug level since it's not actionable)
527+ const IMAGE_ALREADY_PRESENT_ID : & str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9" ;
528+ tracing:: debug!(
529+ message_id = IMAGE_ALREADY_PRESENT_ID ,
530+ bootc. image. reference = & imgref. image,
531+ bootc. image. transport = & imgref. transport,
532+ bootc. status = "already_present" ,
533+ "Image already present: {}" ,
534+ imgref
535+ ) ;
536+ Ok ( existing)
537+ }
538+ PreparedPullResult :: Ready ( prepared_image_meta) => {
539+ // To avoid duplicate success logs, pass a containers-storage imgref to the importer
540+ let cs_imgref = ImageReference {
541+ transport : "containers-storage" . to_string ( ) ,
542+ image : imgref. image . clone ( ) ,
543+ signature : imgref. signature . clone ( ) ,
544+ } ;
545+ pull_from_prepared ( & cs_imgref, quiet, prog, * prepared_image_meta) . await
546+ }
547+ }
548+ }
549+
384550#[ context( "Pulling" ) ]
385551pub ( crate ) async fn pull_from_prepared (
386552 imgref : & ImageReference ,
@@ -430,18 +596,21 @@ pub(crate) async fn pull_from_prepared(
430596 let imgref_canonicalized = imgref. clone ( ) . canonicalize ( ) ?;
431597 tracing:: debug!( "Canonicalized image reference: {imgref_canonicalized:#}" ) ;
432598
433- // Log successful import completion
434- const IMPORT_COMPLETE_JOURNAL_ID : & str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8" ;
435-
436- tracing:: info!(
437- message_id = IMPORT_COMPLETE_JOURNAL_ID ,
438- bootc. image. reference = & imgref. image,
439- bootc. image. transport = & imgref. transport,
440- bootc. manifest_digest = import. manifest_digest. as_ref( ) ,
441- bootc. ostree_commit = & import. merge_commit,
442- "Successfully imported image: {}" ,
443- imgref
444- ) ;
599+ // Log successful import completion (skip if using unified storage to avoid double logging)
600+ let is_unified_path = imgref. transport == "containers-storage" ;
601+ if !is_unified_path {
602+ const IMPORT_COMPLETE_JOURNAL_ID : & str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8" ;
603+
604+ tracing:: info!(
605+ message_id = IMPORT_COMPLETE_JOURNAL_ID ,
606+ bootc. image. reference = & imgref. image,
607+ bootc. image. transport = & imgref. transport,
608+ bootc. manifest_digest = import. manifest_digest. as_ref( ) ,
609+ bootc. ostree_commit = & import. merge_commit,
610+ "Successfully imported image: {}" ,
611+ imgref
612+ ) ;
613+ }
445614
446615 if let Some ( msg) =
447616 ostree_container:: store:: image_filtered_content_warning ( & import. filtered_files )
@@ -490,6 +659,9 @@ pub(crate) async fn pull(
490659 }
491660}
492661
662+ /// Pull selecting unified vs standard path based on persistent storage config.
663+ // pull_auto was reverted per request; keep explicit callers branching.
664+
493665pub ( crate ) async fn wipe_ostree ( sysroot : Sysroot ) -> Result < ( ) > {
494666 tokio:: task:: spawn_blocking ( move || {
495667 sysroot
0 commit comments