@@ -80,6 +80,13 @@ pub(crate) struct UpgradeOpts {
8080 #[ clap( long, conflicts_with = "check" ) ]
8181 pub ( crate ) apply : bool ,
8282
83+ /// Configure soft reboot behavior.
84+ ///
85+ /// 'required' will fail if soft reboot is not available.
86+ /// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
87+ #[ clap( long = "soft-reboot" , conflicts_with = "check" ) ]
88+ pub ( crate ) soft_reboot : Option < SoftRebootMode > ,
89+
8390 #[ clap( flatten) ]
8491 pub ( crate ) progress : ProgressOptions ,
8592}
@@ -99,6 +106,13 @@ pub(crate) struct SwitchOpts {
99106 #[ clap( long) ]
100107 pub ( crate ) apply : bool ,
101108
109+ /// Configure soft reboot behavior.
110+ ///
111+ /// 'required' will fail if soft reboot is not available.
112+ /// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
113+ #[ clap( long = "soft-reboot" ) ]
114+ pub ( crate ) soft_reboot : Option < SoftRebootMode > ,
115+
102116 /// The transport; e.g. oci, oci-archive, containers-storage. Defaults to `registry`.
103117 #[ clap( long, default_value = "registry" ) ]
104118 pub ( crate ) transport : String ,
@@ -142,6 +156,13 @@ pub(crate) struct RollbackOpts {
142156 /// a userspace-only restart.
143157 #[ clap( long) ]
144158 pub ( crate ) apply : bool ,
159+
160+ /// Configure soft reboot behavior.
161+ ///
162+ /// 'required' will fail if soft reboot is not available.
163+ /// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
164+ #[ clap( long = "soft-reboot" ) ]
165+ pub ( crate ) soft_reboot : Option < SoftRebootMode > ,
145166}
146167
147168/// Perform an edit operation
@@ -167,6 +188,15 @@ pub(crate) enum OutputFormat {
167188 Json ,
168189}
169190
191+ #[ derive( Debug , Clone , Copy , ValueEnum , PartialEq , Eq ) ]
192+ #[ clap( rename_all = "lowercase" ) ]
193+ pub ( crate ) enum SoftRebootMode {
194+ /// Require a soft reboot; fail if not possible
195+ Required ,
196+ /// Automatically use soft reboot if possible, otherwise use regular reboot
197+ Auto ,
198+ }
199+
170200/// Perform an status operation
171201#[ derive( Debug , Parser , PartialEq , Eq ) ]
172202pub ( crate ) struct StatusOpts {
@@ -562,7 +592,7 @@ pub(crate) enum Opt {
562592 Note on Rollbacks and the `/etc` Directory:
563593
564594 When you perform a rollback (e.g., with `bootc rollback`), any
565- changes made to files in the `/etc` directory won’ t carry over
595+ changes made to files in the `/etc` directory won' t carry over
566596 to the rolled-back deployment. The `/etc` files will revert
567597 to their state from that previous deployment instead.
568598
@@ -741,6 +771,104 @@ pub(crate) fn require_root(is_container: bool) -> Result<()> {
741771 Ok ( ( ) )
742772}
743773
774+ /// Check if a deployment has soft reboot capability
775+ fn has_soft_reboot_capability ( deployment : Option < & crate :: spec:: BootEntry > ) -> bool {
776+ deployment. map ( |d| d. soft_reboot_capable ) . unwrap_or ( false )
777+ }
778+
779+ /// Prepare a soft reboot for the given deployment
780+ #[ context( "Preparing soft reboot" ) ]
781+ fn prepare_soft_reboot (
782+ sysroot : & crate :: store:: Storage ,
783+ deployment : & ostree:: Deployment ,
784+ ) -> Result < ( ) > {
785+ let cancellable = ostree:: gio:: Cancellable :: NONE ;
786+ sysroot
787+ . sysroot
788+ . deployment_set_soft_reboot ( deployment, false , cancellable)
789+ . context ( "Failed to prepare soft-reboot" ) ?;
790+ Ok ( ( ) )
791+ }
792+
793+ /// Handle soft reboot based on the configured mode
794+ #[ context( "Handling soft reboot" ) ]
795+ fn handle_soft_reboot < F > (
796+ soft_reboot_mode : Option < SoftRebootMode > ,
797+ entry : Option < & crate :: spec:: BootEntry > ,
798+ deployment_type : & str ,
799+ execute_soft_reboot : F ,
800+ ) -> Result < ( ) >
801+ where
802+ F : FnOnce ( ) -> Result < ( ) > ,
803+ {
804+ let Some ( mode) = soft_reboot_mode else {
805+ return Ok ( ( ) ) ;
806+ } ;
807+
808+ let can_soft_reboot = has_soft_reboot_capability ( entry) ;
809+ match mode {
810+ SoftRebootMode :: Required => {
811+ if can_soft_reboot {
812+ execute_soft_reboot ( ) ?;
813+ } else {
814+ anyhow:: bail!(
815+ "Soft reboot was required but {} deployment is not soft-reboot capable" ,
816+ deployment_type
817+ ) ;
818+ }
819+ }
820+ SoftRebootMode :: Auto => {
821+ if can_soft_reboot {
822+ execute_soft_reboot ( ) ?;
823+ }
824+ }
825+ }
826+ Ok ( ( ) )
827+ }
828+
829+ /// Handle soft reboot for staged deployments (used by upgrade and switch)
830+ #[ context( "Handling staged soft reboot" ) ]
831+ fn handle_staged_soft_reboot (
832+ sysroot : & crate :: store:: Storage ,
833+ soft_reboot_mode : Option < SoftRebootMode > ,
834+ host : & crate :: spec:: Host ,
835+ ) -> Result < ( ) > {
836+ handle_soft_reboot (
837+ soft_reboot_mode,
838+ host. status . staged . as_ref ( ) ,
839+ "staged" ,
840+ || soft_reboot_staged ( sysroot) ,
841+ )
842+ }
843+
844+ /// Perform a soft reboot for a staged deployment
845+ #[ context( "Soft reboot staged deployment" ) ]
846+ fn soft_reboot_staged ( sysroot : & crate :: store:: Storage ) -> Result < ( ) > {
847+ println ! ( "Staged deployment is soft-reboot capable, preparing for soft-reboot..." ) ;
848+
849+ let deployments_list = sysroot. deployments ( ) ;
850+ let staged_deployment = deployments_list
851+ . iter ( )
852+ . find ( |d| d. is_staged ( ) )
853+ . ok_or_else ( || anyhow:: anyhow!( "Failed to find staged deployment" ) ) ?;
854+
855+ prepare_soft_reboot ( sysroot, staged_deployment) ?;
856+ Ok ( ( ) )
857+ }
858+
859+ /// Perform a soft reboot for a rollback deployment
860+ #[ context( "Soft reboot rollback deployment" ) ]
861+ fn soft_reboot_rollback ( sysroot : & crate :: store:: Storage ) -> Result < ( ) > {
862+ println ! ( "Rollback deployment is soft-reboot capable, preparing for soft-reboot..." ) ;
863+
864+ let deployments_list = sysroot. deployments ( ) ;
865+ let target_deployment = deployments_list
866+ . first ( )
867+ . ok_or_else ( || anyhow:: anyhow!( "No rollback deployment found!" ) ) ?;
868+
869+ prepare_soft_reboot ( sysroot, target_deployment)
870+ }
871+
744872/// A few process changes that need to be made for writing.
745873/// IMPORTANT: This may end up re-executing the current process,
746874/// so anything that happens before this should be idempotent.
@@ -813,6 +941,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
813941 let booted_image = host
814942 . status
815943 . booted
944+ . as_ref ( )
816945 . map ( |b| b. query_image ( repo) )
817946 . transpose ( ) ?
818947 . flatten ( ) ;
@@ -859,7 +988,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
859988 . unwrap_or_default ( ) ;
860989 if staged_unchanged {
861990 println ! ( "Staged update present, not changed." ) ;
862-
991+ handle_staged_soft_reboot ( sysroot , opts . soft_reboot , & host ) ? ;
863992 if opts. apply {
864993 crate :: reboot:: reboot ( ) ?;
865994 }
@@ -881,6 +1010,13 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
8811010 if changed {
8821011 sysroot. update_mtime ( ) ?;
8831012
1013+ if opts. soft_reboot . is_some ( ) {
1014+ // At this point we have new staged deployment and the host definition has changed.
1015+ // We need the updated host status before we check if we can prepare the soft-reboot.
1016+ let updated_host = crate :: status:: get_status ( sysroot, Some ( & booted_deployment) ) ?. 1 ;
1017+ handle_staged_soft_reboot ( sysroot, opts. soft_reboot , & updated_host) ?;
1018+ }
1019+
8841020 if opts. apply {
8851021 crate :: reboot:: reboot ( ) ?;
8861022 }
@@ -956,6 +1092,13 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
9561092
9571093 sysroot. update_mtime ( ) ?;
9581094
1095+ if opts. soft_reboot . is_some ( ) {
1096+ // At this point we have staged the deployment and the host definition has changed.
1097+ // We need the updated host status before we check if we can prepare the soft-reboot.
1098+ let updated_host = crate :: status:: get_status ( sysroot, Some ( & booted_deployment) ) ?. 1 ;
1099+ handle_staged_soft_reboot ( sysroot, opts. soft_reboot , & updated_host) ?;
1100+ }
1101+
9591102 if opts. apply {
9601103 crate :: reboot:: reboot ( ) ?;
9611104 }
@@ -969,6 +1112,18 @@ async fn rollback(opts: RollbackOpts) -> Result<()> {
9691112 let sysroot = & get_storage ( ) . await ?;
9701113 crate :: deploy:: rollback ( sysroot) . await ?;
9711114
1115+ if opts. soft_reboot . is_some ( ) {
1116+ // Get status of rollback deployment to check soft-reboot capability
1117+ let host = crate :: status:: get_status_require_booted ( sysroot) ?. 2 ;
1118+
1119+ handle_soft_reboot (
1120+ opts. soft_reboot ,
1121+ host. status . rollback . as_ref ( ) ,
1122+ "rollback" ,
1123+ || soft_reboot_rollback ( sysroot) ,
1124+ ) ?;
1125+ }
1126+
9721127 if opts. apply {
9731128 crate :: reboot:: reboot ( ) ?;
9741129 }
0 commit comments