@@ -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 ;
@@ -55,7 +55,10 @@ use serde::{Deserialize, Serialize};
5555use self :: baseline:: InstallBlockDeviceOpts ;
5656use crate :: boundimage:: { BoundImage , ResolvedBoundImage } ;
5757use crate :: containerenv:: ContainerExecutionInfo ;
58- use crate :: deploy:: { prepare_for_pull, pull_from_prepared, PreparedImportMeta , PreparedPullResult } ;
58+ use crate :: deploy:: {
59+ prepare_for_pull, pull_from_prepared, MergeState , PreparedImportMeta , PreparedPullResult ,
60+ } ;
61+ use crate :: kernel_cmdline:: Cmdline ;
5962use crate :: lsm;
6063use crate :: progress_jsonl:: ProgressWriter ;
6164use crate :: spec:: ImageReference ;
@@ -352,6 +355,50 @@ pub(crate) struct InstallToExistingRootOpts {
352355 pub ( crate ) root_path : Utf8PathBuf ,
353356}
354357
358+ #[ derive( Debug , clap:: Parser , PartialEq , Eq ) ]
359+ pub ( crate ) struct InstallResetOpts {
360+ /// Acknowledge that this command is experimental.
361+ #[ clap( long) ]
362+ pub ( crate ) experimental : bool ,
363+
364+ #[ clap( flatten) ]
365+ pub ( crate ) source_opts : InstallSourceOpts ,
366+
367+ #[ clap( flatten) ]
368+ pub ( crate ) target_opts : InstallTargetOpts ,
369+
370+ /// Name of the target stateroot. If not provided, one will be automatically
371+ /// generated of the form s<year>-<serial> where <serial> starts at zero and
372+ /// increments automatically.
373+ #[ clap( long) ]
374+ pub ( crate ) stateroot : Option < String > ,
375+
376+ /// Don't display progress
377+ #[ clap( long) ]
378+ pub ( crate ) quiet : bool ,
379+
380+ #[ clap( flatten) ]
381+ pub ( crate ) progress : crate :: cli:: ProgressOptions ,
382+
383+ /// Restart or reboot into the new target image.
384+ ///
385+ /// Currently, this option always reboots. In the future this command
386+ /// will detect the case where no kernel changes are queued, and perform
387+ /// a userspace-only restart.
388+ #[ clap( long) ]
389+ pub ( crate ) apply : bool ,
390+
391+ /// Skip inheriting any automatically discovered root file system kernel arguments.
392+ #[ clap( long) ]
393+ no_root_kargs : bool ,
394+
395+ /// Add a kernel argument. This option can be provided multiple times.
396+ ///
397+ /// Example: --karg=nosmt --karg=console=ttyS0,114800n8
398+ #[ clap( long) ]
399+ karg : Option < Vec < String > > ,
400+ }
401+
355402/// Global state captured from the container.
356403#[ derive( Debug , Clone ) ]
357404pub ( crate ) struct SourceInfo {
@@ -385,6 +432,24 @@ pub(crate) struct State {
385432 pub ( crate ) tempdir : TempDir ,
386433}
387434
435+ impl InstallTargetOpts {
436+ pub ( crate ) fn imageref ( & self ) -> Result < Option < ostree_container:: OstreeImageReference > > {
437+ let Some ( target_imgname) = self . target_imgref . as_deref ( ) else {
438+ return Ok ( None ) ;
439+ } ;
440+ let target_transport =
441+ ostree_container:: Transport :: try_from ( self . target_transport . as_str ( ) ) ?;
442+ let target_imgref = ostree_container:: OstreeImageReference {
443+ sigverify : ostree_container:: SignatureSource :: ContainerPolicyAllowInsecure ,
444+ imgref : ostree_container:: ImageReference {
445+ transport : target_transport,
446+ name : target_imgname. to_string ( ) ,
447+ } ,
448+ } ;
449+ Ok ( Some ( target_imgref) )
450+ }
451+ }
452+
388453impl State {
389454 #[ context( "Loading SELinux policy" ) ]
390455 pub ( crate ) fn load_policy ( & self ) -> Result < Option < ostree:: SePolicy > > {
@@ -2019,6 +2084,94 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
20192084 install_to_filesystem ( opts, true , cleanup) . await
20202085}
20212086
2087+ pub ( crate ) async fn install_reset ( opts : InstallResetOpts ) -> Result < ( ) > {
2088+ let rootfs = & Dir :: open_ambient_dir ( "/" , cap_std:: ambient_authority ( ) ) ?;
2089+ if !opts. experimental {
2090+ anyhow:: bail!( "This command requires --experimental" ) ;
2091+ }
2092+
2093+ let prog: ProgressWriter = opts. progress . try_into ( ) ?;
2094+
2095+ let sysroot = & crate :: cli:: get_storage ( ) . await ?;
2096+ let repo = & sysroot. repo ( ) ;
2097+ let ( booted_deployment, _deployments, host) =
2098+ crate :: status:: get_status_require_booted ( sysroot) ?;
2099+
2100+ let stateroots = list_stateroots ( sysroot) ?;
2101+ dbg ! ( & stateroots) ;
2102+ let target_stateroot = if let Some ( s) = opts. stateroot {
2103+ s
2104+ } else {
2105+ let now = chrono:: Utc :: now ( ) ;
2106+ let r = allocate_new_stateroot ( & sysroot, & stateroots, now) ?;
2107+ r. name
2108+ } ;
2109+
2110+ let booted_stateroot = booted_deployment. osname ( ) ;
2111+ assert ! ( booted_stateroot. as_str( ) != target_stateroot) ;
2112+ let ( fetched, spec) = if let Some ( target) = opts. target_opts . imageref ( ) ? {
2113+ let mut new_spec = host. spec ;
2114+ new_spec. image = Some ( target. into ( ) ) ;
2115+ let fetched = crate :: deploy:: pull (
2116+ repo,
2117+ & new_spec. image . as_ref ( ) . unwrap ( ) ,
2118+ None ,
2119+ opts. quiet ,
2120+ prog. clone ( ) ,
2121+ )
2122+ . await ?;
2123+ ( fetched, new_spec)
2124+ } else {
2125+ let imgstate = host
2126+ . status
2127+ . booted
2128+ . map ( |b| b. query_image ( repo) )
2129+ . transpose ( ) ?
2130+ . flatten ( )
2131+ . ok_or_else ( || anyhow:: anyhow!( "No image source specified" ) ) ?;
2132+ ( Box :: new ( ( * imgstate) . into ( ) ) , host. spec )
2133+ } ;
2134+ let spec = crate :: deploy:: RequiredHostSpec :: from_spec ( & spec) ?;
2135+
2136+ // Compute the kernel arguments to inherit. By default, that's only those involved
2137+ // in the root filesystem.
2138+ let root_kargs = if opts. no_root_kargs {
2139+ Vec :: new ( )
2140+ } else {
2141+ let bootcfg = booted_deployment
2142+ . bootconfig ( )
2143+ . ok_or_else ( || anyhow ! ( "Missing bootcfg for booted deployment" ) ) ?;
2144+ if let Some ( options) = bootcfg. get ( "options" ) {
2145+ let options = options. split_ascii_whitespace ( ) . collect :: < Vec < _ > > ( ) ;
2146+ crate :: kernel:: root_args_from_cmdline ( & options)
2147+ . into_iter ( )
2148+ . map ( ToOwned :: to_owned)
2149+ . collect :: < Vec < _ > > ( )
2150+ } else {
2151+ Vec :: new ( )
2152+ }
2153+ } ;
2154+
2155+ let kargs = crate :: kargs:: get_kargs_in_root ( rootfs, std:: env:: consts:: ARCH ) ?
2156+ . into_iter ( )
2157+ . chain ( root_kargs. into_iter ( ) )
2158+ . chain ( opts. karg . unwrap_or_default ( ) )
2159+ . collect :: < Vec < _ > > ( ) ;
2160+
2161+ let from = MergeState :: Reset {
2162+ stateroot : target_stateroot,
2163+ kargs,
2164+ } ;
2165+ crate :: deploy:: stage ( sysroot, from, & fetched, & spec, prog. clone ( ) ) . await ?;
2166+
2167+ sysroot. update_mtime ( ) ?;
2168+
2169+ if opts. apply {
2170+ crate :: reboot:: reboot ( ) ?;
2171+ }
2172+ Ok ( ( ) )
2173+ }
2174+
20222175/// Implementation of `bootc install finalize`.
20232176pub ( crate ) async fn install_finalize ( target : & Utf8Path ) -> Result < ( ) > {
20242177 // Log the installation finalization operation to systemd journal
0 commit comments