@@ -44,7 +44,7 @@ use ostree::gio;
4444use ostree_ext:: ostree;
4545use ostree_ext:: ostree_prepareroot:: { ComposefsState , Tristate } ;
4646use ostree_ext:: prelude:: Cast ;
47- use ostree_ext:: sysroot:: SysrootLock ;
47+ use ostree_ext:: sysroot:: { allocate_new_stateroot , list_stateroots , SysrootLock } ;
4848use ostree_ext:: { container as ostree_container, ostree_prepareroot} ;
4949#[ cfg( feature = "install-to-disk" ) ]
5050use rustix:: fs:: FileTypeExt ;
@@ -56,7 +56,10 @@ use self::baseline::InstallBlockDeviceOpts;
5656use crate :: bootc_composefs:: { boot:: setup_composefs_boot, repo:: initialize_composefs_repository} ;
5757use crate :: boundimage:: { BoundImage , ResolvedBoundImage } ;
5858use crate :: containerenv:: ContainerExecutionInfo ;
59- use crate :: deploy:: { prepare_for_pull, pull_from_prepared, PreparedImportMeta , PreparedPullResult } ;
59+ use crate :: deploy:: {
60+ prepare_for_pull, pull_from_prepared, MergeState , PreparedImportMeta , PreparedPullResult ,
61+ } ;
62+ use crate :: kernel_cmdline:: Cmdline ;
6063use crate :: lsm;
6164use crate :: progress_jsonl:: ProgressWriter ;
6265use crate :: spec:: { Bootloader , ImageReference } ;
@@ -390,6 +393,50 @@ pub(crate) struct InstallToExistingRootOpts {
390393 pub ( crate ) composefs_opts : InstallComposefsOpts ,
391394}
392395
396+ #[ derive( Debug , clap:: Parser , PartialEq , Eq ) ]
397+ pub ( crate ) struct InstallResetOpts {
398+ /// Acknowledge that this command is experimental.
399+ #[ clap( long) ]
400+ pub ( crate ) experimental : bool ,
401+
402+ #[ clap( flatten) ]
403+ pub ( crate ) source_opts : InstallSourceOpts ,
404+
405+ #[ clap( flatten) ]
406+ pub ( crate ) target_opts : InstallTargetOpts ,
407+
408+ /// Name of the target stateroot. If not provided, one will be automatically
409+ /// generated of the form s<year>-<serial> where <serial> starts at zero and
410+ /// increments automatically.
411+ #[ clap( long) ]
412+ pub ( crate ) stateroot : Option < String > ,
413+
414+ /// Don't display progress
415+ #[ clap( long) ]
416+ pub ( crate ) quiet : bool ,
417+
418+ #[ clap( flatten) ]
419+ pub ( crate ) progress : crate :: cli:: ProgressOptions ,
420+
421+ /// Restart or reboot into the new target image.
422+ ///
423+ /// Currently, this option always reboots. In the future this command
424+ /// will detect the case where no kernel changes are queued, and perform
425+ /// a userspace-only restart.
426+ #[ clap( long) ]
427+ pub ( crate ) apply : bool ,
428+
429+ /// Skip inheriting any automatically discovered root file system kernel arguments.
430+ #[ clap( long) ]
431+ no_root_kargs : bool ,
432+
433+ /// Add a kernel argument. This option can be provided multiple times.
434+ ///
435+ /// Example: --karg=nosmt --karg=console=ttyS0,114800n8
436+ #[ clap( long) ]
437+ karg : Option < Vec < String > > ,
438+ }
439+
393440/// Global state captured from the container.
394441#[ derive( Debug , Clone ) ]
395442pub ( crate ) struct SourceInfo {
@@ -434,6 +481,24 @@ pub(crate) struct State {
434481 pub ( crate ) detected_bootloader : crate :: spec:: Bootloader ,
435482}
436483
484+ impl InstallTargetOpts {
485+ pub ( crate ) fn imageref ( & self ) -> Result < Option < ostree_container:: OstreeImageReference > > {
486+ let Some ( target_imgname) = self . target_imgref . as_deref ( ) else {
487+ return Ok ( None ) ;
488+ } ;
489+ let target_transport =
490+ ostree_container:: Transport :: try_from ( self . target_transport . as_str ( ) ) ?;
491+ let target_imgref = ostree_container:: OstreeImageReference {
492+ sigverify : ostree_container:: SignatureSource :: ContainerPolicyAllowInsecure ,
493+ imgref : ostree_container:: ImageReference {
494+ transport : target_transport,
495+ name : target_imgname. to_string ( ) ,
496+ } ,
497+ } ;
498+ Ok ( Some ( target_imgref) )
499+ }
500+ }
501+
437502impl State {
438503 #[ context( "Loading SELinux policy" ) ]
439504 pub ( crate ) fn load_policy ( & self ) -> Result < Option < ostree:: SePolicy > > {
@@ -2160,6 +2225,94 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
21602225 install_to_filesystem ( opts, true , cleanup) . await
21612226}
21622227
2228+ pub ( crate ) async fn install_reset ( opts : InstallResetOpts ) -> Result < ( ) > {
2229+ let rootfs = & Dir :: open_ambient_dir ( "/" , cap_std:: ambient_authority ( ) ) ?;
2230+ if !opts. experimental {
2231+ anyhow:: bail!( "This command requires --experimental" ) ;
2232+ }
2233+
2234+ let prog: ProgressWriter = opts. progress . try_into ( ) ?;
2235+
2236+ let sysroot = & crate :: cli:: get_storage ( ) . await ?;
2237+ let repo = & sysroot. repo ( ) ;
2238+ let ( booted_deployment, _deployments, host) =
2239+ crate :: status:: get_status_require_booted ( sysroot) ?;
2240+
2241+ let stateroots = list_stateroots ( sysroot) ?;
2242+ dbg ! ( & stateroots) ;
2243+ let target_stateroot = if let Some ( s) = opts. stateroot {
2244+ s
2245+ } else {
2246+ let now = chrono:: Utc :: now ( ) ;
2247+ let r = allocate_new_stateroot ( & sysroot, & stateroots, now) ?;
2248+ r. name
2249+ } ;
2250+
2251+ let booted_stateroot = booted_deployment. osname ( ) ;
2252+ assert ! ( booted_stateroot. as_str( ) != target_stateroot) ;
2253+ let ( fetched, spec) = if let Some ( target) = opts. target_opts . imageref ( ) ? {
2254+ let mut new_spec = host. spec ;
2255+ new_spec. image = Some ( target. into ( ) ) ;
2256+ let fetched = crate :: deploy:: pull (
2257+ repo,
2258+ & new_spec. image . as_ref ( ) . unwrap ( ) ,
2259+ None ,
2260+ opts. quiet ,
2261+ prog. clone ( ) ,
2262+ )
2263+ . await ?;
2264+ ( fetched, new_spec)
2265+ } else {
2266+ let imgstate = host
2267+ . status
2268+ . booted
2269+ . map ( |b| b. query_image ( repo) )
2270+ . transpose ( ) ?
2271+ . flatten ( )
2272+ . ok_or_else ( || anyhow:: anyhow!( "No image source specified" ) ) ?;
2273+ ( Box :: new ( ( * imgstate) . into ( ) ) , host. spec )
2274+ } ;
2275+ let spec = crate :: deploy:: RequiredHostSpec :: from_spec ( & spec) ?;
2276+
2277+ // Compute the kernel arguments to inherit. By default, that's only those involved
2278+ // in the root filesystem.
2279+ let root_kargs = if opts. no_root_kargs {
2280+ Vec :: new ( )
2281+ } else {
2282+ let bootcfg = booted_deployment
2283+ . bootconfig ( )
2284+ . ok_or_else ( || anyhow ! ( "Missing bootcfg for booted deployment" ) ) ?;
2285+ if let Some ( options) = bootcfg. get ( "options" ) {
2286+ let options = options. split_ascii_whitespace ( ) . collect :: < Vec < _ > > ( ) ;
2287+ crate :: kernel:: root_args_from_cmdline ( & options)
2288+ . into_iter ( )
2289+ . map ( ToOwned :: to_owned)
2290+ . collect :: < Vec < _ > > ( )
2291+ } else {
2292+ Vec :: new ( )
2293+ }
2294+ } ;
2295+
2296+ let kargs = crate :: kargs:: get_kargs_in_root ( rootfs, std:: env:: consts:: ARCH ) ?
2297+ . into_iter ( )
2298+ . chain ( root_kargs. into_iter ( ) )
2299+ . chain ( opts. karg . unwrap_or_default ( ) )
2300+ . collect :: < Vec < _ > > ( ) ;
2301+
2302+ let from = MergeState :: Reset {
2303+ stateroot : target_stateroot,
2304+ kargs,
2305+ } ;
2306+ crate :: deploy:: stage ( sysroot, from, & fetched, & spec, prog. clone ( ) ) . await ?;
2307+
2308+ sysroot. update_mtime ( ) ?;
2309+
2310+ if opts. apply {
2311+ crate :: reboot:: reboot ( ) ?;
2312+ }
2313+ Ok ( ( ) )
2314+ }
2315+
21632316/// Implementation of `bootc install finalize`.
21642317pub ( crate ) async fn install_finalize ( target : & Utf8Path ) -> Result < ( ) > {
21652318 // Log the installation finalization operation to systemd journal
0 commit comments