Minimal bootable disk image builder
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(uniboot,space): add OCI source resolution and GPT disk images

Add Uniboot.Source module for resolving components (kernel, init) from
local files, directories, or OCI registries with XDG caching. Refactor
space build to use uniboot for GPT disk images instead of raw disk.
Update pid1 init to try /dev/vda1 (GPT partition) before /dev/vda.

+142 -1
+3
dune-project
··· 28 28 (mbr (>= 0.1)) 29 29 (squashfs (>= 0.1)) 30 30 (initramfs (>= 0.1)) 31 + oci 32 + (eio (>= 1.0)) 33 + xdge 31 34 (cmdliner (>= 1.2)) 32 35 (fmt (>= 0.9)) 33 36 (logs (>= 0.7))
+13 -1
lib/dune
··· 1 1 (library 2 2 (name uniboot) 3 3 (public_name uniboot) 4 - (libraries bytesrw gpt mbr squashfs initramfs fmt logs uuidm unix)) 4 + (libraries 5 + bytesrw 6 + gpt 7 + mbr 8 + squashfs 9 + initramfs 10 + oci 11 + eio 12 + fmt 13 + logs 14 + uuidm 15 + unix 16 + xdge))
+112
lib/source.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Resolve component images from local files or OCI registries. 7 + 8 + All components (kernel, init, userspace services) follow the same pattern: 9 + specify a local file path or an OCI image reference. OCI images are fetched 10 + and cached in the XDG cache directory. This mirrors LinuxKit's model where 11 + every component is an OCI image. *) 12 + 13 + let is_file fs path = 14 + match Eio.Path.kind ~follow:false Eio.Path.(fs / path) with 15 + | `Regular_file -> true 16 + | _ -> false 17 + | exception Eio.Io _ -> false 18 + 19 + let is_dir fs path = 20 + match Eio.Path.kind ~follow:false Eio.Path.(fs / path) with 21 + | `Directory -> true 22 + | _ -> false 23 + | exception Eio.Io _ -> false 24 + 25 + let rec find_in fs ~names dir = 26 + let entries = 27 + try Eio.Path.read_dir Eio.Path.(fs / dir) with Eio.Io _ -> [] 28 + in 29 + let found = 30 + List.find_opt 31 + (fun name -> List.mem name names && is_file fs (Filename.concat dir name)) 32 + entries 33 + in 34 + match found with 35 + | Some name -> Some (Filename.concat dir name) 36 + | None -> 37 + let subdirs = 38 + List.filter_map 39 + (fun entry -> 40 + let path = Filename.concat dir entry in 41 + if is_dir fs path then Some path else None) 42 + entries 43 + in 44 + List.find_map (find_in fs ~names) subdirs 45 + 46 + let xdge env = 47 + let fs = Eio.Stdenv.fs env in 48 + Xdge.create fs "uniboot" 49 + 50 + let oci_cache xdge = 51 + let cache_dir = Eio.Path.(Xdge.cache_dir xdge / "oci") in 52 + let cache = Oci.Cache.v cache_dir in 53 + Oci.Cache.init cache; 54 + cache 55 + 56 + let checkout_dir xdge = 57 + let path = Eio.Path.native_exn (Xdge.cache_dir xdge) in 58 + Filename.concat path "checkout" 59 + 60 + let oci_fetch ~env ~cache ?platform image = 61 + let net = Eio.Stdenv.net env in 62 + let clock = Eio.Stdenv.clock env in 63 + let domain_mgr = Eio.Stdenv.domain_mgr env in 64 + let client = Oci.Fetch.create_client ~net ~clock in 65 + Fmt.pr "Fetching %a...@." Oci.Image.pp image; 66 + Oci.fetch ~show_progress:true ?platform ~cache ~client ~domain_mgr image 67 + 68 + let oci_checkout ~env ~cache ~checkout_dir image = 69 + let fs = Eio.Stdenv.fs env in 70 + Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 Eio.Path.(fs / checkout_dir); 71 + let root = Eio.Path.(fs / checkout_dir) in 72 + Oci.checkout ~cache ~root image 73 + 74 + let resolve ~env ~names ?platform spec = 75 + let fs = Eio.Stdenv.fs env in 76 + (* 1. Local file *) 77 + if is_file fs spec then spec 78 + (* 2. Local directory — search for named files in it *) 79 + else if is_dir fs spec then 80 + match find_in fs ~names spec with 81 + | Some path -> path 82 + | None -> 83 + failwith 84 + (Fmt.str "no matching file in %s (searched: %s)" spec 85 + (String.concat ", " names)) 86 + else 87 + (* 3. OCI reference — check XDG cache, then fetch *) 88 + match Oci.Image.of_string spec with 89 + | Error (`Msg msg) -> 90 + failwith 91 + (Fmt.str "not found: %s (not a valid OCI reference: %s)" spec msg) 92 + | Ok image -> ( 93 + let xdge = xdge env in 94 + let checkout = checkout_dir xdge in 95 + let image_dir = Filename.concat checkout (Oci.Image.to_string image) in 96 + match find_in fs ~names image_dir with 97 + | Some path -> 98 + Fmt.pr " %s (cached)@." path; 99 + path 100 + | None -> ( 101 + let cache = oci_cache xdge in 102 + oci_fetch ~env ~cache ?platform image; 103 + oci_checkout ~env ~cache ~checkout_dir:checkout image; 104 + match find_in fs ~names image_dir with 105 + | Some path -> 106 + Fmt.pr " %s@." path; 107 + path 108 + | None -> 109 + failwith 110 + (Fmt.str 111 + "no matching file found in OCI image %a (searched: %s)" 112 + Oci.Image.pp image (String.concat ", " names))))
+2
lib/uniboot.ml
··· 247 247 let result = build config writer in 248 248 close_out oc; 249 249 result 250 + 251 + module Source = Source
+9
lib/uniboot.mli
··· 66 66 67 67 val guid_linux_swap : Uuidm.t 68 68 (** Linux swap partition type GUID. *) 69 + 70 + (** {1 OCI Image Resolution} 71 + 72 + Resolve component specifications to local file paths. Accepts local file 73 + paths (used as-is) or OCI image references (fetched from a registry and 74 + extracted). Follows LinuxKit's model where every component is an OCI image. 75 + *) 76 + 77 + module Source = Source
+3
uniboot.opam
··· 18 18 "mbr" {>= "0.1"} 19 19 "squashfs" {>= "0.1"} 20 20 "initramfs" {>= "0.1"} 21 + "oci" 22 + "eio" {>= "1.0"} 23 + "xdge" 21 24 "cmdliner" {>= "1.2"} 22 25 "fmt" {>= "0.9"} 23 26 "logs" {>= "0.7"}