@@ -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,76 @@ 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+ /// Perform a soft reboot for a staged deployment
830+ #[ context( "Soft reboot staged deployment" ) ]
831+ fn soft_reboot_staged ( sysroot : & crate :: store:: Storage ) -> Result < ( ) > {
832+ println ! ( "Staged deployment is soft-reboot capable, preparing for soft-reboot..." ) ;
833+
834+ let deployments_list = sysroot. deployments ( ) ;
835+ let staged_deployment = deployments_list
836+ . iter ( )
837+ . find ( |d| d. is_staged ( ) )
838+ . ok_or_else ( || anyhow:: anyhow!( "Failed to find staged deployment" ) ) ?;
839+
840+ prepare_soft_reboot ( sysroot, staged_deployment) ?;
841+ Ok ( ( ) )
842+ }
843+
744844/// A few process changes that need to be made for writing.
745845/// IMPORTANT: This may end up re-executing the current process,
746846/// so anything that happens before this should be idempotent.
@@ -859,7 +959,12 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
859959 . unwrap_or_default ( ) ;
860960 if staged_unchanged {
861961 println ! ( "Staged update present, not changed." ) ;
862-
962+ handle_soft_reboot (
963+ opts. soft_reboot ,
964+ host. status . staged . as_ref ( ) ,
965+ "staged" ,
966+ || soft_reboot_staged ( sysroot) ,
967+ ) ?;
863968 if opts. apply {
864969 crate :: reboot:: reboot ( ) ?;
865970 }
@@ -881,6 +986,18 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
881986 if changed {
882987 sysroot. update_mtime ( ) ?;
883988
989+ if opts. soft_reboot . is_some ( ) {
990+ // At this point we have new staged deployment and the host definition has changed.
991+ // We need the updated host status before we check if we can prepare the soft-reboot.
992+ let updated_host = crate :: status:: get_status ( sysroot, Some ( & booted_deployment) ) ?. 1 ;
993+ handle_soft_reboot (
994+ opts. soft_reboot ,
995+ updated_host. status . staged . as_ref ( ) ,
996+ "staged" ,
997+ || soft_reboot_staged ( sysroot) ,
998+ ) ?;
999+ }
1000+
8841001 if opts. apply {
8851002 crate :: reboot:: reboot ( ) ?;
8861003 }
@@ -956,6 +1073,18 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
9561073
9571074 sysroot. update_mtime ( ) ?;
9581075
1076+ if opts. soft_reboot . is_some ( ) {
1077+ // At this point we have staged the deployment and the host definition has changed.
1078+ // We need the updated host status before we check if we can prepare the soft-reboot.
1079+ let updated_host = crate :: status:: get_status ( sysroot, Some ( & booted_deployment) ) ?. 1 ;
1080+ handle_soft_reboot (
1081+ opts. soft_reboot ,
1082+ updated_host. status . staged . as_ref ( ) ,
1083+ "staged" ,
1084+ || soft_reboot_staged ( sysroot) ,
1085+ ) ?;
1086+ }
1087+
9591088 if opts. apply {
9601089 crate :: reboot:: reboot ( ) ?;
9611090 }
@@ -969,6 +1098,27 @@ async fn rollback(opts: RollbackOpts) -> Result<()> {
9691098 let sysroot = & get_storage ( ) . await ?;
9701099 crate :: deploy:: rollback ( sysroot) . await ?;
9711100
1101+ if opts. soft_reboot . is_some ( ) {
1102+ // Get status of rollback deployment to check soft-reboot capability
1103+ let host = crate :: status:: get_status_require_booted ( sysroot) ?. 2 ;
1104+
1105+ handle_soft_reboot (
1106+ opts. soft_reboot ,
1107+ host. status . rollback . as_ref ( ) ,
1108+ "rollback" ,
1109+ || {
1110+ println ! (
1111+ "Rollback deployment is soft-reboot capable, preparing for soft-reboot..."
1112+ ) ;
1113+ let deployments_list = sysroot. deployments ( ) ;
1114+ let target_deployment = deployments_list
1115+ . first ( )
1116+ . ok_or_else ( || anyhow:: anyhow!( "No rollback deployment found!" ) ) ?;
1117+ prepare_soft_reboot ( sysroot, target_deployment)
1118+ } ,
1119+ ) ?;
1120+ }
1121+
9721122 if opts. apply {
9731123 crate :: reboot:: reboot ( ) ?;
9741124 }
0 commit comments