Skip to content

Commit c248d2c

Browse files
authored
Merge pull request #276 from cramt/add_target_host_option
nixos: add `--target-host` and `--build-host` options
2 parents 28972b6 + 75e09d0 commit c248d2c

File tree

5 files changed

+107
-15
lines changed

5 files changed

+107
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Added
88

9+
- Nh now supports the `--build-host` and `--target-host` cli arguments
10+
911
- Nh now checks if the current Nix implementation has necessary experimental
1012
features enabled. In mainline Nix (CppNix, etc.) we check for `nix-command`
1113
and `flakes` being set. In Lix, we also use `repl-flake` as it is still

src/commands.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,25 @@ use tracing::{debug, info};
1010

1111
use crate::installable::Installable;
1212

13+
fn ssh_wrap(cmd: Exec, ssh: Option<&str>) -> Exec {
14+
if let Some(ssh) = ssh {
15+
Exec::cmd("ssh")
16+
.arg("-T")
17+
.arg(ssh)
18+
.stdin(cmd.to_cmdline_lossy().as_str())
19+
} else {
20+
cmd
21+
}
22+
}
23+
1324
#[derive(Debug)]
1425
pub struct Command {
1526
dry: bool,
1627
message: Option<String>,
1728
command: OsString,
1829
args: Vec<OsString>,
1930
elevate: bool,
31+
ssh: Option<String>,
2032
}
2133

2234
impl Command {
@@ -27,6 +39,7 @@ impl Command {
2739
command: command.as_ref().to_os_string(),
2840
args: vec![],
2941
elevate: false,
42+
ssh: None,
3043
}
3144
}
3245

@@ -40,6 +53,11 @@ impl Command {
4053
self
4154
}
4255

56+
pub fn ssh(mut self, ssh: Option<String>) -> Self {
57+
self.ssh = ssh;
58+
self
59+
}
60+
4361
pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
4462
self.args.push(arg.as_ref().to_os_string());
4563
self
@@ -94,9 +112,9 @@ impl Command {
94112
cmd.arg(&self.command).args(&self.args)
95113
} else {
96114
Exec::cmd(&self.command).args(&self.args)
97-
}
98-
.stderr(Redirection::None)
99-
.stdout(Redirection::None);
115+
};
116+
let cmd =
117+
ssh_wrap(cmd.stderr(Redirection::None), self.ssh.as_deref()).stdout(Redirection::None);
100118

101119
if let Some(m) = &self.message {
102120
info!("{}", m);
@@ -106,9 +124,9 @@ impl Command {
106124

107125
if !self.dry {
108126
if let Some(m) = &self.message {
109-
cmd.join().wrap_err(m.clone())?;
127+
cmd.capture().wrap_err(m.clone())?;
110128
} else {
111-
cmd.join()?;
129+
cmd.capture()?;
112130
}
113131
}
114132

@@ -141,6 +159,7 @@ pub struct Build {
141159
installable: Installable,
142160
extra_args: Vec<OsString>,
143161
nom: bool,
162+
builder: Option<String>,
144163
}
145164

146165
impl Build {
@@ -150,6 +169,7 @@ impl Build {
150169
installable,
151170
extra_args: vec![],
152171
nom: false,
172+
builder: None,
153173
}
154174
}
155175

@@ -168,6 +188,11 @@ impl Build {
168188
self
169189
}
170190

191+
pub fn builder(mut self, builder: Option<String>) -> Self {
192+
self.builder = builder;
193+
self
194+
}
195+
171196
pub fn extra_args<I>(mut self, args: I) -> Self
172197
where
173198
I: IntoIterator,
@@ -192,9 +217,15 @@ impl Build {
192217
.arg("build")
193218
.args(&installable_args)
194219
.args(&["--log-format", "internal-json", "--verbose"])
220+
.args(&match &self.builder {
221+
Some(host) => {
222+
vec!["--builders".to_string(), format!("ssh://{host} - - - 100")]
223+
}
224+
None => vec![],
225+
})
195226
.args(&self.extra_args)
196-
.stdout(Redirection::Pipe)
197227
.stderr(Redirection::Merge)
228+
.stdout(Redirection::Pipe)
198229
| Exec::cmd("nom").args(&["--json"])
199230
}
200231
.stdout(Redirection::None);
@@ -205,8 +236,8 @@ impl Build {
205236
.arg("build")
206237
.args(&installable_args)
207238
.args(&self.extra_args)
208-
.stdout(Redirection::None)
209-
.stderr(Redirection::Merge);
239+
.stderr(Redirection::Merge)
240+
.stdout(Redirection::None);
210241

211242
debug!(?cmd);
212243
cmd.join()

src/interface.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ pub struct OsRebuildArgs {
132132
/// Don't panic if calling nh as root
133133
#[arg(short = 'R', long, env = "NH_BYPASS_ROOT_CHECK")]
134134
pub bypass_root_check: bool,
135+
136+
/// Deploy the configuration to a different host over ssh
137+
#[arg(long)]
138+
pub target_host: Option<String>,
139+
140+
/// Build the configuration to a different host over ssh
141+
#[arg(long)]
142+
pub build_host: Option<String>,
135143
}
136144

137145
#[derive(Debug, Args)]

src/nixos.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::installable::Installable;
1313
use crate::interface::OsSubcommand::{self};
1414
use crate::interface::{self, OsGenerationsArgs, OsRebuildArgs, OsReplArgs};
1515
use crate::update::update;
16+
use crate::util::ensure_ssh_key_login;
1617
use crate::util::get_hostname;
1718

1819
const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
@@ -51,6 +52,11 @@ impl OsRebuildArgs {
5152
fn rebuild(self, variant: OsRebuildVariant) -> Result<()> {
5253
use OsRebuildVariant::*;
5354

55+
if self.build_host.is_some() || self.target_host.is_some() {
56+
// if it fails its okay
57+
let _ = ensure_ssh_key_login();
58+
}
59+
5460
let elevate = if self.bypass_root_check {
5561
warn!("Bypassing root check, now running nix as root");
5662
false
@@ -102,6 +108,7 @@ impl OsRebuildArgs {
102108
.extra_arg("--out-link")
103109
.extra_arg(out_path.get_path())
104110
.extra_args(&self.extra_args)
111+
.builder(self.build_host.clone())
105112
.message("Building NixOS configuration")
106113
.nom(!self.common.no_nom)
107114
.run()?;
@@ -121,14 +128,18 @@ impl OsRebuildArgs {
121128
Some(spec) => out_path.get_path().join("specialisation").join(spec),
122129
};
123130

131+
debug!("exists: {}", target_profile.exists());
132+
124133
target_profile.try_exists().context("Doesn't exist")?;
125134

126-
Command::new("nvd")
127-
.arg("diff")
128-
.arg(CURRENT_PROFILE)
129-
.arg(&target_profile)
130-
.message("Comparing changes")
131-
.run()?;
135+
if self.build_host.is_none() && self.target_host.is_none() {
136+
Command::new("nvd")
137+
.arg("diff")
138+
.arg(CURRENT_PROFILE)
139+
.arg(&target_profile)
140+
.message("Comparing changes")
141+
.run()?;
142+
}
132143

133144
if self.common.dry || matches!(variant, Build) {
134145
if self.common.ask {
@@ -146,14 +157,28 @@ impl OsRebuildArgs {
146157
}
147158
}
148159

160+
if let Some(target_host) = &self.target_host {
161+
Command::new("nix")
162+
.args([
163+
"copy",
164+
"--to",
165+
format!("ssh://{}", target_host).as_str(),
166+
target_profile.to_str().unwrap(),
167+
])
168+
.message("Copying configuration to target")
169+
.run()?;
170+
};
171+
149172
if let Test | Switch = variant {
150173
// !! Use the target profile aka spec-namespaced
151174
let switch_to_configuration =
152175
target_profile.join("bin").join("switch-to-configuration");
176+
let switch_to_configuration = switch_to_configuration.canonicalize().unwrap();
153177
let switch_to_configuration = switch_to_configuration.to_str().unwrap();
154178

155179
Command::new(switch_to_configuration)
156180
.arg("test")
181+
.ssh(self.target_host.clone())
157182
.message("Activating configuration")
158183
.elevate(elevate)
159184
.run()?;
@@ -163,17 +188,21 @@ impl OsRebuildArgs {
163188
Command::new("nix")
164189
.elevate(elevate)
165190
.args(["build", "--no-link", "--profile", SYSTEM_PROFILE])
166-
.arg(out_path.get_path())
191+
.arg(out_path.get_path().canonicalize().unwrap())
192+
.ssh(self.target_host.clone())
167193
.run()?;
168194

169195
// !! Use the base profile aka no spec-namespace
170196
let switch_to_configuration = out_path
171197
.get_path()
172198
.join("bin")
173199
.join("switch-to-configuration");
200+
let switch_to_configuration = switch_to_configuration.canonicalize().unwrap();
201+
let switch_to_configuration = switch_to_configuration.to_str().unwrap();
174202

175203
Command::new(switch_to_configuration)
176204
.arg("boot")
205+
.ssh(self.target_host.clone())
177206
.elevate(elevate)
178207
.message("Adding configuration to bootloader")
179208
.run()?;

src/util.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashSet;
22
use std::path::{Path, PathBuf};
3+
use std::process::{Command as StdCommand, Stdio};
34
use std::str;
45

56
use color_eyre::{eyre, Result};
@@ -41,6 +42,27 @@ pub fn get_nix_version() -> Result<String> {
4142
Err(eyre::eyre!("Failed to extract version"))
4243
}
4344

45+
/// Prompts the user for ssh key login if needed
46+
pub fn ensure_ssh_key_login() -> Result<()> {
47+
// ssh-add -L checks if there are any currently usable ssh keys
48+
49+
if StdCommand::new("ssh-add")
50+
.arg("-L")
51+
.stdout(Stdio::null())
52+
.status()?
53+
.success()
54+
{
55+
return Ok(());
56+
}
57+
StdCommand::new("ssh-add")
58+
.stdin(Stdio::inherit())
59+
.stdout(Stdio::inherit())
60+
.stderr(Stdio::inherit())
61+
.spawn()?
62+
.wait()?;
63+
Ok(())
64+
}
65+
4466
/// Determines if the Nix binary is actually Lix
4567
///
4668
/// # Returns

0 commit comments

Comments
 (0)