forked from DGNum/colmena
parent
fa07814abf
commit
ca12be27ed
6 changed files with 64 additions and 61 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
- `--reboot` is added to trigger a reboot and wait for the node to come back up.
|
- `--reboot` is added to trigger a reboot and wait for the node to come back up.
|
||||||
- The target user is no longer explicitly set when `deployment.targetUser` is null ([#91](https://github.com/zhaofengli/colmena/pull/91)).
|
- The target user is no longer explicitly set when `deployment.targetUser` is null ([#91](https://github.com/zhaofengli/colmena/pull/91)).
|
||||||
|
- In `apply-local`, we now only escalate privileges during activation ([#85](https://github.com/zhaofengli/colmena/issues/85)).
|
||||||
|
|
||||||
## [Release 0.3.0](https://github.com/zhaofengli/colmena/releases/tag/v0.3.0) (2022/04/27)
|
## [Release 0.3.0](https://github.com/zhaofengli/colmena/releases/tag/v0.3.0) (2022/04/27)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use std::env;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use clap::{Arg, Command as ClapCommand, ArgMatches};
|
use clap::{Arg, Command as ClapCommand, ArgMatches};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
use crate::error::ColmenaError;
|
use crate::error::ColmenaError;
|
||||||
use crate::nix::deployment::{
|
use crate::nix::deployment::{
|
||||||
|
@ -13,7 +11,7 @@ use crate::nix::deployment::{
|
||||||
TargetNode,
|
TargetNode,
|
||||||
Options,
|
Options,
|
||||||
};
|
};
|
||||||
use crate::nix::{NodeName, host};
|
use crate::nix::{NodeName, host::Local as LocalHost};
|
||||||
use crate::progress::SimpleProgressOutput;
|
use crate::progress::SimpleProgressOutput;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
|
@ -29,12 +27,6 @@ pub fn subcommand() -> ClapCommand<'static> {
|
||||||
.arg(Arg::new("sudo")
|
.arg(Arg::new("sudo")
|
||||||
.long("sudo")
|
.long("sudo")
|
||||||
.help("Attempt to escalate privileges if not run as root"))
|
.help("Attempt to escalate privileges if not run as root"))
|
||||||
.arg(Arg::new("sudo-command")
|
|
||||||
.long("sudo-command")
|
|
||||||
.value_name("COMMAND")
|
|
||||||
.help("Command to use to escalate privileges")
|
|
||||||
.default_value("sudo")
|
|
||||||
.takes_value(true))
|
|
||||||
.arg(Arg::new("verbose")
|
.arg(Arg::new("verbose")
|
||||||
.short('v')
|
.short('v')
|
||||||
.long("verbose")
|
.long("verbose")
|
||||||
|
@ -53,13 +45,22 @@ By default, Colmena will deploy keys set in `deployment.keys` before activating
|
||||||
.long("node")
|
.long("node")
|
||||||
.help("Override the node name to use")
|
.help("Override the node name to use")
|
||||||
.takes_value(true))
|
.takes_value(true))
|
||||||
.arg(Arg::new("we-are-launched-by-sudo")
|
|
||||||
.long("we-are-launched-by-sudo")
|
// Removed
|
||||||
|
.arg(Arg::new("sudo-command")
|
||||||
|
.long("sudo-command")
|
||||||
|
.value_name("COMMAND")
|
||||||
|
.help("Removed: Configure deployment.privilegeEscalationCommand in node configuration")
|
||||||
.hide(true)
|
.hide(true)
|
||||||
.takes_value(false))
|
.takes_value(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
|
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
|
||||||
|
if local_args.occurrences_of("sudo-command") > 0 {
|
||||||
|
log::error!("--sudo-command has been removed. Please configure it in deployment.privilegeEscalationCommand in the node configuration.");
|
||||||
|
quit::with_code(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Sanity check: Are we running NixOS?
|
// Sanity check: Are we running NixOS?
|
||||||
if let Ok(os_release) = fs::read_to_string("/etc/os-release").await {
|
if let Ok(os_release) = fs::read_to_string("/etc/os-release").await {
|
||||||
let re = Regex::new(r#"ID="?nixos"?"#).unwrap();
|
let re = Regex::new(r#"ID="?nixos"?"#).unwrap();
|
||||||
|
@ -72,23 +73,14 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
||||||
quit::with_code(5);
|
quit::with_code(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escalate privileges?
|
let escalate_privileges = local_args.is_present("sudo");
|
||||||
|
let verbose = local_args.is_present("verbose") || escalate_privileges; // cannot use spinners with interactive sudo
|
||||||
|
|
||||||
{
|
{
|
||||||
let euid: u32 = unsafe { libc::geteuid() };
|
let euid: u32 = unsafe { libc::geteuid() };
|
||||||
if euid != 0 {
|
if euid != 0 && !escalate_privileges {
|
||||||
if local_args.is_present("we-are-launched-by-sudo") {
|
log::warn!("Colmena was not started by root. This is probably not going to work.");
|
||||||
log::error!("Failed to escalate privileges. We are still not root despite a successful sudo invocation.");
|
log::warn!("Hint: Add the --sudo flag.");
|
||||||
quit::with_code(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if local_args.is_present("sudo") {
|
|
||||||
let sudo = local_args.value_of("sudo-command").unwrap();
|
|
||||||
|
|
||||||
escalate(sudo).await;
|
|
||||||
} else {
|
|
||||||
log::warn!("Colmena was not started by root. This is probably not going to work.");
|
|
||||||
log::warn!("Hint: Add the --sudo flag.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,9 +105,15 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
||||||
log::error!("Hint: Set deployment.allowLocalDeployment to true.");
|
log::error!("Hint: Set deployment.allowLocalDeployment to true.");
|
||||||
quit::with_code(2);
|
quit::with_code(2);
|
||||||
}
|
}
|
||||||
|
let mut host = LocalHost::new(nix_options);
|
||||||
|
if escalate_privileges {
|
||||||
|
let command = info.privilege_escalation_command().to_owned();
|
||||||
|
host.set_privilege_escalation_command(Some(command));
|
||||||
|
}
|
||||||
|
|
||||||
TargetNode::new(
|
TargetNode::new(
|
||||||
hostname.clone(),
|
hostname.clone(),
|
||||||
Some(host::local(nix_options)),
|
Some(host.upcast()),
|
||||||
info.clone(),
|
info.clone(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -127,7 +125,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
||||||
let mut targets = HashMap::new();
|
let mut targets = HashMap::new();
|
||||||
targets.insert(hostname.clone(), target);
|
targets.insert(hostname.clone(), target);
|
||||||
|
|
||||||
let mut output = SimpleProgressOutput::new(local_args.is_present("verbose"));
|
let mut output = SimpleProgressOutput::new(verbose);
|
||||||
let progress = output.get_sender();
|
let progress = output.get_sender();
|
||||||
|
|
||||||
let mut deployment = Deployment::new(hive, targets, goal, progress);
|
let mut deployment = Deployment::new(hive, targets, goal, progress);
|
||||||
|
@ -149,21 +147,3 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn escalate(sudo: &str) -> ! {
|
|
||||||
// Restart ourselves with sudo
|
|
||||||
let argv: Vec<String> = env::args().collect();
|
|
||||||
|
|
||||||
let exit = Command::new(sudo)
|
|
||||||
.arg("--")
|
|
||||||
.args(argv)
|
|
||||||
.arg("--we-are-launched-by-sudo")
|
|
||||||
.spawn()
|
|
||||||
.expect("Failed to run sudo to escalate privileges")
|
|
||||||
.wait()
|
|
||||||
.await
|
|
||||||
.expect("Failed to wait on child");
|
|
||||||
|
|
||||||
// Exit with the same exit code
|
|
||||||
quit::with_code(exit.code().unwrap());
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ use super::{
|
||||||
CopyDirection,
|
CopyDirection,
|
||||||
CopyOptions,
|
CopyOptions,
|
||||||
RebootOptions,
|
RebootOptions,
|
||||||
|
host::Local as LocalHost,
|
||||||
key::{Key, UploadAt as UploadKeyAt},
|
key::{Key, UploadAt as UploadKeyAt},
|
||||||
evaluator::{
|
evaluator::{
|
||||||
DrvSetEvaluator,
|
DrvSetEvaluator,
|
||||||
|
@ -42,7 +43,6 @@ use super::{
|
||||||
EvalError,
|
EvalError,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use super::host;
|
|
||||||
|
|
||||||
/// A deployment.
|
/// A deployment.
|
||||||
pub type DeploymentHandle = Arc<Deployment>;
|
pub type DeploymentHandle = Arc<Deployment>;
|
||||||
|
@ -450,7 +450,7 @@ impl Deployment {
|
||||||
let arc_self = self.clone();
|
let arc_self = self.clone();
|
||||||
let profile: Profile = build_job.run(|job| async move {
|
let profile: Profile = build_job.run(|job| async move {
|
||||||
// FIXME: Remote builder?
|
// FIXME: Remote builder?
|
||||||
let mut builder = host::local(arc_self.nix_options.clone());
|
let mut builder = LocalHost::new(arc_self.nix_options.clone()).upcast();
|
||||||
builder.set_job(Some(job.clone()));
|
builder.set_job(Some(job.clone()));
|
||||||
|
|
||||||
let profile = profile_drv.realize(&mut builder).await?;
|
let profile = profile_drv.realize(&mut builder).await?;
|
||||||
|
|
|
@ -19,6 +19,7 @@ use super::{CopyDirection, CopyOptions, Host, key_uploader};
|
||||||
pub struct Local {
|
pub struct Local {
|
||||||
job: Option<JobHandle>,
|
job: Option<JobHandle>,
|
||||||
nix_options: NixOptions,
|
nix_options: NixOptions,
|
||||||
|
privilege_escalation_command: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
|
@ -26,6 +27,7 @@ impl Local {
|
||||||
Self {
|
Self {
|
||||||
job: None,
|
job: None,
|
||||||
nix_options,
|
nix_options,
|
||||||
|
privilege_escalation_command: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,17 +73,15 @@ impl Host for Local {
|
||||||
|
|
||||||
if goal.should_switch_profile() {
|
if goal.should_switch_profile() {
|
||||||
let path = profile.as_path().to_str().unwrap();
|
let path = profile.as_path().to_str().unwrap();
|
||||||
Command::new("nix-env")
|
self.make_privileged_command(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path])
|
||||||
.args(&["--profile", SYSTEM_PROFILE])
|
|
||||||
.args(&["--set", path])
|
|
||||||
.passthrough()
|
.passthrough()
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let activation_command = profile.activation_command(goal).unwrap();
|
let command = {
|
||||||
let mut command = Command::new(&activation_command[0]);
|
let activation_command = profile.activation_command(goal).unwrap();
|
||||||
command
|
self.make_privileged_command(&activation_command)
|
||||||
.args(&activation_command[1..]);
|
};
|
||||||
|
|
||||||
let mut execution = CommandExecution::new(command);
|
let mut execution = CommandExecution::new(command);
|
||||||
|
|
||||||
|
@ -126,6 +126,14 @@ impl Host for Local {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
|
pub fn set_privilege_escalation_command(&mut self, command: Option<Vec<String>>) {
|
||||||
|
self.privilege_escalation_command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upcast(self) -> Box<dyn Host> {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// "Uploads" a single key.
|
/// "Uploads" a single key.
|
||||||
async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()> {
|
async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()> {
|
||||||
if let Some(job) = &self.job {
|
if let Some(job) = &self.job {
|
||||||
|
@ -145,4 +153,20 @@ impl Local {
|
||||||
let uploader = command.spawn()?;
|
let uploader = command.spawn()?;
|
||||||
key_uploader::feed_uploader(uploader, key, self.job.clone()).await
|
key_uploader::feed_uploader(uploader, key, self.job.clone()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a command with privilege escalation.
|
||||||
|
fn make_privileged_command<S: AsRef<str>>(&self, command: &[S]) -> Command {
|
||||||
|
let mut full_command = Vec::new();
|
||||||
|
if let Some(esc) = &self.privilege_escalation_command {
|
||||||
|
full_command.extend(esc.iter().map(|s| s.as_str()));
|
||||||
|
}
|
||||||
|
full_command.extend(command.iter().map(|s| s.as_ref()));
|
||||||
|
|
||||||
|
let mut result = Command::new(full_command[0]);
|
||||||
|
if full_command.len() > 1 {
|
||||||
|
result.args(&full_command[1..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::error::{ColmenaError, ColmenaResult};
|
use crate::error::{ColmenaError, ColmenaResult};
|
||||||
use crate::job::JobHandle;
|
use crate::job::JobHandle;
|
||||||
use super::{StorePath, Profile, Goal, Key, NixOptions};
|
use super::{StorePath, Profile, Goal, Key};
|
||||||
|
|
||||||
mod ssh;
|
mod ssh;
|
||||||
pub use ssh::Ssh;
|
pub use ssh::Ssh;
|
||||||
|
@ -14,10 +14,6 @@ pub use local::Local;
|
||||||
|
|
||||||
mod key_uploader;
|
mod key_uploader;
|
||||||
|
|
||||||
pub(crate) fn local(nix_options: NixOptions) -> Box<dyn Host + 'static> {
|
|
||||||
Box::new(Local::new(nix_options))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum CopyDirection {
|
pub enum CopyDirection {
|
||||||
ToRemote,
|
ToRemote,
|
||||||
|
|
|
@ -156,6 +156,8 @@ impl NodeConfig {
|
||||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||||
pub fn allows_local_deployment(&self) -> bool { self.allow_local_deployment }
|
pub fn allows_local_deployment(&self) -> bool { self.allow_local_deployment }
|
||||||
|
|
||||||
|
pub fn privilege_escalation_command(&self) -> &Vec<String> { &self.privilege_escalation_command }
|
||||||
|
|
||||||
pub fn build_on_target(&self) -> bool { self.build_on_target }
|
pub fn build_on_target(&self) -> bool { self.build_on_target }
|
||||||
pub fn set_build_on_target(&mut self, enable: bool) {
|
pub fn set_build_on_target(&mut self, enable: bool) {
|
||||||
self.build_on_target = enable;
|
self.build_on_target = enable;
|
||||||
|
|
Loading…
Reference in a new issue