@@ -48,13 +48,41 @@ pub(crate) const SUBPATH: &str = "storage";
4848const RUNROOT : & str = "bootc/storage" ;
4949
5050/// A bootc-owned instance of `containers-storage:`.
51+ ///
52+ /// This struct manages bootc's container image storage, used for:
53+ /// - Logically bound images (LBIs)
54+ /// - Unified image pulls (pulling the host image into bootc storage)
55+ /// - Other container image operations
56+ ///
57+ /// ## Auth file lookup
58+ ///
59+ /// When pulling images that require authentication, we need to locate auth.json.
60+ /// This struct maintains two root directories to handle auth lookup correctly:
61+ ///
62+ /// - `sysroot`: The ostree sysroot directory. This is checked first for auth.json.
63+ /// Depending on the operation, this may be the staged deployment's sysroot (during
64+ /// LBI pulls for an upgrade) or the current sysroot.
65+ ///
66+ /// - `booted_root`: The currently running deployment's root filesystem, obtained via
67+ /// `deployment_fd()`. This is used as a fallback when auth.json is not found in
68+ /// the sysroot. This handles the upgrade scenario where the user has auth.json on
69+ /// their running system but is upgrading to an image that doesn't have it baked in.
70+ ///
71+ /// This fallback is essential for LBI pulls during upgrades: the LBIs are defined
72+ /// in the *new* image, but we may need to authenticate using credentials from the
73+ /// *running* system.
5174pub ( crate ) struct CStorage {
52- /// The root directory
75+ /// The ostree sysroot directory. This is also checked first for auth.json.
5376 sysroot : Dir ,
54- /// The location of container storage
77+ /// The booted (currently running) deployment's root directory, obtained via
78+ /// `deployment_fd()`. Used as a fallback for auth file lookup when the sysroot
79+ /// doesn't contain auth.json. This is `None` during fresh installs where there
80+ /// is no booted deployment.
81+ booted_root : Option < Dir > ,
82+ /// The location of container storage, relative to the sysroot.
5583 storage_root : Dir ,
5684 #[ allow( dead_code) ]
57- /// Our runtime state
85+ /// Our runtime state directory.
5886 run : Dir ,
5987 /// Disallow using this across multiple threads concurrently; while we
6088 /// have internal locking in podman, in the future we may change how
@@ -119,10 +147,50 @@ fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) ->
119147 Ok ( ( ) )
120148}
121149
122- // Initialize a `podman` subprocess with:
123- // - storage overridden to point to to storage_root
124- // - Authentication (auth.json) using the bootc/ostree owned auth
125- fn new_podman_cmd_in ( sysroot : & Dir , storage_root : & Dir , run_root : & Dir ) -> Result < Command > {
150+ /// Get the global authfile from the booted deployment's root filesystem.
151+ ///
152+ /// This is used as a fallback when the authfile is not found in the sysroot.
153+ /// The booted deployment's root is obtained via `deployment_fd()`, which gives us
154+ /// a Dir handle to the on-disk deployment directory.
155+ ///
156+ /// This fallback handles the upgrade scenario where:
157+ /// 1. The user's running system has auth.json (manually added or from the current image)
158+ /// 2. They upgrade to a new image that does NOT have auth.json baked in
159+ /// 3. The new image has LBIs that require authentication
160+ /// 4. We need to use the running system's auth.json to pull those LBIs
161+ fn get_booted_authfile (
162+ booted_root : Option < & Dir > ,
163+ ) -> Result < Option < ( camino:: Utf8PathBuf , std:: fs:: File ) > > {
164+ let Some ( booted_root) = booted_root else {
165+ return Ok ( None ) ;
166+ } ;
167+ ostree_ext:: globals:: get_global_authfile ( booted_root)
168+ }
169+
170+ /// Initialize a `podman` subprocess configured for bootc's container storage.
171+ ///
172+ /// This sets up podman with:
173+ /// - `--root` pointing to bootc's container storage
174+ /// - `--runroot` pointing to runtime state
175+ /// - `REGISTRY_AUTH_FILE` set to an auth.json for authenticated registry access
176+ ///
177+ /// # Auth file lookup order
178+ ///
179+ /// The auth.json is resolved with the following priority:
180+ /// 1. **Sysroot** (`sysroot` param): Check the ostree sysroot for auth.json.
181+ /// This finds credentials in the sysroot, which depending on the operation
182+ /// may be the staged deployment or the current deployment.
183+ /// 2. **Booted deployment** (`booted_root` param): Fall back to the currently running
184+ /// deployment's root. This finds credentials from the user's running system,
185+ /// which is essential during upgrades where the new image lacks auth.json.
186+ /// 3. **Empty auth**: If neither has auth.json, use an empty `{}` to prevent podman
187+ /// from searching user-owned paths.
188+ fn new_podman_cmd_in (
189+ sysroot : & Dir ,
190+ booted_root : Option < & Dir > ,
191+ storage_root : & Dir ,
192+ run_root : & Dir ,
193+ ) -> Result < Command > {
126194 let mut cmd = Command :: new ( "podman" ) ;
127195 bind_storage_roots ( & mut cmd, storage_root, run_root) ?;
128196 let run_root = format ! ( "/proc/self/fd/{STORAGE_RUN_FD}" ) ;
@@ -132,11 +200,21 @@ fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Resul
132200 let mut tempfile = cap_tempfile:: TempFile :: new_anonymous ( tmpd) . map ( std:: io:: BufWriter :: new) ?;
133201
134202 // Keep this in sync with https://github.com/bootc-dev/containers-image-proxy-rs/blob/b5e0861ad5065f47eaf9cda0d48da3529cc1bc43/src/imageproxy.rs#L310
135- // We always override the auth to match the bootc setup.
136- let authfile_fd = ostree_ext:: globals:: get_global_authfile ( sysroot) ?. map ( |v| v. 1 ) ;
137- if let Some ( mut fd) = authfile_fd {
203+ // We always override the auth to match the bootc setup. See the function doc comment
204+ // for the full auth lookup order explanation.
205+ let authfile = if let Some ( ( path, file) ) = ostree_ext:: globals:: get_global_authfile ( sysroot) ? {
206+ tracing:: debug!( "Using authfile from staged sysroot: {path}" ) ;
207+ Some ( file)
208+ } else if let Some ( ( path, file) ) = get_booted_authfile ( booted_root) ? {
209+ tracing:: debug!( "Using authfile from booted deployment: {path}" ) ;
210+ Some ( file)
211+ } else {
212+ None
213+ } ;
214+ if let Some ( mut fd) = authfile {
138215 std:: io:: copy ( & mut fd, & mut tempfile) ?;
139216 } else {
217+ tracing:: debug!( "No authfile found, using empty auth" ) ;
140218 // Note that if there's no bootc-owned auth, then we force an empty authfile to ensure
141219 // that podman doesn't fall back to searching the user-owned paths.
142220 tempfile. write_all ( b"{}" ) ?;
@@ -194,7 +272,12 @@ impl CStorage {
194272 /// Create a `podman image` Command instance prepared to operate on our alternative
195273 /// root.
196274 pub ( crate ) fn new_image_cmd ( & self ) -> Result < Command > {
197- let mut r = new_podman_cmd_in ( & self . sysroot , & self . storage_root , & self . run ) ?;
275+ let mut r = new_podman_cmd_in (
276+ & self . sysroot ,
277+ self . booted_root . as_ref ( ) ,
278+ & self . storage_root ,
279+ & self . run ,
280+ ) ?;
198281 // We want to limit things to only manipulating images by default.
199282 r. arg ( "image" ) ;
200283 Ok ( r)
@@ -237,6 +320,7 @@ impl CStorage {
237320 #[ context( "Creating imgstorage" ) ]
238321 pub ( crate ) fn create (
239322 sysroot : & Dir ,
323+ booted_root : Option < & Dir > ,
240324 run : & Dir ,
241325 sepolicy : Option < & ostree:: SePolicy > ,
242326 ) -> Result < Self > {
@@ -260,7 +344,7 @@ impl CStorage {
260344 // There's no explicit API to initialize a containers-storage:
261345 // root, simply passing a path will attempt to auto-create it.
262346 // We run "podman images" in the new root.
263- new_podman_cmd_in ( & sysroot, & storage_root, & run) ?
347+ new_podman_cmd_in ( & sysroot, booted_root , & storage_root, & run) ?
264348 . stdout ( Stdio :: null ( ) )
265349 . arg ( "images" )
266350 . run_capture_stderr ( )
@@ -277,11 +361,11 @@ impl CStorage {
277361 Self :: ensure_labeled ( & storage_root, sepolicy) ?;
278362 }
279363
280- Self :: open ( sysroot, run)
364+ Self :: open ( sysroot, booted_root , run)
281365 }
282366
283367 #[ context( "Opening imgstorage" ) ]
284- pub ( crate ) fn open ( sysroot : & Dir , run : & Dir ) -> Result < Self > {
368+ pub ( crate ) fn open ( sysroot : & Dir , booted_root : Option < & Dir > , run : & Dir ) -> Result < Self > {
285369 tracing:: trace!( "Opening container image store" ) ;
286370 Self :: init_globals ( ) ?;
287371 let subpath = & Self :: subpath ( ) ;
@@ -294,6 +378,7 @@ impl CStorage {
294378 let run = run. open_dir ( RUNROOT ) ?;
295379 Ok ( Self {
296380 sysroot : sysroot. try_clone ( ) ?,
381+ booted_root : booted_root. map ( |d| d. try_clone ( ) ) . transpose ( ) ?,
297382 storage_root,
298383 run,
299384 _unsync : Default :: default ( ) ,
0 commit comments