Skip to content

Conversation

@NotAShelf
Copy link
Member

@NotAShelf NotAShelf commented Dec 4, 2025

Fixes #428

This is a large architectural change to NH, which lead to me extracting the remote build logic to its own file so that we may implement it for Darwin and Home-Manager as well. The --builders flag was dropped from nh::commands, and it was replaced with the new and shiny logic that hopefully avoids previous pitfalls.

The new nh::remote module handles remote builds, including:

  • Parsing remote host specifications.
  • Copying derivations to remote hosts using nix-copy-closure.
  • Building derivations on remote hosts via nix build.
  • Copying results back to localhost or directly to a target host.

The legacy builder logic is removed from the Build command and re-introduced in a new, more robust remote build implementation for NixOS rebuilds

Change-Id: I236eb1e35dd645f2169462d207bc82e76a6a6964

Summary by CodeRabbit

  • New Features

    • Added an optional --build-host flag and a full remote-build workflow (remote build host, optional target host, nom/substitutes, passthrough args, optional output transfer/out-link).
  • Refactor

    • Removed legacy per-build "builder" option and unified local/remote build flows.
  • Improvements

    • More robust command-line parsing for quoted and escaped arguments; improved SSH/remote handling.
  • Tests

    • Added extensive tests covering parsing and remote-build scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Walkthrough

Adds a public SSH-based remote build subsystem, integrates a new --build-host CLI flag into rebuild flows to route builds through remote::build_remote when provided, removes the Build struct's builder field and its injection into nix build, and replaces custom cmdline tokenization with shlex::split.

Changes

Cohort / File(s) Summary
Commands / parsing
src/commands.rs
Removed pub builder: Option<String> and its builder() setter; replaced manual tokenization in parse_cmdline_with_quotes with shlex::split; removed builder-related argument injection from Build::run; updated tests to cover quoting behaviors and removed builder expectations.
Remote subsystem (new)
src/remote.rs
New public module exposing RemoteHost, RemoteBuildConfig, build_remote, connectivity checks, SSH control/socket handling, shell-safe quoting, remote command execution, closure copy utilities, drvPath evaluation, and extensive unit tests.
CLI fields
src/interface.rs
Added build_host: Option<String> to HomeRebuildArgs and DarwinRebuildArgs (#[arg(long)]).
Rebuild integration
src/nixos.rs, src/darwin.rs, src/home.rs
When --build-host is provided: parse into RemoteHost, construct RemoteBuildConfig (build_host, optional target_host, use_nom, use_substitutes, merged extra_args), run remote::build_remote (with connectivity checks and out-link handling). When absent, retain local commands::Build flow. Renamed/updated execute_build call sites.
Crate wiring
src/lib.rs, src/main.rs
Added pub mod remote; to crate root and imported remote in main.rs.
Deps & tests
Cargo.toml, tests in src/commands.rs
Added dependency shlex = "1.3.0"; expanded quoting tests in src/commands.rs and removed builder-related test expectations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Local as nh (local host)
    participant Build as Build Host (ssh)
    participant Target as Target Host (optional)

    User->>Local: run nh ... --build-host <host> [installable]
    Local->>Local: evaluate installable -> drvPath (nix eval / prepare)
    Local->>Build: copy closure -> build host (scp/ssh, may use substitutes)
    Build->>Build: run nix build (with or without nom)
    alt target_host specified
        Build->>Target: copy build output to target host
        Target-->>User: final path or error
    else
        Build->>Local: copy build output back to local
        Local->>Local: create out-link if requested
        Local-->>User: final path or error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • src/remote.rs: SSH option construction, ControlPath/ControlMaster behavior, NIX_SSHOPTS merging, quoting/escaping, remote command error handling, and unit tests coverage.
    • src/nixos.rs, src/darwin.rs, src/home.rs: parse/validate build_host/target_host, merging extra_args/passthrough, correct branching between remote and local flows.
    • src/commands.rs: verify shlex::split covers all quoted/escaped cases asserted by updated tests and ensure removal of builder injection didn't leave residual behavior.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: refactoring the --build-host option to use remote build semantics across the codebase, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #428 by reimplementing --build-host with nixos-rebuild semantics: user-initiated SSH connections, closure transfer via nix-copy-closure, and remote execution through the new remote module.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing remote build semantics: new remote module, --build-host integration across Darwin/Home-Manager/NixOS, builder flag removal, and command-line parsing improvements are all in scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch notashelf/push-xwtloylwummt

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35fb346 and 7d6d1ba.

📒 Files selected for processing (4)
  • src/darwin.rs (3 hunks)
  • src/home.rs (3 hunks)
  • src/nixos.rs (6 hunks)
  • src/remote.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/home.rs
🧰 Additional context used
🧬 Code graph analysis (3)
src/darwin.rs (1)
src/remote.rs (3)
  • parse (78-118)
  • check_remote_connectivity (517-530)
  • build_remote (545-593)
src/remote.rs (1)
src/util.rs (1)
  • get_nix_variant (56-85)
src/nixos.rs (1)
src/remote.rs (3)
  • parse (78-118)
  • check_remote_connectivity (517-530)
  • build_remote (545-593)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test NH on Linux
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Build NH on Linux
  • GitHub Check: Build NH on Darwin
  • GitHub Check: Test NH on Linux
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Build NH on Linux
  • GitHub Check: Build NH on Darwin
🔇 Additional comments (20)
src/darwin.rs (3)

1-1: LGTM!

The imports are correctly added to support the new remote build functionality. The Into trait is used for converting extra args to OsString.

Also applies to: 18-18


112-142: LGTM!

The remote build implementation for Darwin is well-structured:

  • Correctly parses and validates the build host specification
  • Properly constructs RemoteBuildConfig with target_host: None (appropriate since Darwin activates locally)
  • Performs connectivity check before expensive operations
  • Error context wrapping is consistent

143-153: LGTM!

The local build fallback correctly preserves the existing behavior when no build_host is specified.

src/nixos.rs (4)

2-2: LGTM!

Import additions are appropriate for the new remote build integration.

Also applies to: 26-26


286-289: LGTM!

Documentation formatting improvement for readability.


349-411: LGTM!

The execute_build method correctly implements the remote/local build branching:

  • Properly parses both build_host and target_host using idiomatic Option::map().transpose() pattern
  • Constructs RemoteBuildConfig with all necessary fields
  • Performs connectivity check before expensive operations
  • Falls back to local build when no build_host is specified
  • The out-link is handled via the out_path parameter passed to build_remote

137-137: LGTM!

The call sites are updated to use the new unified execute_build method, correctly passing the installable, output path, and message.

Also applies to: 502-502

src/remote.rs (13)

12-48: LGTM!

The SSH control directory handling is well-designed:

  • Uses XDG_RUNTIME_DIR with sensible fallbacks
  • Process-ID-based naming provides isolation
  • OnceLock ensures single initialization
  • Graceful degradation to /tmp if directory creation fails

50-131: LGTM!

The RemoteHost parsing is robust with comprehensive validation:

  • Correctly strips URI schemes (ssh://, ssh-ng://)
  • Validates empty user/hostname cases
  • Rejects ports with helpful error message directing users to NIX_SSHOPTS or ~/.ssh/config
  • Uses rsplit('@') to correctly isolate the hostname part for validation

133-173: LGTM!

The SSH connectivity check is well-implemented:

  • Uses ConnectTimeout=10 and BatchMode=yes for a quick, non-interactive check
  • Runs minimal true command
  • Provides actionable error message with troubleshooting steps

191-197: LGTM!

The shell quoting now correctly uses shlex::try_quote as recommended, with a reasonable fallback for edge cases. This addresses the previous review feedback about replacing hand-rolled quoting.


199-235: LGTM!

SSH options handling is well-implemented:

  • Uses shlex::split for proper parsing of NIX_SSHOPTS
  • Documents the limitation of nix-copy-closure's simple whitespace splitting
  • Correctly orders user options before defaults

237-251: LGTM!

Correctly handles experimental features based on the Nix variant - Determinate Nix has stable features while Nix/Lix require the experimental flags.


253-302: LGTM!

The remote command execution is well-implemented:

  • Properly quotes all arguments with shell_quote for safety
  • Uses -- separator to prevent SSH from interpreting remote args as options
  • Provides informative error messages with exit status and stderr

304-410: LGTM!

The closure copy functions are correctly implemented:

  • Uses nix-copy-closure for single-host transfers with --to/--from
  • Uses nix copy for remote-to-remote transfers with appropriate ssh:// URIs
  • Correctly translates use_substitutes to the appropriate flag for each tool
  • Consistent error handling with stderr included in error messages

412-487: LGTM!

The derivation path evaluation is correctly implemented:

  • Properly appends .drvPath to the attribute path for each installable variant
  • Correctly rejects Store paths (already built) and Unspecified installables
  • Validates non-empty output to catch silent evaluation failures

489-530: LGTM!

The RemoteBuildConfig struct captures all necessary build parameters, and check_remote_connectivity provides early validation before expensive operations.


532-593: LGTM!

The build_remote function correctly implements the nixos-rebuild-ng workflow:

  1. Local evaluation of derivation path
  2. Copy closure to build host
  3. Remote build execution
  4. Copy result to appropriate destination (target host or localhost)
  5. Optional out-link creation

The logic at lines 576-581 correctly handles the case where local copy is needed for out-link creation even when a target host is specified.


652-746: LGTM!

The nom integration is well-implemented:

  • Uses --log-format internal-json --verbose for nom compatibility
  • Correctly checks the SSH process exit status (not nom's) since nom always succeeds
  • Queries output path separately after build since nom consumed the stdout
  • Uses popen() to access individual process statuses in the pipeline

748-1059: LGTM!

Comprehensive test coverage including:

  • RemoteHost parsing for various formats and error cases
  • shell_quote with roundtrip verification for edge cases (unicode, special chars, drv^* syntax)
  • SSH options handling with and without NIX_SSHOPTS
  • Proper use of #[serial] for tests modifying environment variables

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/remote.rs (2)

18-37: Consider cleanup of SSH control directory on exit.

The SSH control socket directory (nh-ssh-{pid}) persists after the program terminates. While the ControlPersist=60 setting ensures connections close after 60 seconds of inactivity, the empty directory remains. This is a minor resource leak.

You could implement cleanup using a Drop guard or atexit handler, though this is low priority since the impact is minimal (just empty directories in /tmp or XDG_RUNTIME_DIR).


368-409: Consider extracting common attribute-appending logic.

The pattern of cloning the installable and appending "drvPath" to the attribute is repeated for Flake, File, and Expression variants. This could be simplified with a helper method on Installable.

This is a minor refactor opportunity for future cleanup - the current implementation is correct and readable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8be4df and ec92308.

📒 Files selected for processing (7)
  • src/commands.rs (1 hunks)
  • src/home.rs (1 hunks)
  • src/interface.rs (2 hunks)
  • src/lib.rs (1 hunks)
  • src/main.rs (1 hunks)
  • src/nixos.rs (2 hunks)
  • src/remote.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/interface.rs (1)
src/commands.rs (1)
  • arg (248-251)
src/remote.rs (1)
src/util.rs (1)
  • get_nix_variant (56-85)
src/nixos.rs (1)
src/remote.rs (2)
  • parse (67-107)
  • build_remote (474-517)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Build NH on Linux
  • GitHub Check: profile
  • GitHub Check: Test NH on Linux
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Test NH on Linux
  • GitHub Check: Build NH on Linux
  • GitHub Check: Build NH on Darwin
🔇 Additional comments (10)
src/main.rs (1)

12-12: LGTM!

Module declaration is correctly placed and follows the existing naming conventions.

src/lib.rs (1)

13-13: LGTM!

Public module declaration is consistent with other modules in this file. The internal-use comment at line 1 applies to the entire library.

src/nixos.rs (2)

25-25: LGTM!

Import of remote module types is correctly placed and all imported items are used in the execute_build_command method.


354-407: Well-structured remote build implementation matching nixos-rebuild semantics.

The implementation correctly:

  1. Parses host specifications with proper error context
  2. Constructs the RemoteBuildConfig with all necessary parameters
  3. Preserves the local build path as a fallback
  4. Passes extra args from both explicit args and passthrough options

The inline documentation clearly explains the workflow, which helps maintainability.

src/commands.rs (1)

654-752: LGTM! Clean removal of builder field from Build struct.

The Build struct and its tests have been correctly updated to remove the builder field and related method. The test at line 1200 properly removes the .builder("user@host") call, and the struct now cleanly delegates remote build concerns to the new remote module.

src/remote.rs (5)

67-107: LGTM! Robust host specification parsing with helpful error messages.

The RemoteHost::parse function handles various input formats correctly and provides actionable error messages (e.g., suggesting NIX_SSHOPTS for port configuration). The validation of edge cases like empty usernames, hostnames, and invalid characters is comprehensive.


206-254: LGTM! Secure remote command execution.

The function properly:

  • Shell-quotes all arguments to prevent injection
  • Uses -- separator to prevent argument injection to SSH
  • Includes stderr in error messages for debugging
  • Wraps errors with context

256-362: LGTM! Well-structured closure copying functions.

The three copy functions handle the different scenarios correctly:

  • copy_closure_to and copy_closure_from use nix-copy-closure for localhost ↔ remote transfers
  • copy_closure_between_remotes uses nix copy with SSH URIs for remote-to-remote transfers
  • Substitutes flags are correctly mapped (--use-substitutes vs --substitute-on-destination)

577-653: LGTM! Clever approach for nom integration.

The double-invocation pattern (build with nom consuming output, then query for output path) is a reasonable workaround since nom consumes the JSON output. The second build is a no-op due to Nix caching, so the overhead is minimal.


655-762: LGTM! Comprehensive unit tests for parsing and quoting logic.

The tests provide good coverage for:

  • Various RemoteHost input formats and error cases
  • Shell quoting edge cases including the critical drv^* syntax
  • SSH options default values

The unsafe block at lines 753-755 for environment variable manipulation is acceptable in test code.

Comment on lines +551 to +559
// Collect extra args that are valid strings
let extra_args_strings: Vec<String> = config
.extra_args
.iter()
.filter_map(|s| s.to_str().map(String::from))
.collect();
for arg in &extra_args_strings {
args.push(arg);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Non-UTF-8 extra arguments are silently dropped.

The filter_map(|s| s.to_str().map(String::from)) silently discards any OsString arguments that aren't valid UTF-8. While rare, this could cause unexpected behavior if such arguments are passed.

Consider logging a warning when arguments are dropped:

  let extra_args_strings: Vec<String> = config
    .extra_args
    .iter()
-   .filter_map(|s| s.to_str().map(String::from))
+   .filter_map(|s| {
+     match s.to_str() {
+       Some(str) => Some(str.to_string()),
+       None => {
+         tracing::warn!("Dropping non-UTF-8 argument: {:?}", s);
+         None
+       }
+     }
+   })
    .collect();

The same pattern exists at lines 596-600.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Collect extra args that are valid strings
let extra_args_strings: Vec<String> = config
.extra_args
.iter()
.filter_map(|s| s.to_str().map(String::from))
.collect();
for arg in &extra_args_strings {
args.push(arg);
}
// Collect extra args that are valid strings
let extra_args_strings: Vec<String> = config
.extra_args
.iter()
.filter_map(|s| {
match s.to_str() {
Some(str) => Some(str.to_string()),
None => {
tracing::warn!("Dropping non-UTF-8 argument: {:?}", s);
None
}
}
})
.collect();
for arg in &extra_args_strings {
args.push(arg);
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/home.rs (1)

106-124: Consider extracting shared remote build configuration logic.

The RemoteBuildConfig construction here is nearly identical to the one in src/darwin.rs (lines 119-137). A shared helper function could reduce duplication:

// In a shared location (e.g., remote.rs or a new util)
fn build_config_from_args(
    build_host: RemoteHost,
    no_nom: bool,
    use_substitutes: bool,
    extra_args: &[String],
    passthrough: &Passthrough,
) -> RemoteBuildConfig { ... }

This is a minor refactoring opportunity that can be deferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec92308 and 1463ab0.

📒 Files selected for processing (2)
  • src/darwin.rs (2 hunks)
  • src/home.rs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/home.rs (1)
src/remote.rs (2)
  • parse (67-107)
  • build_remote (474-517)
src/darwin.rs (1)
src/remote.rs (2)
  • parse (67-107)
  • build_remote (474-517)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Test NH on Linux
  • GitHub Check: Build NH on Linux
  • GitHub Check: Build NH on Darwin
  • GitHub Check: treewide-checks
  • GitHub Check: profile
  • GitHub Check: Test NH on Linux
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Build NH on Darwin
  • GitHub Check: Build NH on Linux
  • GitHub Check: treewide-checks
🔇 Additional comments (4)
src/darwin.rs (2)

18-18: LGTM!

The new imports for the remote build subsystem are correctly added and all three symbols (remote, RemoteBuildConfig, RemoteHost) are used in the remote build logic below.


112-151: Well-structured remote build integration.

The conditional remote build path correctly:

  • Parses the host specification with proper error context
  • Constructs the RemoteBuildConfig with appropriate field mappings
  • Passes out_path as the out-link so build_remote creates the symlink there
  • Falls back to the local build flow when no build_host is specified

The subsequent code (line 153 onwards) correctly uses out_path which will be a valid symlink after either build path completes.

src/home.rs (2)

14-14: Imports are now properly used.

The previously flagged unused imports (remote, RemoteBuildConfig, RemoteHost) are now correctly used in the remote build logic at lines 103, 106, and 126 respectively.


99-138: Remote build integration looks correct.

The conditional remote build path properly:

  • Parses the host specification with error context
  • Builds RemoteBuildConfig with the correct field mappings
  • Invokes build_remote with the out-link for symlink creation
  • Falls back to local build when no build_host is provided

The post-build steps (activation, diffs, etc.) work correctly since out_path will be a valid symlink after either path.

Fixes #428

This is a large architectural change to NH, which lead to me extracting
the remote build logic to its own file so that we may implement it for
Darwin and Home-Manager as well. The `--builders` flag was dropped from
`nh::commands`, and it was replaced with the new and shiny logic that 
hopefully avoids previous pitfalls.

The new `nh::remote` module handles remote builds, including:

- Parsing remote host specifications.
- Copying derivations to remote hosts using `nix-copy-closure`.
- Building derivations on remote hosts via `nix build`.
- Copying results back to localhost or directly to a target host.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I236eb1e35dd645f2169462d207bc82e76a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I946d8e54261e9136c83f6dfe38b046106a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I0b82e84223c3df61cfa23464bd3d4bcc6a6a6964
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/remote.rs (1)

604-612: Non-UTF-8 extra arguments are silently dropped.

As flagged in a previous review, filter_map(|s| s.to_str().map(String::from)) silently discards any OsString arguments that aren't valid UTF-8. Consider logging a warning when this occurs:

   let extra_args_strings: Vec<String> = config
     .extra_args
     .iter()
-    .filter_map(|s| s.to_str().map(String::from))
+    .filter_map(|s| {
+      match s.to_str() {
+        Some(str) => Some(str.to_string()),
+        None => {
+          tracing::warn!("Dropping non-UTF-8 argument: {:?}", s);
+          None
+        }
+      }
+    })
     .collect();

The same pattern exists at lines 649-656.

🧹 Nitpick comments (5)
src/commands.rs (1)

44-46: Silent failure on malformed input may hide user errors.

Using unwrap_or_default() silently returns an empty vector when shlex::split fails (e.g., unclosed quotes). While the test at line 1421-1424 documents this behavior, returning an empty vector could mask user input errors in contexts like self_elevate_cmd (line 444), where an empty parse leads to a bail at line 447.

Consider logging a warning when parsing fails to aid debugging:

 fn parse_cmdline_with_quotes(cmdline: &str) -> Vec<String> {
-  shlex::split(cmdline).unwrap_or_default()
+  shlex::split(cmdline).unwrap_or_else(|| {
+    tracing::warn!("Failed to parse command line (unclosed quote?): {cmdline}");
+    Vec::new()
+  })
 }
src/darwin.rs (1)

112-140: Consider adding SSH reachability check for consistency with NixOS module.

The NixOS implementation (src/nixos.rs lines 374-385) performs an SSH reachability check before starting expensive evaluation. This provides early feedback if the build host is unreachable. The Darwin implementation omits this check.

     if let Some(ref build_host_str) = self.build_host {
       info!("Building Darwin configuration");
 
       let build_host = RemoteHost::parse(build_host_str)
         .wrap_err("Invalid build host specification")?;
 
+      // Check SSH connectivity before expensive evaluation
+      info!("Checking SSH connectivity to build host...");
+      remote::check_ssh_reachability(&build_host)
+        .wrap_err(format!("Build host ({build_host}) is not reachable"))?;
+
       let config = RemoteBuildConfig {
src/home.rs (1)

99-127: Consider adding SSH reachability check for consistency.

Similar to the Darwin module, the Home-Manager implementation omits the SSH reachability pre-check that exists in the NixOS module (src/nixos.rs lines 374-385). For consistency and better user experience, consider adding the check:

     if let Some(ref build_host_str) = self.build_host {
       info!("Building Home-Manager configuration");
 
       let build_host = RemoteHost::parse(build_host_str)
         .wrap_err("Invalid build host specification")?;
 
+      info!("Checking SSH connectivity to build host...");
+      remote::check_ssh_reachability(&build_host)
+        .wrap_err(format!("Build host ({build_host}) is not reachable"))?;
+
       let config = RemoteBuildConfig {
src/nixos.rs (1)

286-291: Minor: Documentation formatting could be improved.

The Returns section has inconsistent line breaks that affect readability:

   /// # Returns
-  ///
-  /// `Result` containing a tuple:
-  ///
+  /// 
+  /// Returns a `Result` containing a tuple:
+  /// 
   /// - `bool`: `true` if elevation is required, `false` otherwise.
   /// - `String`: The resolved target hostname.
src/remote.rs (1)

18-48: SSH control directory is not cleaned up on exit.

The SSH control directory created at get_ssh_control_dir() persists after the program exits. While the directory itself is small, the SSH control sockets inside may linger. Consider documenting this behavior or implementing cleanup:

+// Note: The SSH control directory and sockets are intentionally not cleaned up
+// on exit to allow connection multiplexing across multiple nh invocations.
+// SSH's ControlPersist=60 handles socket cleanup after idle timeout.
 fn get_ssh_control_dir() -> &'static PathBuf {

Alternatively, you could register an atexit handler to clean up the directory if this is undesirable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e10b61 and 1f912df.

📒 Files selected for processing (5)
  • src/commands.rs (4 hunks)
  • src/darwin.rs (3 hunks)
  • src/home.rs (3 hunks)
  • src/nixos.rs (6 hunks)
  • src/remote.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/home.rs (1)
src/remote.rs (2)
  • parse (78-118)
  • build_remote (522-570)
src/nixos.rs (1)
src/remote.rs (3)
  • check_ssh_reachability (143-173)
  • parse (78-118)
  • build_remote (522-570)
src/darwin.rs (1)
src/remote.rs (2)
  • parse (78-118)
  • build_remote (522-570)
src/remote.rs (1)
src/util.rs (1)
  • get_nix_variant (56-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Build NH on Linux
  • GitHub Check: Build NH on Darwin
  • GitHub Check: treewide-checks
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Test NH on Linux
  • GitHub Check: Build NH on Darwin
  • GitHub Check: Build NH on Linux
  • GitHub Check: treewide-checks
  • GitHub Check: Test NH on Linux
  • GitHub Check: Test NH on Darwin
🔇 Additional comments (9)
src/commands.rs (2)

690-733: LGTM! Correct handling of pipeline exit status.

Using popen() to access individual processes and checking the first process's (nix build) exit status is the right approach. This ensures build failures are properly detected even when nom always succeeds.


1377-1442: LGTM! Comprehensive test coverage for shell parsing.

The new tests thoroughly cover edge cases including escaped quotes, backslash escapes, nix store paths, environment variables in quotes, unclosed quotes, and complex sudo commands. Good coverage of the shlex delegation.

src/darwin.rs (1)

141-151: LGTM! Clean conditional branching between remote and local builds.

The remote/local build branching is well-structured. The local build path correctly preserves the existing behavior with --out-link, extra_args, passthrough, and nom support.

src/home.rs (1)

128-138: LGTM! Local build fallback is correctly preserved.

The existing local build path is properly maintained as a fallback when no build_host is specified.

src/nixos.rs (1)

349-421: LGTM! Well-structured remote build implementation.

The execute_build method properly implements the remote build workflow:

  • Validates both build_host and target_host specifications
  • Performs SSH reachability checks before expensive evaluation
  • Constructs RemoteBuildConfig with all necessary fields
  • Falls back to local build when no build_host is specified

The early SSH connectivity check provides good user feedback before starting the evaluation.

src/remote.rs (4)

191-197: LGTM! Shell quoting uses shlex with appropriate fallback.

The implementation correctly uses shlex::try_quote with a sensible fallback for edge cases (NUL bytes). The fallback uses proper single-quote escaping.


704-722: Re-running nix build to get output path is a reasonable workaround.

Since nom consumes the build output, re-querying with --print-out-paths is a pragmatic solution. The second invocation is effectively a no-op since the derivation is already built.


522-569: LGTM! Well-structured remote build workflow.

The build_remote function correctly implements the four-step workflow:

  1. Evaluate drvPath locally
  2. Copy derivation to build host
  3. Build on remote
  4. Copy result to destination (target_host or localhost)

The logic correctly handles the case where both out_link is requested and target_host is set, ensuring the closure is available locally for specialisation resolution.


725-1036: LGTM! Comprehensive test coverage.

The test suite thoroughly covers:

  • Host parsing (bare, user@host, URI schemes, error cases)
  • Shell quoting (simple strings, special characters, roundtrip verification)
  • SSH options (default, with NIX_SSHOPTS, quoted values)
  • Control directory creation

The roundtrip tests at lines 894-926 are particularly valuable for ensuring shell quoting correctness.

Fixes a minor issue in how commands that are invalid or improperly
handled are forwarded to the Nix command. Replaces `join()` with
`popen()` to access individual processes in the pipeline. This way we
can better check the exist status of the `nix build` process and
properly propagate them.

Also improves naming a little bit because why not?

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I8a44abf924f9c9a1c06d102e5a3f40aa6a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia8506cad3352243001a281e99b8162c26a6a6964
Tiny improvement to how remote connections are made. We now check BEFORE
the connection is made, so that we can avoid all that expensive eval if
it's not reachable. This is not infallible, but it is better. To fix
some target-host quirks, we also have to deal with local symlinks so we
enforce it locally either way.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I65fd7258828459ea82fe6739383567556a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I2366fac6ca7a72fc73eecfc0b07bd2d76a6a6964
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--build-host option in nh is misleading compared to nixos-rebuild

2 participants