@@ -98,21 +98,9 @@ impl OsRebuildArgs {
9898 final_attr : Option < String > ,
9999 elevation : ElevationStrategy ,
100100 ) -> Result < ( ) > {
101- use OsRebuildVariant :: { Boot , Build , BuildVm , Switch , Test } ;
101+ use OsRebuildVariant :: { Build , BuildVm } ;
102102
103- if self . build_host . is_some ( ) || self . target_host . is_some ( ) {
104- // This can fail, we only care about prompting the user
105- // for ssh key login beforehand.
106- let _ = ensure_ssh_key_login ( ) ;
107- }
108-
109- let elevate = check_and_get_elevation_status ( self . bypass_root_check ) ?;
110-
111- if self . update_args . update_all || self . update_args . update_input . is_some ( ) {
112- update ( & self . common . installable , self . update_args . update_input ) ?;
113- }
114-
115- let target_hostname = get_resolved_hostname ( self . hostname . clone ( ) ) ?;
103+ let ( elevate, target_hostname) = self . setup_build_context ( ) ?;
116104
117105 // Only show the warning if we're explicitly building a VM
118106 // and no hostname was explicitly provided (--hostname was None)
@@ -129,49 +117,138 @@ impl OsRebuildArgs {
129117 ) ;
130118 }
131119
132- let ( out_path, _tempdir_guard) = match self . common . out_link {
133- Some ( p) => ( p, None ) ,
120+ let ( out_path, _tempdir_guard) = self . determine_output_path ( variant) ?;
121+
122+ let toplevel =
123+ self . resolve_installable_and_toplevel ( & target_hostname, final_attr) ?;
124+
125+ let message = match variant {
126+ BuildVm => "Building NixOS VM image" ,
127+ _ => "Building NixOS configuration" ,
128+ } ;
129+
130+ self . execute_build_command ( toplevel, & out_path, message) ?;
131+
132+ let target_profile = self . resolve_specialisation_and_profile ( & out_path) ?;
133+
134+ self . handle_dix_diff ( & target_profile) ?;
135+
136+ if self . common . dry || matches ! ( variant, Build | BuildVm ) {
137+ if self . common . ask {
138+ warn ! ( "--ask has no effect as dry run was requested" ) ;
139+ }
140+
141+ // For VM builds, print instructions on how to run the VM
142+ if matches ! ( variant, BuildVm ) && !self . common . dry {
143+ print_vm_instructions ( & out_path) ?;
144+ }
145+
146+ return Ok ( ( ) ) ;
147+ }
148+
149+ self . activate_rebuilt_config (
150+ variant,
151+ & out_path,
152+ & target_profile,
153+ elevate,
154+ elevation,
155+ ) ?;
156+
157+ Ok ( ( ) )
158+ }
159+
160+ /// Performs initial setup and gathers context for an OS rebuild operation.
161+ ///
162+ /// This includes:
163+ /// - Ensuring SSH key login if a remote build/target host is involved.
164+ /// - Checking and determining elevation status.
165+ /// - Performing updates to Nix inputs if specified.
166+ /// - Resolving the target hostname for the build.
167+ ///
168+ /// # Returns
169+ /// A `Result` containing a tuple:
170+ /// - `bool`: `true` if elevation is required, `false` otherwise.
171+ /// - `String`: The resolved target hostname.
172+ fn setup_build_context ( & self ) -> Result < ( bool , String ) > {
173+ if self . build_host . is_some ( ) || self . target_host . is_some ( ) {
174+ // This can fail, we only care about prompting the user
175+ // for ssh key login beforehand.
176+ let _ = ensure_ssh_key_login ( ) ;
177+ }
178+
179+ let elevate = check_and_get_elevation_status ( self . bypass_root_check ) ?;
180+
181+ if self . update_args . update_all || self . update_args . update_input . is_some ( ) {
182+ update (
183+ & self . common . installable ,
184+ self . update_args . update_input . clone ( ) ,
185+ ) ?;
186+ }
187+
188+ let target_hostname = get_resolved_hostname ( self . hostname . clone ( ) ) ?;
189+ Ok ( ( elevate, target_hostname) )
190+ }
191+
192+ fn determine_output_path (
193+ & self ,
194+ variant : & OsRebuildVariant ,
195+ ) -> Result < ( PathBuf , Option < tempfile:: TempDir > ) > {
196+ use OsRebuildVariant :: { Build , BuildVm } ;
197+ match self . common . out_link . clone ( ) {
198+ Some ( p) => Ok ( ( p, None ) ) ,
134199 None => {
135200 let ( path, guard) = if matches ! ( variant, BuildVm | Build ) {
136201 ( PathBuf :: from ( "result" ) , None )
137202 } else {
138203 let dir = tempfile:: Builder :: new ( ) . prefix ( "nh-os" ) . tempdir ( ) ?;
139204 ( dir. as_ref ( ) . join ( "result" ) , Some ( dir) )
140205 } ;
141- ( path, guard)
206+ Ok ( ( path, guard) )
142207 } ,
143- } ;
208+ }
209+ }
144210
145- // Use NH_OS_FLAKE if available, otherwise use the provided installable
211+ fn resolve_installable_and_toplevel (
212+ & self ,
213+ target_hostname : & str ,
214+ final_attr : Option < String > ,
215+ ) -> Result < Installable > {
146216 let installable = if let Some ( flake_installable) = parse_nh_os_flake_env ( ) ?
147217 {
148218 flake_installable
149219 } else {
150220 self . common . installable . clone ( )
151221 } ;
152222
153- let toplevel = toplevel_for (
154- & target_hostname,
223+ Ok ( toplevel_for (
224+ target_hostname,
155225 installable,
156226 final_attr. as_deref ( ) . unwrap_or ( "toplevel" ) ,
157- ) ;
158-
159- let message = match variant {
160- BuildVm => "Building NixOS VM image" ,
161- _ => "Building NixOS configuration" ,
162- } ;
227+ ) )
228+ }
163229
230+ fn execute_build_command (
231+ & self ,
232+ toplevel : Installable ,
233+ out_path : & Path ,
234+ message : & str ,
235+ ) -> Result < ( ) > {
164236 commands:: Build :: new ( toplevel)
165237 . extra_arg ( "--out-link" )
166- . extra_arg ( & out_path)
238+ . extra_arg ( out_path)
167239 . extra_args ( & self . extra_args )
168240 . passthrough ( & self . common . passthrough )
169241 . builder ( self . build_host . clone ( ) )
170242 . message ( message)
171243 . nom ( !self . common . no_nom )
172244 . run ( )
173- . wrap_err ( "Failed to build configuration" ) ?;
245+ . wrap_err ( "Failed to build configuration" )
246+ }
174247
248+ fn resolve_specialisation_and_profile (
249+ & self ,
250+ out_path : & Path ,
251+ ) -> Result < PathBuf > {
175252 let current_specialisation = std:: fs:: read_to_string ( SPEC_LOCATION ) . ok ( ) ;
176253
177254 let target_specialisation = if self . no_specialisation {
@@ -183,7 +260,7 @@ impl OsRebuildArgs {
183260 debug ! ( "Target specialisation: {target_specialisation:?}" ) ;
184261
185262 let target_profile = target_specialisation. as_ref ( ) . map_or_else (
186- || out_path. clone ( ) ,
263+ || out_path. to_path_buf ( ) ,
187264 |spec| out_path. join ( "specialisation" ) . join ( spec) ,
188265 ) ;
189266
@@ -201,10 +278,13 @@ impl OsRebuildArgs {
201278 ) ) ;
202279 }
203280
281+ Ok ( target_profile)
282+ }
283+
284+ fn handle_dix_diff ( & self , target_profile : & Path ) -> Result < ( ) > {
204285 match self . common . diff {
205286 DiffType :: Always => {
206- let _ =
207- print_dix_diff ( & PathBuf :: from ( CURRENT_PROFILE ) , & target_profile) ;
287+ let _ = print_dix_diff ( & PathBuf :: from ( CURRENT_PROFILE ) , target_profile) ;
208288 } ,
209289 DiffType :: Never => {
210290 debug ! ( "Not running dix as the --diff flag is set to never." ) ;
@@ -221,7 +301,7 @@ impl OsRebuildArgs {
221301 target_profile. display( )
222302 ) ;
223303 let _ =
224- print_dix_diff ( & PathBuf :: from ( CURRENT_PROFILE ) , & target_profile) ;
304+ print_dix_diff ( & PathBuf :: from ( CURRENT_PROFILE ) , target_profile) ;
225305 } else {
226306 debug ! (
227307 "Not running dix as a remote host is involved or an explicit \
@@ -230,19 +310,19 @@ impl OsRebuildArgs {
230310 }
231311 } ,
232312 }
313+ Ok ( ( ) )
314+ }
233315
234- if self . common . dry || matches ! ( variant, Build | BuildVm ) {
235- if self . common . ask {
236- warn ! ( "--ask has no effect as dry run was requested" ) ;
237- }
238-
239- // For VM builds, print instructions on how to run the VM
240- if matches ! ( variant, BuildVm ) && !self . common . dry {
241- print_vm_instructions ( & out_path) ?;
242- }
243-
244- return Ok ( ( ) ) ;
245- }
316+ #[ expect( clippy:: too_many_arguments) ]
317+ fn activate_rebuilt_config (
318+ & self ,
319+ variant : & OsRebuildVariant ,
320+ out_path : & Path ,
321+ target_profile : & Path ,
322+ elevate : bool ,
323+ elevation : ElevationStrategy ,
324+ ) -> Result < ( ) > {
325+ use OsRebuildVariant :: { Boot , Switch , Test } ;
246326
247327 if self . common . ask {
248328 let confirmation = inquire:: Confirm :: new ( "Apply the config?" )
@@ -315,8 +395,8 @@ impl OsRebuildArgs {
315395
316396 let mut cmd = Command :: new ( switch_to_configuration)
317397 . arg ( "boot" )
318- . ssh ( self . target_host )
319- . elevate ( elevate. then_some ( elevation) )
398+ . ssh ( self . target_host . clone ( ) )
399+ . elevate ( elevate. then_some ( elevation. clone ( ) ) )
320400 . message ( "Adding configuration to bootloader" )
321401 . preserve_envs ( [ "NIXOS_INSTALL_BOOTLOADER" ] ) ;
322402
@@ -331,7 +411,6 @@ impl OsRebuildArgs {
331411 }
332412
333413 debug ! ( "Completed {variant:?} operation with output path: {out_path:?}" ) ;
334-
335414 Ok ( ( ) )
336415 }
337416}
@@ -517,42 +596,40 @@ impl OsRollbackArgs {
517596fn find_vm_script ( out_path : & Path ) -> Result < PathBuf > {
518597 let bin_dir = out_path. join ( "bin" ) ;
519598
520- if !bin_dir. exists ( ) {
599+ if !bin_dir. is_dir ( ) {
521600 bail ! (
522601 "VM build output missing bin directory at {}" ,
523602 bin_dir. display( )
524603 ) ;
525604 }
526605
527- let entries = fs:: read_dir ( & bin_dir) . wrap_err_with ( || {
528- format ! ( "Failed to read bin directory at {}" , bin_dir. display( ) )
529- } ) ?;
530-
531- let mut vm_script: Option < PathBuf > = None ;
532- for entry_result in entries {
533- match entry_result {
534- Ok ( entry) => {
535- let fname = entry. file_name ( ) ;
536- if fname
537- . to_str ( )
538- . is_some_and ( |name| name. starts_with ( "run-" ) && name. ends_with ( "-vm" ) )
539- {
540- vm_script = Some ( entry. path ( ) ) ;
541- break ;
542- }
543- } ,
544- Err ( e) => {
545- warn ! (
546- "Error reading entry in VM bin directory ({}): {}" ,
547- bin_dir. display( ) ,
548- e
549- ) ;
550- } ,
551- }
552- }
553- let vm_script = vm_script. ok_or_else ( || {
554- eyre ! ( "Could not find VM runner script in {}" , bin_dir. display( ) )
555- } ) ?;
606+ let vm_script = fs:: read_dir ( & bin_dir)
607+ . wrap_err_with ( || {
608+ format ! ( "Failed to read directory {}" , bin_dir. display( ) )
609+ } ) ?
610+ . filter_map ( |entry_result| {
611+ match entry_result {
612+ Ok ( entry) => Some ( entry) ,
613+ Err ( e) => {
614+ warn ! ( "Error reading entry in {}: {}" , bin_dir. display( ) , e) ;
615+ None
616+ } ,
617+ }
618+ } )
619+ . find_map ( |entry| {
620+ let fname = entry. file_name ( ) ;
621+ if fname
622+ . to_str ( )
623+ . is_some_and ( |name| name. starts_with ( "run-" ) && name. ends_with ( "-vm" ) )
624+ {
625+ Some ( entry. path ( ) )
626+ } else {
627+ None
628+ }
629+ } )
630+ . ok_or_else ( || {
631+ eyre ! ( "Could not find VM runner script in {}" , bin_dir. display( ) )
632+ } ) ?;
556633
557634 Ok ( vm_script)
558635}
0 commit comments